# encoding: utf-8moduleMail# Provides access to a header object.# # ===Per RFC2822# # 2.2. Header Fields# # Header fields are lines composed of a field name, followed by a colon# (":"), followed by a field body, and terminated by CRLF. A field# name MUST be composed of printable US-ASCII characters (i.e.,# characters that have values between 33 and 126, inclusive), except# colon. A field body may be composed of any US-ASCII characters,# except for CR and LF. However, a field body may contain CRLF when# used in header "folding" and "unfolding" as described in section# 2.2.3. All field bodies MUST conform to the syntax described in# sections 3 and 4 of this standard.classHeaderincludePatternsincludeUtilitiesincludeEnumerable# Creates a new header object.# # Accepts raw text or nothing. If given raw text will attempt to parse# it and split it into the various fields, instantiating each field as# it goes.# # If it finds a field that should be a structured field (such as content# type), but it fails to parse it, it will simply make it an unstructured# field and leave it alone. This will mean that the data is preserved but# no automatic processing of that field will happen. If you find one of# these cases, please make a patch and send it in, or at the least, send# me the example so we can fix it.definitialize(header_text=nil)self.raw_source=header_text.to_crlfsplit_headerifheader_textend# The preserved raw source of the header as you passed it in, untouched# for your Regexing glory.defraw_source@raw_sourceend# Returns an array of all the fields in the header in order that they# were read in.deffields@fields||=FieldList.newend# 3.6. Field definitions# # It is important to note that the header fields are not guaranteed to# be in a particular order. They may appear in any order, and they# have been known to be reordered occasionally when transported over# the Internet. However, for the purposes of this standard, header# fields SHOULD NOT be reordered when a message is transported or# transformed. More importantly, the trace header fields and resent# header fields MUST NOT be reordered, and SHOULD be kept in blocks# prepended to the message. See sections 3.6.6 and 3.6.7 for more# information.# # Populates the fields container with Field objects in the order it# receives them in.## Acceps an array of field string values, for example:# # h = Header.new# h.fields = ['From: mikel@me.com', 'To: bob@you.com']deffields=(unfolded_fields)@fields=Mail::FieldList.newunfolded_fields.eachdo|field|field=Field.new(field)selected=select_field_for(field.name)ifselected.any?&&limited_field?(field.name)selected.first.update(field.name,field.value)else@fields<<fieldendendend# 3.6. Field definitions# # The following table indicates limits on the number of times each# field may occur in a message header as well as any special# limitations on the use of those fields. An asterisk next to a value# in the minimum or maximum column indicates that a special restriction# appears in the Notes column.## <snip table from 3.6>## As per RFC, many fields can appear more than once, we will return a string# of the value if there is only one header, or if there is more than one # matching header, will return an array of values in order that they appear# in the header ordered from top to bottom.# # Example:# # h = Header.new# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']# h['To'] #=> 'mikel@me.com'# h['X-Mail-SPAM'] #=> ['15', '20']def[](name)name=dasherize(name)selected=select_field_for(name)casewhenselected.length>1selected.map{|f|f}when!selected.blank?selected.firstelsenilendend# Sets the FIRST matching field in the header to passed value, or deletes# the FIRST field matched from the header if passed nil# # Example:# # h = Header.new# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']# h['To'] = 'bob@you.com'# h['To'] #=> 'bob@you.com'# h['X-Mail-SPAM'] = '10000'# h['X-Mail-SPAM'] # => ['15', '20', '10000']# h['X-Mail-SPAM'] = nil# h['X-Mail-SPAM'] # => nildef[]=(name,value)name=dasherize(name)selected=select_field_for(name)case# User wants to delete the fieldwhen!selected.blank?&&value==nilfields.delete_if{|f|selected.include?(f)}# User wants to change the fieldwhen!selected.blank?&&limited_field?(name)selected.first.update(name,value)# User wants to create the fieldelse# Need to insert in correct order for trace fieldsself.fields<<Field.new(name.to_s,value)endendLIMITED_FIELDS=%w[ orig-date from sender reply-to to cc bcc
message-id in-reply-to references subject
return-path content-type mime-version
content-transfer-encoding content-description
content-id content-disposition content-location]defencodedbuffer=''fields.eachdo|field|buffer<<field.encodedendbufferenddefto_sencodedenddefdecodedraiseNoMethodError,'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'end# Returns true if the header has a Message-ID defined (empty or not)defhas_message_id?!fields.select{|f|f.responsible_for?('Message-ID')}.empty?end# Returns true if the header has a Content-ID defined (empty or not)defhas_content_id?!fields.select{|f|f.responsible_for?('Content-ID')}.empty?end# Returns true if the header has a Date defined (empty or not)defhas_date?!fields.select{|f|f.responsible_for?('Date')}.empty?end# Returns true if the header has a message_id defined (empty or not)defhas_mime_version?!fields.select{|f|f.responsible_for?('Mime-Version')}.empty?endprivatedefraw_source=(val)@raw_source=valend# 2.2.3. Long Header Fields# # The process of moving from this folded multiple-line representation# of a header field to its single line representation is called# "unfolding". Unfolding is accomplished by simply removing any CRLF# that is immediately followed by WSP. Each header field should be# treated in its unfolded form for further syntactic and semantic# evaluation.defunfold(string)string.gsub(/#{CRLF}#{WSP}+/,' ').gsub(/#{WSP}+/,' ')end# Returns the header with all the folds removeddefunfolded_header@unfolded_header||=unfold(raw_source)end# Splits an unfolded and line break cleaned header into individual field# strings.defsplit_headerself.fields=unfolded_header.split(CRLF)enddefselect_field_for(name)fields.select{|f|f.responsible_for?(name.to_s)}enddeflimited_field?(name)LIMITED_FIELDS.include?(name.to_s.downcase)endendend