rumbster 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,372 @@
1
+ #
2
+ # parser.y
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
+ class TMail::Parser
12
+
13
+ options no_result_var
14
+
15
+ rule
16
+
17
+ content : DATETIME datetime { val[1] }
18
+ | RECEIVED received { val[1] }
19
+ | MADDRESS addrs_TOP { val[1] }
20
+ | RETPATH retpath { val[1] }
21
+ | KEYWORDS keys { val[1] }
22
+ | ENCRYPTED enc { val[1] }
23
+ | MIMEVERSION version { val[1] }
24
+ | CTYPE ctype { val[1] }
25
+ | CENCODING cencode { val[1] }
26
+ | CDISPOSITION cdisp { val[1] }
27
+ | ADDRESS addr_TOP { val[1] }
28
+ | MAILBOX mbox { val[1] }
29
+
30
+ datetime : day DIGIT ATOM DIGIT hour zone
31
+ # 0 1 2 3 4 5
32
+ # date month year
33
+ {
34
+ t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
35
+ (t + val[4] - val[5]).localtime
36
+ }
37
+
38
+ day : /* none */
39
+ | ATOM ','
40
+
41
+ hour : DIGIT ':' DIGIT
42
+ {
43
+ (val[0].to_i * 60 * 60) +
44
+ (val[2].to_i * 60)
45
+ }
46
+ | DIGIT ':' DIGIT ':' DIGIT
47
+ {
48
+ (val[0].to_i * 60 * 60) +
49
+ (val[2].to_i * 60) +
50
+ (val[4].to_i)
51
+ }
52
+
53
+ zone : ATOM
54
+ {
55
+ timezone_string_to_unixtime(val[0])
56
+ }
57
+
58
+ received : from by via with id for received_datetime
59
+ {
60
+ val
61
+ }
62
+
63
+ from : /* none */
64
+ | FROM received_domain
65
+ {
66
+ val[1]
67
+ }
68
+
69
+ by : /* none */
70
+ | BY received_domain
71
+ {
72
+ val[1]
73
+ }
74
+
75
+ received_domain
76
+ : domain
77
+ {
78
+ join_domain(val[0])
79
+ }
80
+ | domain '@' domain
81
+ {
82
+ join_domain(val[2])
83
+ }
84
+ | domain DOMLIT
85
+ {
86
+ join_domain(val[0])
87
+ }
88
+
89
+ via : /* none */
90
+ | VIA ATOM
91
+ {
92
+ val[1]
93
+ }
94
+
95
+ with : /* none */
96
+ {
97
+ []
98
+ }
99
+ | with WITH ATOM
100
+ {
101
+ val[0].push val[2]
102
+ val[0]
103
+ }
104
+
105
+ id : /* none */
106
+ | ID msgid
107
+ {
108
+ val[1]
109
+ }
110
+ | ID ATOM
111
+ {
112
+ val[1]
113
+ }
114
+
115
+ for : /* none */
116
+ | FOR received_addrspec
117
+ {
118
+ val[1]
119
+ }
120
+
121
+ received_addrspec
122
+ : routeaddr
123
+ {
124
+ val[0].spec
125
+ }
126
+ | spec
127
+ {
128
+ val[0].spec
129
+ }
130
+
131
+ received_datetime
132
+ : /* none */
133
+ | ';' datetime
134
+ {
135
+ val[1]
136
+ }
137
+
138
+ addrs_TOP : addrs
139
+ | group_bare
140
+ | addrs commas group_bare
141
+
142
+ addr_TOP : mbox
143
+ | group
144
+ | group_bare
145
+
146
+ retpath : addrs_TOP
147
+ | '<' '>' { [ Address.new(nil, nil) ] }
148
+
149
+ addrs : addr { val }
150
+ | addrs commas addr { val[0].push val[2]; val[0] }
151
+
152
+ addr : mbox
153
+ | group
154
+
155
+ mboxes : mbox
156
+ {
157
+ val
158
+ }
159
+ | mboxes commas mbox
160
+ {
161
+ val[0].push val[2]
162
+ val[0]
163
+ }
164
+
165
+ mbox : spec
166
+ | routeaddr
167
+ | addr_phrase routeaddr
168
+ {
169
+ val[1].phrase = Decoder.decode(val[0])
170
+ val[1]
171
+ }
172
+
173
+ group : group_bare ';'
174
+
175
+ group_bare: addr_phrase ':' mboxes
176
+ {
177
+ AddressGroup.new(val[0], val[2])
178
+ }
179
+ | addr_phrase ':' { AddressGroup.new(val[0], []) }
180
+
181
+ addr_phrase
182
+ : local_head { val[0].join('.') }
183
+ | addr_phrase local_head { val[0] << ' ' << val[1].join('.') }
184
+
185
+ routeaddr : '<' routes spec '>'
186
+ {
187
+ val[2].routes.replace val[1]
188
+ val[2]
189
+ }
190
+ | '<' spec '>'
191
+ {
192
+ val[1]
193
+ }
194
+
195
+ routes : at_domains ':'
196
+
197
+ at_domains: '@' domain { [ val[1].join('.') ] }
198
+ | at_domains ',' '@' domain { val[0].push val[3].join('.'); val[0] }
199
+
200
+ spec : local '@' domain { Address.new( val[0], val[2] ) }
201
+ | local { Address.new( val[0], nil ) }
202
+
203
+ local: local_head
204
+ | local_head '.' { val[0].push ''; val[0] }
205
+
206
+ local_head: word
207
+ { val }
208
+ | local_head dots word
209
+ {
210
+ val[1].times do
211
+ val[0].push ''
212
+ end
213
+ val[0].push val[2]
214
+ val[0]
215
+ }
216
+
217
+ domain : domword
218
+ { val }
219
+ | domain dots domword
220
+ {
221
+ val[1].times do
222
+ val[0].push ''
223
+ end
224
+ val[0].push val[2]
225
+ val[0]
226
+ }
227
+
228
+ dots : '.' { 0 }
229
+ | '.' '.' { 1 }
230
+
231
+ word : atom
232
+ | QUOTED
233
+ | DIGIT
234
+
235
+ domword : atom
236
+ | DOMLIT
237
+ | DIGIT
238
+
239
+ commas : ','
240
+ | commas ','
241
+
242
+ msgid : '<' spec '>'
243
+ {
244
+ val[1] = val[1].spec
245
+ val.join('')
246
+ }
247
+
248
+ keys : phrase { val }
249
+ | keys ',' phrase { val[0].push val[2]; val[0] }
250
+
251
+ phrase : word
252
+ | phrase word { val[0] << ' ' << val[1] }
253
+
254
+ enc : word
255
+ {
256
+ val.push nil
257
+ val
258
+ }
259
+ | word word
260
+ {
261
+ val
262
+ }
263
+
264
+ version : DIGIT '.' DIGIT
265
+ {
266
+ [ val[0].to_i, val[2].to_i ]
267
+ }
268
+
269
+ ctype : TOKEN '/' TOKEN params opt_semicolon
270
+ {
271
+ [ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
272
+ }
273
+ | TOKEN params opt_semicolon
274
+ {
275
+ [ val[0].downcase, nil, decode_params(val[1]) ]
276
+ }
277
+
278
+ params : /* none */
279
+ {
280
+ {}
281
+ }
282
+ | params ';' TOKEN '=' value
283
+ {
284
+ val[0][ val[2].downcase ] = val[4]
285
+ val[0]
286
+ }
287
+
288
+ value : TOKEN
289
+ | QUOTED
290
+
291
+ cencode : TOKEN
292
+ {
293
+ val[0].downcase
294
+ }
295
+
296
+ cdisp : TOKEN params opt_semicolon
297
+ {
298
+ [ val[0].downcase, decode_params(val[1]) ]
299
+ }
300
+
301
+ opt_semicolon
302
+ :
303
+ | ';'
304
+
305
+ atom : ATOM
306
+ | FROM
307
+ | BY
308
+ | VIA
309
+ | WITH
310
+ | ID
311
+ | FOR
312
+
313
+ end
314
+
315
+
316
+ ---- header
317
+ #
318
+ # parser.rb
319
+ #
320
+ # Copyright (c) 1998-2004 Minero Aoki
321
+ #
322
+ # This program is free software.
323
+ # You can distribute/modify this program under the terms of
324
+ # the GNU Lesser General Public License version 2.1.
325
+ #
326
+
327
+ require 'tmail/scanner'
328
+ require 'tmail/textutils'
329
+
330
+ ---- inner
331
+
332
+ include TextUtils
333
+
334
+ def self.parse( ident, str, cmt = nil )
335
+ new.parse(ident, str, cmt)
336
+ end
337
+
338
+ MAILP_DEBUG = false
339
+
340
+ def initialize
341
+ self.debug = MAILP_DEBUG
342
+ end
343
+
344
+ def debug=( flag )
345
+ @yydebug = flag && Racc_debug_parser
346
+ @scanner_debug = flag
347
+ end
348
+
349
+ def debug
350
+ @yydebug
351
+ end
352
+
353
+ def parse( ident, str, comments = nil )
354
+ @scanner = Scanner.new(str, ident, comments)
355
+ @scanner.debug = @scanner_debug
356
+ @first = [ident, ident]
357
+ result = yyparse(self, :parse_in)
358
+ comments.map! {|c| to_kcode(c) } if comments
359
+ result
360
+ end
361
+
362
+ private
363
+
364
+ def parse_in( &block )
365
+ yield @first
366
+ @scanner.scan(&block)
367
+ end
368
+
369
+ def on_error( t, val, vstack )
370
+ raise SyntaxError, "parse error on token #{racc_token2str t}"
371
+ end
372
+
@@ -0,0 +1,356 @@
1
+ #
2
+ # port.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/stringio'
12
+
13
+ module TMail
14
+
15
+ class Port
16
+ def reproducible?
17
+ false
18
+ end
19
+ end
20
+
21
+
22
+ ###
23
+ ### FilePort
24
+ ###
25
+
26
+ class FilePort < Port
27
+
28
+ def initialize(fname)
29
+ @path = File.expand_path(fname)
30
+ super()
31
+ end
32
+
33
+ attr_reader :path
34
+ alias filename path
35
+
36
+ alias ident path
37
+
38
+ def ==(other)
39
+ other.respond_to?(:path) and @path == other.path
40
+ end
41
+
42
+ alias eql? ==
43
+
44
+ def hash
45
+ @path.hash
46
+ end
47
+
48
+ def inspect
49
+ "#<#{self.class} #{@path}>"
50
+ end
51
+
52
+ def reproducible?
53
+ true
54
+ end
55
+
56
+ def size
57
+ File.size(@path)
58
+ end
59
+
60
+
61
+ def ropen(&block)
62
+ File.open(@path, &block)
63
+ end
64
+
65
+ def wopen(&block)
66
+ File.open(@path, 'w', &block)
67
+ end
68
+
69
+ def aopen(&block)
70
+ File.open(@path, 'a', &block)
71
+ end
72
+
73
+
74
+ def read_all
75
+ ropen {|f|
76
+ return f.read
77
+ }
78
+ end
79
+
80
+
81
+ def remove
82
+ File.unlink @path
83
+ end
84
+
85
+ def move_to(port)
86
+ begin
87
+ File.link @path, port.path
88
+ rescue Errno::EXDEV
89
+ copy_to port
90
+ end
91
+ File.unlink @path
92
+ end
93
+
94
+ alias mv move_to
95
+
96
+ def copy_to(port)
97
+ if port.is_a?(FilePort)
98
+ copy_file @path, port.path
99
+ else
100
+ File.open(@path) {|r|
101
+ port.wopen {|w|
102
+ while s = r.sysread(4096)
103
+ w.write << s
104
+ end
105
+ }
106
+ }
107
+ end
108
+ end
109
+
110
+ alias cp copy_to
111
+
112
+ private
113
+
114
+ def copy_file(src, dest)
115
+ File.open(src, 'rb') {|r|
116
+ File.open(dest, 'wb') {|w|
117
+ while str = r.read(2048)
118
+ w.write str
119
+ end
120
+ }
121
+ }
122
+ end
123
+
124
+ end
125
+
126
+
127
+ module MailFlags
128
+
129
+ def seen=(b)
130
+ set_status 'S', b
131
+ end
132
+
133
+ def seen?
134
+ get_status 'S'
135
+ end
136
+
137
+ def replied=(b)
138
+ set_status 'R', b
139
+ end
140
+
141
+ def replied?
142
+ get_status 'R'
143
+ end
144
+
145
+ def flagged=(b)
146
+ set_status 'F', b
147
+ end
148
+
149
+ def flagged?
150
+ get_status 'F'
151
+ end
152
+
153
+ private
154
+
155
+ def procinfostr(str, tag, true_p)
156
+ a = str.upcase.split(//)
157
+ a.push true_p ? tag : nil
158
+ a.delete tag unless true_p
159
+ a.compact.sort.join('').squeeze
160
+ end
161
+
162
+ end
163
+
164
+
165
+ class MhPort < FilePort
166
+
167
+ include MailFlags
168
+
169
+ private
170
+
171
+ def set_status(tag, flag)
172
+ begin
173
+ tmpfile = "#{@path}.tmailtmp.#{$$}"
174
+ File.open(tmpfile, 'w') {|f|
175
+ write_status f, tag, flag
176
+ }
177
+ File.unlink @path
178
+ File.link tmpfile, @path
179
+ ensure
180
+ File.unlink tmpfile
181
+ end
182
+ end
183
+
184
+ def write_status(f, tag, flag)
185
+ stat = ''
186
+ File.open(@path) {|r|
187
+ while line = r.gets
188
+ if line.strip.empty?
189
+ break
190
+ elsif m = /\AX-TMail-Status:/i.match(line)
191
+ stat = m.post_match.strip
192
+ else
193
+ f.print line
194
+ end
195
+ end
196
+
197
+ s = procinfostr(stat, tag, flag)
198
+ f.puts 'X-TMail-Status: ' + s unless s.empty?
199
+ f.puts
200
+
201
+ while s = r.read(2048)
202
+ f.write s
203
+ end
204
+ }
205
+ end
206
+
207
+ def get_status(tag)
208
+ File.foreach(@path) {|line|
209
+ return false if line.strip.empty?
210
+ if m = /\AX-TMail-Status:/i.match(line)
211
+ return m.post_match.strip.include?(tag[0])
212
+ end
213
+ }
214
+ false
215
+ end
216
+
217
+ end
218
+
219
+
220
+ class MaildirPort < FilePort
221
+
222
+ def move_to_new
223
+ new = replace_dir(@path, 'new')
224
+ File.rename @path, new
225
+ @path = new
226
+ end
227
+
228
+ def move_to_cur
229
+ new = replace_dir(@path, 'cur')
230
+ File.rename @path, new
231
+ @path = new
232
+ end
233
+
234
+ def replace_dir(path, dir)
235
+ "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
236
+ end
237
+ private :replace_dir
238
+
239
+
240
+ include MailFlags
241
+
242
+ private
243
+
244
+ MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
245
+
246
+ def set_status(tag, flag)
247
+ if m = MAIL_FILE.match(File.basename(@path))
248
+ s, uniq, type, info, = m.to_a
249
+ return if type and type != '2' # do not change anything
250
+ newname = File.dirname(@path) + '/' +
251
+ uniq + ':2,' + procinfostr(info.to_s, tag, flag)
252
+ else
253
+ newname = @path + ':2,' + tag
254
+ end
255
+
256
+ File.link @path, newname
257
+ File.unlink @path
258
+ @path = newname
259
+ end
260
+
261
+ def get_status(tag)
262
+ m = MAIL_FILE.match(File.basename(@path)) or return false
263
+ m[2] == '2' and m[3].to_s.include?(tag[0])
264
+ end
265
+
266
+ end
267
+
268
+
269
+ ###
270
+ ### StringPort
271
+ ###
272
+
273
+ class StringPort < Port
274
+
275
+ def initialize(str = '')
276
+ @buffer = str
277
+ super()
278
+ end
279
+
280
+ def string
281
+ @buffer
282
+ end
283
+
284
+ def to_s
285
+ @buffer.dup
286
+ end
287
+
288
+ alias read_all to_s
289
+
290
+ def size
291
+ @buffer.size
292
+ end
293
+
294
+ def ==(other)
295
+ other.is_a?(StringPort) and @buffer.equal?(other.string)
296
+ end
297
+
298
+ alias eql? ==
299
+
300
+ def hash
301
+ @buffer.object_id.hash
302
+ end
303
+
304
+ def inspect
305
+ "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
306
+ end
307
+
308
+ def reproducible?
309
+ true
310
+ end
311
+
312
+ def ropen(&block)
313
+ # FIXME: Should we raise ENOENT?
314
+ raise Errno::ENOENT, "#{inspect} is already removed" unless @buffer
315
+ StringInput.open(@buffer, &block)
316
+ end
317
+
318
+ def wopen(&block)
319
+ @buffer = ''
320
+ StringOutput.new(@buffer, &block)
321
+ end
322
+
323
+ def aopen(&block)
324
+ @buffer ||= ''
325
+ StringOutput.new(@buffer, &block)
326
+ end
327
+
328
+ def remove
329
+ @buffer = nil
330
+ end
331
+
332
+ alias rm remove
333
+
334
+ def copy_to(port)
335
+ port.wopen {|f|
336
+ f.write @buffer
337
+ }
338
+ end
339
+
340
+ alias cp copy_to
341
+
342
+ def move_to(port)
343
+ if port.is_a?(StringPort)
344
+ tmp = @buffer
345
+ port.instance_eval {
346
+ @buffer = tmp
347
+ }
348
+ else
349
+ copy_to port
350
+ end
351
+ remove
352
+ end
353
+
354
+ end
355
+
356
+ end # module TMail