mail-portertech 2.6.2.edge

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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +753 -0
  3. data/CONTRIBUTING.md +60 -0
  4. data/Dependencies.txt +2 -0
  5. data/Gemfile +15 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +683 -0
  8. data/Rakefile +29 -0
  9. data/TODO.rdoc +9 -0
  10. data/lib/mail.rb +91 -0
  11. data/lib/mail/attachments_list.rb +104 -0
  12. data/lib/mail/body.rb +291 -0
  13. data/lib/mail/check_delivery_params.rb +20 -0
  14. data/lib/mail/configuration.rb +75 -0
  15. data/lib/mail/core_extensions/nil.rb +19 -0
  16. data/lib/mail/core_extensions/object.rb +13 -0
  17. data/lib/mail/core_extensions/smtp.rb +24 -0
  18. data/lib/mail/core_extensions/string.rb +43 -0
  19. data/lib/mail/core_extensions/string/access.rb +145 -0
  20. data/lib/mail/core_extensions/string/multibyte.rb +78 -0
  21. data/lib/mail/elements.rb +14 -0
  22. data/lib/mail/elements/address.rb +270 -0
  23. data/lib/mail/elements/address_list.rb +51 -0
  24. data/lib/mail/elements/content_disposition_element.rb +26 -0
  25. data/lib/mail/elements/content_location_element.rb +21 -0
  26. data/lib/mail/elements/content_transfer_encoding_element.rb +17 -0
  27. data/lib/mail/elements/content_type_element.rb +31 -0
  28. data/lib/mail/elements/date_time_element.rb +22 -0
  29. data/lib/mail/elements/envelope_from_element.rb +39 -0
  30. data/lib/mail/elements/message_ids_element.rb +24 -0
  31. data/lib/mail/elements/mime_version_element.rb +22 -0
  32. data/lib/mail/elements/phrase_list.rb +16 -0
  33. data/lib/mail/elements/received_element.rb +26 -0
  34. data/lib/mail/encodings.rb +304 -0
  35. data/lib/mail/encodings/7bit.rb +31 -0
  36. data/lib/mail/encodings/8bit.rb +31 -0
  37. data/lib/mail/encodings/base64.rb +33 -0
  38. data/lib/mail/encodings/binary.rb +31 -0
  39. data/lib/mail/encodings/quoted_printable.rb +39 -0
  40. data/lib/mail/encodings/transfer_encoding.rb +58 -0
  41. data/lib/mail/envelope.rb +30 -0
  42. data/lib/mail/field.rb +247 -0
  43. data/lib/mail/field_list.rb +33 -0
  44. data/lib/mail/fields.rb +35 -0
  45. data/lib/mail/fields/bcc_field.rb +56 -0
  46. data/lib/mail/fields/cc_field.rb +55 -0
  47. data/lib/mail/fields/comments_field.rb +41 -0
  48. data/lib/mail/fields/common/address_container.rb +16 -0
  49. data/lib/mail/fields/common/common_address.rb +135 -0
  50. data/lib/mail/fields/common/common_date.rb +35 -0
  51. data/lib/mail/fields/common/common_field.rb +57 -0
  52. data/lib/mail/fields/common/common_message_id.rb +48 -0
  53. data/lib/mail/fields/common/parameter_hash.rb +58 -0
  54. data/lib/mail/fields/content_description_field.rb +19 -0
  55. data/lib/mail/fields/content_disposition_field.rb +70 -0
  56. data/lib/mail/fields/content_id_field.rb +62 -0
  57. data/lib/mail/fields/content_location_field.rb +42 -0
  58. data/lib/mail/fields/content_transfer_encoding_field.rb +44 -0
  59. data/lib/mail/fields/content_type_field.rb +201 -0
  60. data/lib/mail/fields/date_field.rb +57 -0
  61. data/lib/mail/fields/from_field.rb +55 -0
  62. data/lib/mail/fields/in_reply_to_field.rb +56 -0
  63. data/lib/mail/fields/keywords_field.rb +44 -0
  64. data/lib/mail/fields/message_id_field.rb +82 -0
  65. data/lib/mail/fields/mime_version_field.rb +53 -0
  66. data/lib/mail/fields/optional_field.rb +13 -0
  67. data/lib/mail/fields/received_field.rb +75 -0
  68. data/lib/mail/fields/references_field.rb +56 -0
  69. data/lib/mail/fields/reply_to_field.rb +55 -0
  70. data/lib/mail/fields/resent_bcc_field.rb +55 -0
  71. data/lib/mail/fields/resent_cc_field.rb +55 -0
  72. data/lib/mail/fields/resent_date_field.rb +35 -0
  73. data/lib/mail/fields/resent_from_field.rb +55 -0
  74. data/lib/mail/fields/resent_message_id_field.rb +34 -0
  75. data/lib/mail/fields/resent_sender_field.rb +62 -0
  76. data/lib/mail/fields/resent_to_field.rb +55 -0
  77. data/lib/mail/fields/return_path_field.rb +65 -0
  78. data/lib/mail/fields/sender_field.rb +67 -0
  79. data/lib/mail/fields/structured_field.rb +51 -0
  80. data/lib/mail/fields/subject_field.rb +16 -0
  81. data/lib/mail/fields/to_field.rb +55 -0
  82. data/lib/mail/fields/unstructured_field.rb +204 -0
  83. data/lib/mail/header.rb +274 -0
  84. data/lib/mail/indifferent_hash.rb +146 -0
  85. data/lib/mail/mail.rb +267 -0
  86. data/lib/mail/matchers/has_sent_mail.rb +157 -0
  87. data/lib/mail/message.rb +2160 -0
  88. data/lib/mail/multibyte.rb +42 -0
  89. data/lib/mail/multibyte/chars.rb +474 -0
  90. data/lib/mail/multibyte/exceptions.rb +8 -0
  91. data/lib/mail/multibyte/unicode.rb +400 -0
  92. data/lib/mail/multibyte/utils.rb +60 -0
  93. data/lib/mail/network.rb +14 -0
  94. data/lib/mail/network/delivery_methods/exim.rb +52 -0
  95. data/lib/mail/network/delivery_methods/file_delivery.rb +45 -0
  96. data/lib/mail/network/delivery_methods/sendmail.rb +89 -0
  97. data/lib/mail/network/delivery_methods/smtp.rb +142 -0
  98. data/lib/mail/network/delivery_methods/smtp_connection.rb +61 -0
  99. data/lib/mail/network/delivery_methods/test_mailer.rb +44 -0
  100. data/lib/mail/network/retriever_methods/base.rb +63 -0
  101. data/lib/mail/network/retriever_methods/imap.rb +173 -0
  102. data/lib/mail/network/retriever_methods/pop3.rb +140 -0
  103. data/lib/mail/network/retriever_methods/test_retriever.rb +43 -0
  104. data/lib/mail/parsers.rb +26 -0
  105. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  106. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  107. data/lib/mail/parsers/content_location_parser.rb +35 -0
  108. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  109. data/lib/mail/parsers/content_type_parser.rb +64 -0
  110. data/lib/mail/parsers/date_time_parser.rb +36 -0
  111. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  114. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  115. data/lib/mail/parsers/ragel.rb +17 -0
  116. data/lib/mail/parsers/ragel/common.rl +184 -0
  117. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  118. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  119. data/lib/mail/parsers/ragel/ruby.rb +39 -0
  120. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  121. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  122. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  123. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  124. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  125. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  126. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  127. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  128. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  129. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  130. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  131. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  132. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2129 -0
  133. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  134. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  135. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  136. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  137. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  138. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  139. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  140. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  141. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  142. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  143. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  144. data/lib/mail/parsers/received_parser.rb +47 -0
  145. data/lib/mail/part.rb +120 -0
  146. data/lib/mail/parts_list.rb +57 -0
  147. data/lib/mail/patterns.rb +37 -0
  148. data/lib/mail/utilities.rb +225 -0
  149. data/lib/mail/values/unicode_tables.dat +0 -0
  150. data/lib/mail/version.rb +4 -0
  151. data/lib/mail/version_specific/ruby_1_8.rb +119 -0
  152. data/lib/mail/version_specific/ruby_1_9.rb +159 -0
  153. metadata +276 -0
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class AddressList # :nodoc:
4
+
5
+ # Mail::AddressList is the class that parses To, From and other address fields from
6
+ # emails passed into Mail.
7
+ #
8
+ # AddressList provides a way to query the groups and mailbox lists of the passed in
9
+ # string.
10
+ #
11
+ # It can supply all addresses in an array, or return each address as an address object.
12
+ #
13
+ # Mail::AddressList requires a correctly formatted group or mailbox list per RFC2822 or
14
+ # RFC822. It also handles all obsolete versions in those RFCs.
15
+ #
16
+ # list = 'ada@test.lindsaar.net, My Group: mikel@test.lindsaar.net, Bob <bob@test.lindsaar.net>;'
17
+ # a = AddressList.new(list)
18
+ # a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
19
+ # a.group_names #=> ["My Group"]
20
+ def initialize(string)
21
+ @addresses_grouped_by_group = nil
22
+ @address_list = Parsers::AddressListsParser.new.parse(string)
23
+ end
24
+
25
+ # Returns a list of address objects from the parsed line
26
+ def addresses
27
+ @addresses ||= @address_list.addresses.map do |address_data|
28
+ Mail::Address.new(address_data)
29
+ end
30
+ end
31
+
32
+ def addresses_grouped_by_group
33
+ return @addresses_grouped_by_group if @addresses_grouped_by_group
34
+
35
+ @addresses_grouped_by_group = {}
36
+
37
+ @address_list.addresses.each do |address_data|
38
+ if group = address_data.group
39
+ @addresses_grouped_by_group[group] ||= []
40
+ @addresses_grouped_by_group[group] << Mail::Address.new(address_data)
41
+ end
42
+ end
43
+ @addresses_grouped_by_group
44
+ end
45
+
46
+ # Returns the names as an array of strings of all groups
47
+ def group_names # :nodoc:
48
+ @address_list.group_names
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentDispositionElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ content_disposition = Mail::Parsers::ContentDispositionParser.new.parse(cleaned(string))
9
+ @disposition_type = content_disposition.disposition_type
10
+ @parameters = content_disposition.parameters
11
+ end
12
+
13
+ def disposition_type
14
+ @disposition_type
15
+ end
16
+
17
+ def parameters
18
+ @parameters
19
+ end
20
+
21
+ def cleaned(string)
22
+ string =~ /(.+);\s*$/ ? $1 : string
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentLocationElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ content_location = Mail::Parsers::ContentLocationParser.new.parse(string)
9
+ @location = content_location.location
10
+ end
11
+
12
+ def location
13
+ @location
14
+ end
15
+
16
+ def to_s(*args)
17
+ location.to_s
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentTransferEncodingElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize(string)
8
+ content_transfer_encoding = Mail::Parsers::ContentTransferEncodingParser.new.parse(string)
9
+ @encoding = content_transfer_encoding.encoding
10
+ end
11
+
12
+ def encoding
13
+ @encoding
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentTypeElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ content_type = Mail::Parsers::ContentTypeParser.new.parse(cleaned(string))
9
+ @main_type = content_type.main_type
10
+ @sub_type = content_type.sub_type
11
+ @parameters = content_type.parameters
12
+ end
13
+
14
+ def main_type
15
+ @main_type
16
+ end
17
+
18
+ def sub_type
19
+ @sub_type
20
+ end
21
+
22
+ def parameters
23
+ @parameters
24
+ end
25
+
26
+ def cleaned(string)
27
+ string =~ /(.+);\s*$/ ? $1 : string
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class DateTimeElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ date_time = Mail::Parsers::DateTimeParser.new.parse(string)
9
+ @date_string = date_time.date_string
10
+ @time_string = date_time.time_string
11
+ end
12
+
13
+ def date_string
14
+ @date_string
15
+ end
16
+
17
+ def time_string
18
+ @time_string
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class EnvelopeFromElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ @envelope_from = Mail::Parsers::EnvelopeFromParser.new.parse(string)
9
+ @address = @envelope_from.address
10
+ @date_time = ::DateTime.parse(@envelope_from.ctime_date)
11
+ end
12
+
13
+ def date_time
14
+ @date_time
15
+ end
16
+
17
+ def address
18
+ @address
19
+ end
20
+
21
+ # RFC 4155:
22
+ # a timestamp indicating the UTC date and time when the message
23
+ # was originally received, conformant with the syntax of the
24
+ # traditional UNIX 'ctime' output sans timezone (note that the
25
+ # use of UTC precludes the need for a timezone indicator);
26
+ def formatted_date_time
27
+ if @date_time.respond_to?(:ctime)
28
+ @date_time.ctime
29
+ else
30
+ @date_time.strftime '%a %b %e %T %Y'
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ "#{@address} #{formatted_date_time}"
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class MessageIdsElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize(string)
8
+ @message_ids = Mail::Parsers::MessageIdsParser.new.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
9
+ end
10
+
11
+ def message_ids
12
+ @message_ids
13
+ end
14
+
15
+ def message_id
16
+ @message_ids.first
17
+ end
18
+
19
+ def clean_msg_id( val )
20
+ val =~ /.*<(.*)>.*/ ; $1
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class MimeVersionElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
9
+ @major = mime_version.major
10
+ @minor = mime_version.minor
11
+ end
12
+
13
+ def major
14
+ @major
15
+ end
16
+
17
+ def minor
18
+ @minor
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class PhraseList
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize(string)
8
+ @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
9
+ end
10
+
11
+ def phrases
12
+ @phrase_lists.phrases.map { |p| unquote(p) }
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ReceivedElement
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ received = Mail::Parsers::ReceivedParser.new.parse(string)
9
+ @date_time = ::DateTime.parse("#{received.date} #{received.time}")
10
+ @info = received.info
11
+ end
12
+
13
+ def date_time
14
+ @date_time
15
+ end
16
+
17
+ def info
18
+ @info
19
+ end
20
+
21
+ def to_s(*args)
22
+ "#{@info}; #{@date_time.to_s(*args)}"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,304 @@
1
+ # encoding: utf-8
2
+
3
+ module Mail
4
+ # Raised when attempting to decode an unknown encoding type
5
+ class UnknownEncodingType < StandardError #:nodoc:
6
+ end
7
+
8
+ module Encodings
9
+
10
+ include Mail::Patterns
11
+ extend Mail::Utilities
12
+
13
+ @transfer_encodings = {}
14
+
15
+ # Register transfer encoding
16
+ #
17
+ # Example
18
+ #
19
+ # Encodings.register "base64", Mail::Encodings::Base64
20
+ def Encodings.register(name, cls)
21
+ @transfer_encodings[get_name(name)] = cls
22
+ end
23
+
24
+ # Is the encoding we want defined?
25
+ #
26
+ # Example:
27
+ #
28
+ # Encodings.defined?(:base64) #=> true
29
+ def Encodings.defined?( str )
30
+ @transfer_encodings.include? get_name(str)
31
+ end
32
+
33
+ # Gets a defined encoding type, QuotedPrintable or Base64 for now.
34
+ #
35
+ # Each encoding needs to be defined as a Mail::Encodings::ClassName for
36
+ # this to work, allows us to add other encodings in the future.
37
+ #
38
+ # Example:
39
+ #
40
+ # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
41
+ def Encodings.get_encoding( str )
42
+ @transfer_encodings[get_name(str)]
43
+ end
44
+
45
+ def Encodings.get_all
46
+ @transfer_encodings.values
47
+ end
48
+
49
+ def Encodings.get_name(enc)
50
+ enc = enc.to_s.gsub("-", "_").downcase
51
+ end
52
+
53
+ # Encodes a parameter value using URI Escaping, note the language field 'en' can
54
+ # be set using Mail::Configuration, like so:
55
+ #
56
+ # Mail.defaults do
57
+ # param_encode_language 'jp'
58
+ # end
59
+ #
60
+ # The character set used for encoding will either be the value of $KCODE for
61
+ # Ruby < 1.9 or the encoding on the string passed in.
62
+ #
63
+ # Example:
64
+ #
65
+ # Mail::Encodings.param_encode("This is fun") #=> "us-ascii'en'This%20is%20fun"
66
+ def Encodings.param_encode(str)
67
+ case
68
+ when str.ascii_only? && str =~ TOKEN_UNSAFE
69
+ %Q{"#{str}"}
70
+ when str.ascii_only?
71
+ str
72
+ else
73
+ RubyVer.param_encode(str)
74
+ end
75
+ end
76
+
77
+ # Decodes a parameter value using URI Escaping.
78
+ #
79
+ # Example:
80
+ #
81
+ # Mail::Encodings.param_decode("This%20is%20fun", 'us-ascii') #=> "This is fun"
82
+ #
83
+ # str = Mail::Encodings.param_decode("This%20is%20fun", 'iso-8559-1')
84
+ # str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
85
+ # str #=> "This is fun"
86
+ def Encodings.param_decode(str, encoding)
87
+ RubyVer.param_decode(str, encoding)
88
+ end
89
+
90
+ # Decodes or encodes a string as needed for either Base64 or QP encoding types in
91
+ # the =?<encoding>?[QB]?<string>?=" format.
92
+ #
93
+ # The output type needs to be :decode to decode the input string or :encode to
94
+ # encode the input string. The character set used for encoding will either be
95
+ # the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
96
+ #
97
+ # On encoding, will only send out Base64 encoded strings.
98
+ def Encodings.decode_encode(str, output_type)
99
+ case
100
+ when output_type == :decode
101
+ Encodings.value_decode(str)
102
+ else
103
+ if str.ascii_only?
104
+ str
105
+ else
106
+ Encodings.b_value_encode(str, find_encoding(str))
107
+ end
108
+ end
109
+ end
110
+
111
+ # Decodes a given string as Base64 or Quoted Printable, depending on what
112
+ # type it is.
113
+ #
114
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
115
+ def Encodings.value_decode(str)
116
+ # Optimization: If there's no encoded-words in the string, just return it
117
+ return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
118
+
119
+ lines = collapse_adjacent_encodings(str)
120
+
121
+ # Split on white-space boundaries with capture, so we capture the white-space as well
122
+ lines.map do |line|
123
+ line.split(/([ \t])/).map do |text|
124
+ if text.index('=?').nil?
125
+ text
126
+ else
127
+ # Search for occurences of quoted strings or plain strings
128
+ text.scan(/( # Group around entire regex to include it in matches
129
+ \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
130
+ | # or
131
+ .+?(?=\=\?|$) # Plain String
132
+ )/xmi).map do |matches|
133
+ string, method = *matches
134
+ if method == 'b' || method == 'B'
135
+ b_value_decode(string)
136
+ elsif method == 'q' || method == 'Q'
137
+ q_value_decode(string)
138
+ else
139
+ string
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end.flatten.join("")
145
+ end
146
+
147
+ # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
148
+ def Encodings.unquote_and_convert_to(str, to_encoding)
149
+ output = value_decode( str ).to_s # output is already converted to UTF-8
150
+
151
+ if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
152
+ output
153
+ elsif to_encoding
154
+ begin
155
+ if RUBY_VERSION >= '1.9'
156
+ output.encode(to_encoding)
157
+ else
158
+ require 'iconv'
159
+ Iconv.iconv(to_encoding, 'UTF-8', output).first
160
+ end
161
+ rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
162
+ # the 'from' parameter specifies a charset other than what the text
163
+ # actually is...not much we can do in this case but just return the
164
+ # unconverted text.
165
+ #
166
+ # Ditto if either parameter represents an unknown charset, like
167
+ # X-UNKNOWN.
168
+ output
169
+ end
170
+ else
171
+ output
172
+ end
173
+ end
174
+
175
+ def Encodings.address_encode(address, charset = 'utf-8')
176
+ if address.is_a?(Array)
177
+ # loop back through for each element
178
+ address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
179
+ else
180
+ # find any word boundary that is not ascii and encode it
181
+ encode_non_usascii(address, charset) if address
182
+ end
183
+ end
184
+
185
+ def Encodings.encode_non_usascii(address, charset)
186
+ return address if address.ascii_only? or charset.nil?
187
+ us_ascii = %Q{\x00-\x7f}
188
+ # Encode any non usascii strings embedded inside of quotes
189
+ address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
190
+ # Then loop through all remaining items and encode as needed
191
+ tokens = address.split(/\s/)
192
+ map_with_index(tokens) do |word, i|
193
+ if word.ascii_only?
194
+ word
195
+ else
196
+ previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
197
+ if previous_non_ascii #why are we adding an extra space here?
198
+ word = " #{word}"
199
+ end
200
+ Encodings.b_value_encode(word, charset)
201
+ end
202
+ end.join(' ')
203
+ end
204
+
205
+ # Encode a string with Base64 Encoding and returns it ready to be inserted
206
+ # as a value for a field, that is, in the =?<charset>?B?<string>?= format
207
+ #
208
+ # Example:
209
+ #
210
+ # Encodings.b_value_encode('This is あ string', 'UTF-8')
211
+ # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
212
+ def Encodings.b_value_encode(encoded_str, encoding = nil)
213
+ return encoded_str if encoded_str.to_s.ascii_only?
214
+ string, encoding = RubyVer.b_value_encode(encoded_str, encoding)
215
+ map_lines(string) do |str|
216
+ "=?#{encoding}?B?#{str.chomp}?="
217
+ end.join(" ")
218
+ end
219
+
220
+ # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
221
+ # as a value for a field, that is, in the =?<charset>?Q?<string>?= format
222
+ #
223
+ # Example:
224
+ #
225
+ # Encodings.q_value_encode('This is あ string', 'UTF-8')
226
+ # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
227
+ def Encodings.q_value_encode(encoded_str, encoding = nil)
228
+ return encoded_str if encoded_str.to_s.ascii_only?
229
+ string, encoding = RubyVer.q_value_encode(encoded_str, encoding)
230
+ string.gsub!("=\r\n", '') # We already have limited the string to the length we want
231
+ map_lines(string) do |str|
232
+ "=?#{encoding}?Q?#{str.chomp.gsub(/ /, '_')}?="
233
+ end.join(" ")
234
+ end
235
+
236
+ private
237
+
238
+ # Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
239
+ #
240
+ # Example:
241
+ #
242
+ # Encodings.b_value_decode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
243
+ # #=> 'This is あ string'
244
+ def Encodings.b_value_decode(str)
245
+ RubyVer.b_value_decode(str)
246
+ end
247
+
248
+ # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
249
+ #
250
+ # Example:
251
+ #
252
+ # Encodings.q_value_decode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
253
+ # #=> 'This is あ string'
254
+ def Encodings.q_value_decode(str)
255
+ RubyVer.q_value_decode(str)
256
+ end
257
+
258
+ def Encodings.split_encoding_from_string( str )
259
+ match = str.match(/\=\?([^?]+)?\?[QB]\?(.+)?\?\=/mi)
260
+ if match
261
+ match[1]
262
+ else
263
+ nil
264
+ end
265
+ end
266
+
267
+ def Encodings.find_encoding(str)
268
+ RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
269
+ end
270
+
271
+ # Gets the encoding type (Q or B) from the string.
272
+ def Encodings.split_value_encoding_from_string(str)
273
+ match = str.match(/\=\?[^?]+?\?([QB])\?(.+)?\?\=/mi)
274
+ if match
275
+ match[1]
276
+ else
277
+ nil
278
+ end
279
+ end
280
+
281
+ # When the encoded string consists of multiple lines, lines with the same
282
+ # encoding (Q or B) can be joined together.
283
+ #
284
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
285
+ def Encodings.collapse_adjacent_encodings(str)
286
+ lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
287
+ results = []
288
+ previous_encoding = nil
289
+
290
+ lines.each do |line|
291
+ encoding = split_value_encoding_from_string(line)
292
+
293
+ if encoding == previous_encoding
294
+ line = results.pop + line
295
+ end
296
+
297
+ previous_encoding = encoding
298
+ results << line
299
+ end
300
+
301
+ results
302
+ end
303
+ end
304
+ end