tmail 1.1.1
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.
- data/LICENSE +21 -0
- data/README +157 -0
- data/bat/changelog +19 -0
- data/bat/clobber/package +10 -0
- data/bat/compile +42 -0
- data/bat/config.yaml +8 -0
- data/bat/prepare +8 -0
- data/bat/publish +51 -0
- data/bat/rdoc +42 -0
- data/bat/release +12 -0
- data/bat/setup +1616 -0
- data/bat/stats +138 -0
- data/bat/tag +25 -0
- data/bat/test +25 -0
- data/ext/tmail/Makefile +25 -0
- data/ext/tmail/base64/MANIFEST +4 -0
- data/ext/tmail/base64/base64.c +264 -0
- data/ext/tmail/base64/depend +1 -0
- data/ext/tmail/base64/extconf.rb +38 -0
- data/ext/tmail/scanner_c/MANIFEST +4 -0
- data/ext/tmail/scanner_c/depend +1 -0
- data/ext/tmail/scanner_c/extconf.rb +38 -0
- data/ext/tmail/scanner_c/scanner_c.c +582 -0
- data/lib/tmail.rb +4 -0
- data/lib/tmail/Makefile +19 -0
- data/lib/tmail/address.rb +245 -0
- data/lib/tmail/attachments.rb +47 -0
- data/lib/tmail/base64.rb +75 -0
- data/lib/tmail/compat.rb +39 -0
- data/lib/tmail/config.rb +71 -0
- data/lib/tmail/core_extensions.rb +67 -0
- data/lib/tmail/encode.rb +524 -0
- data/lib/tmail/header.rb +931 -0
- data/lib/tmail/index.rb +8 -0
- data/lib/tmail/interface.rb +540 -0
- data/lib/tmail/loader.rb +1 -0
- data/lib/tmail/mail.rb +507 -0
- data/lib/tmail/mailbox.rb +435 -0
- data/lib/tmail/mbox.rb +1 -0
- data/lib/tmail/net.rb +282 -0
- data/lib/tmail/obsolete.rb +137 -0
- data/lib/tmail/parser.rb +1475 -0
- data/lib/tmail/parser.y +381 -0
- data/lib/tmail/port.rb +379 -0
- data/lib/tmail/quoting.rb +142 -0
- data/lib/tmail/require_arch.rb +56 -0
- data/lib/tmail/scanner.rb +44 -0
- data/lib/tmail/scanner_r.rb +263 -0
- data/lib/tmail/stringio.rb +279 -0
- data/lib/tmail/tmail.rb +1 -0
- data/lib/tmail/utils.rb +281 -0
- data/lib/tmail/version.rb +38 -0
- data/meta/icli.yaml +16 -0
- data/meta/tmail-1.1.1.roll +24 -0
- data/sample/data/multipart +23 -0
- data/sample/data/normal +29 -0
- data/sample/data/sendtest +5 -0
- data/sample/data/simple +14 -0
- data/sample/data/test +27 -0
- data/sample/extract-attachements.rb +33 -0
- data/sample/from-check.rb +26 -0
- data/sample/multipart.rb +26 -0
- data/sample/parse-bench.rb +68 -0
- data/sample/parse-test.rb +19 -0
- data/sample/sendmail.rb +94 -0
- data/test/extctrl.rb +6 -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_quoted_with_0d0a +14 -0
- data/test/fixtures/raw_email_simple +11 -0
- data/test/fixtures/raw_email_with_illegal_boundary +58 -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_illegal_boundary +58 -0
- data/test/kcode.rb +14 -0
- data/test/test_address.rb +1128 -0
- data/test/test_attachments.rb +35 -0
- data/test/test_base64.rb +63 -0
- data/test/test_encode.rb +77 -0
- data/test/test_header.rb +885 -0
- data/test/test_helper.rb +2 -0
- data/test/test_mail.rb +623 -0
- data/test/test_mbox.rb +126 -0
- data/test/test_port.rb +430 -0
- data/test/test_scanner.rb +209 -0
- data/test/test_utils.rb +37 -0
- metadata +205 -0
data/lib/tmail/loader.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'tmail/mailbox'
|
data/lib/tmail/mail.rb
ADDED
@@ -0,0 +1,507 @@
|
|
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
|
+
# == TMail::Mail
|
32
|
+
# === Class Methods
|
33
|
+
#
|
34
|
+
#
|
35
|
+
#
|
36
|
+
#
|
37
|
+
#
|
38
|
+
#
|
39
|
+
#
|
40
|
+
#
|
41
|
+
#
|
42
|
+
#
|
43
|
+
#
|
44
|
+
#
|
45
|
+
#
|
46
|
+
#
|
47
|
+
|
48
|
+
|
49
|
+
require 'tmail/interface'
|
50
|
+
require 'tmail/encode'
|
51
|
+
require 'tmail/header'
|
52
|
+
require 'tmail/port'
|
53
|
+
require 'tmail/config'
|
54
|
+
require 'tmail/utils'
|
55
|
+
require 'tmail/attachments'
|
56
|
+
require 'tmail/quoting'
|
57
|
+
require 'socket'
|
58
|
+
|
59
|
+
module TMail
|
60
|
+
|
61
|
+
class Mail
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def load( fname )
|
65
|
+
new(FilePort.new(fname))
|
66
|
+
end
|
67
|
+
|
68
|
+
alias load_from load
|
69
|
+
alias loadfrom load
|
70
|
+
|
71
|
+
def parse( str )
|
72
|
+
new(StringPort.new(str))
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize( port = nil, conf = DEFAULT_CONFIG )
|
78
|
+
@port = port || StringPort.new
|
79
|
+
@config = Config.to_config(conf)
|
80
|
+
|
81
|
+
@header = {}
|
82
|
+
@body_port = nil
|
83
|
+
@body_parsed = false
|
84
|
+
@epilogue = ''
|
85
|
+
@parts = []
|
86
|
+
|
87
|
+
@port.ropen {|f|
|
88
|
+
parse_header f
|
89
|
+
parse_body f unless @port.reproducible?
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :port
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# to_s interfaces
|
101
|
+
#
|
102
|
+
|
103
|
+
public
|
104
|
+
|
105
|
+
include StrategyInterface
|
106
|
+
|
107
|
+
def write_back( eol = "\n", charset = 'e' )
|
108
|
+
parse_body
|
109
|
+
@port.wopen {|stream| encoded eol, charset, stream }
|
110
|
+
end
|
111
|
+
|
112
|
+
def accept( strategy )
|
113
|
+
with_multipart_encoding(strategy) {
|
114
|
+
ordered_each do |name, field|
|
115
|
+
next if field.empty?
|
116
|
+
strategy.header_name canonical(name)
|
117
|
+
field.accept strategy
|
118
|
+
strategy.puts
|
119
|
+
end
|
120
|
+
strategy.puts
|
121
|
+
body_port().ropen {|r|
|
122
|
+
strategy.write r.read
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def canonical( name )
|
130
|
+
name.split(/-/).map {|s| s.capitalize }.join('-')
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_multipart_encoding( strategy )
|
134
|
+
if parts().empty? # DO NOT USE @parts
|
135
|
+
yield
|
136
|
+
|
137
|
+
else
|
138
|
+
bound = ::TMail.new_boundary
|
139
|
+
if @header.key? 'content-type'
|
140
|
+
@header['content-type'].params['boundary'] = bound
|
141
|
+
else
|
142
|
+
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
|
143
|
+
end
|
144
|
+
|
145
|
+
yield
|
146
|
+
|
147
|
+
parts().each do |tm|
|
148
|
+
strategy.puts
|
149
|
+
strategy.puts '--' + bound
|
150
|
+
tm.accept strategy
|
151
|
+
end
|
152
|
+
strategy.puts
|
153
|
+
strategy.puts '--' + bound + '--'
|
154
|
+
strategy.write epilogue()
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
###
|
159
|
+
### header
|
160
|
+
###
|
161
|
+
|
162
|
+
public
|
163
|
+
|
164
|
+
ALLOW_MULTIPLE = {
|
165
|
+
'received' => true,
|
166
|
+
'resent-date' => true,
|
167
|
+
'resent-from' => true,
|
168
|
+
'resent-sender' => true,
|
169
|
+
'resent-to' => true,
|
170
|
+
'resent-cc' => true,
|
171
|
+
'resent-bcc' => true,
|
172
|
+
'resent-message-id' => true,
|
173
|
+
'comments' => true,
|
174
|
+
'keywords' => true
|
175
|
+
}
|
176
|
+
USE_ARRAY = ALLOW_MULTIPLE
|
177
|
+
|
178
|
+
def header
|
179
|
+
@header.dup
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns a TMail::AddressHeader object of the field you are querying.
|
183
|
+
# Examples:
|
184
|
+
# @mail['from'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
185
|
+
# @mail['to'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
186
|
+
#
|
187
|
+
# You can get the string value of this by passing "to_s" to the query:
|
188
|
+
# Example:
|
189
|
+
# @mail['to'].to_s #=> "mikel@test.com.au"
|
190
|
+
def []( key )
|
191
|
+
@header[key.downcase]
|
192
|
+
end
|
193
|
+
|
194
|
+
def sub_header(key, param)
|
195
|
+
(hdr = self[key]) ? hdr[param] : nil
|
196
|
+
end
|
197
|
+
|
198
|
+
alias fetch []
|
199
|
+
|
200
|
+
# Allows you to set or delete TMail header objects at will.
|
201
|
+
# Eamples:
|
202
|
+
# @mail = TMail::Mail.new
|
203
|
+
# @mail['to'].to_s # => 'mikel@test.com.au'
|
204
|
+
# @mail['to'] = 'mikel@elsewhere.org'
|
205
|
+
# @mail['to'].to_s # => 'mikel@elsewhere.org'
|
206
|
+
# @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n"
|
207
|
+
# @mail['to'] = nil
|
208
|
+
# @mail['to'].to_s # => nil
|
209
|
+
# @mail.encoded # => "\r\n"
|
210
|
+
#
|
211
|
+
# Note: setting mail[] = nil actualy deletes the header field in question from the object,
|
212
|
+
# it does not just set the value of the hash to nil
|
213
|
+
def []=( key, val )
|
214
|
+
dkey = key.downcase
|
215
|
+
|
216
|
+
if val.nil?
|
217
|
+
@header.delete dkey
|
218
|
+
return nil
|
219
|
+
end
|
220
|
+
|
221
|
+
case val
|
222
|
+
when String
|
223
|
+
header = new_hf(key, val)
|
224
|
+
when HeaderField
|
225
|
+
;
|
226
|
+
when Array
|
227
|
+
ALLOW_MULTIPLE.include? dkey or
|
228
|
+
raise ArgumentError, "#{key}: Header must not be multiple"
|
229
|
+
@header[dkey] = val
|
230
|
+
return val
|
231
|
+
else
|
232
|
+
header = new_hf(key, val.to_s)
|
233
|
+
end
|
234
|
+
if ALLOW_MULTIPLE.include? dkey
|
235
|
+
(@header[dkey] ||= []).push header
|
236
|
+
else
|
237
|
+
@header[dkey] = header
|
238
|
+
end
|
239
|
+
|
240
|
+
val
|
241
|
+
end
|
242
|
+
|
243
|
+
alias store []=
|
244
|
+
|
245
|
+
# Allows you to loop through each header in the TMail::Mail object in a block
|
246
|
+
# Example:
|
247
|
+
# @mail['to'] = 'mikel@elsewhere.org'
|
248
|
+
# @mail['from'] = 'me@me.com'
|
249
|
+
# @mail.each_header { |k,v| puts "#{k} = #{v}" }
|
250
|
+
# # => from = me@me.com
|
251
|
+
# # => to = mikel@elsewhere.org
|
252
|
+
def each_header
|
253
|
+
@header.each do |key, val|
|
254
|
+
[val].flatten.each {|v| yield key, v }
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
alias each_pair each_header
|
259
|
+
|
260
|
+
def each_header_name( &block )
|
261
|
+
@header.each_key(&block)
|
262
|
+
end
|
263
|
+
|
264
|
+
alias each_key each_header_name
|
265
|
+
|
266
|
+
def each_field( &block )
|
267
|
+
@header.values.flatten.each(&block)
|
268
|
+
end
|
269
|
+
|
270
|
+
alias each_value each_field
|
271
|
+
|
272
|
+
FIELD_ORDER = %w(
|
273
|
+
return-path received
|
274
|
+
resent-date resent-from resent-sender resent-to
|
275
|
+
resent-cc resent-bcc resent-message-id
|
276
|
+
date from sender reply-to to cc bcc
|
277
|
+
message-id in-reply-to references
|
278
|
+
subject comments keywords
|
279
|
+
mime-version content-type content-transfer-encoding
|
280
|
+
content-disposition content-description
|
281
|
+
)
|
282
|
+
|
283
|
+
def ordered_each
|
284
|
+
list = @header.keys
|
285
|
+
FIELD_ORDER.each do |name|
|
286
|
+
if list.delete(name)
|
287
|
+
[@header[name]].flatten.each {|v| yield name, v }
|
288
|
+
end
|
289
|
+
end
|
290
|
+
list.each do |name|
|
291
|
+
[@header[name]].flatten.each {|v| yield name, v }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def clear
|
296
|
+
@header.clear
|
297
|
+
end
|
298
|
+
|
299
|
+
def delete( key )
|
300
|
+
@header.delete key.downcase
|
301
|
+
end
|
302
|
+
|
303
|
+
def delete_if
|
304
|
+
@header.delete_if do |key,val|
|
305
|
+
if Array === val
|
306
|
+
val.delete_if {|v| yield key, v }
|
307
|
+
val.empty?
|
308
|
+
else
|
309
|
+
yield key, val
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def keys
|
315
|
+
@header.keys
|
316
|
+
end
|
317
|
+
|
318
|
+
def key?( key )
|
319
|
+
@header.key? key.downcase
|
320
|
+
end
|
321
|
+
|
322
|
+
def values_at( *args )
|
323
|
+
args.map {|k| @header[k.downcase] }.flatten
|
324
|
+
end
|
325
|
+
|
326
|
+
alias indexes values_at
|
327
|
+
alias indices values_at
|
328
|
+
|
329
|
+
private
|
330
|
+
|
331
|
+
def parse_header( f )
|
332
|
+
name = field = nil
|
333
|
+
unixfrom = nil
|
334
|
+
|
335
|
+
while line = f.gets
|
336
|
+
case line
|
337
|
+
when /\A[ \t]/ # continue from prev line
|
338
|
+
raise SyntaxError, 'mail is began by space' unless field
|
339
|
+
field << ' ' << line.strip
|
340
|
+
|
341
|
+
when /\A([^\: \t]+):\s*/ # new header line
|
342
|
+
add_hf name, field if field
|
343
|
+
name = $1
|
344
|
+
field = $' #.strip
|
345
|
+
|
346
|
+
when /\A\-*\s*\z/ # end of header
|
347
|
+
add_hf name, field if field
|
348
|
+
name = field = nil
|
349
|
+
break
|
350
|
+
|
351
|
+
when /\AFrom (\S+)/
|
352
|
+
unixfrom = $1
|
353
|
+
|
354
|
+
when /^charset=.*/
|
355
|
+
|
356
|
+
else
|
357
|
+
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
add_hf name, field if name
|
361
|
+
|
362
|
+
if unixfrom
|
363
|
+
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def add_hf( name, field )
|
368
|
+
key = name.downcase
|
369
|
+
field = new_hf(name, field)
|
370
|
+
|
371
|
+
if ALLOW_MULTIPLE.include? key
|
372
|
+
(@header[key] ||= []).push field
|
373
|
+
else
|
374
|
+
@header[key] = field
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def new_hf( name, field )
|
379
|
+
HeaderField.new(name, field, @config)
|
380
|
+
end
|
381
|
+
|
382
|
+
###
|
383
|
+
### body
|
384
|
+
###
|
385
|
+
|
386
|
+
public
|
387
|
+
|
388
|
+
def body_port
|
389
|
+
parse_body
|
390
|
+
@body_port
|
391
|
+
end
|
392
|
+
|
393
|
+
def each( &block )
|
394
|
+
body_port().ropen {|f| f.each(&block) }
|
395
|
+
end
|
396
|
+
|
397
|
+
def quoted_body
|
398
|
+
parse_body
|
399
|
+
@body_port.ropen {|f|
|
400
|
+
return f.read
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
def body=( str )
|
405
|
+
# Sets the body of the email to a new (encoded) string.
|
406
|
+
#
|
407
|
+
# We also reparses the email if the body is ever reassigned, this is a performance hit, however when
|
408
|
+
# you assign the body, you usually want to be able to make sure that you can access the attachments etc.
|
409
|
+
#
|
410
|
+
# Usage:
|
411
|
+
#
|
412
|
+
# mail.body = "Hello, this is\nthe body text"
|
413
|
+
# # => "Hello, this is\nthe body"
|
414
|
+
# mail.body
|
415
|
+
# # => "Hello, this is\nthe body"
|
416
|
+
@body_parsed = false
|
417
|
+
parse_body(StringInput.new(str))
|
418
|
+
parse_body
|
419
|
+
@body_port.wopen {|f| f.write str }
|
420
|
+
str
|
421
|
+
end
|
422
|
+
|
423
|
+
alias preamble body
|
424
|
+
alias preamble= body=
|
425
|
+
|
426
|
+
def epilogue
|
427
|
+
parse_body
|
428
|
+
@epilogue.dup
|
429
|
+
end
|
430
|
+
|
431
|
+
def epilogue=( str )
|
432
|
+
parse_body
|
433
|
+
@epilogue = str
|
434
|
+
str
|
435
|
+
end
|
436
|
+
|
437
|
+
def parts
|
438
|
+
parse_body
|
439
|
+
@parts
|
440
|
+
end
|
441
|
+
|
442
|
+
def each_part( &block )
|
443
|
+
parts().each(&block)
|
444
|
+
end
|
445
|
+
|
446
|
+
private
|
447
|
+
|
448
|
+
def parse_body( f = nil )
|
449
|
+
return if @body_parsed
|
450
|
+
if f
|
451
|
+
parse_body_0 f
|
452
|
+
else
|
453
|
+
@port.ropen {|f|
|
454
|
+
skip_header f
|
455
|
+
parse_body_0 f
|
456
|
+
}
|
457
|
+
end
|
458
|
+
@body_parsed = true
|
459
|
+
end
|
460
|
+
|
461
|
+
def skip_header( f )
|
462
|
+
while line = f.gets
|
463
|
+
return if /\A[\r\n]*\z/ === line
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def parse_body_0( f )
|
468
|
+
if multipart?
|
469
|
+
read_multipart f
|
470
|
+
else
|
471
|
+
@body_port = @config.new_body_port(self)
|
472
|
+
@body_port.wopen {|w|
|
473
|
+
w.write f.read
|
474
|
+
}
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def read_multipart( src )
|
479
|
+
bound = @header['content-type'].params['boundary']
|
480
|
+
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
|
481
|
+
lastbound = "--#{bound}--"
|
482
|
+
|
483
|
+
ports = [ @config.new_preamble_port(self) ]
|
484
|
+
begin
|
485
|
+
f = ports.last.wopen
|
486
|
+
while line = src.gets
|
487
|
+
if is_sep === line
|
488
|
+
f.close
|
489
|
+
break if line.strip == lastbound
|
490
|
+
ports.push @config.new_part_port(self)
|
491
|
+
f = ports.last.wopen
|
492
|
+
else
|
493
|
+
f << line
|
494
|
+
end
|
495
|
+
end
|
496
|
+
@epilogue = (src.read || '')
|
497
|
+
ensure
|
498
|
+
f.close if f and not f.closed?
|
499
|
+
end
|
500
|
+
|
501
|
+
@body_port = ports.shift
|
502
|
+
@parts = ports.map {|p| self.class.new(p, @config) }
|
503
|
+
end
|
504
|
+
|
505
|
+
end # class Mail
|
506
|
+
|
507
|
+
end # module TMail
|