tmail_es 1.2.7.2
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.
- checksums.yaml +7 -0
- data/CHANGES +83 -0
- data/LICENSE +21 -0
- data/NOTES +7 -0
- data/README +182 -0
- data/Rakefile +2 -0
- data/ext/Makefile +20 -0
- data/ext/tmailscanner/tmail/MANIFEST +4 -0
- data/ext/tmailscanner/tmail/depend +1 -0
- data/ext/tmailscanner/tmail/extconf.rb +33 -0
- data/ext/tmailscanner/tmail/tmailscanner.c +614 -0
- data/lib/tmail/Makefile +18 -0
- data/lib/tmail/address.rb +392 -0
- data/lib/tmail/attachments.rb +65 -0
- data/lib/tmail/base64.rb +46 -0
- data/lib/tmail/compat.rb +41 -0
- data/lib/tmail/config.rb +67 -0
- data/lib/tmail/core_extensions.rb +63 -0
- data/lib/tmail/encode.rb +590 -0
- data/lib/tmail/header.rb +962 -0
- data/lib/tmail/index.rb +9 -0
- data/lib/tmail/interface.rb +1162 -0
- data/lib/tmail/loader.rb +3 -0
- data/lib/tmail/mail.rb +578 -0
- data/lib/tmail/mailbox.rb +496 -0
- data/lib/tmail/main.rb +6 -0
- data/lib/tmail/mbox.rb +3 -0
- data/lib/tmail/net.rb +250 -0
- data/lib/tmail/obsolete.rb +132 -0
- data/lib/tmail/parser.rb +1060 -0
- data/lib/tmail/parser.y +416 -0
- data/lib/tmail/port.rb +379 -0
- data/lib/tmail/quoting.rb +164 -0
- data/lib/tmail/require_arch.rb +58 -0
- data/lib/tmail/scanner.rb +49 -0
- data/lib/tmail/scanner_r.rb +261 -0
- data/lib/tmail/stringio.rb +280 -0
- data/lib/tmail/utils.rb +361 -0
- data/lib/tmail/vendor/rchardet-1.3/COPYING +504 -0
- data/lib/tmail/vendor/rchardet-1.3/README +12 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/big5freq.rb +927 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/big5prober.rb +42 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/chardistribution.rb +238 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/charsetgroupprober.rb +112 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/charsetprober.rb +75 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/codingstatemachine.rb +64 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/constants.rb +42 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/escprober.rb +89 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/escsm.rb +244 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/eucjpprober.rb +88 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/euckrfreq.rb +596 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/euckrprober.rb +42 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/euctwfreq.rb +430 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/euctwprober.rb +42 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/gb2312freq.rb +474 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/gb2312prober.rb +42 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/hebrewprober.rb +289 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/jisfreq.rb +570 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/jpcntx.rb +229 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langbulgarianmodel.rb +229 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langcyrillicmodel.rb +330 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langgreekmodel.rb +227 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langhebrewmodel.rb +202 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langhungarianmodel.rb +226 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/langthaimodel.rb +201 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/latin1prober.rb +147 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/mbcharsetprober.rb +89 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/mbcsgroupprober.rb +45 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/mbcssm.rb +542 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/sbcharsetprober.rb +124 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/sbcsgroupprober.rb +56 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/sjisprober.rb +88 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/universaldetector.rb +167 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet/utf8prober.rb +87 -0
- data/lib/tmail/vendor/rchardet-1.3/lib/rchardet.rb +67 -0
- data/lib/tmail/version.rb +40 -0
- data/lib/tmail.rb +6 -0
- data/setup.rb +1482 -0
- data/test/extctrl.rb +6 -0
- data/test/fixtures/apple_unquoted_content_type +44 -0
- data/test/fixtures/inline_attachment.txt +2095 -0
- data/test/fixtures/iso_8859_1_email_without_encoding_and_message_id.txt +16 -0
- data/test/fixtures/mailbox +414 -0
- data/test/fixtures/mailbox.zip +0 -0
- data/test/fixtures/mailbox_without_any_from_or_sender +10 -0
- data/test/fixtures/mailbox_without_from +11 -0
- data/test/fixtures/mailbox_without_return_path +12 -0
- data/test/fixtures/marked_as_iso_8859_1_but_it_is_utf_8.txt +33 -0
- data/test/fixtures/marked_as_utf_8_but_it_is_iso_8859_1.txt +56 -0
- data/test/fixtures/raw_attack_email_with_zero_length_whitespace +29 -0
- data/test/fixtures/raw_base64_decoded_string +0 -0
- data/test/fixtures/raw_base64_email +83 -0
- data/test/fixtures/raw_base64_encoded_string +1 -0
- data/test/fixtures/raw_email +14 -0
- data/test/fixtures/raw_email10 +20 -0
- data/test/fixtures/raw_email11 +34 -0
- data/test/fixtures/raw_email12 +32 -0
- data/test/fixtures/raw_email13 +29 -0
- data/test/fixtures/raw_email2 +114 -0
- data/test/fixtures/raw_email3 +70 -0
- data/test/fixtures/raw_email4 +59 -0
- data/test/fixtures/raw_email5 +19 -0
- data/test/fixtures/raw_email6 +20 -0
- data/test/fixtures/raw_email7 +66 -0
- data/test/fixtures/raw_email8 +47 -0
- data/test/fixtures/raw_email9 +28 -0
- data/test/fixtures/raw_email_bad_time +62 -0
- data/test/fixtures/raw_email_double_at_in_header +14 -0
- data/test/fixtures/raw_email_multiple_from +30 -0
- data/test/fixtures/raw_email_only_attachment +17 -0
- data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
- data/test/fixtures/raw_email_reply +32 -0
- data/test/fixtures/raw_email_simple +11 -0
- data/test/fixtures/raw_email_string_in_date_field +17 -0
- data/test/fixtures/raw_email_trailing_dot +21 -0
- data/test/fixtures/raw_email_with_bad_date +48 -0
- data/test/fixtures/raw_email_with_illegal_boundary +58 -0
- data/test/fixtures/raw_email_with_mimepart_without_content_type +94 -0
- data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
- data/test/fixtures/raw_email_with_nested_attachment +100 -0
- data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
- data/test/fixtures/raw_email_with_quoted_attachment_filename +60 -0
- data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
- data/test/fixtures/raw_email_with_wrong_splitted_multibyte_encoded_word_subject +15 -0
- data/test/fixtures/the_only_part_is_a_word_document.txt +425 -0
- data/test/fixtures/unquoted_filename_in_attachment +177 -0
- data/test/kcode.rb +14 -0
- data/test/temp_test_one.rb +46 -0
- data/test/test_address.rb +1216 -0
- data/test/test_attachments.rb +133 -0
- data/test/test_base64.rb +64 -0
- data/test/test_encode.rb +139 -0
- data/test/test_header.rb +1021 -0
- data/test/test_helper.rb +9 -0
- data/test/test_mail.rb +756 -0
- data/test/test_mbox.rb +184 -0
- data/test/test_port.rb +440 -0
- data/test/test_quote.rb +107 -0
- data/test/test_scanner.rb +209 -0
- data/test/test_utils.rb +36 -0
- data/tmail_es.gemspec +35 -0
- metadata +257 -0
data/lib/tmail/loader.rb
ADDED
data/lib/tmail/mail.rb
ADDED
@@ -0,0 +1,578 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
= Mail class
|
4
|
+
|
5
|
+
=end
|
6
|
+
#--
|
7
|
+
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
#
|
28
|
+
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
29
|
+
# with permission of Minero Aoki.
|
30
|
+
#++
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
require 'tmail/interface'
|
35
|
+
require 'tmail/encode'
|
36
|
+
require 'tmail/header'
|
37
|
+
require 'tmail/port'
|
38
|
+
require 'tmail/config'
|
39
|
+
require 'tmail/utils'
|
40
|
+
require 'tmail/attachments'
|
41
|
+
require 'tmail/quoting'
|
42
|
+
require 'socket'
|
43
|
+
|
44
|
+
module TMail
|
45
|
+
|
46
|
+
# == Mail Class
|
47
|
+
#
|
48
|
+
# Accessing a TMail object done via the TMail::Mail class. As email can be fairly complex
|
49
|
+
# creatures, you will find a large amount of accessor and setter methods in this class!
|
50
|
+
#
|
51
|
+
# Most of the below methods handle the header, in fact, what TMail does best is handle the
|
52
|
+
# header of the email object. There are only a few methods that deal directly with the body
|
53
|
+
# of the email, such as base64_encode and base64_decode.
|
54
|
+
#
|
55
|
+
# === Using TMail inside your code
|
56
|
+
#
|
57
|
+
# The usual way is to install the gem (see the {README}[link:/README] on how to do this) and
|
58
|
+
# then put at the top of your class:
|
59
|
+
#
|
60
|
+
# require 'tmail'
|
61
|
+
#
|
62
|
+
# You can then create a new TMail object in your code with:
|
63
|
+
#
|
64
|
+
# @email = TMail::Mail.new
|
65
|
+
#
|
66
|
+
# Or if you have an email as a string, you can initialize a new TMail::Mail object and get it
|
67
|
+
# to parse that string for you like so:
|
68
|
+
#
|
69
|
+
# @email = TMail::Mail.parse(email_text)
|
70
|
+
#
|
71
|
+
# You can also read a single email off the disk, for example:
|
72
|
+
#
|
73
|
+
# @email = TMail::Mail.load('filename.txt')
|
74
|
+
#
|
75
|
+
# Also, you can read a mailbox (usual unix mbox format) and end up with an array of TMail
|
76
|
+
# objects by doing something like this:
|
77
|
+
#
|
78
|
+
# # Note, we pass true as the last variable to open the mailbox read only
|
79
|
+
# mailbox = TMail::UNIXMbox.new("mailbox", nil, true)
|
80
|
+
# @emails = []
|
81
|
+
# mailbox.each_port { |m| @emails << TMail::Mail.new(m) }
|
82
|
+
#
|
83
|
+
class Mail
|
84
|
+
|
85
|
+
class << self
|
86
|
+
|
87
|
+
# Opens an email that has been saved out as a file by itself.
|
88
|
+
#
|
89
|
+
# This function will read a file non-destructively and then parse
|
90
|
+
# the contents and return a TMail::Mail object.
|
91
|
+
#
|
92
|
+
# Does not handle multiple email mailboxes (like a unix mbox) for that
|
93
|
+
# use the TMail::UNIXMbox class.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
# mail = TMail::Mail.load('filename')
|
97
|
+
#
|
98
|
+
def load( fname )
|
99
|
+
new(FilePort.new(fname))
|
100
|
+
end
|
101
|
+
|
102
|
+
alias load_from load
|
103
|
+
alias loadfrom load
|
104
|
+
|
105
|
+
# Parses an email from the supplied string and returns a TMail::Mail
|
106
|
+
# object.
|
107
|
+
#
|
108
|
+
# Example:
|
109
|
+
# require 'rubygems'; require 'tmail'
|
110
|
+
# email_string =<<HEREDOC
|
111
|
+
# To: mikel@lindsaar.net
|
112
|
+
# From: mikel@me.com
|
113
|
+
# Subject: This is a short Email
|
114
|
+
#
|
115
|
+
# Hello there Mikel!
|
116
|
+
#
|
117
|
+
# HEREDOC
|
118
|
+
# mail = TMail::Mail.parse(email_string)
|
119
|
+
# #=> #<TMail::Mail port=#<TMail::StringPort:id=0xa30ac0> bodyport=nil>
|
120
|
+
# mail.body
|
121
|
+
# #=> "Hello there Mikel!\n\n"
|
122
|
+
def parse( str )
|
123
|
+
new(StringPort.new(str))
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
def initialize( port = nil, conf = DEFAULT_CONFIG ) #:nodoc:
|
129
|
+
@port = port || StringPort.new
|
130
|
+
@config = Config.to_config(conf)
|
131
|
+
|
132
|
+
@header = {}
|
133
|
+
@body_port = nil
|
134
|
+
@body_parsed = false
|
135
|
+
@epilogue = ''
|
136
|
+
@parts = []
|
137
|
+
|
138
|
+
@port.ropen {|f|
|
139
|
+
parse_header f
|
140
|
+
parse_body f unless @port.reproducible?
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
# Provides access to the port this email is using to hold it's data
|
145
|
+
#
|
146
|
+
# Example:
|
147
|
+
# mail = TMail::Mail.parse(email_string)
|
148
|
+
# mail.port
|
149
|
+
# #=> #<TMail::StringPort:id=0xa2c952>
|
150
|
+
attr_reader :port
|
151
|
+
|
152
|
+
def inspect
|
153
|
+
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# to_s interfaces
|
158
|
+
#
|
159
|
+
|
160
|
+
public
|
161
|
+
|
162
|
+
include StrategyInterface
|
163
|
+
|
164
|
+
def write_back( eol = "\n", charset = 'e' )
|
165
|
+
parse_body
|
166
|
+
@port.wopen {|stream| encoded eol, charset, stream }
|
167
|
+
end
|
168
|
+
|
169
|
+
def accept( strategy )
|
170
|
+
with_multipart_encoding(strategy) {
|
171
|
+
ordered_each do |name, field|
|
172
|
+
next if field.empty?
|
173
|
+
strategy.header_name canonical(name)
|
174
|
+
field.accept strategy
|
175
|
+
strategy.puts
|
176
|
+
end
|
177
|
+
strategy.puts
|
178
|
+
body_port().ropen {|r|
|
179
|
+
strategy.write r.read
|
180
|
+
}
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def canonical( name )
|
187
|
+
name.split(/-/).map {|s| s.capitalize }.join('-')
|
188
|
+
end
|
189
|
+
|
190
|
+
def with_multipart_encoding( strategy )
|
191
|
+
if parts().empty? # DO NOT USE @parts
|
192
|
+
yield
|
193
|
+
|
194
|
+
else
|
195
|
+
bound = ::TMail.new_boundary
|
196
|
+
if @header.key? 'content-type'
|
197
|
+
@header['content-type'].params['boundary'] = bound
|
198
|
+
else
|
199
|
+
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
|
200
|
+
end
|
201
|
+
|
202
|
+
yield
|
203
|
+
|
204
|
+
parts().each do |tm|
|
205
|
+
strategy.puts
|
206
|
+
strategy.puts '--' + bound
|
207
|
+
tm.accept strategy
|
208
|
+
end
|
209
|
+
strategy.puts
|
210
|
+
strategy.puts '--' + bound + '--'
|
211
|
+
strategy.write epilogue()
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
###
|
216
|
+
### header
|
217
|
+
###
|
218
|
+
|
219
|
+
public
|
220
|
+
|
221
|
+
ALLOW_MULTIPLE = {
|
222
|
+
'received' => true,
|
223
|
+
'resent-date' => true,
|
224
|
+
'resent-from' => true,
|
225
|
+
'resent-sender' => true,
|
226
|
+
'resent-to' => true,
|
227
|
+
'resent-cc' => true,
|
228
|
+
'resent-bcc' => true,
|
229
|
+
'resent-message-id' => true,
|
230
|
+
'comments' => true,
|
231
|
+
'keywords' => true
|
232
|
+
}
|
233
|
+
USE_ARRAY = ALLOW_MULTIPLE
|
234
|
+
|
235
|
+
def header
|
236
|
+
@header.dup
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns a TMail::AddressHeader object of the field you are querying.
|
240
|
+
# Examples:
|
241
|
+
# @mail['from'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
242
|
+
# @mail['to'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
243
|
+
#
|
244
|
+
# You can get the string value of this by passing "to_s" to the query:
|
245
|
+
# Example:
|
246
|
+
# @mail['to'].to_s #=> "mikel@test.com.au"
|
247
|
+
def []( key )
|
248
|
+
@header[key.downcase]
|
249
|
+
end
|
250
|
+
|
251
|
+
def sub_header(key, param)
|
252
|
+
(hdr = self[key]) ? hdr[param] : nil
|
253
|
+
end
|
254
|
+
|
255
|
+
alias fetch []
|
256
|
+
|
257
|
+
# Allows you to set or delete TMail header objects at will.
|
258
|
+
# Examples:
|
259
|
+
# @mail = TMail::Mail.new
|
260
|
+
# @mail['to'].to_s # => 'mikel@test.com.au'
|
261
|
+
# @mail['to'] = 'mikel@elsewhere.org'
|
262
|
+
# @mail['to'].to_s # => 'mikel@elsewhere.org'
|
263
|
+
# @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n"
|
264
|
+
# @mail['to'] = nil
|
265
|
+
# @mail['to'].to_s # => nil
|
266
|
+
# @mail.encoded # => "\r\n"
|
267
|
+
#
|
268
|
+
# Note: setting mail[] = nil actually deletes the header field in question from the object,
|
269
|
+
# it does not just set the value of the hash to nil
|
270
|
+
def []=( key, val )
|
271
|
+
dkey = key.downcase
|
272
|
+
|
273
|
+
if val.nil?
|
274
|
+
@header.delete dkey
|
275
|
+
return nil
|
276
|
+
end
|
277
|
+
|
278
|
+
case val
|
279
|
+
when String
|
280
|
+
header = new_hf(key, val)
|
281
|
+
when HeaderField
|
282
|
+
;
|
283
|
+
when Array
|
284
|
+
ALLOW_MULTIPLE.include? dkey or
|
285
|
+
raise ArgumentError, "#{key}: Header must not be multiple"
|
286
|
+
@header[dkey] = val
|
287
|
+
return val
|
288
|
+
else
|
289
|
+
header = new_hf(key, val.to_s)
|
290
|
+
end
|
291
|
+
if ALLOW_MULTIPLE.include? dkey
|
292
|
+
(@header[dkey] ||= []).push header
|
293
|
+
else
|
294
|
+
@header[dkey] = header
|
295
|
+
end
|
296
|
+
|
297
|
+
val
|
298
|
+
end
|
299
|
+
|
300
|
+
alias store []=
|
301
|
+
|
302
|
+
# Allows you to loop through each header in the TMail::Mail object in a block
|
303
|
+
# Example:
|
304
|
+
# @mail['to'] = 'mikel@elsewhere.org'
|
305
|
+
# @mail['from'] = 'me@me.com'
|
306
|
+
# @mail.each_header { |k,v| puts "#{k} = #{v}" }
|
307
|
+
# # => from = me@me.com
|
308
|
+
# # => to = mikel@elsewhere.org
|
309
|
+
def each_header
|
310
|
+
@header.each do |key, val|
|
311
|
+
[val].flatten.each {|v| yield key, v }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
alias each_pair each_header
|
316
|
+
|
317
|
+
def each_header_name( &block )
|
318
|
+
@header.each_key(&block)
|
319
|
+
end
|
320
|
+
|
321
|
+
alias each_key each_header_name
|
322
|
+
|
323
|
+
def each_field( &block )
|
324
|
+
@header.values.flatten.each(&block)
|
325
|
+
end
|
326
|
+
|
327
|
+
alias each_value each_field
|
328
|
+
|
329
|
+
FIELD_ORDER = %w(
|
330
|
+
return-path received
|
331
|
+
resent-date resent-from resent-sender resent-to
|
332
|
+
resent-cc resent-bcc resent-message-id
|
333
|
+
date from sender reply-to to cc bcc
|
334
|
+
message-id in-reply-to references
|
335
|
+
subject comments keywords
|
336
|
+
mime-version content-type content-transfer-encoding
|
337
|
+
content-disposition content-description
|
338
|
+
)
|
339
|
+
|
340
|
+
def ordered_each
|
341
|
+
list = @header.keys
|
342
|
+
FIELD_ORDER.each do |name|
|
343
|
+
if list.delete(name)
|
344
|
+
[@header[name]].flatten.each {|v| yield name, v }
|
345
|
+
end
|
346
|
+
end
|
347
|
+
list.each do |name|
|
348
|
+
[@header[name]].flatten.each {|v| yield name, v }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def clear
|
353
|
+
@header.clear
|
354
|
+
end
|
355
|
+
|
356
|
+
def delete( key )
|
357
|
+
@header.delete key.downcase
|
358
|
+
end
|
359
|
+
|
360
|
+
def delete_if
|
361
|
+
@header.delete_if do |key,val|
|
362
|
+
if Array === val
|
363
|
+
val.delete_if {|v| yield key, v }
|
364
|
+
val.empty?
|
365
|
+
else
|
366
|
+
yield key, val
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def keys
|
372
|
+
@header.keys
|
373
|
+
end
|
374
|
+
|
375
|
+
def key?( key )
|
376
|
+
@header.key? key.downcase
|
377
|
+
end
|
378
|
+
|
379
|
+
def values_at( *args )
|
380
|
+
args.map {|k| @header[k.downcase] }.flatten
|
381
|
+
end
|
382
|
+
|
383
|
+
alias indexes values_at
|
384
|
+
alias indices values_at
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def parse_header( f )
|
389
|
+
name = field = nil
|
390
|
+
unixfrom = nil
|
391
|
+
|
392
|
+
while line = f.gets
|
393
|
+
case line
|
394
|
+
when /\A[ \t]/ # continue from prev line
|
395
|
+
raise SyntaxError, 'mail is began by space' unless field
|
396
|
+
field << ' ' << line.strip
|
397
|
+
|
398
|
+
when /\A([^\: \t]+):\s*/ # new header line
|
399
|
+
add_hf name, field if field
|
400
|
+
name = $1
|
401
|
+
field = $' #.strip
|
402
|
+
|
403
|
+
when /\A\-*\s*\z/ # end of header
|
404
|
+
add_hf name, field if field
|
405
|
+
name = field = nil
|
406
|
+
break
|
407
|
+
|
408
|
+
when /\AFrom (\S+)/
|
409
|
+
unixfrom = $1
|
410
|
+
|
411
|
+
when /^charset=.*/
|
412
|
+
|
413
|
+
else
|
414
|
+
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
add_hf name, field if name
|
418
|
+
|
419
|
+
if unixfrom
|
420
|
+
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def add_hf( name, field )
|
425
|
+
key = name.downcase
|
426
|
+
field = new_hf(name, field)
|
427
|
+
|
428
|
+
if ALLOW_MULTIPLE.include? key
|
429
|
+
(@header[key] ||= []).push field
|
430
|
+
else
|
431
|
+
@header[key] = field
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def new_hf( name, field )
|
436
|
+
HeaderField.new(name, field, @config)
|
437
|
+
end
|
438
|
+
|
439
|
+
###
|
440
|
+
### body
|
441
|
+
###
|
442
|
+
|
443
|
+
public
|
444
|
+
|
445
|
+
def body_port
|
446
|
+
parse_body
|
447
|
+
@body_port
|
448
|
+
end
|
449
|
+
|
450
|
+
def each( &block )
|
451
|
+
body_port().ropen {|f| f.each(&block) }
|
452
|
+
end
|
453
|
+
|
454
|
+
def quoted_body
|
455
|
+
body_port.ropen {|f| return f.read }
|
456
|
+
end
|
457
|
+
|
458
|
+
def quoted_body= str
|
459
|
+
body_port.wopen { |f| f.write str }
|
460
|
+
str
|
461
|
+
end
|
462
|
+
|
463
|
+
def body=( str )
|
464
|
+
# Sets the body of the email to a new (encoded) string.
|
465
|
+
#
|
466
|
+
# We also reparses the email if the body is ever reassigned, this is a performance hit, however when
|
467
|
+
# you assign the body, you usually want to be able to make sure that you can access the attachments etc.
|
468
|
+
#
|
469
|
+
# Usage:
|
470
|
+
#
|
471
|
+
# mail.body = "Hello, this is\nthe body text"
|
472
|
+
# # => "Hello, this is\nthe body"
|
473
|
+
# mail.body
|
474
|
+
# # => "Hello, this is\nthe body"
|
475
|
+
@body_parsed = false
|
476
|
+
parse_body(StringInput.new(str))
|
477
|
+
parse_body
|
478
|
+
@body_port.wopen {|f| f.write str }
|
479
|
+
str
|
480
|
+
end
|
481
|
+
|
482
|
+
alias preamble quoted_body
|
483
|
+
alias preamble= quoted_body=
|
484
|
+
|
485
|
+
def epilogue
|
486
|
+
parse_body
|
487
|
+
@epilogue.dup
|
488
|
+
end
|
489
|
+
|
490
|
+
def epilogue=( str )
|
491
|
+
parse_body
|
492
|
+
@epilogue = str
|
493
|
+
str
|
494
|
+
end
|
495
|
+
|
496
|
+
def parts
|
497
|
+
parse_body
|
498
|
+
@parts
|
499
|
+
end
|
500
|
+
|
501
|
+
def each_part( &block )
|
502
|
+
parts().each(&block)
|
503
|
+
end
|
504
|
+
|
505
|
+
# Returns true if the content type of this part of the email is
|
506
|
+
# a disposition attachment
|
507
|
+
def disposition_is_attachment?
|
508
|
+
(self['content-disposition'] && self['content-disposition'].disposition == "attachment")
|
509
|
+
end
|
510
|
+
|
511
|
+
# Returns true if this part's content main type is text, else returns false.
|
512
|
+
# By main type is meant "text/plain" is text. "text/html" is text
|
513
|
+
def content_type_is_text?
|
514
|
+
self.header['content-type'] && (self.header['content-type'].main_type != "text")
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
|
519
|
+
def parse_body( f = nil )
|
520
|
+
return if @body_parsed
|
521
|
+
if f
|
522
|
+
parse_body_0 f
|
523
|
+
else
|
524
|
+
@port.ropen {|f|
|
525
|
+
skip_header f
|
526
|
+
parse_body_0 f
|
527
|
+
}
|
528
|
+
end
|
529
|
+
@body_parsed = true
|
530
|
+
end
|
531
|
+
|
532
|
+
def skip_header( f )
|
533
|
+
while line = f.gets
|
534
|
+
return if /\A[\r\n]*\z/ === line
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def parse_body_0( f )
|
539
|
+
if multipart?
|
540
|
+
read_multipart f
|
541
|
+
else
|
542
|
+
@body_port = @config.new_body_port(self)
|
543
|
+
@body_port.wopen {|w|
|
544
|
+
w.write f.read
|
545
|
+
}
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
def read_multipart( src )
|
550
|
+
bound = @header['content-type'].params['boundary'] || ::TMail.new_boundary
|
551
|
+
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
|
552
|
+
lastbound = "--#{bound}--"
|
553
|
+
|
554
|
+
ports = [ @config.new_preamble_port(self) ]
|
555
|
+
begin
|
556
|
+
f = ports.last.wopen
|
557
|
+
while line = src.gets
|
558
|
+
if is_sep === line
|
559
|
+
f.close
|
560
|
+
break if line.strip == lastbound
|
561
|
+
ports.push @config.new_part_port(self)
|
562
|
+
f = ports.last.wopen
|
563
|
+
else
|
564
|
+
f << line
|
565
|
+
end
|
566
|
+
end
|
567
|
+
@epilogue = (src.read || '')
|
568
|
+
ensure
|
569
|
+
f.close if f and not f.closed?
|
570
|
+
end
|
571
|
+
|
572
|
+
@body_port = ports.shift
|
573
|
+
@parts = ports.map {|p| self.class.new(p, @config) }
|
574
|
+
end
|
575
|
+
|
576
|
+
end # class Mail
|
577
|
+
|
578
|
+
end # module TMail
|