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.
Files changed (102) hide show
  1. data/LICENSE +21 -0
  2. data/README +157 -0
  3. data/bat/changelog +19 -0
  4. data/bat/clobber/package +10 -0
  5. data/bat/compile +42 -0
  6. data/bat/config.yaml +8 -0
  7. data/bat/prepare +8 -0
  8. data/bat/publish +51 -0
  9. data/bat/rdoc +42 -0
  10. data/bat/release +12 -0
  11. data/bat/setup +1616 -0
  12. data/bat/stats +138 -0
  13. data/bat/tag +25 -0
  14. data/bat/test +25 -0
  15. data/ext/tmail/Makefile +25 -0
  16. data/ext/tmail/base64/MANIFEST +4 -0
  17. data/ext/tmail/base64/base64.c +264 -0
  18. data/ext/tmail/base64/depend +1 -0
  19. data/ext/tmail/base64/extconf.rb +38 -0
  20. data/ext/tmail/scanner_c/MANIFEST +4 -0
  21. data/ext/tmail/scanner_c/depend +1 -0
  22. data/ext/tmail/scanner_c/extconf.rb +38 -0
  23. data/ext/tmail/scanner_c/scanner_c.c +582 -0
  24. data/lib/tmail.rb +4 -0
  25. data/lib/tmail/Makefile +19 -0
  26. data/lib/tmail/address.rb +245 -0
  27. data/lib/tmail/attachments.rb +47 -0
  28. data/lib/tmail/base64.rb +75 -0
  29. data/lib/tmail/compat.rb +39 -0
  30. data/lib/tmail/config.rb +71 -0
  31. data/lib/tmail/core_extensions.rb +67 -0
  32. data/lib/tmail/encode.rb +524 -0
  33. data/lib/tmail/header.rb +931 -0
  34. data/lib/tmail/index.rb +8 -0
  35. data/lib/tmail/interface.rb +540 -0
  36. data/lib/tmail/loader.rb +1 -0
  37. data/lib/tmail/mail.rb +507 -0
  38. data/lib/tmail/mailbox.rb +435 -0
  39. data/lib/tmail/mbox.rb +1 -0
  40. data/lib/tmail/net.rb +282 -0
  41. data/lib/tmail/obsolete.rb +137 -0
  42. data/lib/tmail/parser.rb +1475 -0
  43. data/lib/tmail/parser.y +381 -0
  44. data/lib/tmail/port.rb +379 -0
  45. data/lib/tmail/quoting.rb +142 -0
  46. data/lib/tmail/require_arch.rb +56 -0
  47. data/lib/tmail/scanner.rb +44 -0
  48. data/lib/tmail/scanner_r.rb +263 -0
  49. data/lib/tmail/stringio.rb +279 -0
  50. data/lib/tmail/tmail.rb +1 -0
  51. data/lib/tmail/utils.rb +281 -0
  52. data/lib/tmail/version.rb +38 -0
  53. data/meta/icli.yaml +16 -0
  54. data/meta/tmail-1.1.1.roll +24 -0
  55. data/sample/data/multipart +23 -0
  56. data/sample/data/normal +29 -0
  57. data/sample/data/sendtest +5 -0
  58. data/sample/data/simple +14 -0
  59. data/sample/data/test +27 -0
  60. data/sample/extract-attachements.rb +33 -0
  61. data/sample/from-check.rb +26 -0
  62. data/sample/multipart.rb +26 -0
  63. data/sample/parse-bench.rb +68 -0
  64. data/sample/parse-test.rb +19 -0
  65. data/sample/sendmail.rb +94 -0
  66. data/test/extctrl.rb +6 -0
  67. data/test/fixtures/raw_base64_decoded_string +0 -0
  68. data/test/fixtures/raw_base64_email +83 -0
  69. data/test/fixtures/raw_base64_encoded_string +1 -0
  70. data/test/fixtures/raw_email +14 -0
  71. data/test/fixtures/raw_email10 +20 -0
  72. data/test/fixtures/raw_email11 +34 -0
  73. data/test/fixtures/raw_email12 +32 -0
  74. data/test/fixtures/raw_email13 +29 -0
  75. data/test/fixtures/raw_email2 +114 -0
  76. data/test/fixtures/raw_email3 +70 -0
  77. data/test/fixtures/raw_email4 +59 -0
  78. data/test/fixtures/raw_email5 +19 -0
  79. data/test/fixtures/raw_email6 +20 -0
  80. data/test/fixtures/raw_email7 +66 -0
  81. data/test/fixtures/raw_email8 +47 -0
  82. data/test/fixtures/raw_email9 +28 -0
  83. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  84. data/test/fixtures/raw_email_simple +11 -0
  85. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  86. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  87. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  88. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  89. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  90. data/test/kcode.rb +14 -0
  91. data/test/test_address.rb +1128 -0
  92. data/test/test_attachments.rb +35 -0
  93. data/test/test_base64.rb +63 -0
  94. data/test/test_encode.rb +77 -0
  95. data/test/test_header.rb +885 -0
  96. data/test/test_helper.rb +2 -0
  97. data/test/test_mail.rb +623 -0
  98. data/test/test_mbox.rb +126 -0
  99. data/test/test_port.rb +430 -0
  100. data/test/test_scanner.rb +209 -0
  101. data/test/test_utils.rb +37 -0
  102. 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
@@ -0,0 +1 @@
1
+ require 'tmail/mailbox'
@@ -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