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,67 @@
1
+ # encoding: utf-8
2
+ #
3
+ # = Sender Field
4
+ #
5
+ # The Sender field inherits sender StructuredField and handles the Sender: header
6
+ # field in the email.
7
+ #
8
+ # Sending sender to a mail message will instantiate a Mail::Field object that
9
+ # has a SenderField as its field type. This includes all Mail::CommonAddress
10
+ # module instance metods.
11
+ #
12
+ # Only one Sender field can appear in a header, though it can have multiple
13
+ # addresses and groups of addresses.
14
+ #
15
+ # == Examples:
16
+ #
17
+ # mail = Mail.new
18
+ # mail.sender = 'Mikel Lindsaar <mikel@test.lindsaar.net>'
19
+ # mail.sender #=> 'mikel@test.lindsaar.net'
20
+ # mail[:sender] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
21
+ # mail['sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
22
+ # mail['Sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
23
+ #
24
+ # mail[:sender].encoded #=> "Sender: Mikel Lindsaar <mikel@test.lindsaar.net>\r\n"
25
+ # mail[:sender].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>'
26
+ # mail[:sender].addresses #=> ['mikel@test.lindsaar.net']
27
+ # mail[:sender].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>']
28
+ #
29
+ require 'mail/fields/common/common_address'
30
+
31
+ module Mail
32
+ class SenderField < StructuredField
33
+
34
+ include Mail::CommonAddress
35
+
36
+ FIELD_NAME = 'sender'
37
+ CAPITALIZED_FIELD = 'Sender'
38
+
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
+ self.parse
43
+ self
44
+ end
45
+
46
+ def addresses
47
+ [address.address]
48
+ end
49
+
50
+ def address
51
+ address_list.addresses.first
52
+ end
53
+
54
+ def encoded
55
+ do_encode(CAPITALIZED_FIELD)
56
+ end
57
+
58
+ def decoded
59
+ do_decode
60
+ end
61
+
62
+ def default
63
+ address.address
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ require 'mail/fields/common/common_field'
3
+
4
+ module Mail
5
+ # Provides access to a structured header field
6
+ #
7
+ # ===Per RFC 2822:
8
+ # 2.2.2. Structured Header Field Bodies
9
+ #
10
+ # Some field bodies in this standard have specific syntactical
11
+ # structure more restrictive than the unstructured field bodies
12
+ # described above. These are referred to as "structured" field bodies.
13
+ # Structured field bodies are sequences of specific lexical tokens as
14
+ # described in sections 3 and 4 of this standard. Many of these tokens
15
+ # are allowed (according to their syntax) to be introduced or end with
16
+ # comments (as described in section 3.2.3) as well as the space (SP,
17
+ # ASCII value 32) and horizontal tab (HTAB, ASCII value 9) characters
18
+ # (together known as the white space characters, WSP), and those WSP
19
+ # characters are subject to header "folding" and "unfolding" as
20
+ # described in section 2.2.3. Semantic analysis of structured field
21
+ # bodies is given along with their syntax.
22
+ class StructuredField
23
+
24
+ include Mail::CommonField
25
+ include Mail::Utilities
26
+
27
+ def initialize(name = nil, value = nil, charset = nil)
28
+ self.name = name
29
+ self.value = value
30
+ self.charset = charset
31
+ self
32
+ end
33
+
34
+ def charset
35
+ @charset
36
+ end
37
+
38
+ def charset=(val)
39
+ @charset = val
40
+ end
41
+
42
+ def default
43
+ decoded
44
+ end
45
+
46
+ def errors
47
+ []
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ #
3
+ # subject = "Subject:" unstructured CRLF
4
+ module Mail
5
+ class SubjectField < UnstructuredField
6
+
7
+ FIELD_NAME = 'subject'
8
+ CAPITALIZED_FIELD = "Subject"
9
+
10
+ def initialize(value = nil, charset = 'utf-8')
11
+ self.charset = charset
12
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+ #
3
+ # = To Field
4
+ #
5
+ # The To field inherits to StructuredField and handles the To: header
6
+ # field in the email.
7
+ #
8
+ # Sending to to a mail message will instantiate a Mail::Field object that
9
+ # has a ToField as its field type. This includes all Mail::CommonAddress
10
+ # module instance metods.
11
+ #
12
+ # Only one To field can appear in a header, though it can have multiple
13
+ # addresses and groups of addresses.
14
+ #
15
+ # == Examples:
16
+ #
17
+ # mail = Mail.new
18
+ # mail.to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
19
+ # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
20
+ # mail[:to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
21
+ # mail['to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
22
+ # mail['To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
23
+ #
24
+ # mail[:to].encoded #=> 'To: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
25
+ # mail[:to].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
26
+ # mail[:to].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
27
+ # mail[:to].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
28
+ #
29
+ require 'mail/fields/common/common_address'
30
+
31
+ module Mail
32
+ class ToField < StructuredField
33
+
34
+ include Mail::CommonAddress
35
+
36
+ FIELD_NAME = 'to'
37
+ CAPITALIZED_FIELD = 'To'
38
+
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
+ self.parse
43
+ self
44
+ end
45
+
46
+ def encoded
47
+ do_encode(CAPITALIZED_FIELD)
48
+ end
49
+
50
+ def decoded
51
+ do_decode
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,204 @@
1
+ # encoding: utf-8
2
+ require 'mail/fields/common/common_field'
3
+
4
+ module Mail
5
+ # Provides access to an unstructured header field
6
+ #
7
+ # ===Per RFC 2822:
8
+ # 2.2.1. Unstructured Header Field Bodies
9
+ #
10
+ # Some field bodies in this standard are defined simply as
11
+ # "unstructured" (which is specified below as any US-ASCII characters,
12
+ # except for CR and LF) with no further restrictions. These are
13
+ # referred to as unstructured field bodies. Semantically, unstructured
14
+ # field bodies are simply to be treated as a single line of characters
15
+ # with no further processing (except for header "folding" and
16
+ # "unfolding" as described in section 2.2.3).
17
+ class UnstructuredField
18
+
19
+ include Mail::CommonField
20
+ include Mail::Utilities
21
+
22
+ attr_accessor :charset
23
+ attr_reader :errors
24
+
25
+ def initialize(name, value, charset = nil)
26
+ @errors = []
27
+
28
+ if value.is_a?(Array)
29
+ # Probably has arrived here from a failed parse of an AddressList Field
30
+ value = value.join(', ')
31
+ else
32
+ # Ensure we are dealing with a string
33
+ value = value.to_s
34
+ end
35
+
36
+ if charset
37
+ self.charset = charset
38
+ else
39
+ if value.respond_to?(:encoding)
40
+ self.charset = value.encoding
41
+ else
42
+ self.charset = $KCODE
43
+ end
44
+ end
45
+ self.name = name
46
+ self.value = value
47
+ self
48
+ end
49
+
50
+ def encoded
51
+ do_encode
52
+ end
53
+
54
+ def decoded
55
+ do_decode
56
+ end
57
+
58
+ def default
59
+ decoded
60
+ end
61
+
62
+ def parse # An unstructured field does not parse
63
+ self
64
+ end
65
+
66
+ private
67
+
68
+ def do_encode
69
+ value.nil? ? '' : "#{wrapped_value}\r\n"
70
+ end
71
+
72
+ def do_decode
73
+ value.blank? ? nil : Encodings.decode_encode(value, :decode)
74
+ end
75
+
76
+ # 2.2.3. Long Header Fields
77
+ #
78
+ # Each header field is logically a single line of characters comprising
79
+ # the field name, the colon, and the field body. For convenience
80
+ # however, and to deal with the 998/78 character limitations per line,
81
+ # the field body portion of a header field can be split into a multiple
82
+ # line representation; this is called "folding". The general rule is
83
+ # that wherever this standard allows for folding white space (not
84
+ # simply WSP characters), a CRLF may be inserted before any WSP. For
85
+ # example, the header field:
86
+ #
87
+ # Subject: This is a test
88
+ #
89
+ # can be represented as:
90
+ #
91
+ # Subject: This
92
+ # is a test
93
+ #
94
+ # Note: Though structured field bodies are defined in such a way that
95
+ # folding can take place between many of the lexical tokens (and even
96
+ # within some of the lexical tokens), folding SHOULD be limited to
97
+ # placing the CRLF at higher-level syntactic breaks. For instance, if
98
+ # a field body is defined as comma-separated values, it is recommended
99
+ # that folding occur after the comma separating the structured items in
100
+ # preference to other places where the field could be folded, even if
101
+ # it is allowed elsewhere.
102
+ def wrapped_value # :nodoc:
103
+ wrap_lines(name, fold("#{name}: ".length))
104
+ end
105
+
106
+ # 6.2. Display of 'encoded-word's
107
+ #
108
+ # When displaying a particular header field that contains multiple
109
+ # 'encoded-word's, any 'linear-white-space' that separates a pair of
110
+ # adjacent 'encoded-word's is ignored. (This is to allow the use of
111
+ # multiple 'encoded-word's to represent long strings of unencoded text,
112
+ # without having to separate 'encoded-word's where spaces occur in the
113
+ # unencoded text.)
114
+ def wrap_lines(name, folded_lines)
115
+ result = ["#{name}: #{folded_lines.shift}"]
116
+ result.concat(folded_lines)
117
+ result.join("\r\n\s")
118
+ end
119
+
120
+ def fold(prepend = 0) # :nodoc:
121
+ encoding = normalized_encoding
122
+ decoded_string = decoded.to_s
123
+ should_encode = decoded_string.not_ascii_only?
124
+ if should_encode
125
+ first = true
126
+ words = decoded_string.split(/[ \t]/).map do |word|
127
+ if first
128
+ first = !first
129
+ else
130
+ word = " " << word
131
+ end
132
+ if word.not_ascii_only?
133
+ word
134
+ else
135
+ word.scan(/.{7}|.+$/)
136
+ end
137
+ end.flatten
138
+ else
139
+ words = decoded_string.split(/[ \t]/)
140
+ end
141
+
142
+ folded_lines = []
143
+ while !words.empty?
144
+ limit = 78 - prepend
145
+ limit = limit - 7 - encoding.length if should_encode
146
+ line = ""
147
+ first_word = true
148
+ while !words.empty?
149
+ break unless word = words.first.dup
150
+ word.encode!(charset) if charset && word.respond_to?(:encode!)
151
+ word = encode(word) if should_encode
152
+ word = encode_crlf(word)
153
+ # Skip to next line if we're going to go past the limit
154
+ # Unless this is the first word, in which case we're going to add it anyway
155
+ # Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you.
156
+ # (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
157
+ # the linebreak will be ignored)
158
+ break if !line.empty? && (line.length + word.length + 1 > limit)
159
+ # Remove the word from the queue ...
160
+ words.shift
161
+ # Add word separator
162
+ if first_word
163
+ first_word = false
164
+ else
165
+ line << " " if !should_encode
166
+ end
167
+
168
+ # ... add it in encoded form to the current line
169
+ line << word
170
+ end
171
+ # Encode the line if necessary
172
+ line = "=?#{encoding}?Q?#{line}?=" if should_encode
173
+ # Add the line to the output and reset the prepend
174
+ folded_lines << line
175
+ prepend = 0
176
+ end
177
+ folded_lines
178
+ end
179
+
180
+ def encode(value)
181
+ value = [value].pack("M").gsub("=\n", '')
182
+ value.gsub!(/"/, '=22')
183
+ value.gsub!(/\(/, '=28')
184
+ value.gsub!(/\)/, '=29')
185
+ value.gsub!(/\?/, '=3F')
186
+ value.gsub!(/_/, '=5F')
187
+ value.gsub!(/ /, '_')
188
+ value
189
+ end
190
+
191
+ def encode_crlf(value)
192
+ value.gsub!("\r", '=0D')
193
+ value.gsub!("\n", '=0A')
194
+ value
195
+ end
196
+
197
+ def normalized_encoding
198
+ encoding = charset.to_s.upcase.gsub('_', '-')
199
+ encoding = 'UTF-8' if encoding == 'UTF8' # Ruby 1.8.x and $KCODE == 'u'
200
+ encoding
201
+ end
202
+
203
+ end
204
+ end
@@ -0,0 +1,274 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # Provides access to a header object.
5
+ #
6
+ # ===Per RFC2822
7
+ #
8
+ # 2.2. Header Fields
9
+ #
10
+ # Header fields are lines composed of a field name, followed by a colon
11
+ # (":"), followed by a field body, and terminated by CRLF. A field
12
+ # name MUST be composed of printable US-ASCII characters (i.e.,
13
+ # characters that have values between 33 and 126, inclusive), except
14
+ # colon. A field body may be composed of any US-ASCII characters,
15
+ # except for CR and LF. However, a field body may contain CRLF when
16
+ # used in header "folding" and "unfolding" as described in section
17
+ # 2.2.3. All field bodies MUST conform to the syntax described in
18
+ # sections 3 and 4 of this standard.
19
+ class Header
20
+ include Patterns
21
+ include Utilities
22
+ include Enumerable
23
+
24
+ @@maximum_amount = 1000
25
+
26
+ # Large amount of headers in Email might create extra high CPU load
27
+ # Use this parameter to limit number of headers that will be parsed by
28
+ # mail library.
29
+ # Default: 1000
30
+ def self.maximum_amount
31
+ @@maximum_amount
32
+ end
33
+
34
+ def self.maximum_amount=(value)
35
+ @@maximum_amount = value
36
+ end
37
+
38
+ # Creates a new header object.
39
+ #
40
+ # Accepts raw text or nothing. If given raw text will attempt to parse
41
+ # it and split it into the various fields, instantiating each field as
42
+ # it goes.
43
+ #
44
+ # If it finds a field that should be a structured field (such as content
45
+ # type), but it fails to parse it, it will simply make it an unstructured
46
+ # field and leave it alone. This will mean that the data is preserved but
47
+ # no automatic processing of that field will happen. If you find one of
48
+ # these cases, please make a patch and send it in, or at the least, send
49
+ # me the example so we can fix it.
50
+ def initialize(header_text = nil, charset = nil)
51
+ @charset = charset
52
+ self.raw_source = header_text.to_crlf.lstrip
53
+ split_header if header_text
54
+ end
55
+
56
+ def initialize_copy(original)
57
+ super
58
+ @fields = @fields.dup
59
+ end
60
+
61
+ # The preserved raw source of the header as you passed it in, untouched
62
+ # for your Regexing glory.
63
+ def raw_source
64
+ @raw_source
65
+ end
66
+
67
+ # Returns an array of all the fields in the header in order that they
68
+ # were read in.
69
+ def fields
70
+ @fields ||= FieldList.new
71
+ end
72
+
73
+ # 3.6. Field definitions
74
+ #
75
+ # It is important to note that the header fields are not guaranteed to
76
+ # be in a particular order. They may appear in any order, and they
77
+ # have been known to be reordered occasionally when transported over
78
+ # the Internet. However, for the purposes of this standard, header
79
+ # fields SHOULD NOT be reordered when a message is transported or
80
+ # transformed. More importantly, the trace header fields and resent
81
+ # header fields MUST NOT be reordered, and SHOULD be kept in blocks
82
+ # prepended to the message. See sections 3.6.6 and 3.6.7 for more
83
+ # information.
84
+ #
85
+ # Populates the fields container with Field objects in the order it
86
+ # receives them in.
87
+ #
88
+ # Acceps an array of field string values, for example:
89
+ #
90
+ # h = Header.new
91
+ # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
92
+ def fields=(unfolded_fields)
93
+ @fields = Mail::FieldList.new
94
+ warn "Warning: more than #{self.class.maximum_amount} header fields only using the first #{self.class.maximum_amount}" if unfolded_fields.length > self.class.maximum_amount
95
+ unfolded_fields[0..(self.class.maximum_amount-1)].each do |field|
96
+
97
+ field = Field.new(field, nil, charset)
98
+ if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
99
+ selected.first.update(field.name, field.value)
100
+ else
101
+ @fields << field
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ def errors
108
+ @fields.map(&:errors).flatten(1)
109
+ end
110
+
111
+ # 3.6. Field definitions
112
+ #
113
+ # The following table indicates limits on the number of times each
114
+ # field may occur in a message header as well as any special
115
+ # limitations on the use of those fields. An asterisk next to a value
116
+ # in the minimum or maximum column indicates that a special restriction
117
+ # appears in the Notes column.
118
+ #
119
+ # <snip table from 3.6>
120
+ #
121
+ # As per RFC, many fields can appear more than once, we will return a string
122
+ # of the value if there is only one header, or if there is more than one
123
+ # matching header, will return an array of values in order that they appear
124
+ # in the header ordered from top to bottom.
125
+ #
126
+ # Example:
127
+ #
128
+ # h = Header.new
129
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
130
+ # h['To'] #=> 'mikel@me.com'
131
+ # h['X-Mail-SPAM'] #=> ['15', '20']
132
+ def [](name)
133
+ name = dasherize(name).downcase
134
+ selected = select_field_for(name)
135
+ case
136
+ when selected.length > 1
137
+ selected.map { |f| f }
138
+ when !selected.blank?
139
+ selected.first
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ # Sets the FIRST matching field in the header to passed value, or deletes
146
+ # the FIRST field matched from the header if passed nil
147
+ #
148
+ # Example:
149
+ #
150
+ # h = Header.new
151
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
152
+ # h['To'] = 'bob@you.com'
153
+ # h['To'] #=> 'bob@you.com'
154
+ # h['X-Mail-SPAM'] = '10000'
155
+ # h['X-Mail-SPAM'] # => ['15', '20', '10000']
156
+ # h['X-Mail-SPAM'] = nil
157
+ # h['X-Mail-SPAM'] # => nil
158
+ def []=(name, value)
159
+ name = dasherize(name)
160
+ if name.include?(':')
161
+ raise ArgumentError, "Header names may not contain a colon: #{name.inspect}"
162
+ end
163
+ fn = name.downcase
164
+ selected = select_field_for(fn)
165
+
166
+ case
167
+ # User wants to delete the field
168
+ when !selected.blank? && value == nil
169
+ fields.delete_if { |f| selected.include?(f) }
170
+
171
+ # User wants to change the field
172
+ when !selected.blank? && limited_field?(fn)
173
+ selected.first.update(fn, value)
174
+
175
+ # User wants to create the field
176
+ else
177
+ # Need to insert in correct order for trace fields
178
+ self.fields << Field.new(name.to_s, value, charset)
179
+ end
180
+ if dasherize(fn) == "content-type"
181
+ # Update charset if specified in Content-Type
182
+ params = self[:content_type].parameters rescue nil
183
+ @charset = params[:charset] if params && params[:charset]
184
+ end
185
+ end
186
+
187
+ def charset
188
+ @charset
189
+ end
190
+
191
+ def charset=(val)
192
+ params = self[:content_type].parameters rescue nil
193
+ if params
194
+ params[:charset] = val
195
+ end
196
+ @charset = val
197
+ end
198
+
199
+ LIMITED_FIELDS = %w[ date from sender reply-to to cc bcc
200
+ message-id in-reply-to references subject
201
+ return-path content-type mime-version
202
+ content-transfer-encoding content-description
203
+ content-id content-disposition content-location]
204
+
205
+ def encoded
206
+ buffer = ''
207
+ buffer.force_encoding('us-ascii') if buffer.respond_to?(:force_encoding)
208
+ fields.each do |field|
209
+ buffer << field.encoded
210
+ end
211
+ buffer
212
+ end
213
+
214
+ def to_s
215
+ encoded
216
+ end
217
+
218
+ def decoded
219
+ raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'
220
+ end
221
+
222
+ def field_summary
223
+ fields.map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
224
+ end
225
+
226
+ # Returns true if the header has a Message-ID defined (empty or not)
227
+ def has_message_id?
228
+ !fields.select { |f| f.responsible_for?('Message-ID') }.empty?
229
+ end
230
+
231
+ # Returns true if the header has a Content-ID defined (empty or not)
232
+ def has_content_id?
233
+ !fields.select { |f| f.responsible_for?('Content-ID') }.empty?
234
+ end
235
+
236
+ # Returns true if the header has a Date defined (empty or not)
237
+ def has_date?
238
+ !fields.select { |f| f.responsible_for?('Date') }.empty?
239
+ end
240
+
241
+ # Returns true if the header has a MIME version defined (empty or not)
242
+ def has_mime_version?
243
+ !fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
244
+ end
245
+
246
+ private
247
+
248
+ def raw_source=(val)
249
+ @raw_source = val
250
+ end
251
+
252
+ # Splits an unfolded and line break cleaned header into individual field
253
+ # strings.
254
+ def split_header
255
+ self.fields = raw_source.split(HEADER_SPLIT)
256
+ end
257
+
258
+ def select_field_for(name)
259
+ fields.select { |f| f.responsible_for?(name) }
260
+ end
261
+
262
+ def limited_field?(name)
263
+ LIMITED_FIELDS.include?(name.to_s.downcase)
264
+ end
265
+
266
+ # Enumerable support; yield each field in order to the block if there is one,
267
+ # or return an Enumerator for them if there isn't.
268
+ def each( &block )
269
+ return self.fields.each( &block ) if block
270
+ self.fields.each
271
+ end
272
+
273
+ end
274
+ end