mail 2.5.5 → 2.8.1

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 (191) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +170 -108
  4. data/lib/mail/attachments_list.rb +13 -10
  5. data/lib/mail/body.rb +105 -91
  6. data/lib/mail/check_delivery_params.rb +30 -22
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +79 -0
  9. data/lib/mail/elements/address.rb +118 -174
  10. data/lib/mail/elements/address_list.rb +16 -56
  11. data/lib/mail/elements/content_disposition_element.rb +12 -22
  12. data/lib/mail/elements/content_location_element.rb +9 -17
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +8 -19
  14. data/lib/mail/elements/content_type_element.rb +20 -30
  15. data/lib/mail/elements/date_time_element.rb +10 -21
  16. data/lib/mail/elements/envelope_from_element.rb +23 -31
  17. data/lib/mail/elements/message_ids_element.rb +22 -20
  18. data/lib/mail/elements/mime_version_element.rb +10 -21
  19. data/lib/mail/elements/phrase_list.rb +13 -15
  20. data/lib/mail/elements/received_element.rb +26 -21
  21. data/lib/mail/elements.rb +1 -0
  22. data/lib/mail/encodings/7bit.rb +10 -14
  23. data/lib/mail/encodings/8bit.rb +5 -18
  24. data/lib/mail/encodings/base64.rb +15 -10
  25. data/lib/mail/encodings/binary.rb +4 -22
  26. data/lib/mail/encodings/identity.rb +24 -0
  27. data/lib/mail/encodings/quoted_printable.rb +13 -7
  28. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  29. data/lib/mail/encodings/unix_to_unix.rb +20 -0
  30. data/lib/mail/encodings.rb +102 -93
  31. data/lib/mail/envelope.rb +12 -19
  32. data/lib/mail/field.rb +143 -71
  33. data/lib/mail/field_list.rb +73 -19
  34. data/lib/mail/fields/bcc_field.rb +42 -48
  35. data/lib/mail/fields/cc_field.rb +29 -50
  36. data/lib/mail/fields/comments_field.rb +28 -37
  37. data/lib/mail/fields/common_address_field.rb +170 -0
  38. data/lib/mail/fields/common_date_field.rb +58 -0
  39. data/lib/mail/fields/common_field.rb +77 -0
  40. data/lib/mail/fields/common_message_id_field.rb +42 -0
  41. data/lib/mail/fields/content_description_field.rb +8 -14
  42. data/lib/mail/fields/content_disposition_field.rb +20 -44
  43. data/lib/mail/fields/content_id_field.rb +25 -51
  44. data/lib/mail/fields/content_location_field.rb +12 -25
  45. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -36
  46. data/lib/mail/fields/content_type_field.rb +51 -80
  47. data/lib/mail/fields/date_field.rb +24 -52
  48. data/lib/mail/fields/from_field.rb +29 -50
  49. data/lib/mail/fields/in_reply_to_field.rb +39 -49
  50. data/lib/mail/fields/keywords_field.rb +19 -32
  51. data/lib/mail/fields/message_id_field.rb +26 -71
  52. data/lib/mail/fields/mime_version_field.rb +20 -30
  53. data/lib/mail/fields/named_structured_field.rb +11 -0
  54. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  55. data/lib/mail/fields/optional_field.rb +10 -7
  56. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +16 -13
  57. data/lib/mail/fields/received_field.rb +44 -57
  58. data/lib/mail/fields/references_field.rb +36 -49
  59. data/lib/mail/fields/reply_to_field.rb +29 -50
  60. data/lib/mail/fields/resent_bcc_field.rb +29 -50
  61. data/lib/mail/fields/resent_cc_field.rb +29 -50
  62. data/lib/mail/fields/resent_date_field.rb +6 -30
  63. data/lib/mail/fields/resent_from_field.rb +29 -50
  64. data/lib/mail/fields/resent_message_id_field.rb +6 -29
  65. data/lib/mail/fields/resent_sender_field.rb +28 -57
  66. data/lib/mail/fields/resent_to_field.rb +29 -50
  67. data/lib/mail/fields/return_path_field.rb +51 -55
  68. data/lib/mail/fields/sender_field.rb +35 -56
  69. data/lib/mail/fields/structured_field.rb +4 -30
  70. data/lib/mail/fields/subject_field.rb +10 -11
  71. data/lib/mail/fields/to_field.rb +29 -50
  72. data/lib/mail/fields/unstructured_field.rb +43 -51
  73. data/lib/mail/fields.rb +1 -0
  74. data/lib/mail/header.rb +78 -129
  75. data/lib/mail/indifferent_hash.rb +1 -0
  76. data/lib/mail/mail.rb +18 -11
  77. data/lib/mail/matchers/attachment_matchers.rb +44 -0
  78. data/lib/mail/matchers/has_sent_mail.rb +81 -4
  79. data/lib/mail/message.rb +142 -139
  80. data/lib/mail/multibyte/chars.rb +24 -180
  81. data/lib/mail/multibyte/unicode.rb +32 -27
  82. data/lib/mail/multibyte/utils.rb +27 -43
  83. data/lib/mail/multibyte.rb +56 -16
  84. data/lib/mail/network/delivery_methods/exim.rb +6 -4
  85. data/lib/mail/network/delivery_methods/file_delivery.rb +12 -10
  86. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  87. data/lib/mail/network/delivery_methods/sendmail.rb +63 -21
  88. data/lib/mail/network/delivery_methods/smtp.rb +76 -50
  89. data/lib/mail/network/delivery_methods/smtp_connection.rb +4 -4
  90. data/lib/mail/network/delivery_methods/test_mailer.rb +5 -2
  91. data/lib/mail/network/retriever_methods/base.rb +9 -8
  92. data/lib/mail/network/retriever_methods/imap.rb +37 -18
  93. data/lib/mail/network/retriever_methods/pop3.rb +6 -3
  94. data/lib/mail/network/retriever_methods/test_retriever.rb +4 -2
  95. data/lib/mail/network.rb +2 -0
  96. data/lib/mail/parser_tools.rb +15 -0
  97. data/lib/mail/parsers/address_lists_parser.rb +33242 -0
  98. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  99. data/lib/mail/parsers/content_disposition_parser.rb +901 -0
  100. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  101. data/lib/mail/parsers/content_location_parser.rb +822 -0
  102. data/lib/mail/parsers/content_location_parser.rl +78 -0
  103. data/lib/mail/parsers/content_transfer_encoding_parser.rb +522 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  105. data/lib/mail/parsers/content_type_parser.rb +1048 -0
  106. data/lib/mail/parsers/content_type_parser.rl +90 -0
  107. data/lib/mail/parsers/date_time_parser.rb +891 -0
  108. data/lib/mail/parsers/date_time_parser.rl +69 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +3675 -0
  110. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  111. data/lib/mail/parsers/message_ids_parser.rb +5161 -0
  112. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +513 -0
  114. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  115. data/lib/mail/parsers/phrase_lists_parser.rb +884 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  117. data/lib/mail/parsers/received_parser.rb +8782 -0
  118. data/lib/mail/parsers/received_parser.rl +91 -0
  119. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  120. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  121. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  122. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  123. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  124. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  125. data/lib/mail/parsers/rfc5322.rl +74 -0
  126. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  127. data/lib/mail/parsers/rfc5322_date_time.rl +37 -0
  128. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  129. data/lib/mail/parsers.rb +13 -0
  130. data/lib/mail/part.rb +11 -12
  131. data/lib/mail/parts_list.rb +90 -14
  132. data/lib/mail/smtp_envelope.rb +57 -0
  133. data/lib/mail/utilities.rb +415 -76
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/yaml.rb +30 -0
  137. data/lib/mail.rb +9 -32
  138. metadata +127 -79
  139. data/CHANGELOG.rdoc +0 -742
  140. data/CONTRIBUTING.md +0 -45
  141. data/Dependencies.txt +0 -3
  142. data/Gemfile +0 -32
  143. data/Rakefile +0 -21
  144. data/TODO.rdoc +0 -9
  145. data/lib/VERSION +0 -4
  146. data/lib/load_parsers.rb +0 -35
  147. data/lib/mail/core_extensions/nil.rb +0 -19
  148. data/lib/mail/core_extensions/object.rb +0 -13
  149. data/lib/mail/core_extensions/smtp.rb +0 -24
  150. data/lib/mail/core_extensions/string/access.rb +0 -145
  151. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  152. data/lib/mail/core_extensions/string.rb +0 -33
  153. data/lib/mail/fields/common/address_container.rb +0 -16
  154. data/lib/mail/fields/common/common_address.rb +0 -140
  155. data/lib/mail/fields/common/common_date.rb +0 -42
  156. data/lib/mail/fields/common/common_field.rb +0 -57
  157. data/lib/mail/fields/common/common_message_id.rb +0 -48
  158. data/lib/mail/multibyte/exceptions.rb +0 -8
  159. data/lib/mail/parsers/address_lists.rb +0 -64
  160. data/lib/mail/parsers/address_lists.treetop +0 -19
  161. data/lib/mail/parsers/content_disposition.rb +0 -535
  162. data/lib/mail/parsers/content_disposition.treetop +0 -46
  163. data/lib/mail/parsers/content_location.rb +0 -139
  164. data/lib/mail/parsers/content_location.treetop +0 -20
  165. data/lib/mail/parsers/content_transfer_encoding.rb +0 -201
  166. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -18
  167. data/lib/mail/parsers/content_type.rb +0 -971
  168. data/lib/mail/parsers/content_type.treetop +0 -68
  169. data/lib/mail/parsers/date_time.rb +0 -114
  170. data/lib/mail/parsers/date_time.treetop +0 -11
  171. data/lib/mail/parsers/envelope_from.rb +0 -194
  172. data/lib/mail/parsers/envelope_from.treetop +0 -32
  173. data/lib/mail/parsers/message_ids.rb +0 -45
  174. data/lib/mail/parsers/message_ids.treetop +0 -15
  175. data/lib/mail/parsers/mime_version.rb +0 -144
  176. data/lib/mail/parsers/mime_version.treetop +0 -19
  177. data/lib/mail/parsers/phrase_lists.rb +0 -45
  178. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  179. data/lib/mail/parsers/received.rb +0 -71
  180. data/lib/mail/parsers/received.treetop +0 -11
  181. data/lib/mail/parsers/rfc2045.rb +0 -421
  182. data/lib/mail/parsers/rfc2045.treetop +0 -35
  183. data/lib/mail/parsers/rfc2822.rb +0 -5397
  184. data/lib/mail/parsers/rfc2822.treetop +0 -408
  185. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  186. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  187. data/lib/mail/patterns.rb +0 -35
  188. data/lib/mail/version_specific/ruby_1_8.rb +0 -119
  189. data/lib/mail/version_specific/ruby_1_9.rb +0 -147
  190. data/lib/tasks/corpus.rake +0 -125
  191. data/lib/tasks/treetop.rake +0 -10
data/lib/mail/field.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'mail/fields'
3
+ require 'mail/constants'
2
4
 
3
5
  # encoding: utf-8
4
6
  module Mail
@@ -21,8 +23,6 @@ module Mail
21
23
  # sections 3 and 4 of this standard.
22
24
  #
23
25
  class Field
24
-
25
- include Patterns
26
26
  include Comparable
27
27
 
28
28
  STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
@@ -67,6 +67,10 @@ module Mail
67
67
  "content-location" => ContentLocationField,
68
68
  }
69
69
 
70
+ FIELD_NAME_MAP = FIELDS_MAP.inject({}) do |map, (field, field_klass)|
71
+ map.update(field => field_klass::NAME)
72
+ end
73
+
70
74
  # Generic Field Exception
71
75
  class FieldError < StandardError
72
76
  end
@@ -78,9 +82,31 @@ module Mail
78
82
 
79
83
  def initialize(element, value, reason)
80
84
  @element = element
81
- @value = value
82
- @reason = reason
83
- super("#{element} can not parse |#{value}|\nReason was: #{reason}")
85
+ @value = to_utf8(value)
86
+ @reason = to_utf8(reason)
87
+ super("#{@element} can not parse |#{@value}|: #{@reason}")
88
+ end
89
+
90
+ private
91
+ def to_utf8(text)
92
+ if text.respond_to?(:force_encoding)
93
+ text.dup.force_encoding(Encoding::UTF_8)
94
+ else
95
+ text
96
+ end
97
+ end
98
+ end
99
+
100
+ class NilParseError < ParseError #:nodoc:
101
+ def initialize(element)
102
+ super element, nil, 'nil is invalid'
103
+ end
104
+ end
105
+
106
+ class IncompleteParseError < ParseError #:nodoc:
107
+ def initialize(element, original_text, unparsed_index)
108
+ parsed_text = to_utf8(original_text[0...unparsed_index])
109
+ super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
84
110
  end
85
111
  end
86
112
 
@@ -88,49 +114,81 @@ module Mail
88
114
  class SyntaxError < FieldError #:nodoc:
89
115
  end
90
116
 
91
- # Accepts a string:
92
- #
93
- # Field.new("field-name: field data")
94
- #
95
- # Or name, value pair:
96
- #
97
- # Field.new("field-name", "value")
117
+ class << self
118
+ # Parse a field from a raw header line:
119
+ #
120
+ # Mail::Field.parse("field-name: field data")
121
+ # # => #<Mail::Field …>
122
+ def parse(field, charset = 'utf-8')
123
+ name, value = split(field)
124
+ if name && value
125
+ new name, value, charset
126
+ end
127
+ end
128
+
129
+ def split(raw_field) #:nodoc:
130
+ if raw_field.index(Constants::COLON)
131
+ name, value = raw_field.split(Constants::COLON, 2)
132
+ name.rstrip!
133
+ if name =~ /\A#{Constants::FIELD_NAME}\z/
134
+ [ name.rstrip, value.strip ]
135
+ else
136
+ Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
137
+ nil
138
+ end
139
+ else
140
+ raw_field.strip
141
+ end
142
+ rescue => error
143
+ warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
144
+ nil
145
+ end
146
+
147
+ def field_class_for(name) #:nodoc:
148
+ FIELDS_MAP[name.to_s.downcase]
149
+ end
150
+ end
151
+
152
+ attr_reader :unparsed_value
153
+
154
+ # Create a field by name and optional value:
98
155
  #
99
- # Or a name by itself:
156
+ # Mail::Field.new("field-name", "value")
157
+ # # => #<Mail::Field …>
100
158
  #
101
- # Field.new("field-name")
159
+ # Values that aren't strings or arrays are coerced to Strings with `#to_s`.
102
160
  #
103
- # Note, does not want a terminating carriage return. Returns
104
- # self appropriately parsed. If value is not a string, then
105
- # it will be passed through as is, for example, content-type
106
- # field can accept an array with the type and a hash of
107
- # parameters:
161
+ # Mail::Field.new("field-name", 1234)
162
+ # # => #<Mail::Field …>
108
163
  #
109
- # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
164
+ # Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
165
+ # # => #<Mail::Field …>
110
166
  def initialize(name, value = nil, charset = 'utf-8')
111
167
  case
112
- when name =~ /:/ # Field.new("field-name: field data")
113
- charset = value unless value.blank?
114
- name, value = split(name)
115
- create_field(name, value, charset)
116
- when name !~ /:/ && value.blank? # Field.new("field-name")
117
- create_field(name, nil, charset)
118
- else # Field.new("field-name", "value")
119
- create_field(name, value, charset)
168
+ when name.index(Constants::COLON)
169
+ raise ArgumentError, 'Passing an unparsed header field to Mail::Field.new is not supported in Mail 2.8.0+. Use Mail::Field.parse instead.'
170
+ when Utilities.blank?(value)
171
+ @name = name
172
+ @unparsed_value = nil
173
+ @charset = charset
174
+ else
175
+ @name = name
176
+ @unparsed_value = value
177
+ @charset = charset
120
178
  end
121
- return self
179
+ @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
122
180
  end
123
181
 
124
- def field=(value)
125
- @field = value
182
+ def field=(field)
183
+ @field = field
126
184
  end
127
185
 
128
186
  def field
129
- @field
187
+ @field ||= create_field(@name, @unparsed_value, @charset)
130
188
  end
131
189
 
132
190
  def name
133
- field.name
191
+ @name
134
192
  end
135
193
 
136
194
  def value
@@ -138,74 +196,88 @@ module Mail
138
196
  end
139
197
 
140
198
  def value=(val)
141
- create_field(name, val, charset)
199
+ @field = create_field(name, val, @charset)
142
200
  end
143
201
 
144
202
  def to_s
145
203
  field.to_s
146
204
  end
147
205
 
148
- def update(name, value)
149
- create_field(name, value, charset)
206
+ def inspect
207
+ "#<#{self.class.name} 0x#{(object_id * 2).to_s(16)} #{instance_variables.map do |ivar|
208
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
209
+ end.join(" ")}>"
210
+ end
211
+
212
+ def same(other)
213
+ other.kind_of?(self.class) && Utilities.match_to_s(other.name, name)
150
214
  end
151
215
 
152
- def same( other )
153
- match_to_s(other.name, field.name)
216
+ def ==(other)
217
+ same(other) && Utilities.match_to_s(other.value, value)
154
218
  end
155
219
 
156
- alias_method :==, :same
220
+ def responsible_for?(field_name)
221
+ name.to_s.casecmp(field_name.to_s) == 0
222
+ end
157
223
 
158
- def <=>( other )
159
- self.field_order_id <=> other.field_order_id
224
+ def <=>(other)
225
+ field_order_id <=> other.field_order_id
160
226
  end
161
227
 
162
228
  def field_order_id
163
- @field_order_id ||= (FIELD_ORDER_LOOKUP[self.name.to_s.downcase] || 100)
229
+ @field_order_id ||= FIELD_ORDER_LOOKUP.fetch(self.name.to_s.downcase, 100)
164
230
  end
165
231
 
166
232
  def method_missing(name, *args, &block)
167
233
  field.send(name, *args, &block)
168
234
  end
169
235
 
170
- FIELD_ORDER = %w[ return-path received
171
- resent-date resent-from resent-sender resent-to
172
- resent-cc resent-bcc resent-message-id
173
- date from sender reply-to to cc bcc
174
- message-id in-reply-to references
175
- subject comments keywords
176
- mime-version content-type content-transfer-encoding
177
- content-location content-disposition content-description ]
236
+ def respond_to_missing?(method_name, include_private)
237
+ field.respond_to?(method_name, include_private) || super
238
+ end
178
239
 
179
- FIELD_ORDER_LOOKUP = Hash[FIELD_ORDER.each_with_index.to_a]
240
+ FIELD_ORDER_LOOKUP = Hash[%w[
241
+ return-path received
242
+ resent-date resent-from resent-sender resent-to
243
+ resent-cc resent-bcc resent-message-id
244
+ date from sender reply-to to cc bcc
245
+ message-id in-reply-to references
246
+ subject comments keywords
247
+ mime-version content-type content-transfer-encoding
248
+ content-location content-disposition content-description
249
+ ].each_with_index.to_a]
180
250
 
181
251
  private
182
252
 
183
- def split(raw_field)
184
- match_data = raw_field.mb_chars.match(FIELD_SPLIT)
185
- [match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip]
186
- rescue
187
- STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
188
- end
189
-
190
253
  def create_field(name, value, charset)
191
- begin
192
- self.field = new_field(name, value, charset)
193
- rescue Mail::Field::ParseError => e
194
- self.field = Mail::UnstructuredField.new(name, value)
195
- self.field.errors << [name, value, e]
196
- self.field
197
- end
254
+ parse_field(name, value, charset)
255
+ rescue Mail::Field::ParseError => e
256
+ field = Mail::UnstructuredField.new(name, value)
257
+ field.errors << [name, value, e]
258
+ field
198
259
  end
199
260
 
200
- def new_field(name, value, charset)
201
- lower_case_name = name.to_s.downcase
202
- if field_klass = FIELDS_MAP[lower_case_name]
203
- field_klass.new(value, charset)
261
+ def parse_field(name, value, charset)
262
+ value = unfold(value) if value.is_a?(String)
263
+
264
+ if klass = self.class.field_class_for(name)
265
+ klass.parse(value, charset)
204
266
  else
205
- OptionalField.new(name, value, charset)
267
+ OptionalField.parse(name, value, charset)
206
268
  end
207
269
  end
208
270
 
271
+ # 2.2.3. Long Header Fields
272
+ #
273
+ # The process of moving from this folded multiple-line representation
274
+ # of a header field to its single line representation is called
275
+ # "unfolding". Unfolding is accomplished by simply removing any CRLF
276
+ # that is immediately followed by WSP. Each header field should be
277
+ # treated in its unfolded form for further syntactic and semantic
278
+ # evaluation.
279
+ def unfold(string)
280
+ string.gsub(Constants::UNFOLD_WS, '\1')
281
+ end
209
282
  end
210
-
211
283
  end
@@ -1,33 +1,87 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
 
4
5
  # Field List class provides an enhanced array that keeps a list of
5
6
  # email fields in order. And allows you to insert new fields without
6
7
  # having to worry about the order they will appear in.
7
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
8
21
 
9
- 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
10
30
 
11
- def <<( new_field )
12
- current_entry = self.rindex(new_field)
13
- if current_entry
14
- self.insert((current_entry + 1), new_field)
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
15
35
  else
16
- insert_idx = -1
17
- self.each_with_index do |item, idx|
18
- case item <=> new_field
19
- when -1
20
- next
21
- when 0
22
- next
23
- when 1
24
- insert_idx = idx
25
- break
26
- end
36
+ insert_field field
37
+ end
38
+ end
39
+
40
+ # Insert the field in sorted order.
41
+ #
42
+ # Heavily based on bisect.insort from Python, which is:
43
+ # Copyright (C) 2001-2013 Python Software Foundation.
44
+ # Licensed under <http://docs.python.org/license.html>
45
+ # From <http://hg.python.org/cpython/file/2.7/Lib/bisect.py>
46
+ def insert_field(field)
47
+ lo, hi = 0, size
48
+ while lo < hi
49
+ mid = (lo + hi).div(2)
50
+ if field < self[mid]
51
+ hi = mid
52
+ else
53
+ lo = mid + 1
27
54
  end
28
- insert(insert_idx, new_field)
55
+ end
56
+
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
29
84
  end
30
85
  end
31
-
32
86
  end
33
- end
87
+ end
@@ -1,56 +1,50 @@
1
1
  # encoding: utf-8
2
- #
3
- # = Blind Carbon Copy Field
4
- #
5
- # The Bcc field inherits from StructuredField and handles the Bcc: header
6
- # field in the email.
7
- #
8
- # Sending bcc to a mail message will instantiate a Mail::Field object that
9
- # has a BccField as its field type. This includes all Mail::CommonAddress
10
- # module instance metods.
11
- #
12
- # Only one Bcc field can appear in a header, though it can have multiple
13
- # addresses and groups of addresses.
14
- #
15
- # == Examples:
16
- #
17
- # mail = Mail.new
18
- # mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
19
- # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
20
- # mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
21
- # mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
22
- # mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
23
- #
24
- # mail[:bcc].encoded #=> '' # Bcc field does not get output into an email
25
- # mail[:bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
26
- # mail[:bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
27
- # mail[:bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
28
- #
29
- require 'mail/fields/common/common_address'
2
+ # frozen_string_literal: true
3
+ require 'mail/fields/common_address_field'
30
4
 
31
5
  module Mail
32
- class BccField < StructuredField
33
-
34
- include Mail::CommonAddress
35
-
36
- FIELD_NAME = 'bcc'
37
- CAPITALIZED_FIELD = 'Bcc'
38
-
39
- def initialize(value = '', charset = 'utf-8')
40
- @charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
43
- self
6
+ # = Blind Carbon Copy Field
7
+ #
8
+ # The Bcc field inherits from StructuredField and handles the Bcc: header
9
+ # field in the email.
10
+ #
11
+ # Sending bcc to a mail message will instantiate a Mail::Field object that
12
+ # has a BccField as its field type. This includes all Mail::CommonAddress
13
+ # module instance metods.
14
+ #
15
+ # Only one Bcc field can appear in a header, though it can have multiple
16
+ # addresses and groups of addresses.
17
+ #
18
+ # == Examples:
19
+ #
20
+ # mail = Mail.new
21
+ # mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
22
+ # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
23
+ # mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
24
+ # mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
25
+ # mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
26
+ #
27
+ # mail[:bcc].encoded #=> '' # Bcc field does not get output into an email
28
+ # mail[:bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
29
+ # mail[:bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
30
+ # mail[:bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
31
+ class BccField < CommonAddressField #:nodoc:
32
+ NAME = 'Bcc'
33
+
34
+ attr_accessor :include_in_headers
35
+
36
+ def initialize(value = nil, charset = nil)
37
+ super
38
+ self.include_in_headers = false
44
39
  end
45
-
46
- # Bcc field should never be :encoded
40
+
41
+ # Bcc field should not be :encoded by default
47
42
  def encoded
48
- ''
49
- end
50
-
51
- def decoded
52
- do_decode
43
+ if include_in_headers
44
+ super
45
+ else
46
+ ''
47
+ end
53
48
  end
54
-
55
49
  end
56
50
  end
@@ -1,55 +1,34 @@
1
1
  # encoding: utf-8
2
- #
3
- # = Carbon Copy Field
4
- #
5
- # The Cc field inherits from StructuredField and handles the Cc: header
6
- # field in the email.
7
- #
8
- # Sending cc to a mail message will instantiate a Mail::Field object that
9
- # has a CcField as its field type. This includes all Mail::CommonAddress
10
- # module instance metods.
11
- #
12
- # Only one Cc field can appear in a header, though it can have multiple
13
- # addresses and groups of addresses.
14
- #
15
- # == Examples:
16
- #
17
- # mail = Mail.new
18
- # mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
19
- # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
20
- # mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
21
- # mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
22
- # mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
23
- #
24
- # mail[:cc].encoded #=> 'Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
25
- # mail[:cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
26
- # mail[:cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
27
- # mail[:cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
28
- #
29
- require 'mail/fields/common/common_address'
2
+ # frozen_string_literal: true
3
+ require 'mail/fields/common_address_field'
30
4
 
31
5
  module Mail
32
- class CcField < StructuredField
33
-
34
- include Mail::CommonAddress
35
-
36
- FIELD_NAME = 'cc'
37
- CAPITALIZED_FIELD = 'Cc'
38
-
39
- def initialize(value = nil, charset = 'utf-8')
40
- self.charset = charset
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
- self.parse
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
-
6
+ # = Carbon Copy Field
7
+ #
8
+ # The Cc field inherits from StructuredField and handles the Cc: header
9
+ # field in the email.
10
+ #
11
+ # Sending cc to a mail message will instantiate a Mail::Field object that
12
+ # has a CcField as its field type. This includes all Mail::CommonAddress
13
+ # module instance metods.
14
+ #
15
+ # Only one Cc field can appear in a header, though it can have multiple
16
+ # addresses and groups of addresses.
17
+ #
18
+ # == Examples:
19
+ #
20
+ # mail = Mail.new
21
+ # mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
22
+ # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
23
+ # mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
24
+ # mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
25
+ # mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
26
+ #
27
+ # mail[:cc].encoded #=> 'Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
28
+ # mail[:cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
29
+ # mail[:cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
30
+ # mail[:cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
31
+ class CcField < CommonAddressField #:nodoc:
32
+ NAME = 'Cc'
54
33
  end
55
34
  end
@@ -1,41 +1,32 @@
1
1
  # encoding: utf-8
2
- #
3
- # = Comments Field
4
- #
5
- # The Comments field inherits from UnstructuredField and handles the Comments:
6
- # header field in the email.
7
- #
8
- # Sending comments to a mail message will instantiate a Mail::Field object that
9
- # has a CommentsField as its field type.
10
- #
11
- # An email header can have as many comments fields as it wants. There is no upper
12
- # limit, the comments field is also optional (that is, no comment is needed)
13
- #
14
- # == Examples:
15
- #
16
- # mail = Mail.new
17
- # mail.comments = 'This is a comment'
18
- # mail.comments #=> 'This is a comment'
19
- # mail[:comments] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
20
- # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
21
- # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
22
- #
23
- # mail.comments = "This is another comment"
24
- # mail[:comments].map { |c| c.to_s }
25
- # #=> ['This is a comment', "This is another comment"]
26
- #
2
+ # frozen_string_literal: true
3
+ require 'mail/fields/named_unstructured_field'
4
+
27
5
  module Mail
28
- class CommentsField < UnstructuredField
29
-
30
- FIELD_NAME = 'comments'
31
- CAPITALIZED_FIELD = 'Comments'
32
-
33
- def initialize(value = nil, charset = 'utf-8')
34
- @charset = charset
35
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value))
36
- self.parse
37
- self
38
- end
39
-
6
+ # = Comments Field
7
+ #
8
+ # The Comments field inherits from UnstructuredField and handles the Comments:
9
+ # header field in the email.
10
+ #
11
+ # Sending comments to a mail message will instantiate a Mail::Field object that
12
+ # has a CommentsField as its field type.
13
+ #
14
+ # An email header can have as many comments fields as it wants. There is no upper
15
+ # limit, the comments field is also optional (that is, no comment is needed)
16
+ #
17
+ # == Examples:
18
+ #
19
+ # mail = Mail.new
20
+ # mail.comments = 'This is a comment'
21
+ # mail.comments #=> 'This is a comment'
22
+ # mail[:comments] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
23
+ # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
24
+ # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
25
+ #
26
+ # mail.comments = "This is another comment"
27
+ # mail[:comments].map { |c| c.to_s }
28
+ # #=> ['This is a comment', "This is another comment"]
29
+ class CommentsField < NamedUnstructuredField #:nodoc:
30
+ NAME = 'Comments'
40
31
  end
41
32
  end