header.rb 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # encoding: utf-8
  2. module Mail
  3. # Provides access to a header object.
  4. #
  5. # ===Per RFC2822
  6. #
  7. # 2.2. Header Fields
  8. #
  9. # Header fields are lines composed of a field name, followed by a colon
  10. # (":"), followed by a field body, and terminated by CRLF. A field
  11. # name MUST be composed of printable US-ASCII characters (i.e.,
  12. # characters that have values between 33 and 126, inclusive), except
  13. # colon. A field body may be composed of any US-ASCII characters,
  14. # except for CR and LF. However, a field body may contain CRLF when
  15. # used in header "folding" and "unfolding" as described in section
  16. # 2.2.3. All field bodies MUST conform to the syntax described in
  17. # sections 3 and 4 of this standard.
  18. class Header
  19. include Patterns
  20. include Utilities
  21. include Enumerable
  22. # Creates a new header object.
  23. #
  24. # Accepts raw text or nothing. If given raw text will attempt to parse
  25. # it and split it into the various fields, instantiating each field as
  26. # it goes.
  27. #
  28. # If it finds a field that should be a structured field (such as content
  29. # type), but it fails to parse it, it will simply make it an unstructured
  30. # field and leave it alone. This will mean that the data is preserved but
  31. # no automatic processing of that field will happen. If you find one of
  32. # these cases, please make a patch and send it in, or at the least, send
  33. # me the example so we can fix it.
  34. def initialize(header_text = nil, charset = nil)
  35. @errors = []
  36. @charset = charset
  37. self.raw_source = header_text.to_crlf
  38. split_header if header_text
  39. end
  40. # The preserved raw source of the header as you passed it in, untouched
  41. # for your Regexing glory.
  42. def raw_source
  43. @raw_source
  44. end
  45. # Returns an array of all the fields in the header in order that they
  46. # were read in.
  47. def fields
  48. @fields ||= FieldList.new
  49. end
  50. # 3.6. Field definitions
  51. #
  52. # It is important to note that the header fields are not guaranteed to
  53. # be in a particular order. They may appear in any order, and they
  54. # have been known to be reordered occasionally when transported over
  55. # the Internet. However, for the purposes of this standard, header
  56. # fields SHOULD NOT be reordered when a message is transported or
  57. # transformed. More importantly, the trace header fields and resent
  58. # header fields MUST NOT be reordered, and SHOULD be kept in blocks
  59. # prepended to the message. See sections 3.6.6 and 3.6.7 for more
  60. # information.
  61. #
  62. # Populates the fields container with Field objects in the order it
  63. # receives them in.
  64. #
  65. # Acceps an array of field string values, for example:
  66. #
  67. # h = Header.new
  68. # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
  69. def fields=(unfolded_fields)
  70. @fields = Mail::FieldList.new
  71. warn "Warning: more than 1000 header fields only using the first 1000" if unfolded_fields.length > 1000
  72. unfolded_fields[0..1000].each do |field|
  73. field = Field.new(field, nil, charset)
  74. field.errors.each { |error| self.errors << error }
  75. selected = select_field_for(field.name)
  76. if selected.any? && limited_field?(field.name)
  77. selected.first.update(field.name, field.value)
  78. else
  79. @fields << field
  80. end
  81. end
  82. end
  83. def errors
  84. @errors
  85. end
  86. # 3.6. Field definitions
  87. #
  88. # The following table indicates limits on the number of times each
  89. # field may occur in a message header as well as any special
  90. # limitations on the use of those fields. An asterisk next to a value
  91. # in the minimum or maximum column indicates that a special restriction
  92. # appears in the Notes column.
  93. #
  94. # <snip table from 3.6>
  95. #
  96. # As per RFC, many fields can appear more than once, we will return a string
  97. # of the value if there is only one header, or if there is more than one
  98. # matching header, will return an array of values in order that they appear
  99. # in the header ordered from top to bottom.
  100. #
  101. # Example:
  102. #
  103. # h = Header.new
  104. # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
  105. # h['To'] #=> 'mikel@me.com'
  106. # h['X-Mail-SPAM'] #=> ['15', '20']
  107. def [](name)
  108. name = dasherize(name).downcase
  109. selected = select_field_for(name)
  110. case
  111. when selected.length > 1
  112. selected.map { |f| f }
  113. when !selected.blank?
  114. selected.first
  115. else
  116. nil
  117. end
  118. end
  119. # Sets the FIRST matching field in the header to passed value, or deletes
  120. # the FIRST field matched from the header if passed nil
  121. #
  122. # Example:
  123. #
  124. # h = Header.new
  125. # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
  126. # h['To'] = 'bob@you.com'
  127. # h['To'] #=> 'bob@you.com'
  128. # h['X-Mail-SPAM'] = '10000'
  129. # h['X-Mail-SPAM'] # => ['15', '20', '10000']
  130. # h['X-Mail-SPAM'] = nil
  131. # h['X-Mail-SPAM'] # => nil
  132. def []=(name, value)
  133. name = dasherize(name)
  134. fn = name.downcase
  135. selected = select_field_for(fn)
  136. case
  137. # User wants to delete the field
  138. when !selected.blank? && value == nil
  139. fields.delete_if { |f| selected.include?(f) }
  140. # User wants to change the field
  141. when !selected.blank? && limited_field?(fn)
  142. selected.first.update(fn, value)
  143. # User wants to create the field
  144. else
  145. # Need to insert in correct order for trace fields
  146. self.fields << Field.new(name.to_s, value, charset)
  147. end
  148. end
  149. def charset
  150. params = self[:content_type].parameters rescue nil
  151. if params
  152. params[:charset]
  153. else
  154. @charset
  155. end
  156. end
  157. def charset=(val)
  158. params = self[:content_type].parameters rescue nil
  159. if params
  160. params[:charset] = val
  161. end
  162. @charset = val
  163. end
  164. LIMITED_FIELDS = %w[ date from sender reply-to to cc bcc
  165. message-id in-reply-to references subject
  166. return-path content-type mime-version
  167. content-transfer-encoding content-description
  168. content-id content-disposition content-location]
  169. def encoded
  170. buffer = ''
  171. fields.each do |field|
  172. buffer << field.encoded
  173. end
  174. buffer
  175. end
  176. def to_s
  177. encoded
  178. end
  179. def decoded
  180. raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'
  181. end
  182. def field_summary
  183. fields.map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
  184. end
  185. # Returns true if the header has a Message-ID defined (empty or not)
  186. def has_message_id?
  187. !fields.select { |f| f.responsible_for?('Message-ID') }.empty?
  188. end
  189. # Returns true if the header has a Content-ID defined (empty or not)
  190. def has_content_id?
  191. !fields.select { |f| f.responsible_for?('Content-ID') }.empty?
  192. end
  193. # Returns true if the header has a Date defined (empty or not)
  194. def has_date?
  195. !fields.select { |f| f.responsible_for?('Date') }.empty?
  196. end
  197. # Returns true if the header has a MIME version defined (empty or not)
  198. def has_mime_version?
  199. !fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
  200. end
  201. private
  202. def raw_source=(val)
  203. @raw_source = val
  204. end
  205. # 2.2.3. Long Header Fields
  206. #
  207. # The process of moving from this folded multiple-line representation
  208. # of a header field to its single line representation is called
  209. # "unfolding". Unfolding is accomplished by simply removing any CRLF
  210. # that is immediately followed by WSP. Each header field should be
  211. # treated in its unfolded form for further syntactic and semantic
  212. # evaluation.
  213. def unfold(string)
  214. string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
  215. end
  216. # Returns the header with all the folds removed
  217. def unfolded_header
  218. @unfolded_header ||= unfold(raw_source)
  219. end
  220. # Splits an unfolded and line break cleaned header into individual field
  221. # strings.
  222. def split_header
  223. self.fields = unfolded_header.split(CRLF)
  224. end
  225. def select_field_for(name)
  226. fields.select { |f| f.responsible_for?(name.to_s) }
  227. end
  228. def limited_field?(name)
  229. LIMITED_FIELDS.include?(name.to_s.downcase)
  230. end
  231. end
  232. end