mail-portertech 2.6.2.edge

Sign up to get free protection for your applications and to get access to all the features.
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