field.rb 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. require 'mail/fields'
  2. # encoding: utf-8
  3. module Mail
  4. # Provides a single class to call to create a new structured or unstructured
  5. # field. Works out per RFC what field of field it is being given and returns
  6. # the correct field of class back on new.
  7. #
  8. # ===Per RFC 2822
  9. #
  10. # 2.2. Header Fields
  11. #
  12. # Header fields are lines composed of a field name, followed by a colon
  13. # (":"), followed by a field body, and terminated by CRLF. A field
  14. # name MUST be composed of printable US-ASCII characters (i.e.,
  15. # characters that have values between 33 and 126, inclusive), except
  16. # colon. A field body may be composed of any US-ASCII characters,
  17. # except for CR and LF. However, a field body may contain CRLF when
  18. # used in header "folding" and "unfolding" as described in section
  19. # 2.2.3. All field bodies MUST conform to the syntax described in
  20. # sections 3 and 4 of this standard.
  21. #
  22. class Field
  23. include Patterns
  24. include Comparable
  25. STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
  26. content-id content-location content-transfer-encoding
  27. content-type date from in-reply-to keywords message-id
  28. mime-version received references reply-to
  29. resent-bcc resent-cc resent-date resent-from
  30. resent-message-id resent-sender resent-to
  31. return-path sender to ]
  32. KNOWN_FIELDS = STRUCTURED_FIELDS + ['comments', 'subject']
  33. # Generic Field Exception
  34. class FieldError < StandardError
  35. end
  36. # Raised when a parsing error has occurred (ie, a StructuredField has tried
  37. # to parse a field that is invalid or improperly written)
  38. class ParseError < FieldError #:nodoc:
  39. attr_accessor :element, :value, :reason
  40. def initialize(element, value, reason)
  41. @element = element
  42. @value = value
  43. @reason = reason
  44. super("#{element} can not parse |#{value}|\nReason was: #{reason}")
  45. end
  46. end
  47. # Raised when attempting to set a structured field's contents to an invalid syntax
  48. class SyntaxError < FieldError #:nodoc:
  49. end
  50. # Accepts a string:
  51. #
  52. # Field.new("field-name: field data")
  53. #
  54. # Or name, value pair:
  55. #
  56. # Field.new("field-name", "value")
  57. #
  58. # Or a name by itself:
  59. #
  60. # Field.new("field-name")
  61. #
  62. # Note, does not want a terminating carriage return. Returns
  63. # self appropriately parsed. If value is not a string, then
  64. # it will be passed through as is, for example, content-type
  65. # field can accept an array with the type and a hash of
  66. # parameters:
  67. #
  68. # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
  69. def initialize(name, value = nil, charset = 'utf-8')
  70. case
  71. when name =~ /:/ # Field.new("field-name: field data")
  72. charset = value unless value.blank?
  73. name, value = split(name)
  74. create_field(name, value, charset)
  75. when name !~ /:/ && value.blank? # Field.new("field-name")
  76. create_field(name, nil, charset)
  77. else # Field.new("field-name", "value")
  78. create_field(name, value, charset)
  79. end
  80. return self
  81. end
  82. def field=(value)
  83. @field = value
  84. end
  85. def field
  86. @field
  87. end
  88. def name
  89. field.name
  90. end
  91. def value
  92. field.value
  93. end
  94. def value=(val)
  95. create_field(name, val, charset)
  96. end
  97. def to_s
  98. field.to_s
  99. end
  100. def update(name, value)
  101. create_field(name, value, charset)
  102. end
  103. def same( other )
  104. match_to_s(other.name, field.name)
  105. end
  106. alias_method :==, :same
  107. def <=>( other )
  108. self_order = FIELD_ORDER.rindex(self.name.to_s.downcase) || 100
  109. other_order = FIELD_ORDER.rindex(other.name.to_s.downcase) || 100
  110. self_order <=> other_order
  111. end
  112. def method_missing(name, *args, &block)
  113. field.send(name, *args, &block)
  114. end
  115. FIELD_ORDER = %w[ return-path received
  116. resent-date resent-from resent-sender resent-to
  117. resent-cc resent-bcc resent-message-id
  118. date from sender reply-to to cc bcc
  119. message-id in-reply-to references
  120. subject comments keywords
  121. mime-version content-type content-transfer-encoding
  122. content-location content-disposition content-description ]
  123. private
  124. def split(raw_field)
  125. match_data = raw_field.mb_chars.match(/^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/)
  126. [match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip]
  127. rescue
  128. STDERR.puts "WARNING: Could not parse (and so ignorning) '#{raw_field}'"
  129. end
  130. def create_field(name, value, charset)
  131. begin
  132. self.field = new_field(name, value, charset)
  133. rescue Mail::Field::ParseError => e
  134. self.field = Mail::UnstructuredField.new(name, value)
  135. self.field.errors << [name, value, e]
  136. self.field
  137. end
  138. end
  139. def new_field(name, value, charset)
  140. # Could do this with constantize and make it "as DRY as", but a simple case
  141. # statement is, well, simpler...
  142. case name.to_s.downcase
  143. when /^to$/i
  144. ToField.new(value, charset)
  145. when /^cc$/i
  146. CcField.new(value, charset)
  147. when /^bcc$/i
  148. BccField.new(value, charset)
  149. when /^message-id$/i
  150. MessageIdField.new(value, charset)
  151. when /^in-reply-to$/i
  152. InReplyToField.new(value, charset)
  153. when /^references$/i
  154. ReferencesField.new(value, charset)
  155. when /^subject$/i
  156. SubjectField.new(value, charset)
  157. when /^comments$/i
  158. CommentsField.new(value, charset)
  159. when /^keywords$/i
  160. KeywordsField.new(value, charset)
  161. when /^date$/i
  162. DateField.new(value, charset)
  163. when /^from$/i
  164. FromField.new(value, charset)
  165. when /^sender$/i
  166. SenderField.new(value, charset)
  167. when /^reply-to$/i
  168. ReplyToField.new(value, charset)
  169. when /^resent-date$/i
  170. ResentDateField.new(value, charset)
  171. when /^resent-from$/i
  172. ResentFromField.new(value, charset)
  173. when /^resent-sender$/i
  174. ResentSenderField.new(value, charset)
  175. when /^resent-to$/i
  176. ResentToField.new(value, charset)
  177. when /^resent-cc$/i
  178. ResentCcField.new(value, charset)
  179. when /^resent-bcc$/i
  180. ResentBccField.new(value, charset)
  181. when /^resent-message-id$/i
  182. ResentMessageIdField.new(value, charset)
  183. when /^return-path$/i
  184. ReturnPathField.new(value, charset)
  185. when /^received$/i
  186. ReceivedField.new(value, charset)
  187. when /^mime-version$/i
  188. MimeVersionField.new(value, charset)
  189. when /^content-transfer-encoding$/i
  190. ContentTransferEncodingField.new(value, charset)
  191. when /^content-description$/i
  192. ContentDescriptionField.new(value, charset)
  193. when /^content-disposition$/i
  194. ContentDispositionField.new(value, charset)
  195. when /^content-type$/i
  196. ContentTypeField.new(value, charset)
  197. when /^content-id$/i
  198. ContentIdField.new(value, charset)
  199. when /^content-location$/i
  200. ContentLocationField.new(value, charset)
  201. else
  202. OptionalField.new(name, value, charset)
  203. end
  204. end
  205. end
  206. end