mail 2.5.3 → 2.6.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +70 -0
  3. data/CONTRIBUTING.md +17 -4
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +10 -21
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +50 -29
  8. data/Rakefile +11 -20
  9. data/VERSION +4 -0
  10. data/lib/mail/attachments_list.rb +2 -2
  11. data/lib/mail/body.rb +5 -5
  12. data/lib/mail/check_delivery_params.rb +12 -22
  13. data/lib/mail/core_extensions/object.rb +8 -8
  14. data/lib/mail/core_extensions/smtp.rb +12 -13
  15. data/lib/mail/core_extensions/string.rb +16 -6
  16. data/lib/mail/elements/address.rb +43 -79
  17. data/lib/mail/elements/address_list.rb +19 -42
  18. data/lib/mail/elements/content_disposition_element.rb +3 -7
  19. data/lib/mail/elements/content_location_element.rb +2 -6
  20. data/lib/mail/elements/content_transfer_encoding_element.rb +3 -10
  21. data/lib/mail/elements/content_type_element.rb +4 -8
  22. data/lib/mail/elements/date_time_element.rb +3 -7
  23. data/lib/mail/elements/envelope_from_element.rb +18 -13
  24. data/lib/mail/elements/message_ids_element.rb +1 -6
  25. data/lib/mail/elements/mime_version_element.rb +3 -7
  26. data/lib/mail/elements/phrase_list.rb +2 -7
  27. data/lib/mail/elements/received_element.rb +3 -7
  28. data/lib/mail/encodings/quoted_printable.rb +4 -3
  29. data/lib/mail/encodings.rb +60 -28
  30. data/lib/mail/envelope.rb +0 -5
  31. data/lib/mail/field.rb +63 -28
  32. data/lib/mail/field_list.rb +18 -18
  33. data/lib/mail/fields/bcc_field.rb +2 -2
  34. data/lib/mail/fields/cc_field.rb +2 -2
  35. data/lib/mail/fields/comments_field.rb +1 -1
  36. data/lib/mail/fields/common/common_address.rb +26 -21
  37. data/lib/mail/fields/common/common_date.rb +0 -7
  38. data/lib/mail/fields/common/common_field.rb +5 -5
  39. data/lib/mail/fields/content_id_field.rb +1 -2
  40. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -8
  41. data/lib/mail/fields/content_type_field.rb +1 -1
  42. data/lib/mail/fields/date_field.rb +1 -1
  43. data/lib/mail/fields/from_field.rb +2 -2
  44. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  45. data/lib/mail/fields/message_id_field.rb +2 -3
  46. data/lib/mail/fields/references_field.rb +2 -1
  47. data/lib/mail/fields/reply_to_field.rb +2 -2
  48. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  49. data/lib/mail/fields/resent_cc_field.rb +2 -2
  50. data/lib/mail/fields/resent_from_field.rb +2 -2
  51. data/lib/mail/fields/resent_sender_field.rb +3 -3
  52. data/lib/mail/fields/resent_to_field.rb +2 -2
  53. data/lib/mail/fields/sender_field.rb +8 -8
  54. data/lib/mail/fields/to_field.rb +2 -2
  55. data/lib/mail/fields/unstructured_field.rb +8 -2
  56. data/lib/mail/header.rb +13 -23
  57. data/lib/mail/mail.rb +12 -0
  58. data/lib/mail/matchers/has_sent_mail.rb +34 -1
  59. data/lib/mail/message.rb +151 -48
  60. data/lib/mail/multibyte/chars.rb +2 -2
  61. data/lib/mail/multibyte/unicode.rb +6 -4
  62. data/lib/mail/network/delivery_methods/exim.rb +1 -6
  63. data/lib/mail/network/delivery_methods/file_delivery.rb +1 -1
  64. data/lib/mail/network/delivery_methods/sendmail.rb +32 -10
  65. data/lib/mail/network/delivery_methods/smtp.rb +31 -34
  66. data/lib/mail/network/delivery_methods/smtp_connection.rb +6 -6
  67. data/lib/mail/network/delivery_methods/test_mailer.rb +2 -2
  68. data/lib/mail/network/retriever_methods/imap.rb +18 -13
  69. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  70. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  71. data/lib/mail/parsers/content_location_parser.rb +35 -0
  72. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  73. data/lib/mail/parsers/content_type_parser.rb +64 -0
  74. data/lib/mail/parsers/date_time_parser.rb +36 -0
  75. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  76. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  77. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  78. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  79. data/lib/mail/parsers/ragel/common.rl +184 -0
  80. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  81. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  82. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  83. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  84. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  85. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  86. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  87. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  88. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  89. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  90. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  91. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  92. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  93. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  94. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2129 -0
  95. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  96. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  97. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  98. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  99. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  100. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  101. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  102. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  103. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  104. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  105. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  106. data/lib/mail/parsers/ragel/ruby.rb +29 -0
  107. data/lib/mail/parsers/ragel.rb +17 -0
  108. data/lib/mail/parsers/received_parser.rb +47 -0
  109. data/lib/mail/parsers.rb +26 -0
  110. data/lib/mail/part.rb +6 -2
  111. data/lib/mail/parts_list.rb +5 -3
  112. data/lib/mail/patterns.rb +4 -1
  113. data/lib/mail/utilities.rb +28 -18
  114. data/lib/mail/version.rb +1 -1
  115. data/lib/mail/version_specific/ruby_1_8.rb +6 -2
  116. data/lib/mail/version_specific/ruby_1_9.rb +61 -21
  117. data/lib/mail.rb +1 -2
  118. metadata +112 -73
  119. data/lib/VERSION +0 -4
  120. data/lib/load_parsers.rb +0 -35
  121. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  122. data/lib/mail/parsers/address_lists.rb +0 -64
  123. data/lib/mail/parsers/address_lists.treetop +0 -19
  124. data/lib/mail/parsers/content_disposition.rb +0 -535
  125. data/lib/mail/parsers/content_disposition.treetop +0 -46
  126. data/lib/mail/parsers/content_location.rb +0 -139
  127. data/lib/mail/parsers/content_location.treetop +0 -20
  128. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  129. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  130. data/lib/mail/parsers/content_type.rb +0 -967
  131. data/lib/mail/parsers/content_type.treetop +0 -68
  132. data/lib/mail/parsers/date_time.rb +0 -114
  133. data/lib/mail/parsers/date_time.treetop +0 -11
  134. data/lib/mail/parsers/envelope_from.rb +0 -194
  135. data/lib/mail/parsers/envelope_from.treetop +0 -32
  136. data/lib/mail/parsers/message_ids.rb +0 -45
  137. data/lib/mail/parsers/message_ids.treetop +0 -15
  138. data/lib/mail/parsers/mime_version.rb +0 -144
  139. data/lib/mail/parsers/mime_version.treetop +0 -19
  140. data/lib/mail/parsers/phrase_lists.rb +0 -45
  141. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  142. data/lib/mail/parsers/received.rb +0 -71
  143. data/lib/mail/parsers/received.treetop +0 -11
  144. data/lib/mail/parsers/rfc2045.rb +0 -464
  145. data/lib/mail/parsers/rfc2045.treetop +0 -36
  146. data/lib/mail/parsers/rfc2822.rb +0 -5397
  147. data/lib/mail/parsers/rfc2822.treetop +0 -410
  148. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  149. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  150. data/lib/tasks/corpus.rake +0 -125
  151. data/lib/tasks/treetop.rake +0 -10
@@ -0,0 +1,37 @@
1
+ %%{
2
+ machine received;
3
+
4
+ include rb_actions "rb_actions.rl";
5
+ include common "../../common.rl";
6
+
7
+ getkey data_unpacked[p];
8
+
9
+ main := received;
10
+ }%%
11
+
12
+ module Mail
13
+ module Parsers
14
+ module Ragel
15
+ module ReceivedMachine
16
+ %%write data;
17
+
18
+ def self.parse(data)
19
+ p = 0
20
+ eof = data.length
21
+ stack = []
22
+
23
+ actions = []
24
+ data_unpacked = data.bytes.to_a
25
+ %%write init;
26
+ %%write exec;
27
+
28
+ if p == eof && cs >= %%{ write first_final; }%%
29
+ return actions, nil
30
+ else
31
+ return [], "Only able to parse up to #{data[0..p]}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ %%{
2
+ machine <%= parser_name %>;
3
+
4
+ include rb_actions "rb_actions.rl";
5
+ include common "../../common.rl";
6
+
7
+ getkey data_unpacked[p];
8
+
9
+ main := <%= parser_name %>;
10
+ }%%
11
+
12
+ module Mail
13
+ module Parsers
14
+ module Ragel
15
+ module <%= parser_name_cc %>
16
+ %%write data;
17
+
18
+ def self.parse(data)
19
+ p = 0
20
+ eof = data.length
21
+ stack = []
22
+
23
+ actions = []
24
+ data_unpacked = data.bytes.to_a
25
+ %%write init;
26
+ %%write exec;
27
+
28
+ if p == eof && cs >= %%{ write first_final; }%%
29
+ return actions, nil
30
+ else
31
+ return [], "Only able to parse up to #{data[0..p]}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ module Mail
2
+ module Parsers
3
+ module Ragel
4
+ module Ruby
5
+ Mail::Parsers::Ragel::FIELD_PARSERS.each do |field_parser|
6
+ require "mail/parsers/ragel/ruby/machines/#{field_parser}_machine"
7
+ end
8
+
9
+ MACHINE_LIST = {
10
+ :address_lists => AddressListsMachine,
11
+ :phrase_lists => PhraseListsMachine,
12
+ :date_time => DateTimeMachine,
13
+ :received => ReceivedMachine,
14
+ :message_ids => MessageIdsMachine,
15
+ :envelope_from => EnvelopeFromMachine,
16
+ :mime_version => MimeVersionMachine,
17
+ :content_type => ContentTypeMachine,
18
+ :content_disposition => ContentDispositionMachine,
19
+ :content_transfer_encoding => ContentTransferEncodingMachine,
20
+ :content_location => ContentLocationMachine
21
+ }
22
+
23
+ def self.parse(machine, string)
24
+ MACHINE_LIST[machine].parse(string)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Mail
2
+ module Parsers
3
+ module Ragel
4
+ require 'mail/parsers/ragel/parser_info'
5
+ require "mail/parsers/ragel/ruby"
6
+
7
+ def self.parse(machine, string)
8
+ @machine_module ||= Ruby
9
+ @machine_module.parse(machine, string)
10
+ end
11
+
12
+ def self.machine_module=(m)
13
+ @machine_module = m
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ module Mail::Parsers
2
+ class ReceivedParser
3
+
4
+ def parse(s)
5
+ actions, error = Ragel.parse(:received, s)
6
+ if error
7
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, s, error)
8
+ end
9
+
10
+ received = ReceivedStruct.new
11
+
12
+ received_tokens_s = date_s = time_s = nil
13
+ actions.each_slice(2) do |action_id, p|
14
+ action = Mail::Parsers::Ragel::ACTIONS[action_id]
15
+ case action
16
+
17
+ # Received Tokens:
18
+ when :received_tokens_s then received_tokens_s = p
19
+ when :received_tokens_e
20
+ received.info = s[received_tokens_s..(p-1)]
21
+
22
+ # Date
23
+ when :date_s then date_s = p
24
+ when :date_e
25
+ received.date = s[date_s..(p-1)].strip
26
+
27
+ # Time
28
+ when :time_s then time_s = p
29
+ when :time_e
30
+ received.time = s[time_s..(p-1)]
31
+
32
+ when :angle_addr_s, :comment_e, :comment_s,
33
+ :domain_e, :domain_s, :local_dot_atom_e,
34
+ :local_dot_atom_pre_comment_e,
35
+ :local_dot_atom_pre_comment_s,
36
+ :local_dot_atom_s, :qstr_e, :qstr_s,
37
+ :local_quoted_string_s, :local_quoted_string_e
38
+ # ignored actions
39
+
40
+ else
41
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, s, "Failed to process unknown action: #{action}")
42
+ end
43
+ end
44
+ received
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ module Mail
2
+ module Parsers
3
+
4
+ # Low-level ragel based parsers
5
+ require 'mail/parsers/ragel'
6
+
7
+ AddressListStruct = Struct.new(:addresses, :group_names, :error)
8
+ AddressStruct = Struct.new(:raw, :domain, :comments, :local,
9
+ :obs_domain_list, :display_name, :group, :error)
10
+ ContentDispositionStruct = Struct.new(:disposition_type, :parameters, :error)
11
+ ContentLocationStruct = Struct.new(:location, :error)
12
+ ContentTransferEncodingStruct = Struct.new(:encoding, :error)
13
+ ContentTypeStruct = Struct.new(:main_type, :sub_type, :parameters, :error)
14
+ DateTimeStruct = Struct.new(:date_string, :time_string, :error)
15
+ EnvelopeFromStruct = Struct.new(:address, :ctime_date, :error)
16
+ MessageIdsStruct = Struct.new(:message_ids, :error)
17
+ MimeVersionStruct = Struct.new(:major, :minor, :error)
18
+ PhraseListsStruct = Struct.new(:phrases, :error)
19
+ ReceivedStruct = Struct.new(:date, :time, :info, :error)
20
+
21
+ require 'mail/parsers/ragel/parser_info'
22
+ Ragel::FIELD_PARSERS.each do |field_parser|
23
+ require "mail/parsers/#{field_parser}_parser"
24
+ end
25
+ end
26
+ end
data/lib/mail/part.rb CHANGED
@@ -38,8 +38,12 @@ module Mail
38
38
  end
39
39
 
40
40
  def add_required_fields
41
- add_content_id unless has_content_id?
42
41
  super
42
+ add_content_id if !has_content_id? && inline?
43
+ end
44
+
45
+ def add_required_message_fields
46
+ # Override so we don't add Date, MIME-Version, or Message-ID.
43
47
  end
44
48
 
45
49
  def delivery_status_report_part?
@@ -113,4 +117,4 @@ module Mail
113
117
 
114
118
  end
115
119
 
116
- end
120
+ end
@@ -31,11 +31,13 @@ module Mail
31
31
  end
32
32
 
33
33
  def sort!(order)
34
- sorted = self.sort do |a, b|
34
+ # stable sort should be used to maintain the relative order as the parts are added
35
+ i = 0;
36
+ sorted = self.sort_by do |a|
35
37
  # OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
36
38
  # single email message... please show me a use case and I'll put more work into this method,
37
39
  # in the meantime, it works :)
38
- get_order_value(a, order) <=> get_order_value(b, order)
40
+ [get_order_value(a, order), i += 1]
39
41
  end
40
42
  self.clear
41
43
  sorted.each { |p| self << p }
@@ -44,7 +46,7 @@ module Mail
44
46
  private
45
47
 
46
48
  def get_order_value(part, order)
47
- if part.respond_to?(:content_type)
49
+ if part.respond_to?(:content_type) && !part[:content_type].nil?
48
50
  order.index(part[:content_type].string.downcase) || 10000
49
51
  else
50
52
  10000
data/lib/mail/patterns.rb CHANGED
@@ -20,9 +20,12 @@ module Mail
20
20
  FWS = /#{CRLF}#{WSP}*/
21
21
  TEXT = /[#{text}]/ # + obs-text
22
22
  FIELD_NAME = /[#{field_name}]+/
23
- FIELD_BODY = /.+/
23
+ FIELD_PREFIX = /\A(#{FIELD_NAME})/
24
+ FIELD_BODY = /.+/m
24
25
  FIELD_LINE = /^[#{field_name}]+:\s*.+$/
26
+ FIELD_SPLIT = /^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/
25
27
  HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
28
+ HEADER_SPLIT = /#{CRLF}(?!#{WSP})/
26
29
 
27
30
  QP_UNSAFE = /[^#{qp_safe}]/
28
31
  QP_SAFE = /[#{qp_safe}]/
@@ -21,7 +21,9 @@ module Mail
21
21
  original_encoding = str.encoding
22
22
  str.force_encoding('ASCII-8BIT')
23
23
  if (PHRASE_UNSAFE === str)
24
- dquote(str).force_encoding(original_encoding)
24
+ quoted_str = dquote(str).force_encoding(original_encoding)
25
+ str.force_encoding(original_encoding)
26
+ quoted_str
25
27
  else
26
28
  str.force_encoding(original_encoding)
27
29
  end
@@ -41,28 +43,36 @@ module Mail
41
43
  token_safe?( str ) ? str : dquote(str)
42
44
  end
43
45
 
44
- # Wraps supplied string in double quotes unless it is already wrapped.
45
- #
46
- # Additionally will escape any double quotation marks in the string with a single
47
- # backslash in front of the '"' character.
46
+ # Wraps supplied string in double quotes and applies \-escaping as necessary,
47
+ # unless it is already wrapped.
48
+ #
49
+ # Example:
50
+ #
51
+ # string = 'This is a string'
52
+ # dquote(string) #=> '"This is a string"'
53
+ #
54
+ # string = 'This is "a string"'
55
+ # dquote(string #=> '"This is \"a string\"'
48
56
  def dquote( str )
49
- match = str.match(/^"(.*)?"$/)
50
- str = match[1] if match
51
- # First remove all escaped double quotes:
52
- str = str.gsub(/\\"/, '"')
53
- # Then wrap and re-escape all double quotes
54
- '"' + str.gsub(/["]/n) {|s| '\\' + s } + '"'
57
+ '"' + unquote(str).gsub(/[\\"]/n) {|s| '\\' + s } + '"'
55
58
  end
56
-
57
- # Unwraps supplied string from inside double quotes.
58
- #
59
+
60
+ # Unwraps supplied string from inside double quotes and
61
+ # removes any \-escaping.
62
+ #
59
63
  # Example:
60
- #
64
+ #
61
65
  # string = '"This is a string"'
62
66
  # unquote(string) #=> 'This is a string'
67
+ #
68
+ # string = '"This is \"a string\""'
69
+ # unqoute(string) #=> 'This is "a string"'
63
70
  def unquote( str )
64
- match = str.match(/^"(.*?)"$/)
65
- match ? match[1] : str
71
+ if str =~ /^"(.*?)"$/
72
+ $1.gsub(/\\(.)/, '\1')
73
+ else
74
+ str
75
+ end
66
76
  end
67
77
 
68
78
  # Wraps a string in parenthesis and escapes any that are in the string itself.
@@ -135,7 +145,7 @@ module Mail
135
145
  # obj1 = :this_IS_an_object
136
146
  # match_to_s( obj1, obj2 ) #=> true
137
147
  def match_to_s( obj1, obj2 )
138
- obj1.to_s.downcase == obj2.to_s.downcase
148
+ obj1.to_s.casecmp(obj2.to_s) == 0
139
149
  end
140
150
 
141
151
  # Capitalizes a string that is joined by hyphens correctly.
data/lib/mail/version.rb CHANGED
@@ -3,7 +3,7 @@ module Mail
3
3
  module VERSION
4
4
 
5
5
  version = {}
6
- File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
6
+ File.read(File.join(File.dirname(__FILE__), '../', '../', 'VERSION')).each_line do |line|
7
7
  type, value = line.chomp.split(":")
8
8
  next if type =~ /^\s+$/ || value =~ /^\s+$/
9
9
  version[type] = value
@@ -58,7 +58,7 @@ module Mail
58
58
  # Ruby 1.8 requires an encoding to work
59
59
  raise ArgumentError, "Must supply an encoding" if encoding.nil?
60
60
  encoding = encoding.to_s.upcase.gsub('_', '-')
61
- [Encodings::Base64.encode(str), encoding]
61
+ [Encodings::Base64.encode(str), fix_encoding(encoding)]
62
62
  end
63
63
 
64
64
  def Ruby18.b_value_decode(str)
@@ -105,8 +105,12 @@ module Mail
105
105
 
106
106
  def Ruby18.fix_encoding(encoding)
107
107
  case encoding.upcase
108
- when 'UTF8'
108
+ when 'UTF8', 'UTF_8'
109
109
  'UTF-8'
110
+ when 'UTF16', 'UTF-16'
111
+ 'UTF-16BE'
112
+ when 'UTF32', 'UTF-32'
113
+ 'UTF-32BE'
110
114
  else
111
115
  encoding
112
116
  end
@@ -51,9 +51,9 @@ module Mail
51
51
  def Ruby19.b_value_decode(str)
52
52
  match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
53
53
  if match
54
- encoding = match[1]
54
+ charset = match[1]
55
55
  str = Ruby19.decode_base64(match[2])
56
- str.force_encoding(fix_encoding(encoding))
56
+ str.force_encoding(pick_encoding(charset))
57
57
  end
58
58
  decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
59
59
  decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
@@ -67,12 +67,15 @@ module Mail
67
67
  def Ruby19.q_value_decode(str)
68
68
  match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
69
69
  if match
70
- encoding = match[1]
70
+ charset = match[1]
71
71
  string = match[2].gsub(/_/, '=20')
72
72
  # Remove trailing = if it exists in a Q encoding
73
73
  string = string.sub(/\=$/, '')
74
74
  str = Encodings::QuotedPrintable.decode(string)
75
- str.force_encoding(fix_encoding(encoding))
75
+ str.force_encoding(pick_encoding(charset))
76
+ # We assume that binary strings hold utf-8 directly to work around
77
+ # jruby/jruby#829 which subtly changes String#encode semantics.
78
+ str.force_encoding('utf-8') if str.encoding == Encoding::ASCII_8BIT
76
79
  end
77
80
  decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
78
81
  decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
@@ -96,23 +99,60 @@ module Mail
96
99
  @uri_parser ||= URI::Parser.new
97
100
  end
98
101
 
99
- # mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8
100
- # TODO: add this as a test somewhere
101
- # Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
102
- # Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
103
- def Ruby19.fix_encoding(encoding)
104
- case encoding
105
- # ISO-8859-15, ISO-2022-JP and alike
106
- when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}"
107
- # "ISO-2022-JP-KDDI" and alike
108
- when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}"
109
- # UTF-8, UTF-32BE and alike
110
- when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
111
- # Windows-1252 and alike
112
- when /Windows-?(.*)/i then return "Windows-#{$1}"
113
- when /^8bit$/ then return "ASCII-8BIT"
114
- #more aliases to be added if needed
115
- else return encoding
102
+ # Pick a Ruby encoding corresponding to the message charset. Most
103
+ # charsets have a Ruby encoding, but some need manual aliasing here.
104
+ #
105
+ # TODO: add this as a test somewhere:
106
+ # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
107
+ # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
108
+ def Ruby19.pick_encoding(charset)
109
+ case charset
110
+
111
+ # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
112
+ when /^iso-?8859-(\d+)(-i)?$/i
113
+ "ISO-8859-#{$1}"
114
+
115
+ # ISO-8859-15, ISO-2022-JP and alike
116
+ when /iso-?(\d{4})-?(\w{1,2})/i
117
+ "ISO-#{$1}-#{$2}"
118
+
119
+ # "ISO-2022-JP-KDDI" and alike
120
+ when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i
121
+ "ISO-#{$1}-#{$2}-#{$3}"
122
+
123
+ # UTF-8, UTF-32BE and alike
124
+ when /utf[\-_]?(\d{1,2})?(\w{1,2})/i
125
+ "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
126
+
127
+ # Windows-1252 and alike
128
+ when /Windows-?(.*)/i
129
+ "Windows-#{$1}"
130
+
131
+ when /^8bit$/
132
+ Encoding::ASCII_8BIT
133
+
134
+ # alternatives/misspellings of us-ascii seen in the wild
135
+ when /^iso-?646(-us)?$/i, /us=ascii/i
136
+ Encoding::ASCII
137
+
138
+ # Microsoft-specific alias for MACROMAN
139
+ when /^macintosh$/i
140
+ Encoding::MACROMAN
141
+
142
+ # Microsoft-specific alias for CP949 (Korean)
143
+ when 'ks_c_5601-1987'
144
+ Encoding::CP949
145
+
146
+ # Wrongly written Shift_JIS (Japanese)
147
+ when 'shift-jis'
148
+ Encoding::Shift_JIS
149
+
150
+ # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
151
+ when /gb2312/i
152
+ Encoding::GB18030
153
+
154
+ else
155
+ charset
116
156
  end
117
157
  end
118
158
  end
data/lib/mail.rb CHANGED
@@ -29,7 +29,6 @@ module Mail # :doc:
29
29
  require 'mail/core_extensions/nil'
30
30
  require 'mail/core_extensions/object'
31
31
  require 'mail/core_extensions/string'
32
- require 'mail/core_extensions/shell_escape'
33
32
  require 'mail/core_extensions/smtp' if RUBY_VERSION < '1.9.3'
34
33
  require 'mail/indifferent_hash'
35
34
 
@@ -77,7 +76,7 @@ module Mail # :doc:
77
76
 
78
77
  require 'mail/envelope'
79
78
 
80
- require 'load_parsers'
79
+ require 'mail/parsers'
81
80
 
82
81
  # Autoload header field elements and transfer encodings.
83
82
  require 'mail/elements'