mail 2.6.4 → 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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +35 -0
  3. data/Dependencies.txt +0 -1
  4. data/Gemfile +7 -7
  5. data/README.md +8 -5
  6. data/Rakefile +0 -6
  7. data/lib/mail/attachments_list.rb +4 -3
  8. data/lib/mail/body.rb +3 -3
  9. data/lib/mail/check_delivery_params.rb +47 -10
  10. data/lib/mail/core_extensions/smtp.rb +19 -16
  11. data/lib/mail/core_extensions/string.rb +0 -4
  12. data/lib/mail/elements/address.rb +18 -8
  13. data/lib/mail/elements/address_list.rb +10 -18
  14. data/lib/mail/elements/content_disposition_element.rb +8 -15
  15. data/lib/mail/elements/content_location_element.rb +5 -10
  16. data/lib/mail/elements/content_transfer_encoding_element.rb +5 -10
  17. data/lib/mail/elements/content_type_element.rb +8 -19
  18. data/lib/mail/elements/date_time_element.rb +6 -14
  19. data/lib/mail/elements/envelope_from_element.rb +14 -21
  20. data/lib/mail/elements/message_ids_element.rb +8 -12
  21. data/lib/mail/elements/mime_version_element.rb +6 -14
  22. data/lib/mail/elements/phrase_list.rb +6 -9
  23. data/lib/mail/elements/received_element.rb +9 -15
  24. data/lib/mail/encodings.rb +12 -14
  25. data/lib/mail/field.rb +7 -4
  26. data/lib/mail/fields/common/address_container.rb +2 -2
  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 +29 -29
  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/network/retriever_methods/test_retriever.rb +1 -1
  40. data/lib/mail/parsers/address_lists_parser.rb +15321 -112
  41. data/lib/mail/parsers/address_lists_parser.rl +166 -0
  42. data/lib/mail/parsers/content_disposition_parser.rb +698 -55
  43. data/lib/mail/parsers/content_disposition_parser.rl +76 -0
  44. data/lib/mail/parsers/content_location_parser.rb +565 -23
  45. data/lib/mail/parsers/content_location_parser.rl +65 -0
  46. data/lib/mail/parsers/content_transfer_encoding_parser.rb +410 -22
  47. data/lib/mail/parsers/content_transfer_encoding_parser.rl +58 -0
  48. data/lib/mail/parsers/content_type_parser.rb +841 -54
  49. data/lib/mail/parsers/content_type_parser.rl +77 -0
  50. data/lib/mail/parsers/date_time_parser.rb +773 -26
  51. data/lib/mail/parsers/date_time_parser.rl +56 -0
  52. data/lib/mail/parsers/envelope_from_parser.rb +2051 -36
  53. data/lib/mail/parsers/envelope_from_parser.rl +76 -0
  54. data/lib/mail/parsers/message_ids_parser.rb +1494 -25
  55. data/lib/mail/parsers/message_ids_parser.rl +76 -0
  56. data/lib/mail/parsers/mime_version_parser.rb +393 -26
  57. data/lib/mail/parsers/mime_version_parser.rl +55 -0
  58. data/lib/mail/parsers/phrase_lists_parser.rb +529 -19
  59. data/lib/mail/parsers/phrase_lists_parser.rl +77 -0
  60. data/lib/mail/parsers/received_parser.rb +4711 -38
  61. data/lib/mail/parsers/received_parser.rl +78 -0
  62. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +12 -0
  63. data/lib/mail/parsers/rfc2045_content_type.rl +24 -0
  64. data/lib/mail/parsers/rfc2045_mime.rl +15 -0
  65. data/lib/mail/parsers/rfc2183_content_disposition.rl +14 -0
  66. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +16 -0
  67. data/lib/mail/parsers/rfc5322.rl +57 -0
  68. data/lib/mail/parsers/rfc5322_address.rl +70 -0
  69. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +7 -1
  70. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +56 -0
  71. data/lib/mail/parsers.rb +16 -24
  72. data/lib/mail/part.rb +1 -1
  73. data/lib/mail/parts_list.rb +5 -6
  74. data/lib/mail/utilities.rb +19 -3
  75. data/lib/mail/version.rb +3 -3
  76. data/lib/mail/version_specific/ruby_1_8.rb +28 -2
  77. data/lib/mail/version_specific/ruby_1_9.rb +51 -8
  78. data/lib/mail.rb +2 -10
  79. metadata +28 -45
  80. data/lib/mail/core_extensions/string/access.rb +0 -146
  81. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  82. data/lib/mail/parsers/ragel/common.rl +0 -185
  83. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  84. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  85. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  86. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  87. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  88. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  89. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  90. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  91. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  92. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  93. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  94. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  95. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  96. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  97. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  98. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  99. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  100. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  101. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  102. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  103. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  104. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  105. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  106. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  107. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  108. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  109. data/lib/mail/parsers/ragel.rb +0 -18
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+ require 'mail/utilities'
3
+
4
+ %%{
5
+ machine date_time;
6
+
7
+ # Received Tokens
8
+ action received_tokens_s { received_tokens_s = p }
9
+ action received_tokens_e { received.info = data[received_tokens_s..(p-1)] }
10
+
11
+ # Date
12
+ action date_s { date_s = p }
13
+ action date_e { received.date = data[date_s..(p-1)].strip }
14
+
15
+ # Time
16
+ action time_s { time_s = p }
17
+ action time_e { received.time = data[time_s..(p-1)] }
18
+
19
+ # No-op actions
20
+ action address_s {}
21
+ action address_e {}
22
+ action angle_addr_s {}
23
+ action ctime_date_s {}
24
+ action ctime_date_e {}
25
+ action comment_e {}
26
+ action comment_s {}
27
+ action phrase_s {}
28
+ action phrase_e {}
29
+ action domain_e {}
30
+ action domain_s {}
31
+ action local_dot_atom_e {}
32
+ action local_dot_atom_pre_comment_e {}
33
+ action local_dot_atom_pre_comment_s {}
34
+ action local_dot_atom_s {}
35
+ action qstr_e {}
36
+ action qstr_s {}
37
+ action local_quoted_string_s {}
38
+ action local_quoted_string_e {}
39
+ action obs_domain_list_s {}
40
+ action obs_domain_list_e {}
41
+ action group_name_s {}
42
+ action group_name_e {}
43
+ action msg_id_s {}
44
+ action msg_id_e {}
45
+
46
+ include rfc5322 "rfc5322.rl";
47
+ main := received;
48
+ }%%
49
+
50
+ module Mail::Parsers
51
+ module ReceivedParser
52
+ ReceivedStruct = Struct.new(:date, :time, :info, :error)
53
+
54
+ %%write data noprefix;
55
+
56
+ def self.parse(data)
57
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, data, 'nil is invalid') if data.nil?
58
+
59
+ # Parser state
60
+ received = ReceivedStruct.new
61
+ received_tokens_s = date_s = time_s = nil
62
+
63
+ # 5.1 Variables Used by Ragel
64
+ p = 0
65
+ eof = pe = data.length
66
+ stack = []
67
+
68
+ %%write init;
69
+ %%write exec;
70
+
71
+ if p != eof || cs < %%{ write first_final; }%%
72
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, data, "Only able to parse up to #{data[0..p]}")
73
+ end
74
+
75
+ received
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,12 @@
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
+
6
+ include rfc2045_content_type "rfc2045_content_type.rl";
7
+
8
+ encoding = ('7bits' | '8bits' | '7bit' | '8bit' | 'binary' |
9
+ 'quoted-printable' | 'base64' | ietf_token |
10
+ custom_x_token) >encoding_s %encoding_e;
11
+ content_transfer_encoding = CFWS? encoding CFWS? ";"? CFWS?;
12
+ }%%
@@ -0,0 +1,24 @@
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
+
7
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
8
+
9
+ token = 0x21..0x27 | 0x2a..0x2b | 0x2c..0x2e | 0x30..0x39 | 0x41..0x5a | 0x5e..0x7e;
10
+ value = (quoted_string | (token -- '"' | 0x3d)+) >param_val_s %param_val_e;
11
+ attribute = (token+) >param_attr_s %param_attr_e;
12
+ parameter = CFWS? attribute "=" value CFWS?;
13
+
14
+ ietf_token = token+;
15
+ custom_x_token = 'x'i "-" token+;
16
+ extension_token = ietf_token | custom_x_token;
17
+ discrete_type = 'text'i | 'image'i | 'audio'i | 'video'i |
18
+ 'application'i | extension_token;
19
+ composite_type = 'message'i | 'multipart'i | extension_token;
20
+ iana_token = token+;
21
+ main_type = (discrete_type | composite_type) >main_type_s %main_type_e;
22
+ sub_type = (extension_token | iana_token) >sub_type_s %sub_type_e;
23
+ content_type = main_type "/" sub_type (((CFWS? ";"+) | CFWS) parameter CFWS?)*;
24
+ }%%
@@ -0,0 +1,15 @@
1
+ %%{
2
+ # RFC 2045 MIME
3
+ # https://tools.ietf.org/html/rfc2045
4
+ machine rfc2045_mime;
5
+
6
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
7
+
8
+ # 4. MIME-Version Header Field
9
+ # https://tools.ietf.org/html/rfc2045#section-4
10
+ mime_version = CFWS?
11
+ (DIGIT+ >major_digits_s %major_digits_e)
12
+ comment? "." comment?
13
+ (DIGIT+ >minor_digits_s %minor_digits_e)
14
+ CFWS?;
15
+ }%%
@@ -0,0 +1,14 @@
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
+
8
+ include rfc2045_content_type "rfc2045_content_type.rl";
9
+
10
+ disposition_type = 'inline'i | 'attachment'i | extension_token;
11
+ disposition_parm = parameter;
12
+ disposition = (disposition_type >disp_type_s %disp_type_e)
13
+ (";" disposition_parm)*;
14
+ }%%
@@ -0,0 +1,16 @@
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
+
6
+ LF = "\n";
7
+ CR = "\r";
8
+ CRLF = "\r\n";
9
+ SP = " ";
10
+ HTAB = "\t";
11
+ WSP = SP | HTAB;
12
+ DQUOTE = '"';
13
+ DIGIT = [0-9];
14
+ ALPHA = [a-zA-Z];
15
+ VCHAR = 0x21..0x7e;
16
+ }%%
@@ -0,0 +1,57 @@
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
+
9
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
10
+
11
+ # 3.2. Lexical Tokens
12
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
13
+
14
+ # 3.3. Date and Time Specification
15
+ include rfc5322_date_time "rfc5322_date_time.rl";
16
+
17
+ # 3.4. Address Specification
18
+ include rfc5322_address "rfc5322_address.rl";
19
+
20
+ # 3.5. Overall Message Syntax
21
+ #text = 0x01..0x09 | "\v" | "\f" | 0x0e..0x1f;
22
+ #obs_body = ((LF* CR* ((0x00 | text) LF* CR*)*) | CRLF)*
23
+ #body = ((text{,998} CRLF)* text{,998}) | obs_body;
24
+ #message = (fields | obs_fields) (CRLF body)?;
25
+
26
+
27
+ # 3.6. Field Definitions
28
+
29
+ # 3.6.4. Identification Fields
30
+ obs_id_left = local_part;
31
+ id_left = dot_atom_text | obs_id_left;
32
+ # id_right modifications to support multiple '@' in msg_id.
33
+ msg_id_atext = ALPHA | DIGIT | "!" | "#" | "$" | "%" | "&" | "'" | "*" |
34
+ "+" | "-" | "/" | "=" | "?" | "^" | "_" | "`" | "{" | "|" |
35
+ "}" | "~" | "@";
36
+ msg_id_dot_atom_text = (msg_id_atext+ "."?)+;
37
+ obs_id_right = domain;
38
+ no_fold_literal = "[" (dtext)* "]";
39
+ id_right = msg_id_dot_atom_text | no_fold_literal | obs_id_right;
40
+ msg_id = (CFWS)?
41
+ (("<" id_left "@" id_right ">") >msg_id_s %msg_id_e)
42
+ (CFWS)?;
43
+ message_ids = msg_id (CFWS? msg_id)*;
44
+
45
+
46
+ # 3.6.7 Trace Fields
47
+ # Added CFWS? to increase robustness (qmail likes to include a comment)
48
+ received_token = word | angle_addr | addr_spec_no_angle_brackets | domain;
49
+ received = ((CFWS? received_token*) >received_tokens_s %received_tokens_e)
50
+ ";" date_time;
51
+
52
+ # Envelope From
53
+ ctime_date = day_name " "+ month " "+ day " " time_of_day " " year;
54
+ null_sender = ('<>' ' '{0,1});
55
+ envelope_from = (addr_spec_no_angle_brackets | null_sender) >address_s %address_e " "
56
+ (ctime_date >ctime_date_s %ctime_date_e);
57
+ }%%
@@ -0,0 +1,70 @@
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
+
7
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
8
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
9
+
10
+ # local_part:
11
+ domain_text = (DQUOTE (FWS? qcontent)+ FWS? DQUOTE) | atext+;
12
+ local_dot_atom_text = ("."* domain_text "."*)+;
13
+ local_dot_atom = CFWS?
14
+ (local_dot_atom_text >local_dot_atom_s %local_dot_atom_pre_comment_e)
15
+ CFWS?;
16
+ obs_local_part = word ("." word)*;
17
+ local_part = (local_dot_atom >local_dot_atom_s %local_dot_atom_e |
18
+ (quoted_string %local_quoted_string_e) |
19
+ obs_local_part);
20
+
21
+ # Treetop parser behavior was to ignore addresses missing '@' inside of angle
22
+ # brackets. This construction preserves that behavior.
23
+ local_part_no_capture = (local_dot_atom | quoted_string | obs_local_part);
24
+
25
+ # domain:
26
+ domain_dot_atom_text = "."* domain_text ("."* domain_text)*;
27
+ obs_dtext = obs_NO_WS_CTL | quoted_pair;
28
+ dtext = 0x21..0x5a | 0x5e..0x7e | obs_dtext;
29
+ domain_dot_atom = CFWS? domain_dot_atom_text (CFWS? >(comment_after_address,1));
30
+ domain_literal = CFWS? "[" (FWS? dtext)* FWS? "]" CFWS?;
31
+ obs_domain = atom ("." atom)*;
32
+ domain = (domain_dot_atom | domain_literal | obs_domain) >domain_s %domain_e;
33
+
34
+ # 3.4.1. Addr-Spec Specification
35
+
36
+ # The %(end_addr,N) priority resolves uncertainty when whitespace
37
+ # after an addr_spec could cause it to be interpreted as a
38
+ # display name: "bar@example.com ,..."
39
+
40
+ addr_spec_in_angle_brackets =
41
+ (local_part "@" domain) %(end_addr,1) |
42
+ local_part_no_capture %(end_addr,0);
43
+
44
+ addr_spec_no_angle_brackets =
45
+ (local_part "@" domain) %(end_addr,1) |
46
+ local_part %(end_addr,0);
47
+
48
+ # angle_addr:
49
+ obs_domain_list = (CFWS | ",")* "@" domain ("," CFWS? ("@" domain)?)*;
50
+ obs_route = (obs_domain_list ":") >obs_domain_list_s %obs_domain_list_e;
51
+ obs_angle_addr = CFWS? "<" obs_route? addr_spec_in_angle_brackets ">" CFWS?;
52
+
53
+ angle_addr = CFWS? ("<" >angle_addr_s) addr_spec_in_angle_brackets ">" CFWS? |
54
+ obs_angle_addr;
55
+
56
+ # 3.4. Address Specification
57
+ display_name = phrase;
58
+ name_addr = display_name? %(end_addr,2) angle_addr;
59
+ mailbox = (name_addr | addr_spec_no_angle_brackets) >address_s %address_e;
60
+ obs_mbox_list = (CFWS? ",")* mailbox ("," (mailbox | CFWS)?)*;
61
+ mailbox_list = (mailbox (("," | ";") mailbox)*) | obs_mbox_list;
62
+ obs_group_list = (CFWS? ",")+ CFWS?;
63
+ group_list = mailbox_list | CFWS | obs_group_list;
64
+ group = (display_name >group_name_s %group_name_e) ":"
65
+ (group_list?) ";" CFWS?;
66
+ address = group | mailbox;
67
+ #obs_addr_list = (CFWS? ",")* address ("," (address | CFWS)?)*;
68
+ address_lists = address? %(comment_after_address,0)
69
+ (FWS* ("," | ";") FWS* address?)*;
70
+ }%%
@@ -1,6 +1,10 @@
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;
2
6
 
3
- machine date_time;
7
+ include rfc5322_lexical_tokens "rfc5322_lexical_tokens.rl";
4
8
 
5
9
  # day_of_week
6
10
  day_name = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
@@ -27,4 +31,6 @@
27
31
  zone = FWS ((("+" | "-") DIGIT DIGIT DIGIT DIGIT) | obs_zone);
28
32
  time = time_of_day zone;
29
33
 
34
+ date_time = (day_of_week ",")?
35
+ (date >date_s %date_e) <: (time >time_s %time_e) CFWS?;
30
36
  }%%
@@ -0,0 +1,56 @@
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
+
7
+ include rfc5234_abnf_core_rules "rfc5234_abnf_core_rules.rl";
8
+
9
+ # 3.2.1. Quoted characters
10
+ obs_NO_WS_CTL = 0x01..0x08 | "\v" | "\f" | 0x0e..0x1f | 0x7f;
11
+ obs_qp = "\\" (0x00 | obs_NO_WS_CTL | LF | CR);
12
+ quoted_pair = ("\\" (VCHAR | WSP)) | obs_qp;
13
+
14
+ # 3.2.2. Folding White Space and Comments
15
+ obs_FWS = (CRLF? WSP)+;
16
+ FWS = (WSP* CRLF WSP+) | (CRLF WSP+) | obs_FWS;
17
+
18
+ obs_ctext = obs_NO_WS_CTL;
19
+ ctext = 0x21..0x27 | 0x2a..0x5b | 0x5d..0x7e | obs_ctext;
20
+
21
+ # Recursive comments
22
+ action comment_begin { fcall comment_tail; }
23
+ action comment_exit { fret; }
24
+ ccontent = ctext | quoted_pair | "(" @comment_begin;
25
+ comment_tail := ((FWS? ccontent)* >comment_s) FWS? ")" @comment_exit;
26
+ comment = "(" @comment_begin %comment_e;
27
+ CFWS = ((FWS? comment)+ FWS?) | FWS;
28
+
29
+ # 3.2.3. Atom
30
+ atext = ALPHA | DIGIT | "!" | "#" | "$" | "%" | "&" |
31
+ "'" | "*" | "+" | "-" | "/" | "=" | "?" | "^" |
32
+ "_" | "`" | "{" | "|" | "}" | "~";
33
+ atom = CFWS? atext+ CFWS?;
34
+ dot_atom_text = atext ("." atext)*;
35
+ dot_atom = CFWS? dot_atom_text CFWS?;
36
+
37
+ # 3.2.4. Quoted Strings
38
+ obs_qtext = obs_NO_WS_CTL;
39
+ qtext = 0x21 | 0x23..0x5b | 0x5d..0x7e | obs_qtext;
40
+
41
+ qcontent = qtext | quoted_pair;
42
+ quoted_string = CFWS?
43
+ (DQUOTE
44
+ (((FWS? qcontent)* FWS?) >qstr_s %qstr_e)
45
+ DQUOTE)
46
+ CFWS?;
47
+
48
+ # 3.2.5. Miscellaneous Tokens
49
+ word = atom | quoted_string;
50
+
51
+ obs_phrase = (word | "." | "@")+;
52
+ phrase = (obs_phrase | word+) >phrase_s %phrase_e;
53
+
54
+ # Not part of RFC, used for keywords per 3.6.5 Information Fields
55
+ phrase_lists = phrase ("," FWS* phrase)*;
56
+ }%%
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
+ $stderr.puts("Part#inline_content_id is deprecated, please call Part#cid instead")
25
25
  cid
26
26
  end
27
27
 
@@ -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
 
@@ -72,11 +74,26 @@ module Mail
72
74
  # unqoute(string) #=> 'This is "a string"'
73
75
  def unquote( str )
74
76
  if str =~ /^"(.*?)"$/
75
- $1.gsub(/\\(.)/, '\1')
77
+ unescape($1)
76
78
  else
77
79
  str
78
80
  end
79
81
  end
82
+ module_function :unquote
83
+
84
+ # Removes any \-escaping.
85
+ #
86
+ # Example:
87
+ #
88
+ # string = 'This is \"a string\"'
89
+ # unescape(string) #=> 'This is "a string"'
90
+ #
91
+ # string = '"This is \"a string\""'
92
+ # unescape(string) #=> '"This is "a string""'
93
+ def unescape( str )
94
+ str.gsub(/\\(.)/, '\1')
95
+ end
96
+ module_function :unescape
80
97
 
81
98
  # Wraps a string in parenthesis and escapes any that are in the string itself.
82
99
  #
@@ -178,7 +195,7 @@ module Mail
178
195
  # Example:
179
196
  #
180
197
  # string = :resent_from_field
181
- # dasherize ( string ) #=> 'resent_from_field'
198
+ # dasherize( string ) #=> 'resent-from-field'
182
199
  def dasherize( str )
183
200
  str.to_s.tr(UNDERSCORE, HYPHEN)
184
201
  end
@@ -274,6 +291,5 @@ module Mail
274
291
  value.respond_to?(:empty?) ? value.empty? : !value
275
292
  end
276
293
  end
277
-
278
294
  end
279
295
  end
data/lib/mail/version.rb CHANGED
@@ -3,9 +3,9 @@ module Mail
3
3
  module VERSION
4
4
 
5
5
  MAJOR = 2
6
- MINOR = 6
7
- PATCH = 4
8
- BUILD = nil
6
+ MINOR = 7
7
+ PATCH = 0
8
+ BUILD = 'rc1'
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
11
11
 
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'net/imap' # for decode_utf7
3
4
 
4
5
  module Mail
5
6
  class Ruby18
@@ -56,7 +57,27 @@ module Mail
56
57
  end
57
58
 
58
59
  def Ruby18.transcode_charset(str, from_encoding, to_encoding = 'UTF-8')
59
- Iconv.conv("#{normalize_iconv_charset_encoding(to_encoding)}//IGNORE", normalize_iconv_charset_encoding(from_encoding), str)
60
+ case from_encoding
61
+ when /utf-?7/i
62
+ decode_utf7(str)
63
+ else
64
+ retried = false
65
+ begin
66
+ Iconv.conv("#{normalize_iconv_charset_encoding(to_encoding)}//IGNORE", normalize_iconv_charset_encoding(from_encoding), str)
67
+ rescue Iconv::InvalidEncoding
68
+ if retried
69
+ raise
70
+ else
71
+ from_encoding = 'ASCII'
72
+ retried = true
73
+ retry
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def Ruby18.decode_utf7(str)
80
+ Net::IMAP.decode_utf7(str)
60
81
  end
61
82
 
62
83
  def Ruby18.b_value_encode(str, encoding)
@@ -97,7 +118,12 @@ module Mail
97
118
  end
98
119
 
99
120
  def Ruby18.param_decode(str, encoding)
100
- URI.unescape(str)
121
+ str = URI.unescape(str)
122
+ if encoding
123
+ transcode_charset(str, encoding)
124
+ else
125
+ str
126
+ end
101
127
  end
102
128
 
103
129
  def Ruby18.param_encode(str)
@@ -5,13 +5,23 @@ module Mail
5
5
  class Ruby19
6
6
  class StrictCharsetEncoder
7
7
  def encode(string, charset)
8
- string.force_encoding(Mail::Ruby19.pick_encoding(charset))
8
+ case charset
9
+ when /utf-?7/i
10
+ Mail::Ruby19.decode_utf7(string)
11
+ else
12
+ string.force_encoding(Mail::Ruby19.pick_encoding(charset))
13
+ end
9
14
  end
10
15
  end
11
16
 
12
17
  class BestEffortCharsetEncoder
13
18
  def encode(string, charset)
14
- string.force_encoding(pick_encoding(charset))
19
+ case charset
20
+ when /utf-?7/i
21
+ Mail::Ruby19.decode_utf7(string)
22
+ else
23
+ string.force_encoding(pick_encoding(charset))
24
+ end
15
25
  end
16
26
 
17
27
  private
@@ -59,6 +69,9 @@ module Mail
59
69
  end
60
70
 
61
71
  def Ruby19.decode_base64(str)
72
+ if !str.end_with?("=") && str.length % 4 != 0
73
+ str = str.ljust((str.length + 3) & ~3, "=")
74
+ end
62
75
  str.unpack( 'm' ).first
63
76
  end
64
77
 
@@ -75,7 +88,31 @@ module Mail
75
88
  end
76
89
 
77
90
  def Ruby19.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
78
- charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => '')
91
+ to_encoding = Encoding.find(to_encoding)
92
+ replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
93
+ charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
94
+ end
95
+
96
+ # From Ruby stdlib Net::IMAP
97
+ def Ruby19.encode_utf7(string)
98
+ string.gsub(/(&)|[^\x20-\x7e]+/) do
99
+ if $1
100
+ "&-"
101
+ else
102
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
103
+ "&" + base64.delete("=").tr("/", ",") + "-"
104
+ end
105
+ end.force_encoding(Encoding::ASCII_8BIT)
106
+ end
107
+
108
+ def Ruby19.decode_utf7(utf7)
109
+ utf7.gsub(/&([^-]+)?-/n) do
110
+ if $1
111
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
112
+ else
113
+ "&"
114
+ end
115
+ end
79
116
  end
80
117
 
81
118
  def Ruby19.b_value_encode(str, encoding = nil)
@@ -90,8 +127,7 @@ module Mail
90
127
  str = Ruby19.decode_base64(match[2])
91
128
  str = charset_encoder.encode(str, charset)
92
129
  end
93
- decoded = str.encode(Encoding::UTF_8, :invalid => :replace, :replace => "")
94
- decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "").encode(Encoding::UTF_8)
130
+ transcode_to_scrubbed_utf8(str)
95
131
  rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
96
132
  warn "Encoding conversion failed #{$!}"
97
133
  str.dup.force_encoding(Encoding::UTF_8)
@@ -115,8 +151,7 @@ module Mail
115
151
  # jruby/jruby#829 which subtly changes String#encode semantics.
116
152
  str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
117
153
  end
118
- decoded = str.encode(Encoding::UTF_8, :invalid => :replace, :replace => "")
119
- decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "").encode(Encoding::UTF_8)
154
+ transcode_to_scrubbed_utf8(str)
120
155
  rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
121
156
  warn "Encoding conversion failed #{$!}"
122
157
  str.dup.force_encoding(Encoding::UTF_8)
@@ -125,7 +160,10 @@ module Mail
125
160
  def Ruby19.param_decode(str, encoding)
126
161
  str = uri_parser.unescape(str)
127
162
  str = charset_encoder.encode(str, encoding) if encoding
128
- str
163
+ transcode_to_scrubbed_utf8(str)
164
+ rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
165
+ warn "Encoding conversion failed #{$!}"
166
+ str.dup.force_encoding(Encoding::UTF_8)
129
167
  end
130
168
 
131
169
  def Ruby19.param_encode(str)
@@ -218,6 +256,11 @@ module Mail
218
256
  end
219
257
  end
220
258
  end
259
+
260
+ def transcode_to_scrubbed_utf8(str)
261
+ decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
262
+ decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
263
+ end
221
264
  end
222
265
  end
223
266
  end