mail 2.6.4 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +208 -156
  3. data/lib/mail/attachments_list.rb +13 -10
  4. data/lib/mail/body.rb +96 -107
  5. data/lib/mail/configuration.rb +2 -0
  6. data/lib/mail/constants.rb +27 -5
  7. data/lib/mail/elements/address.rb +61 -50
  8. data/lib/mail/elements/address_list.rb +11 -19
  9. data/lib/mail/elements/content_disposition_element.rb +9 -16
  10. data/lib/mail/elements/content_location_element.rb +6 -11
  11. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -11
  12. data/lib/mail/elements/content_type_element.rb +16 -23
  13. data/lib/mail/elements/date_time_element.rb +7 -15
  14. data/lib/mail/elements/envelope_from_element.rb +22 -23
  15. data/lib/mail/elements/message_ids_element.rb +18 -13
  16. data/lib/mail/elements/mime_version_element.rb +7 -15
  17. data/lib/mail/elements/phrase_list.rb +12 -10
  18. data/lib/mail/elements/received_element.rb +27 -19
  19. data/lib/mail/encodings/7bit.rb +9 -14
  20. data/lib/mail/encodings/8bit.rb +2 -21
  21. data/lib/mail/encodings/base64.rb +11 -12
  22. data/lib/mail/encodings/binary.rb +3 -22
  23. data/lib/mail/encodings/identity.rb +24 -0
  24. data/lib/mail/encodings/quoted_printable.rb +6 -6
  25. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  26. data/lib/mail/encodings/unix_to_unix.rb +4 -2
  27. data/lib/mail/encodings.rb +83 -56
  28. data/lib/mail/envelope.rb +11 -14
  29. data/lib/mail/field.rb +181 -130
  30. data/lib/mail/field_list.rb +61 -8
  31. data/lib/mail/fields/bcc_field.rb +33 -52
  32. data/lib/mail/fields/cc_field.rb +27 -49
  33. data/lib/mail/fields/comments_field.rb +26 -37
  34. data/lib/mail/fields/common_address_field.rb +162 -0
  35. data/lib/mail/fields/common_date_field.rb +56 -0
  36. data/lib/mail/fields/common_field.rb +77 -0
  37. data/lib/mail/fields/common_message_id_field.rb +41 -0
  38. data/lib/mail/fields/content_description_field.rb +6 -14
  39. data/lib/mail/fields/content_disposition_field.rb +11 -38
  40. data/lib/mail/fields/content_id_field.rb +23 -51
  41. data/lib/mail/fields/content_location_field.rb +10 -25
  42. data/lib/mail/fields/content_transfer_encoding_field.rb +30 -31
  43. data/lib/mail/fields/content_type_field.rb +53 -84
  44. data/lib/mail/fields/date_field.rb +22 -52
  45. data/lib/mail/fields/from_field.rb +27 -49
  46. data/lib/mail/fields/in_reply_to_field.rb +37 -49
  47. data/lib/mail/fields/keywords_field.rb +17 -31
  48. data/lib/mail/fields/message_id_field.rb +24 -71
  49. data/lib/mail/fields/mime_version_field.rb +18 -30
  50. data/lib/mail/fields/named_structured_field.rb +10 -0
  51. data/lib/mail/fields/named_unstructured_field.rb +10 -0
  52. data/lib/mail/fields/optional_field.rb +9 -8
  53. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +13 -11
  54. data/lib/mail/fields/received_field.rb +42 -57
  55. data/lib/mail/fields/references_field.rb +34 -49
  56. data/lib/mail/fields/reply_to_field.rb +27 -49
  57. data/lib/mail/fields/resent_bcc_field.rb +27 -49
  58. data/lib/mail/fields/resent_cc_field.rb +27 -49
  59. data/lib/mail/fields/resent_date_field.rb +4 -30
  60. data/lib/mail/fields/resent_from_field.rb +27 -49
  61. data/lib/mail/fields/resent_message_id_field.rb +4 -29
  62. data/lib/mail/fields/resent_sender_field.rb +26 -56
  63. data/lib/mail/fields/resent_to_field.rb +27 -49
  64. data/lib/mail/fields/return_path_field.rb +49 -54
  65. data/lib/mail/fields/sender_field.rb +33 -55
  66. data/lib/mail/fields/structured_field.rb +2 -30
  67. data/lib/mail/fields/subject_field.rb +8 -11
  68. data/lib/mail/fields/to_field.rb +27 -49
  69. data/lib/mail/fields/unstructured_field.rb +31 -47
  70. data/lib/mail/fields.rb +9 -0
  71. data/lib/mail/header.rb +71 -110
  72. data/lib/mail/mail.rb +34 -37
  73. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  74. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  75. data/lib/mail/message.rb +126 -127
  76. data/lib/mail/multibyte/chars.rb +24 -181
  77. data/lib/mail/multibyte/unicode.rb +11 -11
  78. data/lib/mail/multibyte/utils.rb +26 -43
  79. data/lib/mail/multibyte.rb +55 -16
  80. data/lib/mail/network/delivery_methods/exim.rb +8 -11
  81. data/lib/mail/network/delivery_methods/file_delivery.rb +15 -18
  82. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  83. data/lib/mail/network/delivery_methods/sendmail.rb +32 -35
  84. data/lib/mail/network/delivery_methods/smtp.rb +125 -68
  85. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -16
  86. data/lib/mail/network/delivery_methods/test_mailer.rb +12 -13
  87. data/lib/mail/network/retriever_methods/base.rb +13 -13
  88. data/lib/mail/network/retriever_methods/imap.rb +25 -9
  89. data/lib/mail/network/retriever_methods/pop3.rb +25 -23
  90. data/lib/mail/network/retriever_methods/test_retriever.rb +3 -2
  91. data/lib/mail/network.rb +1 -0
  92. data/lib/mail/parser_tools.rb +15 -0
  93. data/lib/mail/parsers/address_lists_parser.rb +33228 -116
  94. data/lib/mail/parsers/address_lists_parser.rl +183 -0
  95. data/lib/mail/parsers/content_disposition_parser.rb +885 -49
  96. data/lib/mail/parsers/content_disposition_parser.rl +93 -0
  97. data/lib/mail/parsers/content_location_parser.rb +812 -23
  98. data/lib/mail/parsers/content_location_parser.rl +82 -0
  99. data/lib/mail/parsers/content_transfer_encoding_parser.rb +512 -21
  100. data/lib/mail/parsers/content_transfer_encoding_parser.rl +75 -0
  101. data/lib/mail/parsers/content_type_parser.rb +1039 -55
  102. data/lib/mail/parsers/content_type_parser.rl +94 -0
  103. data/lib/mail/parsers/date_time_parser.rb +880 -25
  104. data/lib/mail/parsers/date_time_parser.rl +73 -0
  105. data/lib/mail/parsers/envelope_from_parser.rb +3672 -40
  106. data/lib/mail/parsers/envelope_from_parser.rl +93 -0
  107. data/lib/mail/parsers/message_ids_parser.rb +5149 -25
  108. data/lib/mail/parsers/message_ids_parser.rl +97 -0
  109. data/lib/mail/parsers/mime_version_parser.rb +500 -26
  110. data/lib/mail/parsers/mime_version_parser.rl +72 -0
  111. data/lib/mail/parsers/phrase_lists_parser.rb +873 -22
  112. data/lib/mail/parsers/phrase_lists_parser.rl +94 -0
  113. data/lib/mail/parsers/received_parser.rb +8779 -43
  114. data/lib/mail/parsers/received_parser.rl +95 -0
  115. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  116. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  117. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  118. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  119. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  120. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  121. data/lib/mail/parsers/rfc5322.rl +74 -0
  122. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  123. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  124. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  125. data/lib/mail/parsers.rb +11 -25
  126. data/lib/mail/part.rb +25 -29
  127. data/lib/mail/parts_list.rb +62 -6
  128. data/lib/mail/smtp_envelope.rb +57 -0
  129. data/lib/mail/utilities.rb +361 -74
  130. data/lib/mail/version.rb +2 -2
  131. data/lib/mail/yaml.rb +30 -0
  132. data/lib/mail.rb +4 -37
  133. metadata +125 -67
  134. data/CHANGELOG.rdoc +0 -787
  135. data/CONTRIBUTING.md +0 -60
  136. data/Dependencies.txt +0 -2
  137. data/Gemfile +0 -11
  138. data/Rakefile +0 -29
  139. data/TODO.rdoc +0 -9
  140. data/lib/mail/check_delivery_params.rb +0 -21
  141. data/lib/mail/core_extensions/smtp.rb +0 -25
  142. data/lib/mail/core_extensions/string/access.rb +0 -146
  143. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  144. data/lib/mail/core_extensions/string.rb +0 -21
  145. data/lib/mail/fields/common/address_container.rb +0 -17
  146. data/lib/mail/fields/common/common_address.rb +0 -136
  147. data/lib/mail/fields/common/common_date.rb +0 -36
  148. data/lib/mail/fields/common/common_field.rb +0 -61
  149. data/lib/mail/fields/common/common_message_id.rb +0 -49
  150. data/lib/mail/multibyte/exceptions.rb +0 -9
  151. data/lib/mail/parsers/ragel/common.rl +0 -185
  152. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  153. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  155. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  157. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  159. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  161. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  163. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  165. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  167. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  169. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  171. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  173. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  174. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  176. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  177. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  178. data/lib/mail/parsers/ragel.rb +0 -18
  179. data/lib/mail/version_specific/ruby_1_8.rb +0 -126
  180. data/lib/mail/version_specific/ruby_1_9.rb +0 -223
data/lib/mail/field.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'mail/fields'
3
+ require 'mail/constants'
3
4
 
4
5
  # encoding: utf-8
5
6
  module Mail
@@ -22,8 +23,6 @@ module Mail
22
23
  # sections 3 and 4 of this standard.
23
24
  #
24
25
  class Field
25
-
26
- include Utilities
27
26
  include Comparable
28
27
 
29
28
  STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
@@ -37,40 +36,68 @@ module Mail
37
36
  KNOWN_FIELDS = STRUCTURED_FIELDS + ['comments', 'subject']
38
37
 
39
38
  FIELDS_MAP = {
40
- "to" => ToField,
41
- "cc" => CcField,
42
- "bcc" => BccField,
43
- "message-id" => MessageIdField,
44
- "in-reply-to" => InReplyToField,
45
- "references" => ReferencesField,
46
- "subject" => SubjectField,
47
- "comments" => CommentsField,
48
- "keywords" => KeywordsField,
49
- "date" => DateField,
50
- "from" => FromField,
51
- "sender" => SenderField,
52
- "reply-to" => ReplyToField,
53
- "resent-date" => ResentDateField,
54
- "resent-from" => ResentFromField,
55
- "resent-sender" => ResentSenderField,
56
- "resent-to" => ResentToField,
57
- "resent-cc" => ResentCcField,
58
- "resent-bcc" => ResentBccField,
59
- "resent-message-id" => ResentMessageIdField,
60
- "return-path" => ReturnPathField,
61
- "received" => ReceivedField,
62
- "mime-version" => MimeVersionField,
63
- "content-transfer-encoding" => ContentTransferEncodingField,
64
- "content-description" => ContentDescriptionField,
65
- "content-disposition" => ContentDispositionField,
66
- "content-type" => ContentTypeField,
67
- "content-id" => ContentIdField,
68
- "content-location" => ContentLocationField,
39
+ "to" => "ToField",
40
+ "cc" => "CcField",
41
+ "bcc" => "BccField",
42
+ "message-id" => "MessageIdField",
43
+ "in-reply-to" => "InReplyToField",
44
+ "references" => "ReferencesField",
45
+ "subject" => "SubjectField",
46
+ "comments" => "CommentsField",
47
+ "keywords" => "KeywordsField",
48
+ "date" => "DateField",
49
+ "from" => "FromField",
50
+ "sender" => "SenderField",
51
+ "reply-to" => "ReplyToField",
52
+ "resent-date" => "ResentDateField",
53
+ "resent-from" => "ResentFromField",
54
+ "resent-sender" => "ResentSenderField",
55
+ "resent-to" => "ResentToField",
56
+ "resent-cc" => "ResentCcField",
57
+ "resent-bcc" => "ResentBccField",
58
+ "resent-message-id" => "ResentMessageIdField",
59
+ "return-path" => "ReturnPathField",
60
+ "received" => "ReceivedField",
61
+ "mime-version" => "MimeVersionField",
62
+ "content-transfer-encoding" => "ContentTransferEncodingField",
63
+ "content-description" => "ContentDescriptionField",
64
+ "content-disposition" => "ContentDispositionField",
65
+ "content-type" => "ContentTypeField",
66
+ "content-id" => "ContentIdField",
67
+ "content-location" => "ContentLocationField",
69
68
  }
70
69
 
71
- FIELD_NAME_MAP = FIELDS_MAP.inject({}) do |map, (field, field_klass)|
72
- map.update(field => field_klass::CAPITALIZED_FIELD)
73
- end
70
+ FIELD_NAME_MAP = {
71
+ "to" => "To",
72
+ "cc" => "Cc",
73
+ "bcc" => "Bcc",
74
+ "message-id" => "Message-ID",
75
+ "in-reply-to" => "In-Reply-To",
76
+ "references" => "References",
77
+ "subject" => "Subject",
78
+ "comments" => "Comments",
79
+ "keywords" => "Keywords",
80
+ "date" => "Date",
81
+ "from" => "From",
82
+ "sender" => "Sender",
83
+ "reply-to" => "Reply-To",
84
+ "resent-date" => "Resent-Date",
85
+ "resent-from" => "Resent-From",
86
+ "resent-sender" => "Resent-Sender",
87
+ "resent-to" => "Resent-To",
88
+ "resent-cc" => "Resent-Cc",
89
+ "resent-bcc" => "Resent-Bcc",
90
+ "resent-message-id" => "Resent-Message-ID",
91
+ "return-path" => "Return-Path",
92
+ "received" => "Received",
93
+ "mime-version" => "MIME-Version",
94
+ "content-transfer-encoding" => "Content-Transfer-Encoding",
95
+ "content-description" => "Content-Description",
96
+ "content-disposition" => "Content-Disposition",
97
+ "content-type" => "Content-Type",
98
+ "content-id" => "Content-ID",
99
+ "content-location" => "Content-Location",
100
+ }
74
101
 
75
102
  # Generic Field Exception
76
103
  class FieldError < StandardError
@@ -83,9 +110,31 @@ module Mail
83
110
 
84
111
  def initialize(element, value, reason)
85
112
  @element = element
86
- @value = value
87
- @reason = reason
88
- super("#{element} can not parse |#{value}|\nReason was: #{reason}")
113
+ @value = to_utf8(value)
114
+ @reason = to_utf8(reason)
115
+ super("#{@element} can not parse |#{@value}|: #{@reason}")
116
+ end
117
+
118
+ private
119
+ def to_utf8(text)
120
+ if text.respond_to?(:force_encoding)
121
+ text.dup.force_encoding(Encoding::UTF_8)
122
+ else
123
+ text
124
+ end
125
+ end
126
+ end
127
+
128
+ class NilParseError < ParseError #:nodoc:
129
+ def initialize(element)
130
+ super element, nil, 'nil is invalid'
131
+ end
132
+ end
133
+
134
+ class IncompleteParseError < ParseError #:nodoc:
135
+ def initialize(element, original_text, unparsed_index)
136
+ parsed_text = to_utf8(original_text[0...unparsed_index])
137
+ super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
89
138
  end
90
139
  end
91
140
 
@@ -93,53 +142,79 @@ module Mail
93
142
  class SyntaxError < FieldError #:nodoc:
94
143
  end
95
144
 
96
- # Accepts a string:
97
- #
98
- # Field.new("field-name: field data")
99
- #
100
- # Or name, value pair:
101
- #
102
- # Field.new("field-name", "value")
145
+ class << self
146
+ # Parse a field from a raw header line:
147
+ #
148
+ # Mail::Field.parse("field-name: field data")
149
+ # # => #<Mail::Field …>
150
+ def parse(field, charset = 'utf-8')
151
+ name, value = split(field)
152
+ if name && value
153
+ new name, value, charset
154
+ end
155
+ end
156
+
157
+ def split(raw_field) #:nodoc:
158
+ if raw_field.index(Constants::COLON)
159
+ name, value = raw_field.split(Constants::COLON, 2)
160
+ name.rstrip!
161
+ if /\A#{Constants::FIELD_NAME}\z/.match?(name)
162
+ [ name.rstrip, value.strip ]
163
+ else
164
+ Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
165
+ nil
166
+ end
167
+ else
168
+ raw_field.strip
169
+ end
170
+ rescue => error
171
+ warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
172
+ nil
173
+ end
174
+
175
+ def field_class_for(name) #:nodoc:
176
+ class_name = FIELDS_MAP[name.to_s.downcase]
177
+ Mail.const_get(class_name) if class_name
178
+ end
179
+ end
180
+
181
+ attr_reader :unparsed_value
182
+
183
+ # Create a field by name and optional value:
103
184
  #
104
- # Or a name by itself:
185
+ # Mail::Field.new("field-name", "value")
186
+ # # => #<Mail::Field …>
105
187
  #
106
- # Field.new("field-name")
188
+ # Values that aren't strings or arrays are coerced to Strings with `#to_s`.
107
189
  #
108
- # Note, does not want a terminating carriage return. Returns
109
- # self appropriately parsed. If value is not a string, then
110
- # it will be passed through as is, for example, content-type
111
- # field can accept an array with the type and a hash of
112
- # parameters:
190
+ # Mail::Field.new("field-name", 1234)
191
+ # # => #<Mail::Field …>
113
192
  #
114
- # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
193
+ # Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
194
+ # # => #<Mail::Field …>
115
195
  def initialize(name, value = nil, charset = 'utf-8')
116
196
  case
117
- when name.index(COLON) # Field.new("field-name: field data")
118
- @charset = Utilities.blank?(value) ? charset : value
119
- @name = name[FIELD_PREFIX]
120
- @raw_value = name
121
- @value = nil
122
- when Utilities.blank?(value) # Field.new("field-name")
197
+ when name.index(Constants::COLON)
198
+ raise ArgumentError, 'Passing an unparsed header field to Mail::Field.new is not supported in Mail 2.8.0+. Use Mail::Field.parse instead.'
199
+ when Utilities.blank?(value)
123
200
  @name = name
124
- @value = nil
125
- @raw_value = nil
201
+ @unparsed_value = nil
126
202
  @charset = charset
127
- else # Field.new("field-name", "value")
203
+ else
128
204
  @name = name
129
- @value = value
130
- @raw_value = nil
205
+ @unparsed_value = value
131
206
  @charset = charset
132
207
  end
133
- @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
208
+ klass = self.class.field_class_for(@name)
209
+ @name = klass ? klass::NAME : @name
134
210
  end
135
211
 
136
- def field=(value)
137
- @field = value
212
+ def field=(field)
213
+ @field = field
138
214
  end
139
215
 
140
216
  def field
141
- _, @value = split(@raw_value) if @raw_value && !@value
142
- @field ||= create_field(@name, @value, @charset)
217
+ @field ||= create_field(@name, @unparsed_value, @charset)
143
218
  end
144
219
 
145
220
  def name
@@ -164,64 +239,63 @@ module Mail
164
239
  end.join(" ")}>"
165
240
  end
166
241
 
167
- def update(name, value)
168
- @field = create_field(name, value, @charset)
169
- end
170
-
171
- def same( other )
172
- return false unless other.kind_of?(self.class)
173
- match_to_s(other.name, self.name)
242
+ def same(other)
243
+ other.kind_of?(self.class) && Utilities.match_to_s(other.name, name)
174
244
  end
175
245
 
176
- def ==( other )
177
- return false unless other.kind_of?(self.class)
178
- match_to_s(other.name, self.name) && match_to_s(other.value, self.value)
246
+ def ==(other)
247
+ same(other) && Utilities.match_to_s(other.value, value)
179
248
  end
180
249
 
181
- def responsible_for?( val )
182
- name.to_s.casecmp(val.to_s) == 0
250
+ def responsible_for?(field_name)
251
+ name.to_s.casecmp(field_name.to_s) == 0
183
252
  end
184
253
 
185
- def <=>( other )
186
- self.field_order_id <=> other.field_order_id
254
+ def <=>(other)
255
+ field_order_id <=> other.field_order_id
187
256
  end
188
257
 
189
258
  def field_order_id
190
- @field_order_id ||= (FIELD_ORDER_LOOKUP[self.name.to_s.downcase] || 100)
259
+ @field_order_id ||= FIELD_ORDER_LOOKUP.fetch(self.name.to_s.downcase, 100)
191
260
  end
192
261
 
193
262
  def method_missing(name, *args, &block)
194
263
  field.send(name, *args, &block)
195
264
  end
196
265
 
197
- if RUBY_VERSION >= '1.9.2'
198
- def respond_to_missing?(method_name, include_private)
199
- field.respond_to?(method_name, include_private) || super
200
- end
201
- else
202
- def respond_to?(method_name, include_private = false)
203
- field.respond_to?(method_name, include_private) || super
204
- end
266
+ def respond_to_missing?(method_name, include_private)
267
+ field.respond_to?(method_name, include_private) || super
205
268
  end
206
269
 
207
- FIELD_ORDER = %w[ return-path received
208
- resent-date resent-from resent-sender resent-to
209
- resent-cc resent-bcc resent-message-id
210
- date from sender reply-to to cc bcc
211
- message-id in-reply-to references
212
- subject comments keywords
213
- mime-version content-type content-transfer-encoding
214
- content-location content-disposition content-description ]
215
-
216
- FIELD_ORDER_LOOKUP = Hash[FIELD_ORDER.each_with_index.to_a]
270
+ FIELD_ORDER_LOOKUP = Hash[%w[
271
+ return-path received
272
+ resent-date resent-from resent-sender resent-to
273
+ resent-cc resent-bcc resent-message-id
274
+ date from sender reply-to to cc bcc
275
+ message-id in-reply-to references
276
+ subject comments keywords
277
+ mime-version content-type content-transfer-encoding
278
+ content-location content-disposition content-description
279
+ ].each_with_index.to_a]
217
280
 
218
281
  private
219
282
 
220
- def split(raw_field)
221
- match_data = raw_field.mb_chars.match(FIELD_SPLIT)
222
- [match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip.to_s]
223
- rescue
224
- STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
283
+ def create_field(name, value, charset)
284
+ parse_field(name, value, charset)
285
+ rescue Mail::Field::ParseError => e
286
+ field = Mail::UnstructuredField.new(name, value)
287
+ field.errors << [name, value, e]
288
+ field
289
+ end
290
+
291
+ def parse_field(name, value, charset)
292
+ value = unfold(value) if value.is_a?(String)
293
+
294
+ if klass = self.class.field_class_for(name)
295
+ klass.parse(value, charset)
296
+ else
297
+ OptionalField.parse(name, value, charset)
298
+ end
225
299
  end
226
300
 
227
301
  # 2.2.3. Long Header Fields
@@ -233,30 +307,7 @@ module Mail
233
307
  # treated in its unfolded form for further syntactic and semantic
234
308
  # evaluation.
235
309
  def unfold(string)
236
- string.gsub(/[\r\n \t]+/m, ' ')
310
+ string.gsub(Constants::UNFOLD_WS, '\1')
237
311
  end
238
-
239
- def create_field(name, value, charset)
240
- value = unfold(value) if value.is_a?(String)
241
-
242
- begin
243
- new_field(name, value, charset)
244
- rescue Mail::Field::ParseError => e
245
- field = Mail::UnstructuredField.new(name, value)
246
- field.errors << [name, value, e]
247
- field
248
- end
249
- end
250
-
251
- def new_field(name, value, charset)
252
- lower_case_name = name.to_s.downcase
253
- if field_klass = FIELDS_MAP[lower_case_name]
254
- field_klass.new(value, charset)
255
- else
256
- OptionalField.new(name, value, charset)
257
- end
258
- end
259
-
260
312
  end
261
-
262
313
  end
@@ -2,12 +2,40 @@
2
2
  # frozen_string_literal: true
3
3
  module Mail
4
4
 
5
- # Field List class provides an enhanced array that keeps a list of
5
+ # Field List class provides an enhanced array that keeps a list of
6
6
  # email fields in order. And allows you to insert new fields without
7
7
  # having to worry about the order they will appear in.
8
8
  class FieldList < Array
9
+ def has_field?(field_name)
10
+ any? { |f| f.responsible_for? field_name }
11
+ end
12
+
13
+ def get_field(field_name)
14
+ fields = select_fields(field_name)
15
+ case fields.size
16
+ when 0; nil
17
+ when 1; fields.first
18
+ else fields
19
+ end
20
+ end
9
21
 
10
- include Enumerable
22
+ def add_field(field)
23
+ if field.singular?
24
+ replace_field field
25
+ else
26
+ insert_field field
27
+ end
28
+ end
29
+ alias_method :<<, :add_field
30
+
31
+ def replace_field(field)
32
+ if first_offset = index { |f| f.responsible_for? field.name }
33
+ delete_field field.name
34
+ insert first_offset, field
35
+ else
36
+ insert_field field
37
+ end
38
+ end
11
39
 
12
40
  # Insert the field in sorted order.
13
41
  #
@@ -15,20 +43,45 @@ module Mail
15
43
  # Copyright (C) 2001-2013 Python Software Foundation.
16
44
  # Licensed under <http://docs.python.org/license.html>
17
45
  # From <http://hg.python.org/cpython/file/2.7/Lib/bisect.py>
18
- def <<( new_field )
19
- lo = 0
20
- hi = size
21
-
46
+ def insert_field(field)
47
+ lo, hi = 0, size
22
48
  while lo < hi
23
49
  mid = (lo + hi).div(2)
24
- if new_field < self[mid]
50
+ if field < self[mid]
25
51
  hi = mid
26
52
  else
27
53
  lo = mid + 1
28
54
  end
29
55
  end
30
56
 
31
- insert(lo, new_field)
57
+ insert lo, field
58
+ end
59
+
60
+ def delete_field(name)
61
+ delete_if { |f| f.responsible_for? name }
62
+ end
63
+
64
+ def summary
65
+ map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
66
+ end
67
+
68
+ private
69
+
70
+ def select_fields(field_name)
71
+ fields = select { |f| f.responsible_for? field_name }
72
+ if fields.size > 1 && singular?(field_name)
73
+ Array(fields.detect { |f| f.errors.size == 0 } || fields.first)
74
+ else
75
+ fields
76
+ end
77
+ end
78
+
79
+ def singular?(field_name)
80
+ if klass = Mail::Field.field_class_for(field_name)
81
+ klass.singular?
82
+ else
83
+ false
84
+ end
32
85
  end
33
86
  end
34
87
  end
@@ -1,68 +1,49 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
- #
4
- # = Blind Carbon Copy Field
5
- #
6
- # The Bcc field inherits from StructuredField and handles the Bcc: header
7
- # field in the email.
8
- #
9
- # Sending bcc to a mail message will instantiate a Mail::Field object that
10
- # has a BccField as its field type. This includes all Mail::CommonAddress
11
- # module instance metods.
12
- #
13
- # Only one Bcc field can appear in a header, though it can have multiple
14
- # addresses and groups of addresses.
15
- #
16
- # == Examples:
17
- #
18
- # mail = Mail.new
19
- # mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
20
- # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
21
- # mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
22
- # mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
23
- # mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
24
- #
25
- # mail[:bcc].encoded #=> '' # Bcc field does not get output into an email
26
- # mail[:bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
27
- # mail[:bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
28
- # mail[:bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
29
- #
30
- require 'mail/fields/common/common_address'
31
3
 
32
4
  module Mail
33
- class BccField < StructuredField
34
-
35
- include Mail::CommonAddress
36
-
37
- FIELD_NAME = 'bcc'
38
- CAPITALIZED_FIELD = 'Bcc'
39
-
40
- def initialize(value = '', charset = 'utf-8')
41
- @charset = charset
42
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
43
- self
44
- end
45
-
46
- def include_in_headers=(include_in_headers)
47
- @include_in_headers = include_in_headers
48
- end
5
+ # = Blind Carbon Copy Field
6
+ #
7
+ # The Bcc field inherits from StructuredField and handles the Bcc: header
8
+ # field in the email.
9
+ #
10
+ # Sending bcc to a mail message will instantiate a Mail::Field object that
11
+ # has a BccField as its field type. This includes all Mail::CommonAddress
12
+ # module instance metods.
13
+ #
14
+ # Only one Bcc field can appear in a header, though it can have multiple
15
+ # addresses and groups of addresses.
16
+ #
17
+ # == Examples:
18
+ #
19
+ # mail = Mail.new
20
+ # mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
21
+ # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
22
+ # mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
23
+ # mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
24
+ # mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
25
+ #
26
+ # mail[:bcc].encoded #=> '' # Bcc field does not get output into an email
27
+ # mail[:bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
28
+ # mail[:bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
29
+ # mail[:bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
30
+ class BccField < CommonAddressField #:nodoc:
31
+ NAME = 'Bcc'
49
32
 
50
- def include_in_headers
51
- defined?(@include_in_headers) ? @include_in_headers : self.include_in_headers = false
33
+ attr_accessor :include_in_headers
34
+
35
+ def initialize(value = nil, charset = nil)
36
+ super
37
+ self.include_in_headers = false
52
38
  end
53
39
 
54
40
  # Bcc field should not be :encoded by default
55
41
  def encoded
56
42
  if include_in_headers
57
- do_encode(CAPITALIZED_FIELD)
43
+ super
58
44
  else
59
45
  ''
60
46
  end
61
47
  end
62
-
63
- def decoded
64
- do_decode
65
- end
66
-
67
48
  end
68
49
  end
@@ -1,55 +1,33 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
- #
4
- # = Carbon Copy Field
5
- #
6
- # The Cc field inherits from StructuredField and handles the Cc: header
7
- # field in the email.
8
- #
9
- # Sending cc to a mail message will instantiate a Mail::Field object that
10
- # has a CcField as its field type. This includes all Mail::CommonAddress
11
- # module instance metods.
12
- #
13
- # Only one Cc field can appear in a header, though it can have multiple
14
- # addresses and groups of addresses.
15
- #
16
- # == Examples:
17
- #
18
- # mail = Mail.new
19
- # mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
20
- # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
21
- # mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
22
- # mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
23
- # mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
24
- #
25
- # mail[:cc].encoded #=> 'Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
26
- # mail[:cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
27
- # mail[:cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
28
- # mail[:cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
29
- #
30
- require 'mail/fields/common/common_address'
31
3
 
32
4
  module Mail
33
- class CcField < StructuredField
34
-
35
- include Mail::CommonAddress
36
-
37
- FIELD_NAME = 'cc'
38
- CAPITALIZED_FIELD = 'Cc'
39
-
40
- def initialize(value = nil, charset = 'utf-8')
41
- self.charset = charset
42
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
43
- self
44
- end
45
-
46
- def encoded
47
- do_encode(CAPITALIZED_FIELD)
48
- end
49
-
50
- def decoded
51
- do_decode
52
- end
53
-
5
+ # = Carbon Copy Field
6
+ #
7
+ # The Cc field inherits from StructuredField and handles the Cc: header
8
+ # field in the email.
9
+ #
10
+ # Sending cc to a mail message will instantiate a Mail::Field object that
11
+ # has a CcField as its field type. This includes all Mail::CommonAddress
12
+ # module instance metods.
13
+ #
14
+ # Only one Cc field can appear in a header, though it can have multiple
15
+ # addresses and groups of addresses.
16
+ #
17
+ # == Examples:
18
+ #
19
+ # mail = Mail.new
20
+ # mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
21
+ # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
22
+ # mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
23
+ # mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
24
+ # mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
25
+ #
26
+ # mail[:cc].encoded #=> 'Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
27
+ # mail[:cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
28
+ # mail[:cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
29
+ # mail[:cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
30
+ class CcField < CommonAddressField #:nodoc:
31
+ NAME = 'Cc'
54
32
  end
55
33
  end