mail 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mail might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/.gitignore +4 -0
  2. data/Manifest.txt +106 -0
  3. data/README.rdoc +441 -0
  4. data/Rakefile +38 -0
  5. data/lib/mail.rb +86 -0
  6. data/lib/mail/attachment.rb +90 -0
  7. data/lib/mail/body.rb +149 -0
  8. data/lib/mail/configuration.rb +90 -0
  9. data/lib/mail/core_extensions.rb +6 -0
  10. data/lib/mail/core_extensions/blank.rb +41 -0
  11. data/lib/mail/core_extensions/nil.rb +15 -0
  12. data/lib/mail/core_extensions/string.rb +31 -0
  13. data/lib/mail/elements/address.rb +293 -0
  14. data/lib/mail/elements/address_list.rb +62 -0
  15. data/lib/mail/elements/content_disposition_element.rb +34 -0
  16. data/lib/mail/elements/content_transfer_encoding_element.rb +21 -0
  17. data/lib/mail/elements/content_type_element.rb +39 -0
  18. data/lib/mail/elements/date_time_element.rb +26 -0
  19. data/lib/mail/elements/envelope_from_element.rb +34 -0
  20. data/lib/mail/elements/message_ids_element.rb +29 -0
  21. data/lib/mail/elements/mime_version_element.rb +26 -0
  22. data/lib/mail/elements/phrase_list.rb +21 -0
  23. data/lib/mail/elements/received_element.rb +30 -0
  24. data/lib/mail/encodings/base64.rb +17 -0
  25. data/lib/mail/encodings/encodings.rb +24 -0
  26. data/lib/mail/encodings/quoted_printable.rb +26 -0
  27. data/lib/mail/envelope.rb +35 -0
  28. data/lib/mail/field.rb +202 -0
  29. data/lib/mail/field_list.rb +33 -0
  30. data/lib/mail/fields/bcc_field.rb +40 -0
  31. data/lib/mail/fields/cc_field.rb +40 -0
  32. data/lib/mail/fields/comments_field.rb +41 -0
  33. data/lib/mail/fields/common/common_address.rb +62 -0
  34. data/lib/mail/fields/common/common_date.rb +35 -0
  35. data/lib/mail/fields/common/common_field.rb +128 -0
  36. data/lib/mail/fields/common/common_message_id.rb +35 -0
  37. data/lib/mail/fields/content_description_field.rb +15 -0
  38. data/lib/mail/fields/content_disposition_field.rb +34 -0
  39. data/lib/mail/fields/content_id_field.rb +50 -0
  40. data/lib/mail/fields/content_transfer_encoding_field.rb +28 -0
  41. data/lib/mail/fields/content_type_field.rb +50 -0
  42. data/lib/mail/fields/date_field.rb +44 -0
  43. data/lib/mail/fields/from_field.rb +40 -0
  44. data/lib/mail/fields/in_reply_to_field.rb +42 -0
  45. data/lib/mail/fields/keywords_field.rb +22 -0
  46. data/lib/mail/fields/message_id_field.rb +70 -0
  47. data/lib/mail/fields/mime_version_field.rb +42 -0
  48. data/lib/mail/fields/optional_field.rb +11 -0
  49. data/lib/mail/fields/received_field.rb +49 -0
  50. data/lib/mail/fields/references_field.rb +42 -0
  51. data/lib/mail/fields/reply_to_field.rb +40 -0
  52. data/lib/mail/fields/resent_bcc_field.rb +40 -0
  53. data/lib/mail/fields/resent_cc_field.rb +40 -0
  54. data/lib/mail/fields/resent_date_field.rb +16 -0
  55. data/lib/mail/fields/resent_from_field.rb +40 -0
  56. data/lib/mail/fields/resent_message_id_field.rb +20 -0
  57. data/lib/mail/fields/resent_sender_field.rb +48 -0
  58. data/lib/mail/fields/resent_to_field.rb +40 -0
  59. data/lib/mail/fields/return_path_field.rb +34 -0
  60. data/lib/mail/fields/sender_field.rb +48 -0
  61. data/lib/mail/fields/structured_field.rb +32 -0
  62. data/lib/mail/fields/subject_field.rb +14 -0
  63. data/lib/mail/fields/to_field.rb +40 -0
  64. data/lib/mail/fields/unstructured_field.rb +27 -0
  65. data/lib/mail/header.rb +213 -0
  66. data/lib/mail/mail.rb +120 -0
  67. data/lib/mail/message.rb +648 -0
  68. data/lib/mail/network/deliverable.rb +42 -0
  69. data/lib/mail/network/retrievable.rb +63 -0
  70. data/lib/mail/parsers/address_lists.rb +61 -0
  71. data/lib/mail/parsers/address_lists.treetop +19 -0
  72. data/lib/mail/parsers/content_disposition.rb +358 -0
  73. data/lib/mail/parsers/content_disposition.treetop +45 -0
  74. data/lib/mail/parsers/content_transfer_encoding.rb +179 -0
  75. data/lib/mail/parsers/content_transfer_encoding.treetop +25 -0
  76. data/lib/mail/parsers/content_type.rb +507 -0
  77. data/lib/mail/parsers/content_type.treetop +58 -0
  78. data/lib/mail/parsers/date_time.rb +111 -0
  79. data/lib/mail/parsers/date_time.treetop +11 -0
  80. data/lib/mail/parsers/envelope_from.rb +188 -0
  81. data/lib/mail/parsers/envelope_from.treetop +32 -0
  82. data/lib/mail/parsers/message_ids.rb +42 -0
  83. data/lib/mail/parsers/message_ids.treetop +15 -0
  84. data/lib/mail/parsers/mime_version.rb +141 -0
  85. data/lib/mail/parsers/mime_version.treetop +19 -0
  86. data/lib/mail/parsers/phrase_lists.rb +42 -0
  87. data/lib/mail/parsers/phrase_lists.treetop +15 -0
  88. data/lib/mail/parsers/received.rb +68 -0
  89. data/lib/mail/parsers/received.treetop +11 -0
  90. data/lib/mail/parsers/rfc2045.rb +406 -0
  91. data/lib/mail/parsers/rfc2045.treetop +35 -0
  92. data/lib/mail/parsers/rfc2822.rb +5005 -0
  93. data/lib/mail/parsers/rfc2822.treetop +402 -0
  94. data/lib/mail/parsers/rfc2822_obsolete.rb +3607 -0
  95. data/lib/mail/parsers/rfc2822_obsolete.treetop +241 -0
  96. data/lib/mail/part.rb +120 -0
  97. data/lib/mail/patterns.rb +42 -0
  98. data/lib/mail/utilities.rb +142 -0
  99. data/lib/mail/version.rb +10 -0
  100. data/lib/mail/version_specific/multibyte.rb +62 -0
  101. data/lib/mail/version_specific/multibyte/chars.rb +701 -0
  102. data/lib/mail/version_specific/multibyte/exceptions.rb +8 -0
  103. data/lib/mail/version_specific/multibyte/unicode_database.rb +71 -0
  104. data/lib/mail/version_specific/ruby_1_8.rb +61 -0
  105. data/lib/mail/version_specific/ruby_1_8_string.rb +88 -0
  106. data/lib/mail/version_specific/ruby_1_9.rb +49 -0
  107. metadata +192 -0
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ #
3
+ # subject = "Subject:" unstructured CRLF
4
+ module Mail
5
+ class SubjectField < UnstructuredField
6
+
7
+ FIELD_NAME = 'subject'
8
+
9
+ def initialize(*args)
10
+ super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,40 @@
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 it's 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 #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
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.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
25
+ # mail.to.addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
26
+ # mail.to.formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
27
+ #
28
+ module Mail
29
+ class ToField < StructuredField
30
+
31
+ include Mail::CommonAddress
32
+
33
+ FIELD_NAME = 'to'
34
+
35
+ def initialize(*args)
36
+ super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ # Provides access to an unstructured header field
4
+ #
5
+ # ===Per RFC 2822:
6
+ # 2.2.1. Unstructured Header Field Bodies
7
+ #
8
+ # Some field bodies in this standard are defined simply as
9
+ # "unstructured" (which is specified below as any US-ASCII characters,
10
+ # except for CR and LF) with no further restrictions. These are
11
+ # referred to as unstructured field bodies. Semantically, unstructured
12
+ # field bodies are simply to be treated as a single line of characters
13
+ # with no further processing (except for header "folding" and
14
+ # "unfolding" as described in section 2.2.3).
15
+ class UnstructuredField
16
+
17
+ include Mail::CommonField
18
+ include Mail::Utilities
19
+
20
+ def initialize(*args)
21
+ self.name = args.first
22
+ self.value = args.last
23
+ self
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,213 @@
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
+ # Creates a new header object.
25
+ #
26
+ # Accepts raw text or nothing. If given raw text will attempt to parse
27
+ # it and split it into the various fields, instantiating each field as
28
+ # it goes.
29
+ #
30
+ # If it finds a field that should be a structured field (such as content
31
+ # type), but it fails to parse it, it will simply make it an unstructured
32
+ # field and leave it alone. This will mean that the data is preserved but
33
+ # no automatic processing of that field will happen. If you find one of
34
+ # these cases, please make a patch and send it in, or at the least, send
35
+ # me the example so we can fix it.
36
+ def initialize(header_text = nil)
37
+ self.raw_source = header_text.to_crlf
38
+ split_header if header_text
39
+ end
40
+
41
+ # The preserved raw source of the header as you passed it in, untouched
42
+ # for your Regexing glory.
43
+ def raw_source
44
+ @raw_source
45
+ end
46
+
47
+ # Returns an array of all the fields in the header in order that they
48
+ # were read in.
49
+ def fields
50
+ @fields ||= []
51
+ end
52
+
53
+ # 3.6. Field definitions
54
+ #
55
+ # It is important to note that the header fields are not guaranteed to
56
+ # be in a particular order. They may appear in any order, and they
57
+ # have been known to be reordered occasionally when transported over
58
+ # the Internet. However, for the purposes of this standard, header
59
+ # fields SHOULD NOT be reordered when a message is transported or
60
+ # transformed. More importantly, the trace header fields and resent
61
+ # header fields MUST NOT be reordered, and SHOULD be kept in blocks
62
+ # prepended to the message. See sections 3.6.6 and 3.6.7 for more
63
+ # information.
64
+ #
65
+ # Populates the fields container with Field objects in the order it
66
+ # receives them in.
67
+ #
68
+ # Acceps an array of field string values, for example:
69
+ #
70
+ # h = Header.new
71
+ # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
72
+ def fields=(unfolded_fields)
73
+ @fields = Mail::FieldList.new
74
+ unfolded_fields.each do |field|
75
+ @fields << Field.new(field)
76
+ end
77
+ end
78
+
79
+ # 3.6. Field definitions
80
+ #
81
+ # The following table indicates limits on the number of times each
82
+ # field may occur in a message header as well as any special
83
+ # limitations on the use of those fields. An asterisk next to a value
84
+ # in the minimum or maximum column indicates that a special restriction
85
+ # appears in the Notes column.
86
+ #
87
+ # <snip table from 3.6>
88
+ #
89
+ # As per RFC, many fields can appear more than once, we will return a string
90
+ # of the value if there is only one header, or if there is more than one
91
+ # matching header, will return an array of values in order that they appear
92
+ # in the header ordered from top to bottom.
93
+ #
94
+ # Example:
95
+ #
96
+ # h = Header.new
97
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
98
+ # h['To'] #=> 'mikel@me.com'
99
+ # h['X-Mail-SPAM'] #=> ['15', '20']
100
+ def [](name)
101
+ selected = fields.select { |f| f.responsible_for?(name) }
102
+ case
103
+ when selected.length > 1
104
+ selected.map { |f| f }
105
+ when !selected.blank?
106
+ selected.first
107
+ else
108
+ nil
109
+ end
110
+ end
111
+
112
+ # Sets the FIRST matching field in the header to passed value, or deletes
113
+ # the FIRST field matched from the header if passed nil
114
+ #
115
+ # Example:
116
+ #
117
+ # h = Header.new
118
+ # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
119
+ # h['To'] = 'bob@you.com'
120
+ # h['To'] #=> 'bob@you.com'
121
+ # h['X-Mail-SPAM'] = '10000'
122
+ # h['X-Mail-SPAM'] # => ['15', '20', '10000']
123
+ # h['X-Mail-SPAM'] = nil
124
+ # h['X-Mail-SPAM'] # => nil
125
+ def []=(name, value)
126
+ selected = fields.select { |f| f.responsible_for?(name) }
127
+ case
128
+ # User wants to delete the field
129
+ when !selected.blank? && value == nil
130
+ fields.delete_if { |f| selected.include?(f) }
131
+
132
+ # User wants to change the field
133
+ when !selected.blank? && LIMITED_FIELDS.include?(name.downcase)
134
+ selected.first.update(name, value)
135
+
136
+ # User wants to create the field
137
+ else
138
+ # Need to insert in correct order for trace fields
139
+ if value.blank?
140
+ self.fields << Field.new(name)
141
+ else
142
+ self.fields << Field.new("#{name}: #{value}")
143
+ end
144
+ end
145
+ end
146
+
147
+ LIMITED_FIELDS = %w[ orig-date from sender reply-to to cc bcc
148
+ message-id in-reply-to references subject
149
+ return-path content-type mime-version
150
+ content-transfer-encoding content-description
151
+ content-id content-type content-disposition]
152
+
153
+ def encoded
154
+ buffer = ''
155
+ fields.each do |field|
156
+ buffer << field.encoded.to_s
157
+ end
158
+ buffer
159
+ end
160
+
161
+ alias :to_s :encoded
162
+
163
+ # Returns true if the header has a Message-ID defined (empty or not)
164
+ def has_message_id?
165
+ !fields.select { |f| f.responsible_for?('Message-ID') }.empty?
166
+ end
167
+
168
+ # Returns true if the header has a Content-ID defined (empty or not)
169
+ def has_content_id?
170
+ !fields.select { |f| f.responsible_for?('Content-ID') }.empty?
171
+ end
172
+
173
+ # Returns true if the header has a Date defined (empty or not)
174
+ def has_date?
175
+ !fields.select { |f| f.responsible_for?('Date') }.empty?
176
+ end
177
+
178
+ # Returns true if the header has a message_id defined (empty or not)
179
+ def has_mime_version?
180
+ !fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
181
+ end
182
+
183
+ private
184
+
185
+ def raw_source=(val)
186
+ @raw_source = val
187
+ end
188
+
189
+ # 2.2.3. Long Header Fields
190
+ #
191
+ # The process of moving from this folded multiple-line representation
192
+ # of a header field to its single line representation is called
193
+ # "unfolding". Unfolding is accomplished by simply removing any CRLF
194
+ # that is immediately followed by WSP. Each header field should be
195
+ # treated in its unfolded form for further syntactic and semantic
196
+ # evaluation.
197
+ def unfold(string)
198
+ string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
199
+ end
200
+
201
+ # Returns the header with all the folds removed
202
+ def unfolded_header
203
+ @unfolded_header ||= unfold(raw_source)
204
+ end
205
+
206
+ # Splits an unfolded and line break cleaned header into individual field
207
+ # strings.
208
+ def split_header
209
+ self.fields = unfolded_header.split(CRLF)
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # Allows you to create a new Mail::Message object.
5
+ #
6
+ # You can make an email via passing a string or passing a block.
7
+ #
8
+ # For example, the following two examples will create the same email
9
+ # message:
10
+ #
11
+ # Creating via a string:
12
+ #
13
+ # string = 'To: mikel@test.lindsaar.net\r\n'
14
+ # string << 'From: bob@test.lindsaar.net\r\n\r\n'
15
+ # string << 'Subject: This is an email\r\n'
16
+ # string << '\r\n'
17
+ # string << 'This is the body'
18
+ # Mail.new(string)
19
+ #
20
+ # Or creating via a block:
21
+ #
22
+ # message = Mail.new do
23
+ # to 'mikel@test.lindsaar.net'
24
+ # from 'bob@test.lindsaar.net'
25
+ # subject 'This is an email'
26
+ # body 'This is the body'
27
+ # end
28
+ #
29
+ # As a side note, you can also create a new email through creating
30
+ # a Mail::Message object directly and then passing in values via string,
31
+ # symbol or direct method calls. See Mail::Message for more information.
32
+ #
33
+ # mail = Mail.new
34
+ # mail.to = 'mikel@test.lindsaar.net'
35
+ # mail[:from] = 'bob@test.lindsaar.net'
36
+ # mail['subject'] = 'This is an email'
37
+ # mail.body = 'This is the body'
38
+ def Mail.new(*args, &block)
39
+ if block_given?
40
+ Mail::Message.new(args, &block)
41
+ else
42
+ Mail::Message.new(args)
43
+ end
44
+ end
45
+
46
+ # Set the default configuration to send and receive emails. The defaults
47
+ # are global, allowing you to just call them once and use them everywhere.
48
+ # if port values are omitted from the SMTP and POP3 method calls, then it
49
+ # is assumed to use the default ports of 25 and 110 respectively.
50
+ #
51
+ # The methods you can call in the default configuration are:
52
+ #
53
+ # smtp(server_name, port):: Sets the SMTP server domain name and port
54
+ # pop3(server_name, port):: Sets the POP3 server domain name and port
55
+ # user(username):: Sets the username used for POP3 sessions
56
+ # pass(password):: Sets the password used for POP3 sessions
57
+ #
58
+ # Mail.defaults do
59
+ # smtp 'smtp.myhost.fr', 587
60
+ # pop3 'pop.myhost.fr'
61
+ # user 'bernardo'
62
+ # pass 'mypass'
63
+ # enable_tls
64
+ # end
65
+ #
66
+ # Once you have set defaults, you can call Mail.deliver to send an email
67
+ # through the Mail.deliver method.
68
+ def Mail.defaults(&block)
69
+ if block_given?
70
+ Mail::Configuration.instance.defaults(&block)
71
+ end
72
+ end
73
+
74
+ # Send an email using the default configuration. You do need to set a default
75
+ # configuration first before you use Mail.deliver, if you don't, an appropriate
76
+ # error will be raised telling you to.
77
+ #
78
+ # Mail.deliver do
79
+ # to 'mikel@test.lindsaar.net'
80
+ # from 'ada@test.lindsaar.net'
81
+ # subject 'This is a test email'
82
+ # body 'Not much to say here'
83
+ # end
84
+ #
85
+ # And your email object will be created and sent.
86
+
87
+ def Mail.deliver(*args, &block)
88
+ Mail.new(args, &block).deliver!
89
+ end
90
+
91
+ def Mail.get_all_mail(&block)
92
+ Mail::Message.get_all_mail(&block)
93
+ end
94
+
95
+ def Mail.read(filename)
96
+ Mail.new(File.read(filename))
97
+ end
98
+
99
+ protected
100
+
101
+ def Mail.random_tag
102
+ t = Time.now
103
+ sprintf('%x%x_%x%x%d%x',
104
+ t.to_i, t.tv_usec,
105
+ $$, Thread.current.object_id, Mail.uniq, rand(255))
106
+ end
107
+
108
+ private
109
+
110
+ def Mail.something_random
111
+ (Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
112
+ end
113
+
114
+ def Mail.uniq
115
+ @@uniq += 1
116
+ end
117
+
118
+ @@uniq = Mail.something_random
119
+
120
+ end
@@ -0,0 +1,648 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ # The Message class provides a single point of access to all things to do with an
4
+ # email message.
5
+ #
6
+ # You create a new email message by calling the Mail::Message.new method, or just
7
+ # Mail.new
8
+ #
9
+ # A Message object by default has the following objects inside it:
10
+ #
11
+ # * A Header object which contians all information and settings of the header of the email
12
+ # * Body object which contains all parts of the email that are not part of the header, this
13
+ # includes any attachments, body text, mime parts etc.
14
+ #
15
+ # ==Per RFC2822
16
+ #
17
+ # 2.1. General Description
18
+ #
19
+ # At the most basic level, a message is a series of characters. A
20
+ # message that is conformant with this standard is comprised of
21
+ # characters with values in the range 1 through 127 and interpreted as
22
+ # US-ASCII characters [ASCII]. For brevity, this document sometimes
23
+ # refers to this range of characters as simply "US-ASCII characters".
24
+ #
25
+ # Note: This standard specifies that messages are made up of characters
26
+ # in the US-ASCII range of 1 through 127. There are other documents,
27
+ # specifically the MIME document series [RFC2045, RFC2046, RFC2047,
28
+ # RFC2048, RFC2049], that extend this standard to allow for values
29
+ # outside of that range. Discussion of those mechanisms is not within
30
+ # the scope of this standard.
31
+ #
32
+ # Messages are divided into lines of characters. A line is a series of
33
+ # characters that is delimited with the two characters carriage-return
34
+ # and line-feed; that is, the carriage return (CR) character (ASCII
35
+ # value 13) followed immediately by the line feed (LF) character (ASCII
36
+ # value 10). (The carriage-return/line-feed pair is usually written in
37
+ # this document as "CRLF".)
38
+ #
39
+ # A message consists of header fields (collectively called "the header
40
+ # of the message") followed, optionally, by a body. The header is a
41
+ # sequence of lines of characters with special syntax as defined in
42
+ # this standard. The body is simply a sequence of characters that
43
+ # follows the header and is separated from the header by an empty line
44
+ # (i.e., a line with nothing preceding the CRLF).
45
+ class Message
46
+
47
+ include Patterns
48
+ include Utilities
49
+ include Deliverable
50
+ include Retrievable
51
+
52
+ # Creates a new Mail::Message object through .new
53
+ def initialize(*args, &block)
54
+ self.raw_source = args.flatten[0].to_s.strip
55
+ set_envelope_header
56
+ parse_message
57
+ separate_parts if multipart?
58
+ if block_given?
59
+ instance_eval(&block)
60
+ end
61
+ self
62
+ end
63
+
64
+ # Provides access to the raw source of the message as it was when it
65
+ # was instantiated. This is set at initialization and so is untouched
66
+ # by the parsers or decoder / encoders
67
+ #
68
+ # Example:
69
+ #
70
+ # mail = Mail.new('This is an invalid email message')
71
+ # mail.raw_source #=> "This is an invalid email message"
72
+ def raw_source
73
+ @raw_source
74
+ end
75
+
76
+ def raw_source=(value)
77
+ @raw_source = value.to_crlf
78
+ end
79
+
80
+ def set_envelope( val )
81
+ @raw_envelope = val
82
+ @envelope = Mail::Envelope.new( val )
83
+ end
84
+
85
+ def set_envelope( val )
86
+ @raw_envelope = val
87
+ @envelope = Mail::Envelope.new( val )
88
+ end
89
+
90
+ # The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009
91
+ # type field that you can see at the top of any email that has come
92
+ # from a mailbox
93
+ def raw_envelope
94
+ @raw_envelope
95
+ end
96
+
97
+ def envelope_from
98
+ @envelope ? @envelope.from : nil
99
+ end
100
+
101
+ def envelope_date
102
+ @envelope ? @envelope.date : nil
103
+ end
104
+
105
+ # Sets the header of the message object.
106
+ #
107
+ # Example:
108
+ #
109
+ # mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
110
+ # mail.header #=> <#Mail::Header
111
+ def header=(value)
112
+ @header = Mail::Header.new(value)
113
+ end
114
+
115
+ # Returns the header object of the message object. Or, if passed
116
+ # a parameter sets the value.
117
+ #
118
+ # Example:
119
+ #
120
+ # mail = Mail::Message.new('To: mikel\r\nFrom: you')
121
+ # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
122
+ #
123
+ # mail.header #=> nil
124
+ # mail.header 'To: mikel\r\nFrom: you'
125
+ # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
126
+ def header(value = nil)
127
+ value ? self.header = value : @header
128
+ end
129
+
130
+ # Sets the body object of the message object.
131
+ #
132
+ # Example:
133
+ #
134
+ # mail.body = 'This is the body'
135
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
136
+ def body=(value)
137
+ @body = Mail::Body.new(value)
138
+ end
139
+
140
+ # Returns the body of the message object. Or, if passed
141
+ # a parameter sets the value.
142
+ #
143
+ # Example:
144
+ #
145
+ # mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body')
146
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
147
+ #
148
+ # mail.body 'This is another body'
149
+ # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
150
+ def body(value = nil)
151
+ value ? self.body = value : @body
152
+ end
153
+
154
+ # Sets the to filed of the message header.
155
+ #
156
+ # Example:
157
+ #
158
+ # mail.to = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
159
+ # mail.to #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
160
+ def to=(value)
161
+ header[:to] = value
162
+ end
163
+
164
+ # Returns the to field in the message header. Or, if passed
165
+ # a parameter sets the value.
166
+ #
167
+ # Example:
168
+ #
169
+ # mail.to = '"M L" <mikel@test.lindsaar.net>'
170
+ # mail.to.to_s #=> '"M L" <mikel@test.lindsaar.net>'
171
+ # mail.to.formatted #=> ['"M L" <mikel@test.lindsaar.net>']
172
+ # mail.to.addresses #=> ['mikel@test.lindsaar.net']
173
+ def to(value = nil)
174
+ value ? self.to = value : header[:to]
175
+ end
176
+
177
+ # Sets the from field in the message header.
178
+ #
179
+ # Example:
180
+ #
181
+ # mail.from = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
182
+ # mail.from.to_s #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
183
+ def from=(value)
184
+ header[:from] = value
185
+ end
186
+
187
+ # Returns the from field in the message header. Or, if passed
188
+ # a parameter sets the value.
189
+ #
190
+ # Example:
191
+ #
192
+ # mail.from = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
193
+ # mail.from.to_s #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
194
+ #
195
+ # mail.from '"M L" <mikel@test.lindsaar.net>'
196
+ # mail.from.to_s #=> '"M L" <mikel@test.lindsaar.net>'
197
+ # mail.from.formatted #=> ['"M L" <mikel@test.lindsaar.net>']
198
+ # mail.from.addresses #=> ['mikel@test.lindsaar.net']
199
+ def from(value = nil)
200
+ value ? self.from = value : header[:from]
201
+ end
202
+
203
+ # Sets the subject field in the message header.
204
+ #
205
+ # Example:
206
+ #
207
+ # mail.subject = 'This is the subject'
208
+ # mail.subject.to_s #=> 'This is the subject'
209
+ def subject=(value)
210
+ header[:subject] = value
211
+ end
212
+
213
+ # Returns the subject field in the message header. Or, if passed
214
+ # a parameter sets the value.
215
+ #
216
+ # Example:
217
+ #
218
+ # mail.subject = 'This is the subject'
219
+ # mail.subject.to_s #=> 'This is the subject'
220
+ #
221
+ # mail.subject 'This is another subject'
222
+ # mail.subject.to_s #=> 'This is another subject'
223
+ def subject(value = nil)
224
+ value ? self.subject = value : header[:subject]
225
+ end
226
+
227
+ # Allows you to add an arbitrary header
228
+ #
229
+ # Example:
230
+ #
231
+ # mail['foo'] = '1234'
232
+ # mail['foo'].to_s #=> '1234'
233
+ def []=(name, value)
234
+ if name.to_s == 'body'
235
+ self.body = value
236
+ else
237
+ header[underscoreize(name)] = value
238
+ end
239
+ end
240
+
241
+ # Allows you to read an arbitrary header
242
+ #
243
+ # Example:
244
+ #
245
+ # mail['foo'] = '1234'
246
+ # mail['foo'].to_s #=> '1234'
247
+ def [](name)
248
+ header[underscoreize(name)]
249
+ end
250
+
251
+ # Method Missing in this implementation allows you to set any of the
252
+ # standard fields directly as you would the "to", "subject" etc.
253
+ #
254
+ # Those fields used most often (to, subject et al) are given their
255
+ # own method for ease of documentation and also to avoid the hook
256
+ # call to method missing.
257
+ #
258
+ # This will only catch the known fields listed in:
259
+ #
260
+ # Mail::Field::KNOWN_FIELDS
261
+ #
262
+ # as per RFC 2822, any ruby string or method name could pretty much
263
+ # be a field name, so we don't want to just catch ANYTHING sent to
264
+ # a message object and interpret it as a header.
265
+ #
266
+ # This method provides all three types of header call to set, read
267
+ # and explicitly set with the = operator
268
+ #
269
+ # Examples:
270
+ #
271
+ # mail.comments = 'These are some comments'
272
+ # mail.comments.to_s #=> 'These are some comments'
273
+ #
274
+ # mail.comments 'These are other comments'
275
+ # mail.comments.to_s #=> 'These are other comments'
276
+ #
277
+ #
278
+ # mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200'
279
+ # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
280
+ #
281
+ # mail.date 'Tue, 1 Jul 2003 10:52:37 +0200'
282
+ # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
283
+ #
284
+ #
285
+ # mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>'
286
+ # mail.resent_msg_id.to_s #=> '<1234@resent_msg_id.lindsaar.net>'
287
+ #
288
+ # mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>'
289
+ # mail.resent_msg_id.to_s #=> '<4567@resent_msg_id.lindsaar.net>'
290
+ def method_missing(name, *args, &block)
291
+ #:nodoc:
292
+ # Only take the structured fields, as we could take _anything_ really
293
+ # as it could become an optional field... "but therin lies the dark side"
294
+ field_name = underscoreize(name).chomp("=")
295
+ if Mail::Field::KNOWN_FIELDS.include?(field_name)
296
+ if args.empty?
297
+ header[field_name]
298
+ else
299
+ header[field_name] = args.first
300
+ end
301
+ else
302
+ super # otherwise pass it on
303
+ end
304
+ #:startdoc:
305
+ end
306
+
307
+ # Returns an FieldList of all the fields in the header in the order that
308
+ # they appear in the header
309
+ def header_fields
310
+ header.fields
311
+ end
312
+
313
+ # Returns true if the message has a message ID field, the field may or may
314
+ # not have a value, but the field exists or not.
315
+ def has_message_id?
316
+ header.has_message_id?
317
+ end
318
+
319
+ # Returns true if the message has a Date field, the field may or may
320
+ # not have a value, but the field exists or not.
321
+ def has_date?
322
+ header.has_date?
323
+ end
324
+
325
+ # Returns true if the message has a Date field, the field may or may
326
+ # not have a value, but the field exists or not.
327
+ def has_mime_version?
328
+ header.has_mime_version?
329
+ end
330
+
331
+ def has_content_type?
332
+ !!content_type
333
+ end
334
+
335
+ def has_charset?
336
+ !!charset
337
+ end
338
+
339
+ def has_transfer_encoding?
340
+ transfer_encoding
341
+ end
342
+
343
+ # Creates a new empty Message-ID field and inserts it in the correct order
344
+ # into the Header. The MessageIdField object will automatically generate
345
+ # a unique message ID if you try and encode it or output it to_s without
346
+ # specifying a message id.
347
+ #
348
+ # It will preserve the message ID you specify if you do.
349
+ def add_message_id(msg_id_val = '')
350
+ header['message-id'] = msg_id_val
351
+ end
352
+
353
+ # Creates a new empty Date field and inserts it in the correct order
354
+ # into the Header. The DateField object will automatically generate
355
+ # DateTime.now's date if you try and encode it or output it to_s without
356
+ # specifying a date yourself.
357
+ #
358
+ # It will preserve any date you specify if you do.
359
+ def add_date(date_val = '')
360
+ header['date'] = date_val
361
+ end
362
+
363
+ # Creates a new empty Mime Version field and inserts it in the correct order
364
+ # into the Header. The MimeVersion object will automatically generate
365
+ # DateTime.now's date if you try and encode it or output it to_s without
366
+ # specifying a date yourself.
367
+ #
368
+ # It will preserve any date you specify if you do.
369
+ def add_mime_version(ver_val = '')
370
+ header['mime-version'] = ver_val
371
+ end
372
+
373
+ # Adds a content type and charset if the body is US-ASCII
374
+ #
375
+ # Otherwise raises a warning
376
+ def add_content_type
377
+ header['Content-Type'] = 'text/plain'
378
+ end
379
+
380
+ # Adds a content type and charset if the body is US-ASCII
381
+ #
382
+ # Otherwise raises a warning
383
+ def add_charset
384
+ if body.only_us_ascii?
385
+ content_type.parameters['charset'] = 'US-ASCII'
386
+ else
387
+ STDERR.puts("Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.")
388
+ content_type.parameters['charset'] = 'UTF-8'
389
+ end
390
+ end
391
+
392
+ # Adds a transfer encoding
393
+ #
394
+ # Otherwise raises a warning
395
+ def add_transfer_encoding
396
+ if body.only_us_ascii?
397
+ header['Content-Transfer-Encoding'] = '7bit'
398
+ else
399
+ STDERR.puts("Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.")
400
+ header['Content-Transfer-Encoding'] = '8bit'
401
+ end
402
+ end
403
+
404
+ # Returns the content transfer encoding of the email
405
+ def transfer_encoding
406
+ content_transfer_encoding
407
+ end
408
+
409
+ # Returns the content type
410
+ def message_content_type
411
+ content_type ? content_type.content_type : nil
412
+ end
413
+
414
+ # Returns the character set defined in the content type field
415
+ def charset
416
+ content_type ? content_type.parameters['charset'] : nil
417
+ end
418
+
419
+ # Returns the main content type
420
+ def main_type
421
+ has_content_type? ? content_type.main_type : nil
422
+ end
423
+
424
+ # Returns the sub content type
425
+ def sub_type
426
+ has_content_type? ? content_type.sub_type : nil
427
+ end
428
+
429
+ # Returns the content type parameters
430
+ def mime_parameters
431
+ has_content_type? ? content_type.parameters : nil
432
+ end
433
+
434
+ # Returns true if the message is multipart
435
+ def multipart?
436
+ !!(main_type =~ /^multipart$/i)
437
+ end
438
+
439
+ # Returns true if the message is a multipart/report
440
+ def multipart_report?
441
+ multipart? && sub_type =~ /^report$/i
442
+ end
443
+
444
+ # Returns true if the message is a multipart/report; report-type=delivery-status;
445
+ def delivery_status_report?
446
+ multipart_report? && mime_parameters['report-type'] =~ /^delivery-status$/i
447
+ end
448
+
449
+ # returns the part in a multipart/report email that has the content-type delivery-status
450
+ def delivery_status_part
451
+ @delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
452
+ end
453
+
454
+ def bounced?
455
+ delivery_status_part.bounced?
456
+ end
457
+
458
+ def action
459
+ delivery_status_part.action
460
+ end
461
+
462
+ def final_recipient
463
+ delivery_status_part.final_recipient
464
+ end
465
+
466
+ def error_status
467
+ delivery_status_part.error_status
468
+ end
469
+
470
+ def diagnostic_code
471
+ delivery_status_part.diagnostic_code
472
+ end
473
+
474
+ def remote_mta
475
+ delivery_status_part.remote_mta
476
+ end
477
+
478
+ def retryable?
479
+ delivery_status_part.retryable?
480
+ end
481
+
482
+ # Returns the current boundary for this message part
483
+ def boundary
484
+ mime_parameters ? mime_parameters['boundary'] : nil
485
+ end
486
+
487
+ # Returns an array of parts in the message
488
+ def parts
489
+ body.parts
490
+ end
491
+
492
+ def attachments
493
+ body.parts.select { |p| p.attachment? }.map { |p| p.attachment }
494
+ end
495
+
496
+ # Accessor for html_part
497
+ def html_part(&block)
498
+ if block_given?
499
+ @html_part = Mail::Part.new(&block)
500
+ add_part(@html_part)
501
+ else
502
+ @html_part
503
+ end
504
+ end
505
+
506
+ # Accessor for text_part
507
+ def text_part(&block)
508
+ if block_given?
509
+ @text_part = Mail::Part.new(&block)
510
+ add_part(@text_part)
511
+ else
512
+ @text_part
513
+ end
514
+ end
515
+
516
+ # Helper to add a html part to a multipart/alternative email. If this and
517
+ # text_part are both defined in a message, then it will be a multipart/alternative
518
+ # message and set itself that way.
519
+ def html_part=(msg = nil)
520
+ if msg
521
+ @html_part = msg
522
+ else
523
+ @html_part = Mail::Part.new('Content-Type: text/html;')
524
+ end
525
+ add_part(@html_part)
526
+ end
527
+
528
+ # Helper to add a text part to a multipart/alternative email. If this and
529
+ # html_part are both defined in a message, then it will be a multipart/alternative
530
+ # message and set itself that way.
531
+ def text_part=(msg = nil)
532
+ if msg
533
+ @text_part = msg
534
+ else
535
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
536
+ end
537
+ add_part(@text_part)
538
+ end
539
+
540
+ # Adds a part to the parts list or creates the part list
541
+ def add_part(part)
542
+ add_multipart_alternate_header
543
+ self.body << part
544
+ end
545
+
546
+ # Adds a part to the parts list or creates the part list
547
+ def add_file(options)
548
+ add_multipart_mixed_header
549
+ if options.is_a?(Hash)
550
+ self.body << Mail::Part.new(options)
551
+ else
552
+ self.body << Mail::Part.new(:filename => options)
553
+ end
554
+ end
555
+
556
+ # Encodes the message, calls encode on all it's parts, gets an email message
557
+ # ready to send
558
+ def encode!
559
+ parts.each { |part| part.encode! }
560
+ add_required_fields
561
+ end
562
+
563
+ # Decodes the message, calls decode on all it's parts, gets an email message
564
+ # ready to send
565
+ def decode!
566
+ parts.each { |part| part.decode! }
567
+ add_required_fields
568
+ end
569
+
570
+ # Outputs an encoded string representation of the mail message including
571
+ # all headers, attachments, etc. This is an encoded email in US-ASCII,
572
+ # so it is able to be directly sent to an email server.
573
+ def encoded
574
+ encode!
575
+ buffer = header.encoded
576
+ buffer << "\r\n"
577
+ buffer << body.encoded
578
+ buffer
579
+ end
580
+
581
+ alias :to_s :encoded
582
+
583
+ private
584
+
585
+ # 2.1. General Description
586
+ # A message consists of header fields (collectively called "the header
587
+ # of the message") followed, optionally, by a body. The header is a
588
+ # sequence of lines of characters with special syntax as defined in
589
+ # this standard. The body is simply a sequence of characters that
590
+ # follows the header and is separated from the header by an empty line
591
+ # (i.e., a line with nothing preceding the CRLF).
592
+ #
593
+ # Additionally, I allow for the case where someone might have put whitespace
594
+ # on the "gap line"
595
+ def parse_message
596
+ header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
597
+ self.header = header_part
598
+ self.body = body_part
599
+ end
600
+
601
+ def set_envelope_header
602
+ if match_data = raw_source.to_s.match(/From\s(#{TEXT}+)#{CRLF}(.*)/m)
603
+ set_envelope(match_data[1])
604
+ self.raw_source = match_data[2]
605
+ end
606
+ end
607
+
608
+ def separate_parts
609
+ body.split!(boundary)
610
+ end
611
+
612
+ def add_required_fields
613
+ @body = Mail::Body.new('') if body.nil?
614
+ add_message_id unless (has_message_id? || self.class == Mail::Part)
615
+ add_date unless has_date?
616
+ add_mime_version unless has_mime_version?
617
+ add_content_type unless has_content_type?
618
+ add_charset unless has_charset?
619
+ add_transfer_encoding unless has_transfer_encoding?
620
+ end
621
+
622
+ def add_multipart_alternate_header
623
+ if html_part && text_part
624
+ unless header['content-type']
625
+ header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
626
+ body.boundary = boundary
627
+ end
628
+ end
629
+ end
630
+
631
+ def add_multipart_mixed_header
632
+ unless header['content-type']
633
+ header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
634
+ body.boundary = boundary
635
+ end
636
+ end
637
+
638
+ class << self
639
+
640
+ # Only POP3 is supported for now
641
+ def get_all_mail(&block)
642
+ self.pop3_get_all_mail(&block)
643
+ end
644
+
645
+ end
646
+
647
+ end
648
+ end