rumbster 1.0.0

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