mail 2.5.2 → 2.6.4

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +124 -0
  3. data/CONTRIBUTING.md +21 -6
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +7 -22
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +86 -31
  8. data/Rakefile +11 -22
  9. data/lib/mail/attachments_list.rb +4 -2
  10. data/lib/mail/body.rb +36 -11
  11. data/lib/mail/check_delivery_params.rb +13 -22
  12. data/lib/mail/configuration.rb +1 -0
  13. data/lib/mail/{patterns.rb → constants.rb} +29 -6
  14. data/lib/mail/core_extensions/smtp.rb +13 -13
  15. data/lib/mail/core_extensions/string/access.rb +1 -0
  16. data/lib/mail/core_extensions/string/multibyte.rb +1 -0
  17. data/lib/mail/core_extensions/string.rb +5 -17
  18. data/lib/mail/elements/address.rb +86 -145
  19. data/lib/mail/elements/address_list.rb +10 -42
  20. data/lib/mail/elements/content_disposition_element.rb +4 -7
  21. data/lib/mail/elements/content_location_element.rb +8 -11
  22. data/lib/mail/elements/content_transfer_encoding_element.rb +4 -10
  23. data/lib/mail/elements/content_type_element.rb +5 -8
  24. data/lib/mail/elements/date_time_element.rb +4 -7
  25. data/lib/mail/elements/envelope_from_element.rb +19 -13
  26. data/lib/mail/elements/message_ids_element.rb +8 -11
  27. data/lib/mail/elements/mime_version_element.rb +4 -7
  28. data/lib/mail/elements/phrase_list.rb +3 -7
  29. data/lib/mail/elements/received_element.rb +4 -7
  30. data/lib/mail/elements.rb +1 -0
  31. data/lib/mail/encodings/7bit.rb +1 -0
  32. data/lib/mail/encodings/8bit.rb +8 -2
  33. data/lib/mail/encodings/base64.rb +7 -1
  34. data/lib/mail/encodings/binary.rb +1 -0
  35. data/lib/mail/encodings/quoted_printable.rb +11 -4
  36. data/lib/mail/encodings/transfer_encoding.rb +27 -17
  37. data/lib/mail/encodings/unix_to_unix.rb +18 -0
  38. data/lib/mail/encodings.rb +54 -39
  39. data/lib/mail/envelope.rb +1 -5
  40. data/lib/mail/field.rb +81 -31
  41. data/lib/mail/field_list.rb +19 -18
  42. data/lib/mail/fields/bcc_field.rb +17 -5
  43. data/lib/mail/fields/cc_field.rb +3 -3
  44. data/lib/mail/fields/comments_field.rb +2 -1
  45. data/lib/mail/fields/common/address_container.rb +1 -0
  46. data/lib/mail/fields/common/common_address.rb +34 -28
  47. data/lib/mail/fields/common/common_date.rb +2 -8
  48. data/lib/mail/fields/common/common_field.rb +9 -5
  49. data/lib/mail/fields/common/common_message_id.rb +3 -2
  50. data/lib/mail/fields/common/parameter_hash.rb +4 -3
  51. data/lib/mail/fields/content_description_field.rb +1 -0
  52. data/lib/mail/fields/content_disposition_field.rb +13 -12
  53. data/lib/mail/fields/content_id_field.rb +4 -4
  54. data/lib/mail/fields/content_location_field.rb +2 -1
  55. data/lib/mail/fields/content_transfer_encoding_field.rb +4 -9
  56. data/lib/mail/fields/content_type_field.rb +4 -3
  57. data/lib/mail/fields/date_field.rb +16 -15
  58. data/lib/mail/fields/from_field.rb +3 -3
  59. data/lib/mail/fields/in_reply_to_field.rb +3 -1
  60. data/lib/mail/fields/keywords_field.rb +2 -2
  61. data/lib/mail/fields/message_id_field.rb +4 -4
  62. data/lib/mail/fields/mime_version_field.rb +3 -2
  63. data/lib/mail/fields/optional_field.rb +1 -0
  64. data/lib/mail/fields/received_field.rb +4 -3
  65. data/lib/mail/fields/references_field.rb +3 -1
  66. data/lib/mail/fields/reply_to_field.rb +3 -3
  67. data/lib/mail/fields/resent_bcc_field.rb +3 -3
  68. data/lib/mail/fields/resent_cc_field.rb +3 -3
  69. data/lib/mail/fields/resent_date_field.rb +2 -1
  70. data/lib/mail/fields/resent_from_field.rb +3 -3
  71. data/lib/mail/fields/resent_message_id_field.rb +1 -0
  72. data/lib/mail/fields/resent_sender_field.rb +4 -4
  73. data/lib/mail/fields/resent_to_field.rb +3 -3
  74. data/lib/mail/fields/return_path_field.rb +1 -1
  75. data/lib/mail/fields/sender_field.rb +9 -9
  76. data/lib/mail/fields/structured_field.rb +1 -0
  77. data/lib/mail/fields/subject_field.rb +1 -0
  78. data/lib/mail/fields/to_field.rb +3 -3
  79. data/lib/mail/fields/unstructured_field.rb +15 -8
  80. data/lib/mail/fields.rb +1 -0
  81. data/lib/mail/header.rb +21 -29
  82. data/lib/mail/indifferent_hash.rb +1 -0
  83. data/lib/mail/mail.rb +16 -1
  84. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  85. data/lib/mail/matchers/has_sent_mail.rb +61 -4
  86. data/lib/mail/message.rb +175 -72
  87. data/lib/mail/multibyte/chars.rb +3 -2
  88. data/lib/mail/multibyte/exceptions.rb +1 -0
  89. data/lib/mail/multibyte/unicode.rb +22 -15
  90. data/lib/mail/multibyte/utils.rb +1 -0
  91. data/lib/mail/multibyte.rb +1 -0
  92. data/lib/mail/network/delivery_methods/exim.rb +3 -7
  93. data/lib/mail/network/delivery_methods/file_delivery.rb +2 -1
  94. data/lib/mail/network/delivery_methods/sendmail.rb +33 -10
  95. data/lib/mail/network/delivery_methods/smtp.rb +32 -34
  96. data/lib/mail/network/delivery_methods/smtp_connection.rb +7 -6
  97. data/lib/mail/network/delivery_methods/test_mailer.rb +3 -2
  98. data/lib/mail/network/retriever_methods/base.rb +1 -0
  99. data/lib/mail/network/retriever_methods/imap.rb +19 -13
  100. data/lib/mail/network/retriever_methods/pop3.rb +1 -0
  101. data/lib/mail/network/retriever_methods/test_retriever.rb +1 -0
  102. data/lib/mail/network.rb +1 -0
  103. data/lib/mail/parsers/address_lists_parser.rb +133 -0
  104. data/lib/mail/parsers/content_disposition_parser.rb +68 -0
  105. data/lib/mail/parsers/content_location_parser.rb +36 -0
  106. data/lib/mail/parsers/content_transfer_encoding_parser.rb +34 -0
  107. data/lib/mail/parsers/content_type_parser.rb +67 -0
  108. data/lib/mail/parsers/date_time_parser.rb +39 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +46 -0
  110. data/lib/mail/parsers/message_ids_parser.rb +40 -0
  111. data/lib/mail/parsers/mime_version_parser.rb +42 -0
  112. data/lib/mail/parsers/phrase_lists_parser.rb +36 -0
  113. data/lib/mail/parsers/ragel/common.rl +185 -0
  114. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  115. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  116. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  117. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  118. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  119. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  120. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  121. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  122. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  123. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  124. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  125. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  126. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  127. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  128. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2149 -0
  129. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  130. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  131. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  132. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  133. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  134. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  135. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  136. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  137. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  138. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  139. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  140. data/lib/mail/parsers/ragel/ruby.rb +40 -0
  141. data/lib/mail/parsers/ragel.rb +18 -0
  142. data/lib/mail/parsers/received_parser.rb +49 -0
  143. data/lib/mail/parsers.rb +27 -0
  144. data/lib/mail/part.rb +11 -4
  145. data/lib/mail/parts_list.rb +30 -10
  146. data/lib/mail/utilities.rb +127 -63
  147. data/lib/mail/values/unicode_tables.dat +0 -0
  148. data/lib/mail/version.rb +8 -15
  149. data/lib/mail/version_specific/ruby_1_8.rb +18 -7
  150. data/lib/mail/version_specific/ruby_1_9.rb +136 -32
  151. data/lib/mail.rb +12 -6
  152. metadata +115 -77
  153. data/lib/VERSION +0 -4
  154. data/lib/load_parsers.rb +0 -41
  155. data/lib/mail/core_extensions/nil.rb +0 -19
  156. data/lib/mail/core_extensions/object.rb +0 -13
  157. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  158. data/lib/mail/parsers/address_lists.rb +0 -64
  159. data/lib/mail/parsers/address_lists.treetop +0 -19
  160. data/lib/mail/parsers/content_disposition.rb +0 -535
  161. data/lib/mail/parsers/content_disposition.treetop +0 -46
  162. data/lib/mail/parsers/content_location.rb +0 -139
  163. data/lib/mail/parsers/content_location.treetop +0 -20
  164. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  165. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  166. data/lib/mail/parsers/content_type.rb +0 -967
  167. data/lib/mail/parsers/content_type.treetop +0 -68
  168. data/lib/mail/parsers/date_time.rb +0 -114
  169. data/lib/mail/parsers/date_time.treetop +0 -11
  170. data/lib/mail/parsers/envelope_from.rb +0 -194
  171. data/lib/mail/parsers/envelope_from.treetop +0 -32
  172. data/lib/mail/parsers/message_ids.rb +0 -45
  173. data/lib/mail/parsers/message_ids.treetop +0 -15
  174. data/lib/mail/parsers/mime_version.rb +0 -144
  175. data/lib/mail/parsers/mime_version.treetop +0 -19
  176. data/lib/mail/parsers/phrase_lists.rb +0 -45
  177. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  178. data/lib/mail/parsers/received.rb +0 -71
  179. data/lib/mail/parsers/received.treetop +0 -11
  180. data/lib/mail/parsers/rfc2045.rb +0 -464
  181. data/lib/mail/parsers/rfc2045.treetop +0 -36
  182. data/lib/mail/parsers/rfc2822.rb +0 -5397
  183. data/lib/mail/parsers/rfc2822.treetop +0 -410
  184. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  185. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  186. data/lib/tasks/corpus.rake +0 -125
  187. data/lib/tasks/treetop.rake +0 -10
@@ -0,0 +1,37 @@
1
+ %%{
2
+ machine received;
3
+
4
+ include rb_actions "rb_actions.rl";
5
+ include common "../../common.rl";
6
+
7
+ getkey data_unpacked[p];
8
+
9
+ main := received;
10
+ }%%
11
+
12
+ module Mail
13
+ module Parsers
14
+ module Ragel
15
+ module ReceivedMachine
16
+ %%write data;
17
+
18
+ def self.parse(data)
19
+ p = 0
20
+ eof = data.length
21
+ stack = []
22
+
23
+ actions = []
24
+ data_unpacked = data.bytes.to_a
25
+ %%write init;
26
+ %%write exec;
27
+
28
+ if p == eof && cs >= %%{ write first_final; }%%
29
+ return actions, nil
30
+ else
31
+ return [], "Only able to parse up to #{data[0..p]}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ %%{
2
+ machine <%= parser_name %>;
3
+
4
+ include rb_actions "rb_actions.rl";
5
+ include common "../../common.rl";
6
+
7
+ getkey data_unpacked[p];
8
+
9
+ main := <%= parser_name %>;
10
+ }%%
11
+
12
+ module Mail
13
+ module Parsers
14
+ module Ragel
15
+ module <%= parser_name_cc %>
16
+ %%write data;
17
+
18
+ def self.parse(data)
19
+ p = 0
20
+ eof = data.length
21
+ stack = []
22
+
23
+ actions = []
24
+ data_unpacked = data.bytes.to_a
25
+ %%write init;
26
+ %%write exec;
27
+
28
+ if p == eof && cs >= %%{ write first_final; }%%
29
+ return actions, nil
30
+ else
31
+ return [], "Only able to parse up to #{data[0..p]}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ module Mail
2
+ module Parsers
3
+ module Ragel
4
+ module Ruby
5
+ def self.silence_warnings
6
+ old, $VERBOSE = $VERBOSE, nil
7
+ yield
8
+ ensure
9
+ $VERBOSE = old
10
+ end
11
+
12
+ # Ragel-generated parsers give a lot of warnings
13
+ # and may cause logs to balloon in size
14
+ silence_warnings do
15
+ Mail::Parsers::Ragel::FIELD_PARSERS.each do |field_parser|
16
+ require "mail/parsers/ragel/ruby/machines/#{field_parser}_machine"
17
+ end
18
+ end
19
+
20
+ MACHINE_LIST = {
21
+ :address_lists => AddressListsMachine,
22
+ :phrase_lists => PhraseListsMachine,
23
+ :date_time => DateTimeMachine,
24
+ :received => ReceivedMachine,
25
+ :message_ids => MessageIdsMachine,
26
+ :envelope_from => EnvelopeFromMachine,
27
+ :mime_version => MimeVersionMachine,
28
+ :content_type => ContentTypeMachine,
29
+ :content_disposition => ContentDispositionMachine,
30
+ :content_transfer_encoding => ContentTransferEncodingMachine,
31
+ :content_location => ContentLocationMachine
32
+ }
33
+
34
+ def self.parse(machine, string)
35
+ MACHINE_LIST[machine].parse(string)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Mail
3
+ module Parsers
4
+ module Ragel
5
+ require 'mail/parsers/ragel/parser_info'
6
+ require "mail/parsers/ragel/ruby"
7
+
8
+ def self.parse(machine, string)
9
+ @machine_module ||= Ruby
10
+ @machine_module.parse(machine, string)
11
+ end
12
+
13
+ def self.machine_module=(m)
14
+ @machine_module = m
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ module Mail::Parsers
3
+ class ReceivedParser
4
+
5
+ def parse(s)
6
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, s, 'nil is invalid') if s.nil?
7
+ actions, error = Ragel.parse(:received, s)
8
+ if error
9
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, s, error)
10
+ end
11
+
12
+ received = ReceivedStruct.new
13
+
14
+ received_tokens_s = date_s = time_s = nil
15
+ actions.each_slice(2) do |action_id, p|
16
+ action = Mail::Parsers::Ragel::ACTIONS[action_id]
17
+ case action
18
+
19
+ # Received Tokens:
20
+ when :received_tokens_s then received_tokens_s = p
21
+ when :received_tokens_e
22
+ received.info = s[received_tokens_s..(p-1)]
23
+
24
+ # Date
25
+ when :date_s then date_s = p
26
+ when :date_e
27
+ received.date = s[date_s..(p-1)].strip
28
+
29
+ # Time
30
+ when :time_s then time_s = p
31
+ when :time_e
32
+ received.time = s[time_s..(p-1)]
33
+
34
+ when :angle_addr_s, :comment_e, :comment_s,
35
+ :domain_e, :domain_s, :local_dot_atom_e,
36
+ :local_dot_atom_pre_comment_e,
37
+ :local_dot_atom_pre_comment_s,
38
+ :local_dot_atom_s, :qstr_e, :qstr_s,
39
+ :local_quoted_string_s, :local_quoted_string_e
40
+ # ignored actions
41
+
42
+ else
43
+ raise Mail::Field::ParseError.new(Mail::ReceivedElement, s, "Failed to process unknown action: #{action}")
44
+ end
45
+ end
46
+ received
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Mail
3
+ module Parsers
4
+
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
27
+ end
data/lib/mail/part.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class Part < Message
4
5
 
@@ -34,12 +35,16 @@ module Mail
34
35
  end
35
36
 
36
37
  def inline?
37
- header[:content_disposition].disposition_type == 'inline' if header[:content_disposition]
38
+ header[:content_disposition].disposition_type == 'inline' if header[:content_disposition].respond_to?(:disposition_type)
38
39
  end
39
40
 
40
41
  def add_required_fields
41
- add_content_id unless has_content_id?
42
42
  super
43
+ add_content_id if !has_content_id? && inline?
44
+ end
45
+
46
+ def add_required_message_fields
47
+ # Override so we don't add Date, MIME-Version, or Message-ID.
43
48
  end
44
49
 
45
50
  def delivery_status_report_part?
@@ -90,8 +95,10 @@ module Mail
90
95
  def get_return_values(key)
91
96
  if delivery_status_data[key].is_a?(Array)
92
97
  delivery_status_data[key].map { |a| a.value }
93
- else
98
+ elsif !delivery_status_data[key].nil?
94
99
  delivery_status_data[key].value
100
+ else
101
+ nil
95
102
  end
96
103
  end
97
104
 
@@ -113,4 +120,4 @@ module Mail
113
120
 
114
121
  end
115
122
 
116
- end
123
+ end
@@ -1,8 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'delegate'
3
+
1
4
  module Mail
2
- class PartsList < Array
5
+ class PartsList < DelegateClass(Array)
6
+ attr_reader :parts
7
+
8
+ def initialize(*args)
9
+ @parts = Array.new(*args)
10
+ super @parts
11
+ end
12
+
13
+ # The #encode_with and #to_yaml methods are just implemented
14
+ # for the sake of backward compatibility ; the delegator does
15
+ # not correctly delegate these calls to the delegated object
16
+ def encode_with(coder) # :nodoc:
17
+ coder.represent_object(nil, @parts)
18
+ end
19
+
20
+ def to_yaml(options = {}) # :nodoc:
21
+ @parts.to_yaml(options)
22
+ end
3
23
 
4
24
  def attachments
5
- Mail::AttachmentsList.new(self)
25
+ Mail::AttachmentsList.new(@parts)
6
26
  end
7
27
 
8
28
  def collect
@@ -14,8 +34,6 @@ module Mail
14
34
  to_a
15
35
  end
16
36
  end
17
-
18
- undef :map
19
37
  alias_method :map, :collect
20
38
 
21
39
  def map!
@@ -27,24 +45,26 @@ module Mail
27
45
  end
28
46
 
29
47
  def sort
30
- self.class.new(super)
48
+ self.class.new(@parts.sort)
31
49
  end
32
50
 
33
51
  def sort!(order)
34
- sorted = self.sort do |a, b|
52
+ # stable sort should be used to maintain the relative order as the parts are added
53
+ i = 0;
54
+ sorted = @parts.sort_by do |a|
35
55
  # OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
36
56
  # single email message... please show me a use case and I'll put more work into this method,
37
57
  # in the meantime, it works :)
38
- get_order_value(a, order) <=> get_order_value(b, order)
58
+ [get_order_value(a, order), i += 1]
39
59
  end
40
- self.clear
41
- sorted.each { |p| self << p }
60
+ @parts.clear
61
+ sorted.each { |p| @parts << p }
42
62
  end
43
63
 
44
64
  private
45
65
 
46
66
  def get_order_value(part, order)
47
- if part.respond_to?(:content_type)
67
+ if part.respond_to?(:content_type) && !part[:content_type].nil?
48
68
  order.index(part[:content_type].string.downcase) || 10000
49
69
  else
50
70
  10000
@@ -1,29 +1,34 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  module Utilities
4
- include Patterns
5
-
5
+
6
+ LF = "\n"
7
+ CRLF = "\r\n"
8
+
9
+ include Constants
10
+
6
11
  # Returns true if the string supplied is free from characters not allowed as an ATOM
7
12
  def atom_safe?( str )
8
13
  not ATOM_UNSAFE === str
9
14
  end
10
15
 
11
- # If the string supplied has ATOM unsafe characters in it, will return the string quoted
16
+ # If the string supplied has ATOM unsafe characters in it, will return the string quoted
12
17
  # in double quotes, otherwise returns the string unmodified
13
18
  def quote_atom( str )
14
19
  atom_safe?( str ) ? str : dquote(str)
15
20
  end
16
21
 
17
- # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
22
+ # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
18
23
  # in double quotes, otherwise returns the string unmodified
19
24
  def quote_phrase( str )
20
25
  if RUBY_VERSION >= '1.9'
21
26
  original_encoding = str.encoding
22
- str.force_encoding('ASCII-8BIT')
23
- if (PHRASE_UNSAFE === str)
24
- dquote(str).force_encoding(original_encoding)
27
+ ascii_str = str.dup.force_encoding('ASCII-8BIT')
28
+ if (PHRASE_UNSAFE === ascii_str)
29
+ dquote(ascii_str).force_encoding(original_encoding)
25
30
  else
26
- str.force_encoding(original_encoding)
31
+ str
27
32
  end
28
33
  else
29
34
  (PHRASE_UNSAFE === str) ? dquote(str) : str
@@ -35,150 +40,158 @@ module Mail
35
40
  not TOKEN_UNSAFE === str
36
41
  end
37
42
 
38
- # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
43
+ # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
39
44
  # in double quotes, otherwise returns the string unmodified
40
45
  def quote_token( str )
41
46
  token_safe?( str ) ? str : dquote(str)
42
47
  end
43
48
 
44
- # Wraps supplied string in double quotes unless it is already wrapped.
45
- #
46
- # Additionally will escape any double quotation marks in the string with a single
47
- # backslash in front of the '"' character.
49
+ # Wraps supplied string in double quotes and applies \-escaping as necessary,
50
+ # unless it is already wrapped.
51
+ #
52
+ # Example:
53
+ #
54
+ # string = 'This is a string'
55
+ # dquote(string) #=> '"This is a string"'
56
+ #
57
+ # string = 'This is "a string"'
58
+ # dquote(string #=> '"This is \"a string\"'
48
59
  def dquote( str )
49
- match = str.match(/^"(.*)?"$/)
50
- str = match[1] if match
51
- # First remove all escaped double quotes:
52
- str = str.gsub(/\\"/, '"')
53
- # Then wrap and re-escape all double quotes
54
- '"' + str.gsub(/["]/n) {|s| '\\' + s } + '"'
55
- end
56
-
57
- # Unwraps supplied string from inside double quotes.
58
- #
60
+ '"' + unquote(str).gsub(/[\\"]/n) {|s| '\\' + s } + '"'
61
+ end
62
+
63
+ # Unwraps supplied string from inside double quotes and
64
+ # removes any \-escaping.
65
+ #
59
66
  # Example:
60
- #
67
+ #
61
68
  # string = '"This is a string"'
62
69
  # unquote(string) #=> 'This is a string'
70
+ #
71
+ # string = '"This is \"a string\""'
72
+ # unqoute(string) #=> 'This is "a string"'
63
73
  def unquote( str )
64
- match = str.match(/^"(.*?)"$/)
65
- match ? match[1] : str
74
+ if str =~ /^"(.*?)"$/
75
+ $1.gsub(/\\(.)/, '\1')
76
+ else
77
+ str
78
+ end
66
79
  end
67
-
80
+
68
81
  # Wraps a string in parenthesis and escapes any that are in the string itself.
69
- #
82
+ #
70
83
  # Example:
71
- #
84
+ #
72
85
  # paren( 'This is a string' ) #=> '(This is a string)'
73
86
  def paren( str )
74
87
  RubyVer.paren( str )
75
88
  end
76
-
89
+
77
90
  # Unwraps a string from being wrapped in parenthesis
78
- #
91
+ #
79
92
  # Example:
80
- #
93
+ #
81
94
  # str = '(This is a string)'
82
95
  # unparen( str ) #=> 'This is a string'
83
96
  def unparen( str )
84
97
  match = str.match(/^\((.*?)\)$/)
85
98
  match ? match[1] : str
86
99
  end
87
-
100
+
88
101
  # Wraps a string in angle brackets and escapes any that are in the string itself
89
- #
102
+ #
90
103
  # Example:
91
- #
104
+ #
92
105
  # bracket( 'This is a string' ) #=> '<This is a string>'
93
106
  def bracket( str )
94
107
  RubyVer.bracket( str )
95
108
  end
96
-
109
+
97
110
  # Unwraps a string from being wrapped in parenthesis
98
- #
111
+ #
99
112
  # Example:
100
- #
113
+ #
101
114
  # str = '<This is a string>'
102
115
  # unbracket( str ) #=> 'This is a string'
103
116
  def unbracket( str )
104
117
  match = str.match(/^\<(.*?)\>$/)
105
118
  match ? match[1] : str
106
119
  end
107
-
120
+
108
121
  # Escape parenthesies in a string
109
- #
122
+ #
110
123
  # Example:
111
- #
124
+ #
112
125
  # str = 'This is (a) string'
113
126
  # escape_paren( str ) #=> 'This is \(a\) string'
114
127
  def escape_paren( str )
115
128
  RubyVer.escape_paren( str )
116
129
  end
117
-
130
+
118
131
  def uri_escape( str )
119
132
  uri_parser.escape(str)
120
133
  end
121
-
134
+
122
135
  def uri_unescape( str )
123
136
  uri_parser.unescape(str)
124
137
  end
125
-
138
+
126
139
  def uri_parser
127
140
  @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
128
141
  end
129
-
142
+
130
143
  # Matches two objects with their to_s values case insensitively
131
- #
144
+ #
132
145
  # Example:
133
- #
146
+ #
134
147
  # obj2 = "This_is_An_object"
135
148
  # obj1 = :this_IS_an_object
136
149
  # match_to_s( obj1, obj2 ) #=> true
137
150
  def match_to_s( obj1, obj2 )
138
- obj1.to_s.downcase == obj2.to_s.downcase
151
+ obj1.to_s.casecmp(obj2.to_s) == 0
139
152
  end
140
-
153
+
141
154
  # Capitalizes a string that is joined by hyphens correctly.
142
- #
155
+ #
143
156
  # Example:
144
- #
157
+ #
145
158
  # string = 'resent-from-field'
146
159
  # capitalize_field( string ) #=> 'Resent-From-Field'
147
160
  def capitalize_field( str )
148
161
  str.to_s.split("-").map { |v| v.capitalize }.join("-")
149
162
  end
150
-
163
+
151
164
  # Takes an underscored word and turns it into a class name
152
- #
165
+ #
153
166
  # Example:
154
- #
167
+ #
155
168
  # constantize("hello") #=> "Hello"
156
169
  # constantize("hello-there") #=> "HelloThere"
157
170
  # constantize("hello-there-mate") #=> "HelloThereMate"
158
171
  def constantize( str )
159
172
  str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
160
173
  end
161
-
174
+
162
175
  # Swaps out all underscores (_) for hyphens (-) good for stringing from symbols
163
176
  # a field name.
164
- #
177
+ #
165
178
  # Example:
166
- #
179
+ #
167
180
  # string = :resent_from_field
168
181
  # dasherize ( string ) #=> 'resent_from_field'
169
182
  def dasherize( str )
170
- str.to_s.gsub('_', '-')
183
+ str.to_s.tr(UNDERSCORE, HYPHEN)
171
184
  end
172
185
 
173
186
  # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
174
187
  # a field name.
175
- #
188
+ #
176
189
  # Example:
177
- #
190
+ #
178
191
  # string = :resent_from_field
179
192
  # underscoreize ( string ) #=> 'resent_from_field'
180
193
  def underscoreize( str )
181
- str.to_s.downcase.gsub('-', '_')
194
+ str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
182
195
  end
183
196
 
184
197
  if RUBY_VERSION <= '1.8.6'
@@ -190,7 +203,7 @@ module Mail
190
203
  end
191
204
  results
192
205
  end
193
-
206
+
194
207
  def map_with_index( enum, &block )
195
208
  results = []
196
209
  enum.each_with_index do |token, i|
@@ -198,7 +211,7 @@ module Mail
198
211
  end
199
212
  results
200
213
  end
201
-
214
+
202
215
  else
203
216
 
204
217
  def map_lines( str, &block )
@@ -211,5 +224,56 @@ module Mail
211
224
 
212
225
  end
213
226
 
227
+ # Test String#encode works correctly with line endings.
228
+ # Some versions of Ruby (e.g. MRI <1.9, JRuby, Rubinius) line ending
229
+ # normalization does not work correctly or did not have #encode.
230
+ if ("\r".encode(:universal_newline => true) rescue nil) == LF &&
231
+ (LF.encode(:crlf_newline => true) rescue nil) == CRLF
232
+ # Using String#encode is better performing than Regexp
233
+
234
+ def self.to_lf(input)
235
+ input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true) : ''
236
+ end
237
+
238
+ def self.to_crlf(input)
239
+ input.kind_of?(String) ? input.to_str.encode(input.encoding, :universal_newline => true).encode!(input.encoding, :crlf_newline => true) : ''
240
+ end
241
+
242
+ else
243
+
244
+ def self.to_lf(input)
245
+ input.kind_of?(String) ? input.to_str.gsub(/\r\n|\r/, LF) : ''
246
+ end
247
+
248
+ if RUBY_VERSION >= '1.9'
249
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
250
+ # by not matching "\r\n" so the string is returned unchanged in
251
+ # the common case.
252
+ CRLF_REGEX = Regexp.new("(?<!\r)\n|\r(?!\n)")
253
+ else
254
+ CRLF_REGEX = /\n|\r\n|\r/
255
+ end
256
+
257
+ def self.to_crlf(input)
258
+ input.kind_of?(String) ? input.to_str.gsub(CRLF_REGEX, CRLF) : ''
259
+ end
260
+
261
+ end
262
+
263
+ # Returns true if the object is considered blank.
264
+ # A blank includes things like '', ' ', nil,
265
+ # and arrays and hashes that have nothing in them.
266
+ #
267
+ # This logic is mostly shared with ActiveSupport's blank?
268
+ def self.blank?(value)
269
+ if value.kind_of?(NilClass)
270
+ true
271
+ elsif value.kind_of?(String)
272
+ value !~ /\S/
273
+ else
274
+ value.respond_to?(:empty?) ? value.empty? : !value
275
+ end
276
+ end
277
+
214
278
  end
215
279
  end
Binary file
data/lib/mail/version.rb CHANGED
@@ -1,24 +1,17 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
  module Mail
3
3
  module VERSION
4
-
5
- version = {}
6
- File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
7
- type, value = line.chomp.split(":")
8
- next if type =~ /^\s+$/ || value =~ /^\s+$/
9
- version[type] = value
10
- end
11
-
12
- MAJOR = version['major']
13
- MINOR = version['minor']
14
- PATCH = version['patch']
15
- BUILD = version['build']
4
+
5
+ MAJOR = 2
6
+ MINOR = 6
7
+ PATCH = 4
8
+ BUILD = nil
16
9
 
17
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
18
-
11
+
19
12
  def self.version
20
13
  STRING
21
14
  end
22
-
15
+
23
16
  end
24
17
  end