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
@@ -0,0 +1,435 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
= Mailbox and Mbox interaction 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
|
+
require 'tmail/port'
|
33
|
+
require 'socket'
|
34
|
+
require 'mutex_m'
|
35
|
+
|
36
|
+
|
37
|
+
unless [].respond_to?(:sort_by)
|
38
|
+
module Enumerable#:nodoc:
|
39
|
+
def sort_by
|
40
|
+
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
module TMail
|
47
|
+
|
48
|
+
class MhMailbox
|
49
|
+
|
50
|
+
PORT_CLASS = MhPort
|
51
|
+
|
52
|
+
def initialize( dir )
|
53
|
+
edir = File.expand_path(dir)
|
54
|
+
raise ArgumentError, "not directory: #{dir}"\
|
55
|
+
unless FileTest.directory? edir
|
56
|
+
@dirname = edir
|
57
|
+
@last_file = nil
|
58
|
+
@last_atime = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def directory
|
62
|
+
@dirname
|
63
|
+
end
|
64
|
+
|
65
|
+
alias dirname directory
|
66
|
+
|
67
|
+
attr_accessor :last_atime
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
"#<#{self.class} #{@dirname}>"
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_port
|
77
|
+
PORT_CLASS.new(next_file_name())
|
78
|
+
end
|
79
|
+
|
80
|
+
def each_port
|
81
|
+
mail_files().each do |path|
|
82
|
+
yield PORT_CLASS.new(path)
|
83
|
+
end
|
84
|
+
@last_atime = Time.now
|
85
|
+
end
|
86
|
+
|
87
|
+
alias each each_port
|
88
|
+
|
89
|
+
def reverse_each_port
|
90
|
+
mail_files().reverse_each do |path|
|
91
|
+
yield PORT_CLASS.new(path)
|
92
|
+
end
|
93
|
+
@last_atime = Time.now
|
94
|
+
end
|
95
|
+
|
96
|
+
alias reverse_each reverse_each_port
|
97
|
+
|
98
|
+
# old #each_mail returns Port
|
99
|
+
#def each_mail
|
100
|
+
# each_port do |port|
|
101
|
+
# yield Mail.new(port)
|
102
|
+
# end
|
103
|
+
#end
|
104
|
+
|
105
|
+
def each_new_port( mtime = nil, &block )
|
106
|
+
mtime ||= @last_atime
|
107
|
+
return each_port(&block) unless mtime
|
108
|
+
return unless File.mtime(@dirname) >= mtime
|
109
|
+
|
110
|
+
mail_files().each do |path|
|
111
|
+
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
|
112
|
+
end
|
113
|
+
@last_atime = Time.now
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def mail_files
|
119
|
+
Dir.entries(@dirname)\
|
120
|
+
.select {|s| /\A\d+\z/ === s }\
|
121
|
+
.map {|s| s.to_i }\
|
122
|
+
.sort\
|
123
|
+
.map {|i| "#{@dirname}/#{i}" }\
|
124
|
+
.select {|path| FileTest.file? path }
|
125
|
+
end
|
126
|
+
|
127
|
+
def next_file_name
|
128
|
+
unless n = @last_file
|
129
|
+
n = 0
|
130
|
+
Dir.entries(@dirname)\
|
131
|
+
.select {|s| /\A\d+\z/ === s }\
|
132
|
+
.map {|s| s.to_i }.sort\
|
133
|
+
.each do |i|
|
134
|
+
next unless FileTest.file? "#{@dirname}/#{i}"
|
135
|
+
n = i
|
136
|
+
end
|
137
|
+
end
|
138
|
+
begin
|
139
|
+
n += 1
|
140
|
+
end while FileTest.exist? "#{@dirname}/#{n}"
|
141
|
+
@last_file = n
|
142
|
+
|
143
|
+
"#{@dirname}/#{n}"
|
144
|
+
end
|
145
|
+
|
146
|
+
end # MhMailbox
|
147
|
+
|
148
|
+
MhLoader = MhMailbox
|
149
|
+
|
150
|
+
|
151
|
+
class UNIXMbox
|
152
|
+
|
153
|
+
def UNIXMbox.lock( fname )
|
154
|
+
begin
|
155
|
+
f = File.open(fname)
|
156
|
+
f.flock File::LOCK_EX
|
157
|
+
yield f
|
158
|
+
ensure
|
159
|
+
f.flock File::LOCK_UN
|
160
|
+
f.close if f and not f.closed?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class << self
|
165
|
+
alias newobj new
|
166
|
+
end
|
167
|
+
|
168
|
+
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
|
169
|
+
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
170
|
+
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
171
|
+
end
|
172
|
+
|
173
|
+
def UNIXMbox.static_new( fname, dir, readonly = false )
|
174
|
+
newobj(fname, dir, readonly, true)
|
175
|
+
end
|
176
|
+
|
177
|
+
def initialize( fname, mhdir, readonly, static )
|
178
|
+
@filename = fname
|
179
|
+
@readonly = readonly
|
180
|
+
@closed = false
|
181
|
+
|
182
|
+
Dir.mkdir mhdir
|
183
|
+
@real = MhMailbox.new(mhdir)
|
184
|
+
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
|
185
|
+
ObjectSpace.define_finalizer self, @finalizer
|
186
|
+
end
|
187
|
+
|
188
|
+
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
|
189
|
+
lambda {
|
190
|
+
if writeback_p
|
191
|
+
lock(mboxfile) {|f|
|
192
|
+
mh.each_port do |port|
|
193
|
+
f.puts create_from_line(port)
|
194
|
+
port.ropen {|r|
|
195
|
+
f.puts r.read
|
196
|
+
}
|
197
|
+
end
|
198
|
+
}
|
199
|
+
end
|
200
|
+
if cleanup_p
|
201
|
+
Dir.foreach(mh.dirname) do |fname|
|
202
|
+
next if /\A\.\.?\z/ === fname
|
203
|
+
File.unlink "#{mh.dirname}/#{fname}"
|
204
|
+
end
|
205
|
+
Dir.rmdir mh.dirname
|
206
|
+
end
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
# make _From line
|
211
|
+
def UNIXMbox.create_from_line( port )
|
212
|
+
sprintf 'From %s %s',
|
213
|
+
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
214
|
+
end
|
215
|
+
|
216
|
+
def UNIXMbox.fromaddr
|
217
|
+
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
218
|
+
HeaderField.new_from_port(port, 'From') or return 'nobody'
|
219
|
+
a = h.addrs[0] or return 'nobody'
|
220
|
+
a.spec
|
221
|
+
end
|
222
|
+
private_class_method :fromaddr
|
223
|
+
|
224
|
+
def close
|
225
|
+
return if @closed
|
226
|
+
|
227
|
+
ObjectSpace.undefine_finalizer self
|
228
|
+
@finalizer.call
|
229
|
+
@finalizer = nil
|
230
|
+
@real = nil
|
231
|
+
@closed = true
|
232
|
+
@updated = nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def each_port( &block )
|
236
|
+
close_check
|
237
|
+
update
|
238
|
+
@real.each_port(&block)
|
239
|
+
end
|
240
|
+
|
241
|
+
alias each each_port
|
242
|
+
|
243
|
+
def reverse_each_port( &block )
|
244
|
+
close_check
|
245
|
+
update
|
246
|
+
@real.reverse_each_port(&block)
|
247
|
+
end
|
248
|
+
|
249
|
+
alias reverse_each reverse_each_port
|
250
|
+
|
251
|
+
# old #each_mail returns Port
|
252
|
+
#def each_mail( &block )
|
253
|
+
# each_port do |port|
|
254
|
+
# yield Mail.new(port)
|
255
|
+
# end
|
256
|
+
#end
|
257
|
+
|
258
|
+
def each_new_port( mtime = nil )
|
259
|
+
close_check
|
260
|
+
update
|
261
|
+
@real.each_new_port(mtime) {|p| yield p }
|
262
|
+
end
|
263
|
+
|
264
|
+
def new_port
|
265
|
+
close_check
|
266
|
+
@real.new_port
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
def close_check
|
272
|
+
@closed and raise ArgumentError, 'accessing already closed mbox'
|
273
|
+
end
|
274
|
+
|
275
|
+
def update
|
276
|
+
return if FileTest.zero?(@filename)
|
277
|
+
return if @updated and File.mtime(@filename) < @updated
|
278
|
+
w = nil
|
279
|
+
port = nil
|
280
|
+
time = nil
|
281
|
+
UNIXMbox.lock(@filename) {|f|
|
282
|
+
begin
|
283
|
+
f.each do |line|
|
284
|
+
if /\AFrom / === line
|
285
|
+
w.close if w
|
286
|
+
File.utime time, time, port.filename if time
|
287
|
+
|
288
|
+
port = @real.new_port
|
289
|
+
w = port.wopen
|
290
|
+
time = fromline2time(line)
|
291
|
+
else
|
292
|
+
w.print line if w
|
293
|
+
end
|
294
|
+
end
|
295
|
+
ensure
|
296
|
+
if w and not w.closed?
|
297
|
+
w.close
|
298
|
+
File.utime time, time, port.filename if time
|
299
|
+
end
|
300
|
+
end
|
301
|
+
f.truncate(0) unless @readonly
|
302
|
+
@updated = Time.now
|
303
|
+
}
|
304
|
+
end
|
305
|
+
|
306
|
+
def fromline2time( line )
|
307
|
+
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
|
308
|
+
or return nil
|
309
|
+
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
|
310
|
+
end
|
311
|
+
|
312
|
+
end # UNIXMbox
|
313
|
+
|
314
|
+
MboxLoader = UNIXMbox
|
315
|
+
|
316
|
+
|
317
|
+
class Maildir
|
318
|
+
|
319
|
+
extend Mutex_m
|
320
|
+
|
321
|
+
PORT_CLASS = MaildirPort
|
322
|
+
|
323
|
+
@seq = 0
|
324
|
+
def Maildir.unique_number
|
325
|
+
synchronize {
|
326
|
+
@seq += 1
|
327
|
+
return @seq
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
def initialize( dir = nil )
|
332
|
+
@dirname = dir || ENV['MAILDIR']
|
333
|
+
raise ArgumentError, "not directory: #{@dirname}"\
|
334
|
+
unless FileTest.directory? @dirname
|
335
|
+
@new = "#{@dirname}/new"
|
336
|
+
@tmp = "#{@dirname}/tmp"
|
337
|
+
@cur = "#{@dirname}/cur"
|
338
|
+
end
|
339
|
+
|
340
|
+
def directory
|
341
|
+
@dirname
|
342
|
+
end
|
343
|
+
|
344
|
+
def inspect
|
345
|
+
"#<#{self.class} #{@dirname}>"
|
346
|
+
end
|
347
|
+
|
348
|
+
def close
|
349
|
+
end
|
350
|
+
|
351
|
+
def each_port
|
352
|
+
mail_files(@cur).each do |path|
|
353
|
+
yield PORT_CLASS.new(path)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
alias each each_port
|
358
|
+
|
359
|
+
def reverse_each_port
|
360
|
+
mail_files(@cur).reverse_each do |path|
|
361
|
+
yield PORT_CLASS.new(path)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
alias reverse_each reverse_each_port
|
366
|
+
|
367
|
+
def new_port
|
368
|
+
fname = nil
|
369
|
+
tmpfname = nil
|
370
|
+
newfname = nil
|
371
|
+
|
372
|
+
begin
|
373
|
+
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
|
374
|
+
|
375
|
+
tmpfname = "#{@tmp}/#{fname}"
|
376
|
+
newfname = "#{@new}/#{fname}"
|
377
|
+
end while FileTest.exist? tmpfname
|
378
|
+
|
379
|
+
if block_given?
|
380
|
+
File.open(tmpfname, 'w') {|f| yield f }
|
381
|
+
File.rename tmpfname, newfname
|
382
|
+
PORT_CLASS.new(newfname)
|
383
|
+
else
|
384
|
+
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
|
385
|
+
PORT_CLASS.new(tmpfname)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def each_new_port
|
390
|
+
mail_files(@new).each do |path|
|
391
|
+
dest = @cur + '/' + File.basename(path)
|
392
|
+
File.rename path, dest
|
393
|
+
yield PORT_CLASS.new(dest)
|
394
|
+
end
|
395
|
+
|
396
|
+
check_tmp
|
397
|
+
end
|
398
|
+
|
399
|
+
TOO_OLD = 60 * 60 * 36 # 36 hour
|
400
|
+
|
401
|
+
def check_tmp
|
402
|
+
old = Time.now.to_i - TOO_OLD
|
403
|
+
|
404
|
+
each_filename(@tmp) do |full, fname|
|
405
|
+
if FileTest.file? full and
|
406
|
+
File.stat(full).mtime.to_i < old
|
407
|
+
File.unlink full
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
private
|
413
|
+
|
414
|
+
def mail_files( dir )
|
415
|
+
Dir.entries(dir)\
|
416
|
+
.select {|s| s[0] != ?. }\
|
417
|
+
.sort_by {|s| s.slice(/\A\d+/).to_i }\
|
418
|
+
.map {|s| "#{dir}/#{s}" }\
|
419
|
+
.select {|path| FileTest.file? path }
|
420
|
+
end
|
421
|
+
|
422
|
+
def each_filename( dir )
|
423
|
+
Dir.foreach(dir) do |fname|
|
424
|
+
path = "#{dir}/#{fname}"
|
425
|
+
if fname[0] != ?. and FileTest.file? path
|
426
|
+
yield path, fname
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
end # Maildir
|
432
|
+
|
433
|
+
MaildirLoader = Maildir
|
434
|
+
|
435
|
+
end # module TMail
|
data/lib/tmail/mbox.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'tmail/mailbox'
|
data/lib/tmail/net.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
= Net provides SMTP wrapping
|
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
|
+
require 'nkf'
|
33
|
+
|
34
|
+
|
35
|
+
module TMail
|
36
|
+
|
37
|
+
class Mail
|
38
|
+
|
39
|
+
def send_to( smtp )
|
40
|
+
do_send_to(smtp) do
|
41
|
+
ready_to_send
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_text_to( smtp )
|
46
|
+
do_send_to(smtp) do
|
47
|
+
ready_to_send
|
48
|
+
mime_encode
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def do_send_to( smtp )
|
53
|
+
from = from_address or raise ArgumentError, 'no from address'
|
54
|
+
(dests = destinations).empty? and raise ArgumentError, 'no receipient'
|
55
|
+
yield
|
56
|
+
send_to_0 smtp, from, dests
|
57
|
+
end
|
58
|
+
private :do_send_to
|
59
|
+
|
60
|
+
def send_to_0( smtp, from, to )
|
61
|
+
smtp.ready(from, to) do |f|
|
62
|
+
encoded "\r\n", 'j', f, ''
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def ready_to_send
|
67
|
+
delete_no_send_fields
|
68
|
+
add_message_id
|
69
|
+
add_date
|
70
|
+
end
|
71
|
+
|
72
|
+
NOSEND_FIELDS = %w(
|
73
|
+
received
|
74
|
+
bcc
|
75
|
+
)
|
76
|
+
|
77
|
+
def delete_no_send_fields
|
78
|
+
NOSEND_FIELDS.each do |nm|
|
79
|
+
delete nm
|
80
|
+
end
|
81
|
+
delete_if {|n,v| v.empty? }
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_message_id( fqdn = nil )
|
85
|
+
self.message_id = ::TMail::new_message_id(fqdn)
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_date
|
89
|
+
self.date = Time.now
|
90
|
+
end
|
91
|
+
|
92
|
+
def mime_encode
|
93
|
+
if parts.empty?
|
94
|
+
mime_encode_singlepart
|
95
|
+
else
|
96
|
+
mime_encode_multipart true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def mime_encode_singlepart
|
101
|
+
self.mime_version = '1.0'
|
102
|
+
b = body
|
103
|
+
if NKF.guess(b) != NKF::BINARY
|
104
|
+
mime_encode_text b
|
105
|
+
else
|
106
|
+
mime_encode_binary b
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def mime_encode_text( body )
|
111
|
+
self.body = NKF.nkf('-j -m0', body)
|
112
|
+
self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
113
|
+
self.encoding = '7bit'
|
114
|
+
end
|
115
|
+
|
116
|
+
def mime_encode_binary( body )
|
117
|
+
self.body = [body].pack('m')
|
118
|
+
self.set_content_type 'application', 'octet-stream'
|
119
|
+
self.encoding = 'Base64'
|
120
|
+
end
|
121
|
+
|
122
|
+
def mime_encode_multipart( top = true )
|
123
|
+
self.mime_version = '1.0' if top
|
124
|
+
self.set_content_type 'multipart', 'mixed'
|
125
|
+
e = encoding(nil)
|
126
|
+
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
127
|
+
raise ArgumentError,
|
128
|
+
'using C.T.Encoding with multipart mail is not permitted'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def create_empty_mail
|
133
|
+
self.class.new(StringPort.new(''), @config)
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_reply
|
137
|
+
setup_reply create_empty_mail()
|
138
|
+
end
|
139
|
+
|
140
|
+
def setup_reply( m )
|
141
|
+
if tmp = reply_addresses(nil)
|
142
|
+
m.to_addrs = tmp
|
143
|
+
end
|
144
|
+
|
145
|
+
mid = message_id(nil)
|
146
|
+
tmp = references(nil) || []
|
147
|
+
tmp.push mid if mid
|
148
|
+
m.in_reply_to = [mid] if mid
|
149
|
+
m.references = tmp unless tmp.empty?
|
150
|
+
m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
|
151
|
+
|
152
|
+
m
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_forward
|
156
|
+
setup_forward create_empty_mail()
|
157
|
+
end
|
158
|
+
|
159
|
+
def setup_forward( mail )
|
160
|
+
m = Mail.new(StringPort.new(''))
|
161
|
+
m.body = decoded
|
162
|
+
m.set_content_type 'message', 'rfc822'
|
163
|
+
m.encoding = encoding('7bit')
|
164
|
+
mail.parts.push m
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
class DeleteFields
|
171
|
+
|
172
|
+
NOSEND_FIELDS = %w(
|
173
|
+
received
|
174
|
+
bcc
|
175
|
+
)
|
176
|
+
|
177
|
+
def initialize( nosend = nil, delempty = true )
|
178
|
+
@no_send_fields = nosend || NOSEND_FIELDS.dup
|
179
|
+
@delete_empty_fields = delempty
|
180
|
+
end
|
181
|
+
|
182
|
+
attr :no_send_fields
|
183
|
+
attr :delete_empty_fields, true
|
184
|
+
|
185
|
+
def exec( mail )
|
186
|
+
@no_send_fields.each do |nm|
|
187
|
+
delete nm
|
188
|
+
end
|
189
|
+
delete_if {|n,v| v.empty? } if @delete_empty_fields
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
class AddMessageId
|
196
|
+
|
197
|
+
def initialize( fqdn = nil )
|
198
|
+
@fqdn = fqdn
|
199
|
+
end
|
200
|
+
|
201
|
+
attr :fqdn, true
|
202
|
+
|
203
|
+
def exec( mail )
|
204
|
+
mail.message_id = ::TMail::new_msgid(@fqdn)
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
class AddDate
|
211
|
+
|
212
|
+
def exec( mail )
|
213
|
+
mail.date = Time.now
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
class MimeEncodeAuto
|
220
|
+
|
221
|
+
def initialize( s = nil, m = nil )
|
222
|
+
@singlepart_composer = s || MimeEncodeSingle.new
|
223
|
+
@multipart_composer = m || MimeEncodeMulti.new
|
224
|
+
end
|
225
|
+
|
226
|
+
attr :singlepart_composer
|
227
|
+
attr :multipart_composer
|
228
|
+
|
229
|
+
def exec( mail )
|
230
|
+
if mail._builtin_multipart?
|
231
|
+
then @multipart_composer
|
232
|
+
else @singlepart_composer end.exec mail
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
class MimeEncodeSingle
|
239
|
+
|
240
|
+
def exec( mail )
|
241
|
+
mail.mime_version = '1.0'
|
242
|
+
b = mail.body
|
243
|
+
if NKF.guess(b) != NKF::BINARY
|
244
|
+
on_text b
|
245
|
+
else
|
246
|
+
on_binary b
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def on_text( body )
|
251
|
+
mail.body = NKF.nkf('-j -m0', body)
|
252
|
+
mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
253
|
+
mail.encoding = '7bit'
|
254
|
+
end
|
255
|
+
|
256
|
+
def on_binary( body )
|
257
|
+
mail.body = [body].pack('m')
|
258
|
+
mail.set_content_type 'application', 'octet-stream'
|
259
|
+
mail.encoding = 'Base64'
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
class MimeEncodeMulti
|
266
|
+
|
267
|
+
def exec( mail, top = true )
|
268
|
+
mail.mime_version = '1.0' if top
|
269
|
+
mail.set_content_type 'multipart', 'mixed'
|
270
|
+
e = encoding(nil)
|
271
|
+
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
272
|
+
raise ArgumentError,
|
273
|
+
'using C.T.Encoding with multipart mail is not permitted'
|
274
|
+
end
|
275
|
+
mail.parts.each do |m|
|
276
|
+
exec m, false if m._builtin_multipart?
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end # module TMail
|