mail 2.6.5 → 2.7.0.rc1

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +28 -1
  3. data/Dependencies.txt +0 -1
  4. data/Gemfile +5 -8
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +5 -5
  7. data/Rakefile +0 -6
  8. data/lib/mail/attachments_list.rb +3 -2
  9. data/lib/mail/body.rb +3 -3
  10. data/lib/mail/check_delivery_params.rb +47 -10
  11. data/lib/mail/core_extensions/smtp.rb +19 -16
  12. data/lib/mail/core_extensions/string.rb +0 -4
  13. data/lib/mail/elements/address.rb +9 -4
  14. data/lib/mail/elements/address_list.rb +10 -18
  15. data/lib/mail/elements/content_disposition_element.rb +8 -15
  16. data/lib/mail/elements/content_location_element.rb +5 -10
  17. data/lib/mail/elements/content_transfer_encoding_element.rb +5 -10
  18. data/lib/mail/elements/content_type_element.rb +8 -19
  19. data/lib/mail/elements/date_time_element.rb +6 -14
  20. data/lib/mail/elements/envelope_from_element.rb +14 -21
  21. data/lib/mail/elements/message_ids_element.rb +8 -12
  22. data/lib/mail/elements/mime_version_element.rb +6 -14
  23. data/lib/mail/elements/phrase_list.rb +6 -9
  24. data/lib/mail/elements/received_element.rb +9 -15
  25. data/lib/mail/encodings.rb +12 -14
  26. data/lib/mail/field.rb +7 -4
  27. data/lib/mail/fields/common/parameter_hash.rb +1 -1
  28. data/lib/mail/fields/content_type_field.rb +2 -7
  29. data/lib/mail/fields/unstructured_field.rb +2 -2
  30. data/lib/mail/header.rb +1 -0
  31. data/lib/mail/message.rb +23 -25
  32. data/lib/mail/multibyte/chars.rb +27 -27
  33. data/lib/mail/multibyte.rb +51 -0
  34. data/lib/mail/network/delivery_methods/file_delivery.rb +4 -8
  35. data/lib/mail/network/delivery_methods/sendmail.rb +2 -4
  36. data/lib/mail/network/delivery_methods/smtp.rb +16 -11
  37. data/lib/mail/network/delivery_methods/smtp_connection.rb +2 -6
  38. data/lib/mail/network/delivery_methods/test_mailer.rb +5 -8
  39. data/lib/mail/parsers/address_lists_parser.rb +15321 -112
  40. data/lib/mail/parsers/address_lists_parser.rl +166 -0
  41. data/lib/mail/parsers/content_disposition_parser.rb +698 -55
  42. data/lib/mail/parsers/content_disposition_parser.rl +76 -0
  43. data/lib/mail/parsers/content_location_parser.rb +565 -23
  44. data/lib/mail/parsers/content_location_parser.rl +65 -0
  45. data/lib/mail/parsers/content_transfer_encoding_parser.rb +410 -22
  46. data/lib/mail/parsers/content_transfer_encoding_parser.rl +58 -0
  47. data/lib/mail/parsers/content_type_parser.rb +841 -54
  48. data/lib/mail/parsers/content_type_parser.rl +77 -0
  49. data/lib/mail/parsers/date_time_parser.rb +773 -26
  50. data/lib/mail/parsers/date_time_parser.rl +56 -0
  51. data/lib/mail/parsers/envelope_from_parser.rb +2051 -36
  52. data/lib/mail/parsers/envelope_from_parser.rl +76 -0
  53. data/lib/mail/parsers/message_ids_parser.rb +1494 -25
  54. data/lib/mail/parsers/message_ids_parser.rl +76 -0
  55. data/lib/mail/parsers/mime_version_parser.rb +393 -26
  56. data/lib/mail/parsers/mime_version_parser.rl +55 -0
  57. data/lib/mail/parsers/phrase_lists_parser.rb +529 -19
  58. data/lib/mail/parsers/phrase_lists_parser.rl +77 -0
  59. data/lib/mail/parsers/received_parser.rb +4711 -38
  60. data/lib/mail/parsers/received_parser.rl +78 -0
  61. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +12 -0
  62. data/lib/mail/parsers/rfc2045_content_type.rl +24 -0
  63. data/lib/mail/parsers/rfc2045_mime.rl +15 -0
  64. data/lib/mail/parsers/rfc2183_content_disposition.rl +14 -0
  65. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +16 -0
  66. data/lib/mail/parsers/rfc5322.rl +57 -0
  67. data/lib/mail/parsers/rfc5322_address.rl +70 -0
  68. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +7 -1
  69. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +56 -0
  70. data/lib/mail/parsers.rb +16 -24
  71. data/lib/mail/part.rb +1 -1
  72. data/lib/mail/parts_list.rb +5 -6
  73. data/lib/mail/utilities.rb +4 -2
  74. data/lib/mail/version.rb +3 -3
  75. data/lib/mail/version_specific/ruby_1_8.rb +28 -2
  76. data/lib/mail/version_specific/ruby_1_9.rb +48 -8
  77. data/lib/mail.rb +2 -10
  78. metadata +28 -45
  79. data/lib/mail/core_extensions/string/access.rb +0 -146
  80. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  81. data/lib/mail/parsers/ragel/common.rl +0 -185
  82. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  83. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  84. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  85. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  86. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  87. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  88. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  89. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  90. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  91. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  92. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  93. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  94. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  95. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  96. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  97. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  98. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  99. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  100. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  101. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  102. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  103. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  104. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  105. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  106. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  107. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  108. data/lib/mail/parsers/ragel.rb +0 -18
@@ -1,23 +1,15 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/parsers/mime_version_parser'
4
+
3
5
  module Mail
4
6
  class MimeVersionElement
5
-
6
- include Mail::Utilities
7
-
8
- def initialize( string )
9
- mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
7
+ attr_reader :major, :minor
8
+
9
+ def initialize(string)
10
+ mime_version = Mail::Parsers::MimeVersionParser.parse(string)
10
11
  @major = mime_version.major
11
12
  @minor = mime_version.minor
12
13
  end
13
-
14
- def major
15
- @major
16
- end
17
-
18
- def minor
19
- @minor
20
- end
21
-
22
14
  end
23
15
  end
@@ -1,17 +1,14 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/parsers/phrase_lists_parser'
4
+ require 'mail/utilities'
5
+
3
6
  module Mail
4
7
  class PhraseList
5
-
6
- include Mail::Utilities
7
-
8
+ attr_reader :phrases
9
+
8
10
  def initialize(string)
9
- @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
11
+ @phrases = Mail::Parsers::PhraseListsParser.parse(string).phrases.map { |p| Mail::Utilities.unquote(p) }
10
12
  end
11
-
12
- def phrases
13
- @phrase_lists.phrases.map { |p| unquote(p) }
14
- end
15
-
16
13
  end
17
14
  end
@@ -1,27 +1,21 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/parsers/received_parser'
4
+ require 'date'
5
+
3
6
  module Mail
4
7
  class ReceivedElement
5
-
6
8
  include Mail::Utilities
7
-
8
- def initialize( string )
9
- received = Mail::Parsers::ReceivedParser.new.parse(string)
9
+ attr_reader :date_time, :info
10
+
11
+ def initialize(string)
12
+ received = Mail::Parsers::ReceivedParser.parse(string)
10
13
  @date_time = ::DateTime.parse("#{received.date} #{received.time}")
11
14
  @info = received.info
12
15
  end
13
-
14
- def date_time
15
- @date_time
16
- end
17
-
18
- def info
19
- @info
20
- end
21
-
16
+
22
17
  def to_s(*args)
23
- "#{@info}; #{@date_time.to_s(*args)}"
18
+ "#{info}; #{date_time.to_s(*args)}"
24
19
  end
25
-
26
20
  end
27
21
  end
@@ -178,9 +178,8 @@ module Mail
178
178
 
179
179
  def Encodings.encode_non_usascii(address, charset)
180
180
  return address if address.ascii_only? or charset.nil?
181
- us_ascii = %Q{\x00-\x7f}
182
- # Encode any non usascii strings embedded inside of quotes
183
- address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
181
+ # Encode all strings embedded inside of quotes
182
+ address = address.gsub(/("[^"]*")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
184
183
  # Then loop through all remaining items and encode as needed
185
184
  tokens = address.split(/\s/)
186
185
  map_with_index(tokens) do |word, i|
@@ -258,24 +257,23 @@ module Mail
258
257
  str[ENCODED_VALUE, 1]
259
258
  end
260
259
 
261
- # When the encoded string consists of multiple lines, lines with the same
262
- # encoding (Q or B) can be joined together.
260
+ # Split header line into proper encoded and unencoded parts.
263
261
  #
264
262
  # String has to be of the format =?<encoding>?[QB]?<string>?=
263
+ #
264
+ # Omit unencoded space after an encoded-word.
265
265
  def Encodings.collapse_adjacent_encodings(str)
266
266
  results = []
267
- previous_encoding = nil
267
+ last_encoded = nil # Track whether to preserve or drop whitespace
268
+
268
269
  lines = str.split(FULL_ENCODED_VALUE)
269
270
  lines.each_slice(2) do |unencoded, encoded|
270
- if encoded
271
- encoding = value_encoding_from_string(encoded)
272
- if encoding == previous_encoding && Utilities.blank?(unencoded)
273
- results.last << encoded
274
- else
275
- results << unencoded unless unencoded == EMPTY
276
- results << encoded
271
+ if last_encoded = encoded
272
+ if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
273
+ results << unencoded
277
274
  end
278
- previous_encoding = encoding
275
+
276
+ results << encoded
279
277
  else
280
278
  results << unencoded
281
279
  end
data/lib/mail/field.rb CHANGED
@@ -218,10 +218,13 @@ module Mail
218
218
  private
219
219
 
220
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]
221
+ match_data = Mail::Multibyte.mb_chars(raw_field).match(FIELD_SPLIT)
222
+ [
223
+ Mail::Multibyte.mb_chars(match_data[1].to_s).strip,
224
+ Mail::Multibyte.mb_chars(match_data[2].to_s).strip.to_s
225
+ ]
223
226
  rescue
224
- STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
227
+ $stderr.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
225
228
  end
226
229
 
227
230
  # 2.2.3. Long Header Fields
@@ -233,7 +236,7 @@ module Mail
233
236
  # treated in its unfolded form for further syntactic and semantic
234
237
  # evaluation.
235
238
  def unfold(string)
236
- string.gsub(/[\r\n \t]+/m, ' ')
239
+ string.gsub(/#{CRLF}(#{WSP})/m, '\1')
237
240
  end
238
241
 
239
242
  def create_field(name, value, charset)
@@ -30,7 +30,7 @@ module Mail
30
30
  super(exact || key_name)
31
31
  else # Dealing with a multiple value pair or a single encoded value pair
32
32
  string = pairs.sort { |a,b| a.first.to_s <=> b.first.to_s }.map { |v| v.last }.join('')
33
- if mt = string.match(/([\w\-]+)'(\w\w)'(.*)/)
33
+ if mt = string.match(/([\w\-]+)?'(\w\w)?'(.*)/)
34
34
  string = mt[3]
35
35
  encoding = mt[1]
36
36
  else
@@ -145,9 +145,7 @@ module Mail
145
145
  # TODO: check if there are cases where whitespace is not a separator
146
146
  val = val.
147
147
  gsub(/\s*=\s*/,'='). # remove whitespaces around equal sign
148
- tr(' ',';').
149
- squeeze(';').
150
- gsub(';', '; '). #use '; ' as a separator (or EOL)
148
+ gsub(/[; ]+/, '; '). #use '; ' as a separator (or EOL)
151
149
  gsub(/;\s*$/,'') #remove trailing to keep examples below
152
150
 
153
151
  if val =~ /(boundary=(\S*))/i
@@ -157,9 +155,6 @@ module Mail
157
155
  end
158
156
 
159
157
  case
160
- when val.chomp =~ /^\s*([\w\-]+)\/([\w\-]+)\s*;;+(.*)$/i
161
- # Handles 'text/plain;; format="flowed"' (double semi colon)
162
- "#{$1}/#{$2}; #{$3}"
163
158
  when val.chomp =~ /^\s*([\w\-]+)\/([\w\-]+)\s*;\s?(ISO[\w\-]+)$/i
164
159
  # Microsoft helper:
165
160
  # Handles 'type/subtype;ISO-8559-1'
@@ -186,7 +181,7 @@ module Mail
186
181
  when val =~ /^\s*$/
187
182
  'text/plain'
188
183
  else
189
- ''
184
+ val
190
185
  end
191
186
  end
192
187
 
@@ -121,7 +121,7 @@ module Mail
121
121
  def fold(prepend = 0) # :nodoc:
122
122
  encoding = normalized_encoding
123
123
  decoded_string = decoded.to_s
124
- should_encode = decoded_string.not_ascii_only?
124
+ should_encode = !decoded_string.ascii_only?
125
125
  if should_encode
126
126
  first = true
127
127
  words = decoded_string.split(/[ \t]/).map do |word|
@@ -130,7 +130,7 @@ module Mail
130
130
  else
131
131
  word = " #{word}"
132
132
  end
133
- if word.not_ascii_only?
133
+ if !word.ascii_only?
134
134
  word
135
135
  else
136
136
  word.scan(/.{7}|.+$/)
data/lib/mail/header.rb CHANGED
@@ -57,6 +57,7 @@ module Mail
57
57
  def initialize_copy(original)
58
58
  super
59
59
  @fields = @fields.dup
60
+ @fields.map!(&:dup)
60
61
  end
61
62
 
62
63
  # The preserved raw source of the header as you passed it in, untouched
data/lib/mail/message.rb CHANGED
@@ -213,7 +213,7 @@ module Mail
213
213
  self.default_charset = 'UTF-8'
214
214
 
215
215
  def register_for_delivery_notification(observer)
216
- STDERR.puts("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
216
+ $stderr.puts("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
217
217
  Mail.register_observer(observer)
218
218
  end
219
219
 
@@ -360,13 +360,8 @@ module Mail
360
360
  if self.message_id && other.message_id
361
361
  self.encoded == other.encoded
362
362
  else
363
- self_message_id, other_message_id = self.message_id, other.message_id
364
- begin
365
- self.message_id, other.message_id = '<temp@test>', '<temp@test>'
366
- self.encoded == other.encoded
367
- ensure
368
- self.message_id, other.message_id = self_message_id, other_message_id
369
- end
363
+ dup.tap { |m| m.message_id = '<temp@test>' }.encoded ==
364
+ other.dup.tap { |m| m.message_id = '<temp@test>' }.encoded
370
365
  end
371
366
  end
372
367
 
@@ -1420,7 +1415,7 @@ module Mail
1420
1415
  end
1421
1416
 
1422
1417
  def has_transfer_encoding? # :nodoc:
1423
- STDERR.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
1418
+ $stderr.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
1424
1419
  has_content_transfer_encoding?
1425
1420
  end
1426
1421
 
@@ -1468,9 +1463,9 @@ module Mail
1468
1463
  if !body.empty?
1469
1464
  # Only give a warning if this isn't an attachment, has non US-ASCII and the user
1470
1465
  # has not specified an encoding explicitly.
1471
- if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
1466
+ if @defaulted_charset && !body.raw_source.ascii_only? && !self.attachment?
1472
1467
  warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1473
- STDERR.puts(warning)
1468
+ $stderr.puts(warning)
1474
1469
  end
1475
1470
  header[:content_type].parameters['charset'] = @charset
1476
1471
  end
@@ -1484,18 +1479,18 @@ module Mail
1484
1479
  header[:content_transfer_encoding] = '7bit'
1485
1480
  else
1486
1481
  warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
1487
- STDERR.puts(warning)
1482
+ $stderr.puts(warning)
1488
1483
  header[:content_transfer_encoding] = '8bit'
1489
1484
  end
1490
1485
  end
1491
1486
 
1492
1487
  def add_transfer_encoding # :nodoc:
1493
- STDERR.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
1488
+ $stderr.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
1494
1489
  add_content_transfer_encoding
1495
1490
  end
1496
1491
 
1497
1492
  def transfer_encoding # :nodoc:
1498
- STDERR.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
1493
+ $stderr.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
1499
1494
  content_transfer_encoding
1500
1495
  end
1501
1496
 
@@ -1505,7 +1500,7 @@ module Mail
1505
1500
  end
1506
1501
 
1507
1502
  def message_content_type
1508
- STDERR.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
1503
+ $stderr.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
1509
1504
  mime_type
1510
1505
  end
1511
1506
 
@@ -1537,7 +1532,7 @@ module Mail
1537
1532
 
1538
1533
  # Returns the content type parameters
1539
1534
  def mime_parameters
1540
- STDERR.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
1535
+ $stderr.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
1541
1536
  content_type_parameters
1542
1537
  end
1543
1538
 
@@ -1563,7 +1558,14 @@ module Mail
1563
1558
 
1564
1559
  # returns the part in a multipart/report email that has the content-type delivery-status
1565
1560
  def delivery_status_part
1566
- @delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
1561
+ unless defined? @delivery_status_part
1562
+ @delivery_status_part =
1563
+ if delivery_status_report?
1564
+ parts.detect(&:delivery_status_report_part?)
1565
+ end
1566
+ end
1567
+
1568
+ @delivery_status_part
1567
1569
  end
1568
1570
 
1569
1571
  def bounced?
@@ -1798,7 +1800,6 @@ module Mail
1798
1800
  # ready to send
1799
1801
  def ready_to_send!
1800
1802
  identify_and_set_transfer_encoding
1801
- parts.sort!([ "text/plain", "text/enriched", "text/html", "multipart/alternative" ])
1802
1803
  parts.each do |part|
1803
1804
  part.transport_encoding = transport_encoding
1804
1805
  part.ready_to_send!
@@ -1807,7 +1808,7 @@ module Mail
1807
1808
  end
1808
1809
 
1809
1810
  def encode!
1810
- STDERR.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
1811
+ $stderr.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
1811
1812
  ready_to_send!
1812
1813
  end
1813
1814
 
@@ -1975,7 +1976,7 @@ module Mail
1975
1976
 
1976
1977
  private
1977
1978
 
1978
- HEADER_SEPARATOR = /#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m
1979
+ HEADER_SEPARATOR = /#{CRLF}#{CRLF}/
1979
1980
 
1980
1981
  # 2.1. General Description
1981
1982
  # A message consists of header fields (collectively called "the header
@@ -1984,9 +1985,6 @@ module Mail
1984
1985
  # this standard. The body is simply a sequence of characters that
1985
1986
  # follows the header and is separated from the header by an empty line
1986
1987
  # (i.e., a line with nothing preceding the CRLF).
1987
- #
1988
- # Additionally, I allow for the case where someone might have put whitespace
1989
- # on the "gap line"
1990
1988
  def parse_message
1991
1989
  header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
1992
1990
  self.header = header_part
@@ -2130,10 +2128,10 @@ module Mail
2130
2128
  content_disp_name = header[:content_disposition].filename rescue nil
2131
2129
  content_loc_name = header[:content_location].location rescue nil
2132
2130
  case
2133
- when content_type && content_type_name
2134
- filename = content_type_name
2135
2131
  when content_disposition && content_disp_name
2136
2132
  filename = content_disp_name
2133
+ when content_type && content_type_name
2134
+ filename = content_type_name
2137
2135
  when content_location && content_loc_name
2138
2136
  filename = content_loc_name
2139
2137
  else
@@ -103,7 +103,7 @@ module Mail #:nodoc:
103
103
  # Returns a new Chars object containing the _other_ object concatenated to the string.
104
104
  #
105
105
  # Example:
106
- # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
106
+ # (Mail::Multibyte.mb_chars('Café') + ' périferôl').to_s # => "Café périferôl"
107
107
  def +(other)
108
108
  chars(@wrapped_string + other)
109
109
  end
@@ -111,7 +111,7 @@ module Mail #:nodoc:
111
111
  # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
112
112
  #
113
113
  # Example:
114
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
114
+ # Mail::Multibyte.mb_chars('Café périferôl') =~ /ô/ # => 12
115
115
  def =~(other)
116
116
  translate_offset(@wrapped_string =~ other)
117
117
  end
@@ -119,7 +119,7 @@ module Mail #:nodoc:
119
119
  # Inserts the passed string at specified codepoint offsets.
120
120
  #
121
121
  # Example:
122
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
122
+ # Mail::Multibyte.mb_chars('Café').insert(4, ' périferôl').to_s # => "Café périferôl"
123
123
  def insert(offset, fragment)
124
124
  unpacked = Unicode.u_unpack(@wrapped_string)
125
125
  unless offset > unpacked.length
@@ -135,7 +135,7 @@ module Mail #:nodoc:
135
135
  # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
136
136
  #
137
137
  # Example:
138
- # 'Café'.mb_chars.include?('é') # => true
138
+ # Mail::Multibyte.mb_chars('Café').include?('é') # => true
139
139
  def include?(other)
140
140
  # We have to redefine this method because Enumerable defines it.
141
141
  @wrapped_string.include?(other)
@@ -144,8 +144,8 @@ module Mail #:nodoc:
144
144
  # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
145
145
  #
146
146
  # Example:
147
- # 'Café périferôl'.mb_chars.index('ô') # => 12
148
- # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
147
+ # Mail::Multibyte.mb_chars('Café périferôl').index('ô') # => 12
148
+ # Mail::Multibyte.mb_chars('Café périferôl').index(/\w/u) # => 0
149
149
  def index(needle, offset=0)
150
150
  wrapped_offset = first(offset).wrapped_string.length
151
151
  index = @wrapped_string.index(needle, wrapped_offset)
@@ -157,8 +157,8 @@ module Mail #:nodoc:
157
157
  # string. Returns +nil+ if _needle_ isn't found.
158
158
  #
159
159
  # Example:
160
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
161
- # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
160
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex('é') # => 6
161
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex(/\w/u) # => 13
162
162
  def rindex(needle, offset=nil)
163
163
  offset ||= length
164
164
  wrapped_offset = first(offset).wrapped_string.length
@@ -190,7 +190,7 @@ module Mail #:nodoc:
190
190
  # Returns the codepoint of the first character in the string.
191
191
  #
192
192
  # Example:
193
- # 'こんにちは'.mb_chars.ord # => 12371
193
+ # Mail::Multibyte.mb_chars('こんにちは').ord # => 12371
194
194
  def ord
195
195
  Unicode.u_unpack(@wrapped_string)[0]
196
196
  end
@@ -199,10 +199,10 @@ module Mail #:nodoc:
199
199
  #
200
200
  # Example:
201
201
  #
202
- # "¾ cup".mb_chars.rjust(8).to_s
202
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
203
203
  # # => " ¾ cup"
204
204
  #
205
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
205
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
206
206
  # # => "   ¾ cup"
207
207
  def rjust(integer, padstr=' ')
208
208
  justify(integer, :right, padstr)
@@ -212,10 +212,10 @@ module Mail #:nodoc:
212
212
  #
213
213
  # Example:
214
214
  #
215
- # "¾ cup".mb_chars.rjust(8).to_s
215
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
216
216
  # # => "¾ cup "
217
217
  #
218
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
218
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
219
219
  # # => "¾ cup   "
220
220
  def ljust(integer, padstr=' ')
221
221
  justify(integer, :left, padstr)
@@ -225,10 +225,10 @@ module Mail #:nodoc:
225
225
  #
226
226
  # Example:
227
227
  #
228
- # "¾ cup".mb_chars.center(8).to_s
228
+ # Mail::Multibyte.mb_chars("¾ cup").center(8).to_s
229
229
  # # => " ¾ cup "
230
230
  #
231
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
231
+ # Mail::Multibyte.mb_chars("¾ cup").center(8, " ").to_s # Use non-breaking whitespace
232
232
  # # => " ¾ cup  "
233
233
  def center(integer, padstr=' ')
234
234
  justify(integer, :center, padstr)
@@ -244,7 +244,7 @@ module Mail #:nodoc:
244
244
  # instances instead of String. This makes chaining methods easier.
245
245
  #
246
246
  # Example:
247
- # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
247
+ # Mail::Multibyte.mb_chars('Café périferôl').split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
248
248
  def split(*args)
249
249
  @wrapped_string.split(*args).map { |i| i.mb_chars }
250
250
  end
@@ -292,7 +292,7 @@ module Mail #:nodoc:
292
292
  # Reverses all characters in the string.
293
293
  #
294
294
  # Example:
295
- # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
295
+ # Mail::Multibyte.mb_chars('Café').reverse.to_s # => 'éfaC'
296
296
  def reverse
297
297
  chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
298
298
  end
@@ -301,7 +301,7 @@ module Mail #:nodoc:
301
301
  # character.
302
302
  #
303
303
  # Example:
304
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
304
+ # Mail::Multibyte.mb_chars('こんにちは').slice(2..3).to_s # => "にち"
305
305
  def slice(*args)
306
306
  if args.size > 2
307
307
  raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
@@ -338,7 +338,7 @@ module Mail #:nodoc:
338
338
  # Convert characters in the string to uppercase.
339
339
  #
340
340
  # Example:
341
- # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
341
+ # Mail::Multibyte.mb_chars('Laurent, où sont les tests ?').upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
342
342
  def upcase
343
343
  chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
344
344
  end
@@ -346,7 +346,7 @@ module Mail #:nodoc:
346
346
  # Convert characters in the string to lowercase.
347
347
  #
348
348
  # Example:
349
- # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
349
+ # Mail::Multibyte.mb_chars('VĚDA A VÝZKUM').downcase.to_s # => "věda a výzkum"
350
350
  def downcase
351
351
  chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
352
352
  end
@@ -354,7 +354,7 @@ module Mail #:nodoc:
354
354
  # Converts the first character to uppercase and the remainder to lowercase.
355
355
  #
356
356
  # Example:
357
- # 'über'.mb_chars.capitalize.to_s # => "Über"
357
+ # Mail::Multibyte.mb_chars('über').capitalize.to_s # => "Über"
358
358
  def capitalize
359
359
  (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
360
360
  end
@@ -362,8 +362,8 @@ module Mail #:nodoc:
362
362
  # Capitalizes the first letter of every word, when possible.
363
363
  #
364
364
  # Example:
365
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
366
- # "日本語".mb_chars.titleize # => "日本語"
365
+ # Mail::Multibyte.mb_chars("ÉL QUE SE ENTERÓ").titleize # => "Él Que Se Enteró"
366
+ # Mail::Multibyte.mb_chars("日本語").titleize # => "日本語"
367
367
  def titleize
368
368
  chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
369
369
  end
@@ -383,7 +383,7 @@ module Mail #:nodoc:
383
383
  #
384
384
  # Example:
385
385
  # 'é'.length # => 2
386
- # 'é'.mb_chars.decompose.to_s.length # => 3
386
+ # Mail::Multibyte.mb_chars('é').decompose.to_s.length # => 3
387
387
  def decompose
388
388
  chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
389
389
  end
@@ -392,7 +392,7 @@ module Mail #:nodoc:
392
392
  #
393
393
  # Example:
394
394
  # 'é'.length # => 3
395
- # 'é'.mb_chars.compose.to_s.length # => 2
395
+ # Mail::Multibyte.mb_chars('é').compose.to_s.length # => 2
396
396
  def compose
397
397
  chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
398
398
  end
@@ -400,8 +400,8 @@ module Mail #:nodoc:
400
400
  # Returns the number of grapheme clusters in the string.
401
401
  #
402
402
  # Example:
403
- # 'क्षि'.mb_chars.length # => 4
404
- # 'क्षि'.mb_chars.g_length # => 3
403
+ # Mail::Multibyte.mb_chars('क्षि').length # => 4
404
+ # Mail::Multibyte.mb_chars('क्षि').g_length # => 3
405
405
  def g_length
406
406
  Unicode.g_unpack(@wrapped_string).length
407
407
  end
@@ -21,6 +21,57 @@ module Mail #:nodoc:
21
21
  @proxy_class ||= Mail::Multibyte::Chars
22
22
  end
23
23
 
24
+ if RUBY_VERSION >= "1.9"
25
+ # == Multibyte proxy
26
+ #
27
+ # +mb_chars+ is a multibyte safe proxy for string methods.
28
+ #
29
+ # In Ruby 1.8 and older it creates and returns an instance of the Mail::Multibyte::Chars class which
30
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
31
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
32
+ #
33
+ # name = 'Claus Müller'
34
+ # name.reverse # => "rell??M sualC"
35
+ # name.length # => 13
36
+ #
37
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
38
+ # name.mb_chars.length # => 12
39
+ #
40
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
41
+ # it becomes easy to run one version of your code on multiple Ruby versions.
42
+ #
43
+ # == Method chaining
44
+ #
45
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
46
+ # method chaining on the result of any of these methods.
47
+ #
48
+ # name.mb_chars.reverse.length # => 12
49
+ #
50
+ # == Interoperability and configuration
51
+ #
52
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
53
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
54
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
55
+ #
56
+ # For more information about the methods defined on the Chars proxy see Mail::Multibyte::Chars. For
57
+ # information about how to change the default Multibyte behaviour see Mail::Multibyte.
58
+ def self.mb_chars(str)
59
+ if proxy_class.consumes?(str)
60
+ proxy_class.new(str)
61
+ else
62
+ str
63
+ end
64
+ end
65
+ else
66
+ def self.mb_chars(str)
67
+ if proxy_class.wants?(str)
68
+ proxy_class.new(str)
69
+ else
70
+ str
71
+ end
72
+ end
73
+ end
74
+
24
75
  # Regular expressions that describe valid byte sequences for a character
25
76
  VALID_CHARACTER = {
26
77
  # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)