mail 2.5.5 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ class SmtpEnvelope #:nodoc:
5
+ # Reasonable cap on address length to avoid SMTP line length
6
+ # overflow on old SMTP servers.
7
+ MAX_ADDRESS_BYTESIZE = 2000
8
+
9
+ attr_reader :from, :to, :message
10
+
11
+ def initialize(mail)
12
+ self.from = mail.smtp_envelope_from
13
+ self.to = mail.smtp_envelope_to
14
+ self.message = mail.encoded
15
+ end
16
+
17
+ def from=(addr)
18
+ if Utilities.blank? addr
19
+ raise ArgumentError, "SMTP From address may not be blank: #{addr.inspect}"
20
+ end
21
+
22
+ @from = validate_addr 'From', addr
23
+ end
24
+
25
+ def to=(addr)
26
+ if Utilities.blank?(addr)
27
+ raise ArgumentError, "SMTP To address may not be blank: #{addr.inspect}"
28
+ end
29
+
30
+ @to = Array(addr).map do |addr|
31
+ validate_addr 'To', addr
32
+ end
33
+ end
34
+
35
+ def message=(message)
36
+ if Utilities.blank?(message)
37
+ raise ArgumentError, 'SMTP message may not be blank'
38
+ end
39
+
40
+ @message = message
41
+ end
42
+
43
+
44
+ private
45
+ def validate_addr(addr_name, addr)
46
+ if addr.bytesize > MAX_ADDRESS_BYTESIZE
47
+ raise ArgumentError, "SMTP #{addr_name} address may not exceed #{MAX_ADDRESS_BYTESIZE} bytes: #{addr.inspect}"
48
+ end
49
+
50
+ if /[\r\n]/ =~ addr
51
+ raise ArgumentError, "SMTP #{addr_name} address may not contain CR or LF line breaks: #{addr.inspect}"
52
+ end
53
+
54
+ addr
55
+ end
56
+ end
57
+ end
@@ -1,44 +1,58 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/constants'
4
+ require 'socket'
5
+
2
6
  module Mail
3
7
  module Utilities
4
- include Patterns
5
-
8
+ extend self
9
+
6
10
  # Returns true if the string supplied is free from characters not allowed as an ATOM
7
11
  def atom_safe?( str )
8
- not ATOM_UNSAFE === str
12
+ not Constants::ATOM_UNSAFE === str
9
13
  end
10
14
 
11
- # If the string supplied has ATOM unsafe characters in it, will return the string quoted
15
+ # If the string supplied has ATOM unsafe characters in it, will return the string quoted
12
16
  # in double quotes, otherwise returns the string unmodified
13
17
  def quote_atom( str )
14
18
  atom_safe?( str ) ? str : dquote(str)
15
19
  end
16
20
 
17
- # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
21
+ # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
18
22
  # in double quotes, otherwise returns the string unmodified
19
23
  def quote_phrase( str )
20
- if RUBY_VERSION >= '1.9'
24
+ if str.respond_to?(:force_encoding)
21
25
  original_encoding = str.encoding
22
- str.force_encoding('ASCII-8BIT')
23
- if (PHRASE_UNSAFE === str)
24
- dquote(str).force_encoding(original_encoding)
26
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
27
+ if Constants::PHRASE_UNSAFE === ascii_str
28
+ dquote(ascii_str).force_encoding(original_encoding)
25
29
  else
26
- str.force_encoding(original_encoding)
30
+ str
27
31
  end
28
32
  else
29
- (PHRASE_UNSAFE === str) ? dquote(str) : str
33
+ Constants::PHRASE_UNSAFE === str ? dquote(str) : str
30
34
  end
31
35
  end
32
36
 
33
37
  # Returns true if the string supplied is free from characters not allowed as a TOKEN
34
38
  def token_safe?( str )
35
- not TOKEN_UNSAFE === str
39
+ not Constants::TOKEN_UNSAFE === str
36
40
  end
37
41
 
38
- # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
42
+ # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
39
43
  # in double quotes, otherwise returns the string unmodified
40
44
  def quote_token( str )
41
- token_safe?( str ) ? str : dquote(str)
45
+ if str.respond_to?(:force_encoding)
46
+ original_encoding = str.encoding
47
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
48
+ if token_safe?( ascii_str )
49
+ str
50
+ else
51
+ dquote(ascii_str).force_encoding(original_encoding)
52
+ end
53
+ else
54
+ token_safe?( str ) ? str : dquote(str)
55
+ end
42
56
  end
43
57
 
44
58
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -67,157 +81,482 @@ module Mail
67
81
  # unqoute(string) #=> 'This is "a string"'
68
82
  def unquote( str )
69
83
  if str =~ /^"(.*?)"$/
70
- $1.gsub(/\\(.)/, '\1')
84
+ unescape($1)
71
85
  else
72
86
  str
73
87
  end
74
88
  end
75
-
89
+
90
+ # Removes any \-escaping.
91
+ #
92
+ # Example:
93
+ #
94
+ # string = 'This is \"a string\"'
95
+ # unescape(string) #=> 'This is "a string"'
96
+ #
97
+ # string = '"This is \"a string\""'
98
+ # unescape(string) #=> '"This is "a string""'
99
+ def unescape( str )
100
+ str.gsub(/\\(.)/, '\1')
101
+ end
102
+
76
103
  # Wraps a string in parenthesis and escapes any that are in the string itself.
77
- #
104
+ #
78
105
  # Example:
79
- #
106
+ #
80
107
  # paren( 'This is a string' ) #=> '(This is a string)'
81
108
  def paren( str )
82
- RubyVer.paren( str )
109
+ Utilities.paren( str )
83
110
  end
84
-
111
+
85
112
  # Unwraps a string from being wrapped in parenthesis
86
- #
113
+ #
87
114
  # Example:
88
- #
115
+ #
89
116
  # str = '(This is a string)'
90
117
  # unparen( str ) #=> 'This is a string'
91
118
  def unparen( str )
92
- match = str.match(/^\((.*?)\)$/)
93
- match ? match[1] : str
119
+ if str.start_with?('(') && str.end_with?(')')
120
+ str.slice(1..-2)
121
+ else
122
+ str
123
+ end
94
124
  end
95
-
125
+
96
126
  # Wraps a string in angle brackets and escapes any that are in the string itself
97
- #
127
+ #
98
128
  # Example:
99
- #
129
+ #
100
130
  # bracket( 'This is a string' ) #=> '<This is a string>'
101
131
  def bracket( str )
102
- RubyVer.bracket( str )
132
+ Utilities.bracket( str )
103
133
  end
104
-
134
+
105
135
  # Unwraps a string from being wrapped in parenthesis
106
- #
136
+ #
107
137
  # Example:
108
- #
138
+ #
109
139
  # str = '<This is a string>'
110
140
  # unbracket( str ) #=> 'This is a string'
111
141
  def unbracket( str )
112
- match = str.match(/^\<(.*?)\>$/)
113
- match ? match[1] : str
142
+ if str.start_with?('<') && str.end_with?('>')
143
+ str.slice(1..-2)
144
+ else
145
+ str
146
+ end
114
147
  end
115
-
148
+
116
149
  # Escape parenthesies in a string
117
- #
150
+ #
118
151
  # Example:
119
- #
152
+ #
120
153
  # str = 'This is (a) string'
121
154
  # escape_paren( str ) #=> 'This is \(a\) string'
122
155
  def escape_paren( str )
123
- RubyVer.escape_paren( str )
156
+ Utilities.escape_paren( str )
124
157
  end
125
-
158
+
126
159
  def uri_escape( str )
127
160
  uri_parser.escape(str)
128
161
  end
129
-
162
+
130
163
  def uri_unescape( str )
131
164
  uri_parser.unescape(str)
132
165
  end
133
-
166
+
134
167
  def uri_parser
135
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
168
+ @uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
136
169
  end
137
-
170
+
138
171
  # Matches two objects with their to_s values case insensitively
139
- #
172
+ #
140
173
  # Example:
141
- #
174
+ #
142
175
  # obj2 = "This_is_An_object"
143
176
  # obj1 = :this_IS_an_object
144
177
  # match_to_s( obj1, obj2 ) #=> true
145
178
  def match_to_s( obj1, obj2 )
146
179
  obj1.to_s.casecmp(obj2.to_s) == 0
147
180
  end
148
-
181
+
149
182
  # Capitalizes a string that is joined by hyphens correctly.
150
- #
183
+ #
151
184
  # Example:
152
- #
185
+ #
153
186
  # string = 'resent-from-field'
154
187
  # capitalize_field( string ) #=> 'Resent-From-Field'
155
188
  def capitalize_field( str )
156
189
  str.to_s.split("-").map { |v| v.capitalize }.join("-")
157
190
  end
158
-
191
+
159
192
  # Takes an underscored word and turns it into a class name
160
- #
193
+ #
161
194
  # Example:
162
- #
195
+ #
163
196
  # constantize("hello") #=> "Hello"
164
197
  # constantize("hello-there") #=> "HelloThere"
165
198
  # constantize("hello-there-mate") #=> "HelloThereMate"
166
199
  def constantize( str )
167
200
  str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
168
201
  end
169
-
202
+
170
203
  # Swaps out all underscores (_) for hyphens (-) good for stringing from symbols
171
204
  # a field name.
172
- #
205
+ #
173
206
  # Example:
174
- #
207
+ #
175
208
  # string = :resent_from_field
176
- # dasherize ( string ) #=> 'resent_from_field'
209
+ # dasherize( string ) #=> 'resent-from-field'
177
210
  def dasherize( str )
178
- str.to_s.gsub('_', '-')
211
+ str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
179
212
  end
180
213
 
181
214
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
182
215
  # a field name.
183
- #
216
+ #
184
217
  # Example:
185
- #
218
+ #
186
219
  # string = :resent_from_field
187
220
  # underscoreize ( string ) #=> 'resent_from_field'
188
221
  def underscoreize( str )
189
- str.to_s.downcase.gsub('-', '_')
222
+ str.to_s.downcase.tr(Constants::HYPHEN, Constants::UNDERSCORE)
223
+ end
224
+
225
+ def map_lines( str, &block )
226
+ str.each_line.map(&block)
227
+ end
228
+
229
+ def map_with_index( enum, &block )
230
+ enum.each_with_index.map(&block)
231
+ end
232
+
233
+ def self.binary_unsafe_to_lf(string) #:nodoc:
234
+ string.gsub(/\r\n|\r/, Constants::LF)
235
+ end
236
+
237
+ TO_CRLF_REGEX =
238
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
239
+ # by not matching "\r\n" so the string is returned unchanged in
240
+ # the common case.
241
+ Regexp.new("(?<!\r)\n|\r(?!\n)")
242
+
243
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
244
+ string.gsub(TO_CRLF_REGEX, Constants::CRLF)
245
+ end
246
+
247
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
248
+ if string.encoding == Encoding::BINARY
249
+ string.ascii_only?
250
+ else
251
+ string.valid_encoding?
252
+ end
253
+ end
254
+
255
+ # Convert line endings to \n unless the string is binary. Used for
256
+ # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
257
+ def self.to_lf(string)
258
+ string = string.to_s
259
+ if safe_for_line_ending_conversion? string
260
+ binary_unsafe_to_lf string
261
+ else
262
+ string
263
+ end
264
+ end
265
+
266
+ # Convert line endings to \r\n unless the string is binary. Used for
267
+ # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
268
+ # when parsing emails with \n line endings instead of the required \r\n.
269
+ def self.to_crlf(string)
270
+ string = string.to_s
271
+ if safe_for_line_ending_conversion? string
272
+ binary_unsafe_to_crlf string
273
+ else
274
+ string
275
+ end
276
+ end
277
+
278
+ # Returns true if the object is considered blank.
279
+ # A blank includes things like '', ' ', nil,
280
+ # and arrays and hashes that have nothing in them.
281
+ #
282
+ # This logic is mostly shared with ActiveSupport's blank?
283
+ def blank?(value)
284
+ if value.kind_of?(NilClass)
285
+ true
286
+ elsif value.kind_of?(String)
287
+ value !~ /\S/
288
+ else
289
+ value.respond_to?(:empty?) ? value.empty? : !value
290
+ end
291
+ end
292
+
293
+ def generate_message_id
294
+ "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
190
295
  end
191
296
 
192
- if RUBY_VERSION <= '1.8.6'
297
+ class StrictCharsetEncoder
298
+ def encode(string, charset)
299
+ case charset
300
+ when /utf-?7/i
301
+ Mail::Utilities.decode_utf7(string)
302
+ else
303
+ string.force_encoding(Mail::Utilities.pick_encoding(charset))
304
+ end
305
+ end
306
+ end
193
307
 
194
- def map_lines( str, &block )
195
- results = []
196
- str.each_line do |line|
197
- results << yield(line)
308
+ class BestEffortCharsetEncoder
309
+ def encode(string, charset)
310
+ case charset
311
+ when /utf-?7/i
312
+ Mail::Utilities.decode_utf7(string)
313
+ else
314
+ string.force_encoding(pick_encoding(charset))
198
315
  end
199
- results
200
316
  end
201
-
202
- def map_with_index( enum, &block )
203
- results = []
204
- enum.each_with_index do |token, i|
205
- results[i] = yield(token, i)
317
+
318
+ private
319
+
320
+ def pick_encoding(charset)
321
+ charset = case charset
322
+ when /ansi_x3.110-1983/
323
+ 'ISO-8859-1'
324
+ when /Windows-?1258/i # Windows-1258 is similar to 1252
325
+ "Windows-1252"
326
+ else
327
+ charset
206
328
  end
207
- results
329
+ Mail::Utilities.pick_encoding(charset)
208
330
  end
209
-
210
- else
331
+ end
332
+
333
+ class << self
334
+ attr_accessor :charset_encoder
335
+ end
336
+ self.charset_encoder = BestEffortCharsetEncoder.new
337
+
338
+ # Escapes any parenthesis in a string that are unescaped this uses
339
+ # a Ruby 1.9.1 regexp feature of negative look behind
340
+ def Utilities.escape_paren( str )
341
+ re = /(?<!\\)([\(\)])/ # Only match unescaped parens
342
+ str.gsub(re) { |s| '\\' + s }
343
+ end
344
+
345
+ def Utilities.paren( str )
346
+ str = ::Mail::Utilities.unparen( str )
347
+ str = escape_paren( str )
348
+ '(' + str + ')'
349
+ end
350
+
351
+ def Utilities.escape_bracket( str )
352
+ re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
353
+ str.gsub(re) { |s| '\\' + s }
354
+ end
355
+
356
+ def Utilities.bracket( str )
357
+ str = ::Mail::Utilities.unbracket( str )
358
+ str = escape_bracket( str )
359
+ '<' + str + '>'
360
+ end
211
361
 
212
- def map_lines( str, &block )
213
- str.each_line.map(&block)
362
+ def Utilities.decode_base64(str)
363
+ if !str.end_with?("=") && str.length % 4 != 0
364
+ str = str.ljust((str.length + 3) & ~3, "=")
214
365
  end
366
+ str.unpack( 'm' ).first
367
+ end
368
+
369
+ def Utilities.encode_base64(str)
370
+ [str].pack( 'm' )
371
+ end
372
+
373
+ def Utilities.has_constant?(klass, string)
374
+ klass.const_defined?( string, false )
375
+ end
376
+
377
+ def Utilities.get_constant(klass, string)
378
+ klass.const_get( string )
379
+ end
380
+
381
+ def Utilities.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
382
+ to_encoding = Encoding.find(to_encoding)
383
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
384
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
385
+ end
386
+
387
+ # From Ruby stdlib Net::IMAP
388
+ def Utilities.encode_utf7(string)
389
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
390
+ if $1
391
+ "&-"
392
+ else
393
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
394
+ "&" + base64.delete("=").tr("/", ",") + "-"
395
+ end
396
+ end.force_encoding(Encoding::ASCII_8BIT)
397
+ end
215
398
 
216
- def map_with_index( enum, &block )
217
- enum.each_with_index.map(&block)
399
+ def Utilities.decode_utf7(utf7)
400
+ utf7.gsub(/&([^-]+)?-/n) do
401
+ if $1
402
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
403
+ else
404
+ "&"
405
+ end
218
406
  end
407
+ end
219
408
 
409
+ def Utilities.b_value_encode(str, encoding = nil)
410
+ encoding = str.encoding.to_s
411
+ [Utilities.encode_base64(str), encoding]
220
412
  end
221
413
 
414
+ def Utilities.b_value_decode(str)
415
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
416
+ if match
417
+ charset = match[1]
418
+ str = Utilities.decode_base64(match[2])
419
+ str = charset_encoder.encode(str, charset)
420
+ end
421
+ transcode_to_scrubbed_utf8(str)
422
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError, Encoding::InvalidByteSequenceError
423
+ warn "Encoding conversion failed #{$!}"
424
+ str.dup.force_encoding(Encoding::UTF_8)
425
+ end
426
+
427
+ def Utilities.q_value_encode(str, encoding = nil)
428
+ encoding = str.encoding.to_s
429
+ [Encodings::QuotedPrintable.encode(str), encoding]
430
+ end
431
+
432
+ def Utilities.q_value_decode(str)
433
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
434
+ if match
435
+ charset = match[1]
436
+ string = match[2].gsub(/_/, '=20')
437
+ # Remove trailing = if it exists in a Q encoding
438
+ string = string.sub(/\=$/, '')
439
+ str = Encodings::QuotedPrintable.decode(string)
440
+ str = charset_encoder.encode(str, charset)
441
+ # We assume that binary strings hold utf-8 directly to work around
442
+ # jruby/jruby#829 which subtly changes String#encode semantics.
443
+ str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
444
+ end
445
+ transcode_to_scrubbed_utf8(str)
446
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
447
+ warn "Encoding conversion failed #{$!}"
448
+ str.dup.force_encoding(Encoding::UTF_8)
449
+ end
450
+
451
+ def Utilities.param_decode(str, encoding)
452
+ str = uri_parser.unescape(str)
453
+ str = charset_encoder.encode(str, encoding) if encoding
454
+ transcode_to_scrubbed_utf8(str)
455
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
456
+ warn "Encoding conversion failed #{$!}"
457
+ str.dup.force_encoding(Encoding::UTF_8)
458
+ end
459
+
460
+ def Utilities.param_encode(str)
461
+ encoding = str.encoding.to_s.downcase
462
+ language = Configuration.instance.param_encode_language
463
+ "#{encoding}'#{language}'#{uri_parser.escape(str)}"
464
+ end
465
+
466
+ def Utilities.uri_parser
467
+ URI::DEFAULT_PARSER
468
+ end
469
+
470
+ # Pick a Ruby encoding corresponding to the message charset. Most
471
+ # charsets have a Ruby encoding, but some need manual aliasing here.
472
+ #
473
+ # TODO: add this as a test somewhere:
474
+ # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
475
+ # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
476
+ def Utilities.pick_encoding(charset)
477
+ charset = charset.to_s
478
+ encoding = case charset.downcase
479
+
480
+ # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
481
+ when /^iso[-_]?8859-(\d+)(-i)?$/
482
+ "ISO-8859-#{$1}"
483
+
484
+ # ISO-8859-15, ISO-2022-JP and alike
485
+ when /^iso[-_]?(\d{4})-?(\w{1,2})$/
486
+ "ISO-#{$1}-#{$2}"
487
+
488
+ # "ISO-2022-JP-KDDI" and alike
489
+ when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
490
+ "ISO-#{$1}-#{$2}-#{$3}"
491
+
492
+ # UTF-8, UTF-32BE and alike
493
+ when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
494
+ "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
495
+
496
+ # Windows-1252 and alike
497
+ when /^windows-?(.*)$/
498
+ "Windows-#{$1}"
499
+
500
+ when '8bit'
501
+ Encoding::ASCII_8BIT
502
+
503
+ # alternatives/misspellings of us-ascii seen in the wild
504
+ when /^iso[-_]?646(-us)?$/, 'us=ascii'
505
+ Encoding::ASCII
506
+
507
+ # Microsoft-specific alias for MACROMAN
508
+ when 'macintosh'
509
+ Encoding::MACROMAN
510
+
511
+ # Microsoft-specific alias for CP949 (Korean)
512
+ when 'ks_c_5601-1987'
513
+ Encoding::CP949
514
+
515
+ # Wrongly written Shift_JIS (Japanese)
516
+ when 'shift-jis'
517
+ Encoding::Shift_JIS
518
+
519
+ # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
520
+ when 'gb2312'
521
+ Encoding::GB18030
522
+
523
+ when 'cp-850'
524
+ Encoding::CP850
525
+
526
+ when 'latin2'
527
+ Encoding::ISO_8859_2
528
+
529
+ else
530
+ charset
531
+ end
532
+
533
+ convert_to_encoding(encoding)
534
+ end
535
+
536
+ def Utilities.string_byteslice(str, *args)
537
+ str.byteslice(*args)
538
+ end
539
+
540
+ class << self
541
+ private
542
+
543
+ def convert_to_encoding(encoding)
544
+ if encoding.is_a?(Encoding)
545
+ encoding
546
+ else
547
+ # Fall back to ASCII for charsets that Ruby doesn't recognize
548
+ begin
549
+ Encoding.find(encoding)
550
+ rescue ArgumentError
551
+ Encoding::BINARY
552
+ end
553
+ end
554
+ end
555
+
556
+ def transcode_to_scrubbed_utf8(str)
557
+ decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
558
+ decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
559
+ end
560
+ end
222
561
  end
223
562
  end
Binary file