mail 2.6.6 → 2.7.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 (160) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +74 -90
  4. data/lib/mail/attachments_list.rb +8 -4
  5. data/lib/mail/body.rb +50 -38
  6. data/lib/mail/check_delivery_params.rb +8 -6
  7. data/lib/mail/configuration.rb +2 -0
  8. data/lib/mail/constants.rb +1 -1
  9. data/lib/mail/core_extensions/smtp.rb +19 -16
  10. data/lib/mail/core_extensions/string.rb +0 -4
  11. data/lib/mail/elements/address.rb +28 -22
  12. data/lib/mail/elements/address_list.rb +10 -18
  13. data/lib/mail/elements/content_disposition_element.rb +8 -15
  14. data/lib/mail/elements/content_location_element.rb +5 -10
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +5 -10
  16. data/lib/mail/elements/content_type_element.rb +8 -19
  17. data/lib/mail/elements/date_time_element.rb +6 -14
  18. data/lib/mail/elements/envelope_from_element.rb +14 -21
  19. data/lib/mail/elements/message_ids_element.rb +8 -12
  20. data/lib/mail/elements/mime_version_element.rb +6 -14
  21. data/lib/mail/elements/phrase_list.rb +6 -9
  22. data/lib/mail/elements/received_element.rb +9 -15
  23. data/lib/mail/encodings/7bit.rb +5 -15
  24. data/lib/mail/encodings/8bit.rb +2 -21
  25. data/lib/mail/encodings/base64.rb +11 -12
  26. data/lib/mail/encodings/binary.rb +3 -22
  27. data/lib/mail/encodings/identity.rb +24 -0
  28. data/lib/mail/encodings/quoted_printable.rb +6 -6
  29. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  30. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  31. data/lib/mail/encodings.rb +99 -43
  32. data/lib/mail/envelope.rb +1 -1
  33. data/lib/mail/field.rb +96 -59
  34. data/lib/mail/fields/bcc_field.rb +2 -2
  35. data/lib/mail/fields/cc_field.rb +1 -1
  36. data/lib/mail/fields/comments_field.rb +1 -1
  37. data/lib/mail/fields/common/common_address.rb +32 -7
  38. data/lib/mail/fields/common/common_field.rb +1 -10
  39. data/lib/mail/fields/common/parameter_hash.rb +1 -1
  40. data/lib/mail/fields/content_description_field.rb +1 -1
  41. data/lib/mail/fields/content_disposition_field.rb +3 -3
  42. data/lib/mail/fields/content_id_field.rb +2 -2
  43. data/lib/mail/fields/content_location_field.rb +1 -1
  44. data/lib/mail/fields/content_transfer_encoding_field.rb +1 -1
  45. data/lib/mail/fields/content_type_field.rb +4 -9
  46. data/lib/mail/fields/date_field.rb +2 -3
  47. data/lib/mail/fields/from_field.rb +1 -1
  48. data/lib/mail/fields/in_reply_to_field.rb +1 -1
  49. data/lib/mail/fields/keywords_field.rb +1 -1
  50. data/lib/mail/fields/message_id_field.rb +1 -1
  51. data/lib/mail/fields/mime_version_field.rb +1 -1
  52. data/lib/mail/fields/optional_field.rb +4 -1
  53. data/lib/mail/fields/received_field.rb +1 -1
  54. data/lib/mail/fields/references_field.rb +1 -1
  55. data/lib/mail/fields/reply_to_field.rb +1 -1
  56. data/lib/mail/fields/resent_bcc_field.rb +1 -1
  57. data/lib/mail/fields/resent_cc_field.rb +1 -1
  58. data/lib/mail/fields/resent_date_field.rb +0 -1
  59. data/lib/mail/fields/resent_from_field.rb +1 -1
  60. data/lib/mail/fields/resent_message_id_field.rb +1 -1
  61. data/lib/mail/fields/resent_sender_field.rb +1 -1
  62. data/lib/mail/fields/resent_to_field.rb +1 -1
  63. data/lib/mail/fields/return_path_field.rb +1 -1
  64. data/lib/mail/fields/sender_field.rb +1 -1
  65. data/lib/mail/fields/subject_field.rb +1 -1
  66. data/lib/mail/fields/to_field.rb +1 -1
  67. data/lib/mail/fields/unstructured_field.rb +21 -4
  68. data/lib/mail/header.rb +10 -8
  69. data/lib/mail/mail.rb +2 -10
  70. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  71. data/lib/mail/message.rb +78 -68
  72. data/lib/mail/multibyte/chars.rb +29 -28
  73. data/lib/mail/multibyte/unicode.rb +10 -10
  74. data/lib/mail/multibyte.rb +64 -15
  75. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  76. data/lib/mail/network/delivery_methods/sendmail.rb +8 -5
  77. data/lib/mail/network/delivery_methods/smtp.rb +58 -49
  78. data/lib/mail/network/delivery_methods/smtp_connection.rb +9 -1
  79. data/lib/mail/network/retriever_methods/imap.rb +18 -5
  80. data/lib/mail/network/retriever_methods/pop3.rb +3 -1
  81. data/lib/mail/network.rb +1 -0
  82. data/lib/mail/parser_tools.rb +15 -0
  83. data/lib/mail/parsers/address_lists_parser.rb +33207 -104
  84. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  85. data/lib/mail/parsers/content_disposition_parser.rb +876 -49
  86. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  87. data/lib/mail/parsers/content_location_parser.rb +803 -23
  88. data/lib/mail/parsers/content_location_parser.rl +71 -0
  89. data/lib/mail/parsers/content_transfer_encoding_parser.rb +501 -19
  90. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  91. data/lib/mail/parsers/content_type_parser.rb +1023 -48
  92. data/lib/mail/parsers/content_type_parser.rl +83 -0
  93. data/lib/mail/parsers/date_time_parser.rb +870 -24
  94. data/lib/mail/parsers/date_time_parser.rl +62 -0
  95. data/lib/mail/parsers/envelope_from_parser.rb +3569 -34
  96. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  97. data/lib/mail/parsers/message_ids_parser.rb +2839 -25
  98. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  99. data/lib/mail/parsers/mime_version_parser.rb +491 -26
  100. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  101. data/lib/mail/parsers/phrase_lists_parser.rb +860 -18
  102. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  103. data/lib/mail/parsers/received_parser.rb +8764 -37
  104. data/lib/mail/parsers/received_parser.rl +84 -0
  105. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  106. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  107. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  108. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  109. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  110. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  111. data/lib/mail/parsers/rfc5322.rl +59 -0
  112. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  113. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  114. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  115. data/lib/mail/parsers.rb +16 -24
  116. data/lib/mail/part.rb +3 -3
  117. data/lib/mail/parts_list.rb +5 -6
  118. data/lib/mail/utilities.rb +59 -28
  119. data/lib/mail/version.rb +2 -2
  120. data/lib/mail/version_specific/ruby_1_8.rb +40 -3
  121. data/lib/mail/version_specific/ruby_1_9.rb +61 -9
  122. data/lib/mail.rb +3 -16
  123. metadata +44 -53
  124. data/CHANGELOG.rdoc +0 -803
  125. data/CONTRIBUTING.md +0 -60
  126. data/Dependencies.txt +0 -2
  127. data/Gemfile +0 -14
  128. data/Rakefile +0 -29
  129. data/TODO.rdoc +0 -9
  130. data/lib/mail/core_extensions/string/access.rb +0 -146
  131. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  132. data/lib/mail/multibyte/exceptions.rb +0 -9
  133. data/lib/mail/parsers/ragel/common.rl +0 -185
  134. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  135. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  136. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  137. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  138. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  139. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  140. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  141. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  142. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  143. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  144. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  145. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  146. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  147. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  148. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  149. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  150. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  151. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  152. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  153. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  154. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  155. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  156. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  157. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  159. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  160. data/lib/mail/parsers/ragel.rb +0 -18
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+ require 'mail/utilities'
3
+ require 'mail/parser_tools'
4
+
5
+ %%{
6
+ machine date_time;
7
+ alphtype int;
8
+
9
+ # Received Tokens
10
+ action received_tokens_s { received_tokens_s = p }
11
+ action received_tokens_e { received.info = chars(data, received_tokens_s, p-1) }
12
+
13
+ # Date
14
+ action date_s { date_s = p }
15
+ action date_e { received.date = chars(data, date_s, p-1).strip }
16
+
17
+ # Time
18
+ action time_s { time_s = p }
19
+ action time_e { received.time = chars(data, time_s, p-1) }
20
+
21
+ # No-op actions
22
+ action address_s {}
23
+ action address_e {}
24
+ action angle_addr_s {}
25
+ action ctime_date_s {}
26
+ action ctime_date_e {}
27
+ action comment_e {}
28
+ action comment_s {}
29
+ action phrase_s {}
30
+ action phrase_e {}
31
+ action domain_e {}
32
+ action domain_s {}
33
+ action local_dot_atom_e {}
34
+ action local_dot_atom_pre_comment_e {}
35
+ action local_dot_atom_pre_comment_s {}
36
+ action local_dot_atom_s {}
37
+ action qstr_e {}
38
+ action qstr_s {}
39
+ action local_quoted_string_s {}
40
+ action local_quoted_string_e {}
41
+ action obs_domain_list_s {}
42
+ action obs_domain_list_e {}
43
+ action group_name_s {}
44
+ action group_name_e {}
45
+ action msg_id_s {}
46
+ action msg_id_e {}
47
+
48
+ include rfc5322 "rfc5322.rl";
49
+ main := received;
50
+ }%%
51
+
52
+ module Mail::Parsers
53
+ module ReceivedParser
54
+ extend Mail::ParserTools
55
+
56
+ ReceivedStruct = Struct.new(:date, :time, :info, :error)
57
+
58
+ %%write data noprefix;
59
+
60
+ def self.parse(data)
61
+ data = data.dup.force_encoding(Encoding::ASCII_8BIT) if data.respond_to?(:force_encoding)
62
+
63
+ raise Mail::Field::NilParseError.new(Mail::ReceivedElement) if data.nil?
64
+
65
+ # Parser state
66
+ received = ReceivedStruct.new
67
+ received_tokens_s = date_s = time_s = nil
68
+
69
+ # 5.1 Variables Used by Ragel
70
+ p = 0
71
+ eof = pe = data.length
72
+ stack = []
73
+
74
+ %%write init;
75
+ %%write exec;
76
+
77
+ if p != eof || cs < %%{ write first_final; }%%
78
+ raise Mail::Field::IncompleteParseError.new(Mail::ReceivedElement, data, p)
79
+ end
80
+
81
+ received
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ %%{
2
+ # RFC 2045 Section 6.1: Content-Transfer-Encoding Header Field
3
+ # https://tools.ietf.org/html/rfc2045#section-6.1
4
+ machine rfc2045_content_transfer_encoding;
5
+ alphtype int;
6
+
7
+ include rfc2045_content_type "rfc2045_content_type.rl";
8
+
9
+ encoding = ('7bits' | '8bits' | '7bit' | '8bit' | 'binary' |
10
+ 'quoted-printable' | 'base64' | ietf_token |
11
+ custom_x_token) >encoding_s %encoding_e;
12
+ content_transfer_encoding = CFWS? encoding CFWS? ";"? CFWS?;
13
+ }%%
@@ -0,0 +1,25 @@
1
+ %%{
2
+ # RFC 2045 Section 5.1: Content-Type Header Field
3
+ # https://tools.ietf.org/html/rfc2045#section-5.1
4
+ # Previously: https://tools.ietf.org/html/rfc1049#section-3
5
+ machine rfc2045_content_type;
6
+ alphtype int;
7
+
8
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
9
+
10
+ token = 0x21..0x27 | 0x2a..0x2b | 0x2c..0x2e | 0x30..0x39 | 0x41..0x5a | 0x5e..0x7e;
11
+ value = (quoted_string | (token -- '"' | 0x3d)+) >param_val_s %param_val_e;
12
+ attribute = (token+) >param_attr_s %param_attr_e;
13
+ parameter = CFWS? attribute "=" value CFWS?;
14
+
15
+ ietf_token = token+;
16
+ custom_x_token = 'x'i "-" token+;
17
+ extension_token = ietf_token | custom_x_token;
18
+ discrete_type = 'text'i | 'image'i | 'audio'i | 'video'i |
19
+ 'application'i | extension_token;
20
+ composite_type = 'message'i | 'multipart'i | extension_token;
21
+ iana_token = token+;
22
+ main_type = (discrete_type | composite_type) >main_type_s %main_type_e;
23
+ sub_type = (extension_token | iana_token) >sub_type_s %sub_type_e;
24
+ content_type = main_type "/" sub_type (((CFWS? ";"+) | CFWS) parameter CFWS?)*;
25
+ }%%
@@ -0,0 +1,16 @@
1
+ %%{
2
+ # RFC 2045 MIME
3
+ # https://tools.ietf.org/html/rfc2045
4
+ machine rfc2045_mime;
5
+ alphtype int;
6
+
7
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
8
+
9
+ # 4. MIME-Version Header Field
10
+ # https://tools.ietf.org/html/rfc2045#section-4
11
+ mime_version = CFWS?
12
+ (DIGIT+ >major_digits_s %major_digits_e)
13
+ comment? "." comment?
14
+ (DIGIT+ >minor_digits_s %minor_digits_e)
15
+ CFWS?;
16
+ }%%
@@ -0,0 +1,15 @@
1
+ %%{
2
+ # RFC 2183 The Content-Disposition Header Field
3
+ # https://tools.ietf.org/html/rfc2183#section-2
4
+ #
5
+ # TODO: recognize filename, size, creation date, etc.
6
+ machine rfc2183_content_disposition;
7
+ alphtype int;
8
+
9
+ include rfc2045_content_type "rfc2045_content_type.rl";
10
+
11
+ disposition_type = 'inline'i | 'attachment'i | extension_token;
12
+ disposition_parm = parameter;
13
+ disposition = (disposition_type >disp_type_s %disp_type_e)
14
+ (";" disposition_parm)*;
15
+ }%%
@@ -0,0 +1,19 @@
1
+ %%{
2
+ # RFC 3629 4. Syntax of UTF-8 Byte Sequences
3
+ # https://tools.ietf.org/html/rfc3629#section-4
4
+ machine rfc3629_utf8;
5
+ alphtype int;
6
+
7
+ utf8_tail = 0x80..0xBF;
8
+
9
+ utf8_2byte = 0xC2..0xDF utf8_tail;
10
+ utf8_3byte = 0xE0 0xA0..0xBF utf8_tail |
11
+ 0xE1..0xEC utf8_tail utf8_tail |
12
+ 0xED 0x80..0x9F utf8_tail |
13
+ 0xEE..0xEF utf8_tail utf8_tail;
14
+ utf8_4byte = 0xF0 0x90..0xBF utf8_tail utf8_tail |
15
+ 0xF1..0xF3 utf8_tail utf8_tail utf8_tail |
16
+ 0xF4 0x80..0x8F utf8_tail utf8_tail;
17
+
18
+ utf8_non_ascii = utf8_2byte | utf8_3byte | utf8_4byte;
19
+ }%%
@@ -0,0 +1,22 @@
1
+ %%{
2
+ # RFC 5234 B.1. Core Rules
3
+ # https://tools.ietf.org/html/rfc5234#appendix-B.1
4
+ machine rfc5234_abnf_core_rules;
5
+ alphtype int;
6
+
7
+ include rfc3629_utf8 "rfc3629_utf8.rl";
8
+
9
+ LF = "\n";
10
+ CR = "\r";
11
+ CRLF = "\r\n";
12
+ SP = " ";
13
+ HTAB = "\t";
14
+ WSP = SP | HTAB;
15
+ DQUOTE = '"';
16
+ DIGIT = [0-9];
17
+ ALPHA = [a-zA-Z];
18
+
19
+ # RFC6532 extension for UTF-8 content
20
+ rfc5234_VCHAR = 0x21..0x7e;
21
+ VCHAR = rfc5234_VCHAR | utf8_non_ascii;
22
+ }%%
@@ -0,0 +1,59 @@
1
+ %%{
2
+ # RFC 5322 Internet Message Format
3
+ # https://tools.ietf.org/html/rfc5322
4
+ #
5
+ # RFC 6854 Update to Internet Message Format to Allow Group Syntax in the "From:" and "Sender:" Header Fields
6
+ # https://tools.ietf.org/html/rfc6854
7
+ machine rfc5322;
8
+ alphtype int;
9
+
10
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
11
+
12
+ # 3.2. Lexical Tokens
13
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
14
+
15
+ # 3.3. Date and Time Specification
16
+ include rfc5322_date_time "rfc5322_date_time.rl";
17
+
18
+ # 3.4. Address Specification
19
+ include rfc5322_address "rfc5322_address.rl";
20
+
21
+ # 3.5. Overall Message Syntax
22
+ #rfc5322_text = 0x01..0x09 | "\v" | "\f" | 0x0e..0x1f;
23
+ #text = rfc5322_text | utf8_non_ascii; # RFC6532 for UTF-8
24
+ #obs_body = ((LF* CR* ((0x00 | text) LF* CR*)*) | CRLF)*
25
+ #body = ((text{,998} CRLF)* text{,998}) | obs_body;
26
+ #message = (fields | obs_fields) (CRLF body)?;
27
+
28
+
29
+ # 3.6. Field Definitions
30
+
31
+ # 3.6.4. Identification Fields
32
+ obs_id_left = local_part;
33
+ id_left = dot_atom_text | obs_id_left;
34
+ # id_right modifications to support multiple '@' in msg_id.
35
+ msg_id_atext = ALPHA | DIGIT | "!" | "#" | "$" | "%" | "&" | "'" | "*" |
36
+ "+" | "-" | "/" | "=" | "?" | "^" | "_" | "`" | "{" | "|" |
37
+ "}" | "~" | "@";
38
+ msg_id_dot_atom_text = (msg_id_atext+ "."?)+;
39
+ obs_id_right = domain;
40
+ no_fold_literal = "[" (dtext)* "]";
41
+ id_right = msg_id_dot_atom_text | no_fold_literal | obs_id_right;
42
+ msg_id = (CFWS)?
43
+ (("<" id_left "@" id_right ">") >msg_id_s %msg_id_e)
44
+ (CFWS)?;
45
+ message_ids = msg_id (CFWS? msg_id)*;
46
+
47
+
48
+ # 3.6.7 Trace Fields
49
+ # Added CFWS? to increase robustness (qmail likes to include a comment)
50
+ received_token = word | angle_addr | addr_spec_no_angle_brackets | domain;
51
+ received = ((CFWS? received_token*) >received_tokens_s %received_tokens_e)
52
+ ";" date_time;
53
+
54
+ # Envelope From
55
+ ctime_date = day_name " "+ month " "+ day " " time_of_day " " year;
56
+ null_sender = ('<>' ' '{0,1});
57
+ envelope_from = (addr_spec_no_angle_brackets | null_sender) >address_s %address_e " "
58
+ (ctime_date >ctime_date_s %ctime_date_e);
59
+ }%%
@@ -0,0 +1,72 @@
1
+ %%{
2
+ # RFC 5322 Internet Message Format
3
+ # Section 3.4. Address Specification
4
+ # https://tools.ietf.org/html/rfc5322#section-3.4
5
+ machine rfc5322_address;
6
+ alphtype int;
7
+
8
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
9
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
10
+
11
+ # local_part:
12
+ domain_text = (DQUOTE (FWS? qcontent)+ FWS? DQUOTE) | atext+;
13
+ local_dot_atom_text = ("."* domain_text "."*)+;
14
+ local_dot_atom = CFWS?
15
+ (local_dot_atom_text >local_dot_atom_s %local_dot_atom_pre_comment_e)
16
+ CFWS?;
17
+ obs_local_part = word ("." word)*;
18
+ local_part = (local_dot_atom >local_dot_atom_s %local_dot_atom_e |
19
+ (quoted_string %local_quoted_string_e) |
20
+ obs_local_part);
21
+
22
+ # Treetop parser behavior was to ignore addresses missing '@' inside of angle
23
+ # brackets. This construction preserves that behavior.
24
+ local_part_no_capture = (local_dot_atom | quoted_string | obs_local_part);
25
+
26
+ # domain:
27
+ domain_dot_atom_text = "."* domain_text ("."* domain_text)*;
28
+ obs_dtext = obs_NO_WS_CTL | quoted_pair;
29
+ rfc5322_dtext = 0x21..0x5a | 0x5e..0x7e | obs_dtext;
30
+ dtext = rfc5322_dtext | utf8_non_ascii; # RFC6532 for UTF-8
31
+ domain_dot_atom = CFWS? domain_dot_atom_text (CFWS? >(comment_after_address,1));
32
+ domain_literal = CFWS? "[" (FWS? dtext)* FWS? "]" CFWS?;
33
+ obs_domain = atom ("." atom)*;
34
+ domain = (domain_dot_atom | domain_literal | obs_domain) >domain_s %domain_e;
35
+
36
+ # 3.4.1. Addr-Spec Specification
37
+
38
+ # The %(end_addr,N) priority resolves uncertainty when whitespace
39
+ # after an addr_spec could cause it to be interpreted as a
40
+ # display name: "bar@example.com ,..."
41
+
42
+ addr_spec_in_angle_brackets =
43
+ (local_part "@" domain) %(end_addr,1) |
44
+ local_part_no_capture %(end_addr,0);
45
+
46
+ addr_spec_no_angle_brackets =
47
+ (local_part "@" domain) %(end_addr,1) |
48
+ local_part %(end_addr,0);
49
+
50
+ # angle_addr:
51
+ obs_domain_list = (CFWS | ",")* "@" domain ("," CFWS? ("@" domain)?)*;
52
+ obs_route = (obs_domain_list ":") >obs_domain_list_s %obs_domain_list_e;
53
+ obs_angle_addr = CFWS? "<" obs_route? addr_spec_in_angle_brackets ">" CFWS?;
54
+
55
+ angle_addr = CFWS? ("<" >angle_addr_s) addr_spec_in_angle_brackets ">" CFWS? |
56
+ obs_angle_addr;
57
+
58
+ # 3.4. Address Specification
59
+ display_name = phrase;
60
+ name_addr = display_name? %(end_addr,2) angle_addr;
61
+ mailbox = (name_addr | addr_spec_no_angle_brackets) >address_s %address_e;
62
+ obs_mbox_list = (CFWS? ",")* mailbox ("," (mailbox | CFWS)?)*;
63
+ mailbox_list = (mailbox (("," | ";") mailbox)*) | obs_mbox_list;
64
+ obs_group_list = (CFWS? ",")+ CFWS?;
65
+ group_list = mailbox_list | CFWS | obs_group_list;
66
+ group = (display_name >group_name_s %group_name_e) ":"
67
+ (group_list?) ";" CFWS?;
68
+ address = group | mailbox;
69
+ #obs_addr_list = (CFWS? ",")* address ("," (address | CFWS)?)*;
70
+ address_lists = address? %(comment_after_address,0)
71
+ (FWS* ("," | ";") FWS* address?)*;
72
+ }%%
@@ -1,6 +1,11 @@
1
1
  %%{
2
+ # RFC 5322 Internet Message Format
3
+ # Section 3.3. Date and Time Specification
4
+ # https://tools.ietf.org/html/rfc5322#section-3.3
5
+ machine rfc5322_date_time;
6
+ alphtype int;
2
7
 
3
- machine date_time;
8
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
4
9
 
5
10
  # day_of_week
6
11
  day_name = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
@@ -27,4 +32,6 @@
27
32
  zone = FWS ((("+" | "-") DIGIT DIGIT DIGIT DIGIT) | obs_zone);
28
33
  time = time_of_day zone;
29
34
 
35
+ date_time = (day_of_week ",")?
36
+ (date >date_s %date_e) <: (time >time_s %time_e) CFWS?;
30
37
  }%%
@@ -0,0 +1,60 @@
1
+ %%{
2
+ # RFC 5322 Internet Message Format
3
+ # Section 3.2. Lexical Tokens
4
+ # https://tools.ietf.org/html/rfc5322#section-3.2
5
+ machine rfc5322_lexical_tokens;
6
+ alphtype int;
7
+
8
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
9
+
10
+ # 3.2.1. Quoted characters
11
+ obs_NO_WS_CTL = 0x01..0x08 | "\v" | "\f" | 0x0e..0x1f | 0x7f;
12
+ obs_qp = "\\" (0x00 | obs_NO_WS_CTL | LF | CR);
13
+ quoted_pair = ("\\" (VCHAR | WSP)) | obs_qp;
14
+
15
+ # 3.2.2. Folding White Space and Comments
16
+ obs_FWS = (CRLF? WSP)+;
17
+ FWS = (WSP* CRLF WSP+) | (CRLF WSP+) | obs_FWS;
18
+
19
+ obs_ctext = obs_NO_WS_CTL;
20
+ rfc5322_ctext = 0x21..0x27 | 0x2a..0x5b | 0x5d..0x7e | obs_ctext;
21
+ ctext = rfc5322_ctext | utf8_non_ascii; # RFC6532 for UTF-8
22
+
23
+ # Recursive comments
24
+ action comment_begin { fcall comment_tail; }
25
+ action comment_exit { fret; }
26
+ ccontent = ctext | quoted_pair | "(" @comment_begin;
27
+ comment_tail := ((FWS? ccontent)* >comment_s) FWS? ")" @comment_exit;
28
+ comment = "(" @comment_begin %comment_e;
29
+ CFWS = ((FWS? comment)+ FWS?) | FWS;
30
+
31
+ # 3.2.3. Atom
32
+ rfc5322_atext = ALPHA | DIGIT | "!" | "#" | "$" | "%" | "&" |
33
+ "'" | "*" | "+" | "-" | "/" | "=" | "?" | "^" |
34
+ "_" | "`" | "{" | "|" | "}" | "~";
35
+ atext = rfc5322_atext | utf8_non_ascii; # RFC6532 for UTF-8
36
+ atom = CFWS? atext+ CFWS?;
37
+ dot_atom_text = atext ("." atext)*;
38
+ dot_atom = CFWS? dot_atom_text CFWS?;
39
+
40
+ # 3.2.4. Quoted Strings
41
+ obs_qtext = obs_NO_WS_CTL;
42
+ rfc5322_qtext = 0x21 | 0x23..0x5b | 0x5d..0x7e | obs_qtext;
43
+ qtext = rfc5322_qtext | utf8_non_ascii; # RFC6532 for UTF-8
44
+
45
+ qcontent = qtext | quoted_pair;
46
+ quoted_string = CFWS?
47
+ (DQUOTE
48
+ (((FWS? qcontent)* FWS?) >qstr_s %qstr_e)
49
+ DQUOTE)
50
+ CFWS?;
51
+
52
+ # 3.2.5. Miscellaneous Tokens
53
+ word = atom | quoted_string;
54
+
55
+ obs_phrase = (word | "." | "@")+;
56
+ phrase = (obs_phrase | word+) >phrase_s %phrase_e;
57
+
58
+ # Not part of RFC, used for keywords per 3.6.5 Information Fields
59
+ phrase_lists = phrase ("," FWS* phrase)*;
60
+ }%%
data/lib/mail/parsers.rb CHANGED
@@ -1,27 +1,19 @@
1
1
  # frozen_string_literal: true
2
- module Mail
3
- module Parsers
2
+ # Ragel-generated parsers are full of known warnings. Suppress them.
3
+ begin
4
+ orig, $VERBOSE = $VERBOSE, nil
4
5
 
5
- # Low-level ragel based parsers
6
- require 'mail/parsers/ragel'
7
-
8
- AddressListStruct = Struct.new(:addresses, :group_names, :error)
9
- AddressStruct = Struct.new(:raw, :domain, :comments, :local,
10
- :obs_domain_list, :display_name, :group, :error)
11
- ContentDispositionStruct = Struct.new(:disposition_type, :parameters, :error)
12
- ContentLocationStruct = Struct.new(:location, :error)
13
- ContentTransferEncodingStruct = Struct.new(:encoding, :error)
14
- ContentTypeStruct = Struct.new(:main_type, :sub_type, :parameters, :error)
15
- DateTimeStruct = Struct.new(:date_string, :time_string, :error)
16
- EnvelopeFromStruct = Struct.new(:address, :ctime_date, :error)
17
- MessageIdsStruct = Struct.new(:message_ids, :error)
18
- MimeVersionStruct = Struct.new(:major, :minor, :error)
19
- PhraseListsStruct = Struct.new(:phrases, :error)
20
- ReceivedStruct = Struct.new(:date, :time, :info, :error)
21
-
22
- require 'mail/parsers/ragel/parser_info'
23
- Ragel::FIELD_PARSERS.each do |field_parser|
24
- require "mail/parsers/#{field_parser}_parser"
25
- end
26
- end
6
+ require 'mail/parsers/address_lists_parser'
7
+ require 'mail/parsers/content_disposition_parser'
8
+ require 'mail/parsers/content_location_parser'
9
+ require 'mail/parsers/content_transfer_encoding_parser'
10
+ require 'mail/parsers/content_type_parser'
11
+ require 'mail/parsers/date_time_parser'
12
+ require 'mail/parsers/envelope_from_parser'
13
+ require 'mail/parsers/message_ids_parser'
14
+ require 'mail/parsers/mime_version_parser'
15
+ require 'mail/parsers/phrase_lists_parser'
16
+ require 'mail/parsers/received_parser'
17
+ ensure
18
+ $VERBOSE = orig
27
19
  end
data/lib/mail/part.rb CHANGED
@@ -21,7 +21,7 @@ module Mail
21
21
 
22
22
  def inline_content_id
23
23
  # TODO: Deprecated in 2.2.2 - Remove in 2.3
24
- STDERR.puts("Part#inline_content_id is deprecated, please call Part#cid instead")
24
+ warn("Part#inline_content_id is deprecated, please call Part#cid instead")
25
25
  cid
26
26
  end
27
27
 
@@ -104,8 +104,8 @@ module Mail
104
104
 
105
105
  # A part may not have a header.... so, just init a body if no header
106
106
  def parse_message
107
- header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
108
- if header_part =~ HEADER_LINE
107
+ header_part, body_part = raw_source.split(/#{Constants::CRLF}#{Constants::WSP}*#{Constants::CRLF}/m, 2)
108
+ if header_part =~ Constants::HEADER_LINE
109
109
  self.header = header_part
110
110
  self.body = body_part
111
111
  else
@@ -55,7 +55,7 @@ module Mail
55
55
  # OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
56
56
  # single email message... please show me a use case and I'll put more work into this method,
57
57
  # in the meantime, it works :)
58
- [get_order_value(a, order), i += 1]
58
+ get_order_value(a, order) << (i += 1)
59
59
  end
60
60
  @parts.clear
61
61
  sorted.each { |p| @parts << p }
@@ -64,11 +64,10 @@ module Mail
64
64
  private
65
65
 
66
66
  def get_order_value(part, order)
67
- if part.respond_to?(:content_type) && !part[:content_type].nil?
68
- order.index(part[:content_type].string.downcase) || 10000
69
- else
70
- 10000
71
- end
67
+ is_attachment = part.respond_to?(:attachment?) && part.attachment?
68
+ has_content_type = part.respond_to?(:content_type) && !part[:content_type].nil?
69
+
70
+ [is_attachment ? 1 : 0, (has_content_type ? order.index(part[:content_type].string.downcase) : nil) || 10000]
72
71
  end
73
72
 
74
73
  end
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/constants'
4
+
3
5
  module Mail
4
6
  module Utilities
5
7
 
@@ -22,9 +24,9 @@ module Mail
22
24
  # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
23
25
  # in double quotes, otherwise returns the string unmodified
24
26
  def quote_phrase( str )
25
- if RUBY_VERSION >= '1.9'
27
+ if str.respond_to?(:force_encoding)
26
28
  original_encoding = str.encoding
27
- ascii_str = str.dup.force_encoding('ASCII-8BIT')
29
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
28
30
  if (PHRASE_UNSAFE === ascii_str)
29
31
  dquote(ascii_str).force_encoding(original_encoding)
30
32
  else
@@ -43,7 +45,17 @@ module Mail
43
45
  # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
44
46
  # in double quotes, otherwise returns the string unmodified
45
47
  def quote_token( str )
46
- token_safe?( str ) ? str : dquote(str)
48
+ if str.respond_to?(:force_encoding)
49
+ original_encoding = str.encoding
50
+ ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
51
+ if token_safe?( ascii_str )
52
+ str
53
+ else
54
+ dquote(ascii_str).force_encoding(original_encoding)
55
+ end
56
+ else
57
+ token_safe?( str ) ? str : dquote(str)
58
+ end
47
59
  end
48
60
 
49
61
  # Wraps supplied string in double quotes and applies \-escaping as necessary,
@@ -77,6 +89,7 @@ module Mail
77
89
  str
78
90
  end
79
91
  end
92
+ module_function :unquote
80
93
 
81
94
  # Removes any \-escaping.
82
95
  #
@@ -192,7 +205,7 @@ module Mail
192
205
  # Example:
193
206
  #
194
207
  # string = :resent_from_field
195
- # dasherize ( string ) #=> 'resent_from_field'
208
+ # dasherize( string ) #=> 'resent-from-field'
196
209
  def dasherize( str )
197
210
  str.to_s.tr(UNDERSCORE, HYPHEN)
198
211
  end
@@ -238,40 +251,59 @@ module Mail
238
251
 
239
252
  end
240
253
 
241
- # Test String#encode works correctly with line endings.
242
- # Some versions of Ruby (e.g. MRI <1.9, JRuby, Rubinius) line ending
243
- # normalization does not work correctly or did not have #encode.
244
- if ("\r".encode(:universal_newline => true) rescue nil) == LF &&
245
- (LF.encode(:crlf_newline => true) rescue nil) == CRLF
246
- # Using String#encode is better performing than Regexp
254
+ def self.binary_unsafe_to_lf(string) #:nodoc:
255
+ string.gsub(/\r\n|\r/, LF)
256
+ end
247
257
 
248
- def self.to_lf(input)
249
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true) : ''
258
+ TO_CRLF_REGEX =
259
+ if RUBY_VERSION >= '1.9'
260
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
261
+ # by not matching "\r\n" so the string is returned unchanged in
262
+ # the common case.
263
+ Regexp.new("(?<!\r)\n|\r(?!\n)")
264
+ else
265
+ /\n|\r\n|\r/
250
266
  end
251
267
 
252
- def self.to_crlf(input)
253
- input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true).encode!(input.encoding, :crlf_newline => true) : ''
254
- end
268
+ def self.binary_unsafe_to_crlf(string) #:nodoc:
269
+ string.gsub(TO_CRLF_REGEX, CRLF)
270
+ end
255
271
 
272
+ if RUBY_VERSION < '1.9'
273
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
274
+ string.ascii_only?
275
+ end
256
276
  else
257
-
258
- def self.to_lf(input)
259
- input.kind_of?(String) ? input.to_str.gsub(/\r\n|\r/, LF) : ''
277
+ def self.safe_for_line_ending_conversion?(string) #:nodoc:
278
+ if string.encoding == Encoding::BINARY
279
+ string.ascii_only?
280
+ else
281
+ string.valid_encoding?
282
+ end
260
283
  end
284
+ end
261
285
 
262
- if RUBY_VERSION >= '1.9'
263
- # This 1.9 only regex can save a reasonable amount of time (~20%)
264
- # by not matching "\r\n" so the string is returned unchanged in
265
- # the common case.
266
- CRLF_REGEX = Regexp.new("(?<!\r)\n|\r(?!\n)")
286
+ # Convert line endings to \n unless the string is binary. Used for
287
+ # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
288
+ def self.to_lf(string)
289
+ string = string.to_s
290
+ if safe_for_line_ending_conversion? string
291
+ binary_unsafe_to_lf string
267
292
  else
268
- CRLF_REGEX = /\n|\r\n|\r/
293
+ string
269
294
  end
295
+ end
270
296
 
271
- def self.to_crlf(input)
272
- input.kind_of?(String) ? input.to_str.gsub(CRLF_REGEX, CRLF) : ''
297
+ # Convert line endings to \r\n unless the string is binary. Used for
298
+ # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
299
+ # when parsing emails with \n line endings instead of the required \r\n.
300
+ def self.to_crlf(string)
301
+ string = string.to_s
302
+ if safe_for_line_ending_conversion? string
303
+ binary_unsafe_to_crlf string
304
+ else
305
+ string
273
306
  end
274
-
275
307
  end
276
308
 
277
309
  # Returns true if the object is considered blank.
@@ -288,6 +320,5 @@ module Mail
288
320
  value.respond_to?(:empty?) ? value.empty? : !value
289
321
  end
290
322
  end
291
-
292
323
  end
293
324
  end
data/lib/mail/version.rb CHANGED
@@ -3,8 +3,8 @@ module Mail
3
3
  module VERSION
4
4
 
5
5
  MAJOR = 2
6
- MINOR = 6
7
- PATCH = 6
6
+ MINOR = 7
7
+ PATCH = 1
8
8
  BUILD = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')