rumbster 1.0.0 → 1.0.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/.autotest +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +202 -0
- data/README.rdoc +50 -0
- data/Rakefile +50 -8
- data/VERSION +1 -0
- data/lib/message_observers.rb +3 -3
- data/lib/rumbster.rb +0 -0
- data/lib/smtp_protocol.rb +7 -7
- data/lib/smtp_states.rb +24 -58
- data/rumbster.gemspec +70 -0
- data/test/{message_observers_test.rb → test_message_observers.rb} +1 -1
- data/test/{rumbster_test.rb → test_rumbster.rb} +1 -1
- data/test/{smtp_protocol_test.rb → test_smtp_protocol.rb} +2 -19
- data/test/{smtp_states_test.rb → test_smtp_states.rb} +31 -87
- metadata +128 -76
- data/COPYING +0 -515
- data/README +0 -56
- data/vendor/tmail.rb +0 -4
- data/vendor/tmail/.cvsignore +0 -3
- data/vendor/tmail/Makefile +0 -19
- data/vendor/tmail/address.rb +0 -222
- data/vendor/tmail/base64.rb +0 -52
- data/vendor/tmail/compat.rb +0 -39
- data/vendor/tmail/config.rb +0 -50
- data/vendor/tmail/encode.rb +0 -447
- data/vendor/tmail/header.rb +0 -895
- data/vendor/tmail/info.rb +0 -14
- data/vendor/tmail/loader.rb +0 -1
- data/vendor/tmail/mail.rb +0 -869
- data/vendor/tmail/mailbox.rb +0 -386
- data/vendor/tmail/mbox.rb +0 -1
- data/vendor/tmail/net.rb +0 -260
- data/vendor/tmail/obsolete.rb +0 -122
- data/vendor/tmail/parser.rb +0 -1475
- data/vendor/tmail/parser.y +0 -372
- data/vendor/tmail/port.rb +0 -356
- data/vendor/tmail/scanner.rb +0 -22
- data/vendor/tmail/scanner_r.rb +0 -243
- data/vendor/tmail/stringio.rb +0 -256
- data/vendor/tmail/textutils.rb +0 -197
- data/vendor/tmail/tmail.rb +0 -1
- data/vendor/tmail/utils.rb +0 -23
data/vendor/tmail/encode.rb
DELETED
@@ -1,447 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# encode.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
|
-
require 'tmail/base64.rb'
|
13
|
-
require 'tmail/stringio'
|
14
|
-
require 'tmail/textutils'
|
15
|
-
|
16
|
-
|
17
|
-
module TMail
|
18
|
-
|
19
|
-
module StrategyInterface
|
20
|
-
|
21
|
-
def create_dest(obj)
|
22
|
-
case obj
|
23
|
-
when nil
|
24
|
-
StringOutput.new
|
25
|
-
when String
|
26
|
-
StringOutput.new(obj)
|
27
|
-
when IO, StringOutput
|
28
|
-
obj
|
29
|
-
else
|
30
|
-
raise TypeError, 'cannot handle this type of object for dest'
|
31
|
-
end
|
32
|
-
end
|
33
|
-
module_function :create_dest
|
34
|
-
|
35
|
-
def encoded(eol = "\r\n", charset = 'j', dest = nil)
|
36
|
-
accept_strategy Encoder, eol, charset, dest
|
37
|
-
end
|
38
|
-
|
39
|
-
def decoded(eol = "\n", charset = 'e', dest = nil)
|
40
|
-
accept_strategy Decoder, eol, charset, dest
|
41
|
-
end
|
42
|
-
|
43
|
-
alias to_s decoded
|
44
|
-
|
45
|
-
def accept_strategy(klass, eol, charset, dest = nil)
|
46
|
-
dest ||= ''
|
47
|
-
accept klass.new(create_dest(dest), charset, eol)
|
48
|
-
dest
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
###
|
55
|
-
### MIME B encoding decoder
|
56
|
-
###
|
57
|
-
|
58
|
-
class Decoder
|
59
|
-
|
60
|
-
include TextUtils
|
61
|
-
|
62
|
-
encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
|
63
|
-
ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
|
64
|
-
|
65
|
-
OUTPUT_ENCODING = {
|
66
|
-
'EUC' => 'e',
|
67
|
-
'SJIS' => 's',
|
68
|
-
}
|
69
|
-
|
70
|
-
def self.decode(str, encoding = nil)
|
71
|
-
encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
|
72
|
-
opt = '-m' + encoding
|
73
|
-
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
|
74
|
-
end
|
75
|
-
|
76
|
-
def initialize(dest, encoding = nil, eol = "\n")
|
77
|
-
@f = StrategyInterface.create_dest(dest)
|
78
|
-
@encoding = (/\A[ejs]/ =~ encoding) ? encoding[0,1] : nil
|
79
|
-
@eol = eol
|
80
|
-
end
|
81
|
-
|
82
|
-
def decode(str)
|
83
|
-
self.class.decode(str, @encoding)
|
84
|
-
end
|
85
|
-
private :decode
|
86
|
-
|
87
|
-
def terminate
|
88
|
-
end
|
89
|
-
|
90
|
-
def header_line(str)
|
91
|
-
@f << decode(str)
|
92
|
-
end
|
93
|
-
|
94
|
-
def header_name(nm)
|
95
|
-
@f << nm << ': '
|
96
|
-
end
|
97
|
-
|
98
|
-
def header_body(str)
|
99
|
-
@f << decode(str)
|
100
|
-
end
|
101
|
-
|
102
|
-
def space
|
103
|
-
@f << ' '
|
104
|
-
end
|
105
|
-
|
106
|
-
alias spc space
|
107
|
-
|
108
|
-
def lwsp(str)
|
109
|
-
@f << str
|
110
|
-
end
|
111
|
-
|
112
|
-
def meta(str)
|
113
|
-
@f << str
|
114
|
-
end
|
115
|
-
|
116
|
-
def text(str)
|
117
|
-
@f << decode(str)
|
118
|
-
end
|
119
|
-
|
120
|
-
def phrase(str)
|
121
|
-
@f << quote_phrase(decode(str))
|
122
|
-
end
|
123
|
-
|
124
|
-
def kv_pair(k, v)
|
125
|
-
@f << k << '=' << v
|
126
|
-
end
|
127
|
-
|
128
|
-
def puts(str = nil)
|
129
|
-
@f << str if str
|
130
|
-
@f << @eol
|
131
|
-
end
|
132
|
-
|
133
|
-
def write(str)
|
134
|
-
@f << str
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
|
140
|
-
###
|
141
|
-
### MIME B-encoding encoder
|
142
|
-
###
|
143
|
-
|
144
|
-
#
|
145
|
-
# FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
|
146
|
-
#
|
147
|
-
class Encoder
|
148
|
-
|
149
|
-
include TextUtils
|
150
|
-
|
151
|
-
BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
|
152
|
-
|
153
|
-
def Encoder.encode(str)
|
154
|
-
e = new()
|
155
|
-
e.header_body str
|
156
|
-
e.terminate
|
157
|
-
e.dest.string
|
158
|
-
end
|
159
|
-
|
160
|
-
SPACER = "\t"
|
161
|
-
MAX_LINE_LEN = 70
|
162
|
-
|
163
|
-
OPTIONS = {
|
164
|
-
'EUC' => '-Ej -m0',
|
165
|
-
'SJIS' => '-Sj -m0',
|
166
|
-
'UTF8' => nil, # FIXME
|
167
|
-
'NONE' => nil
|
168
|
-
}
|
169
|
-
|
170
|
-
def initialize(dest = nil, encoding = nil, eol = "\r\n", limit = nil)
|
171
|
-
@f = StrategyInterface.create_dest(dest)
|
172
|
-
@opt = OPTIONS[$KCODE]
|
173
|
-
@eol = eol
|
174
|
-
reset
|
175
|
-
end
|
176
|
-
|
177
|
-
def normalize_encoding(str)
|
178
|
-
if @opt
|
179
|
-
then NKF.nkf(@opt, str)
|
180
|
-
else str
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def reset
|
185
|
-
@text = ''
|
186
|
-
@lwsp = ''
|
187
|
-
@curlen = 0
|
188
|
-
end
|
189
|
-
|
190
|
-
def terminate
|
191
|
-
add_lwsp ''
|
192
|
-
reset
|
193
|
-
end
|
194
|
-
|
195
|
-
def dest
|
196
|
-
@f
|
197
|
-
end
|
198
|
-
|
199
|
-
def puts(str = nil)
|
200
|
-
@f << str if str
|
201
|
-
@f << @eol
|
202
|
-
end
|
203
|
-
|
204
|
-
def write(str)
|
205
|
-
@f << str
|
206
|
-
end
|
207
|
-
|
208
|
-
#
|
209
|
-
# add
|
210
|
-
#
|
211
|
-
|
212
|
-
def header_line(line)
|
213
|
-
scanadd line
|
214
|
-
end
|
215
|
-
|
216
|
-
def header_name(name)
|
217
|
-
add_text name.split(/-/).map {|i| i.capitalize }.join('-')
|
218
|
-
add_text ':'
|
219
|
-
add_lwsp ' '
|
220
|
-
end
|
221
|
-
|
222
|
-
def header_body(str)
|
223
|
-
scanadd normalize_encoding(str)
|
224
|
-
end
|
225
|
-
|
226
|
-
def space
|
227
|
-
add_lwsp ' '
|
228
|
-
end
|
229
|
-
|
230
|
-
alias spc space
|
231
|
-
|
232
|
-
def lwsp(str)
|
233
|
-
add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
|
234
|
-
end
|
235
|
-
|
236
|
-
def meta(str)
|
237
|
-
add_text str
|
238
|
-
end
|
239
|
-
|
240
|
-
def text(str)
|
241
|
-
scanadd normalize_encoding(str)
|
242
|
-
end
|
243
|
-
|
244
|
-
def phrase(str)
|
245
|
-
str = normalize_encoding(str)
|
246
|
-
if CONTROL_CHAR =~ str
|
247
|
-
scanadd str
|
248
|
-
else
|
249
|
-
add_text quote_phrase(str)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
# FIXME: implement line folding
|
254
|
-
#
|
255
|
-
def kv_pair(k, v)
|
256
|
-
v = normalize_encoding(v)
|
257
|
-
if token_safe?(v)
|
258
|
-
add_text k + '=' + v
|
259
|
-
elsif not CONTROL_CHAR =~ v
|
260
|
-
add_text k + '=' + quote_token(v)
|
261
|
-
else
|
262
|
-
# apply RFC2231 encoding
|
263
|
-
kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
|
264
|
-
add_text kv
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def encode_value(str)
|
269
|
-
str.gsub(RFC2231_UNSAFE) {|s| '%%%02X' % s[0] }
|
270
|
-
end
|
271
|
-
|
272
|
-
private
|
273
|
-
|
274
|
-
def scanadd(str, force = false)
|
275
|
-
types = ''
|
276
|
-
strs = []
|
277
|
-
|
278
|
-
until str.empty?
|
279
|
-
if m = /\A[^\e\t\r\n ]+/.match(str)
|
280
|
-
types << (force ? 'j' : 'a')
|
281
|
-
strs.push m[0]
|
282
|
-
|
283
|
-
elsif m = /\A[\t\r\n ]+/.match(str)
|
284
|
-
types << 's'
|
285
|
-
strs.push m[0]
|
286
|
-
|
287
|
-
elsif m = /\A\e../.match(str)
|
288
|
-
esc = m[0]
|
289
|
-
str = m.post_match
|
290
|
-
if esc != "\e(B" and m = /\A[^\e]+/.match(str)
|
291
|
-
types << 'j'
|
292
|
-
strs.push m[0]
|
293
|
-
end
|
294
|
-
|
295
|
-
else
|
296
|
-
raise 'TMail FATAL: encoder scan fail'
|
297
|
-
end
|
298
|
-
str = m.post_match
|
299
|
-
end
|
300
|
-
|
301
|
-
do_encode types, strs
|
302
|
-
end
|
303
|
-
|
304
|
-
def do_encode(types, strs)
|
305
|
-
#
|
306
|
-
# result : (A|E)(S(A|E))*
|
307
|
-
# E : W(SW)*
|
308
|
-
# W : (J|A)+ but must contain J # (J|A)*J(J|A)*
|
309
|
-
# A : <<A character string not to be encoded>>
|
310
|
-
# J : <<A character string to be encoded>>
|
311
|
-
# S : <<LWSP>>
|
312
|
-
#
|
313
|
-
# An encoding unit is `E'.
|
314
|
-
# Input (parameter `types') is (J|A)(J|A|S)*(J|A)
|
315
|
-
#
|
316
|
-
if BENCODE_DEBUG
|
317
|
-
puts
|
318
|
-
puts '-- do_encode ------------'
|
319
|
-
puts types.split(//).join(' ')
|
320
|
-
p strs
|
321
|
-
end
|
322
|
-
|
323
|
-
e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
|
324
|
-
|
325
|
-
while m = e.match(types)
|
326
|
-
pre = m.pre_match
|
327
|
-
concat_A_S pre, strs[0, pre.size] unless pre.empty?
|
328
|
-
concat_E m[0], strs[m.begin(0) ... m.end(0)]
|
329
|
-
types = m.post_match
|
330
|
-
strs.slice! 0, m.end(0)
|
331
|
-
end
|
332
|
-
concat_A_S types, strs
|
333
|
-
end
|
334
|
-
|
335
|
-
def concat_A_S(types, strs)
|
336
|
-
i = 0
|
337
|
-
types.each_byte do |t|
|
338
|
-
case t
|
339
|
-
when ?a then add_text strs[i]
|
340
|
-
when ?s then add_lwsp strs[i]
|
341
|
-
else
|
342
|
-
raise "TMail FATAL: unknown flag: #{t.chr}"
|
343
|
-
end
|
344
|
-
i += 1
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
METHOD_ID = {
|
349
|
-
?j => :extract_J,
|
350
|
-
?e => :extract_E,
|
351
|
-
?a => :extract_A,
|
352
|
-
?s => :extract_S
|
353
|
-
}
|
354
|
-
|
355
|
-
def concat_E(types, strs)
|
356
|
-
if BENCODE_DEBUG
|
357
|
-
puts '---- concat_E'
|
358
|
-
puts "types=#{types.split(//).join(' ')}"
|
359
|
-
puts "strs =#{strs.inspect}"
|
360
|
-
end
|
361
|
-
|
362
|
-
flush() unless @text.empty?
|
363
|
-
|
364
|
-
chunk = ''
|
365
|
-
strs.each_with_index do |s,i|
|
366
|
-
mid = METHOD_ID[types[i]]
|
367
|
-
until s.empty?
|
368
|
-
unless c = __send__(mid, chunk.size, s)
|
369
|
-
add_with_encode chunk unless chunk.empty?
|
370
|
-
flush
|
371
|
-
chunk = ''
|
372
|
-
fold
|
373
|
-
c = __send__(mid, 0, s)
|
374
|
-
raise 'TMail FATAL: extract fail' unless c
|
375
|
-
end
|
376
|
-
chunk << c
|
377
|
-
end
|
378
|
-
end
|
379
|
-
add_with_encode chunk unless chunk.empty?
|
380
|
-
end
|
381
|
-
|
382
|
-
def extract_J(chunksize, str)
|
383
|
-
size = max_bytes(chunksize, str.size) - 6
|
384
|
-
size = (size % 2 == 0) ? (size) : (size - 1)
|
385
|
-
return nil if size <= 0
|
386
|
-
"\e$B#{str.slice!(0, size)}\e(B"
|
387
|
-
end
|
388
|
-
|
389
|
-
def extract_A(chunksize, str)
|
390
|
-
size = max_bytes(chunksize, str.size)
|
391
|
-
return nil if size <= 0
|
392
|
-
str.slice!(0, size)
|
393
|
-
end
|
394
|
-
|
395
|
-
alias extract_S extract_A
|
396
|
-
|
397
|
-
def max_bytes(chunksize, ssize)
|
398
|
-
(restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
|
399
|
-
end
|
400
|
-
|
401
|
-
#
|
402
|
-
# free length buffer
|
403
|
-
#
|
404
|
-
|
405
|
-
def add_text(str)
|
406
|
-
@text << str
|
407
|
-
# puts '---- text -------------------------------------'
|
408
|
-
# puts "+ #{str.inspect}"
|
409
|
-
# puts "txt >>>#{@text.inspect}<<<"
|
410
|
-
end
|
411
|
-
|
412
|
-
def add_with_encode(str)
|
413
|
-
@text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
|
414
|
-
end
|
415
|
-
|
416
|
-
def add_lwsp(lwsp)
|
417
|
-
# puts '---- lwsp -------------------------------------'
|
418
|
-
# puts "+ #{lwsp.inspect}"
|
419
|
-
fold if restsize() <= 0
|
420
|
-
flush
|
421
|
-
@lwsp = lwsp
|
422
|
-
end
|
423
|
-
|
424
|
-
def flush
|
425
|
-
# puts '---- flush ----'
|
426
|
-
# puts "spc >>>#{@lwsp.inspect}<<<"
|
427
|
-
# puts "txt >>>#{@text.inspect}<<<"
|
428
|
-
@f << @lwsp << @text
|
429
|
-
@curlen += (@lwsp.size + @text.size)
|
430
|
-
@text = ''
|
431
|
-
@lwsp = ''
|
432
|
-
end
|
433
|
-
|
434
|
-
def fold
|
435
|
-
# puts '---- fold ----'
|
436
|
-
@f << @eol
|
437
|
-
@curlen = 0
|
438
|
-
@lwsp = SPACER
|
439
|
-
end
|
440
|
-
|
441
|
-
def restsize
|
442
|
-
MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
|
443
|
-
end
|
444
|
-
|
445
|
-
end
|
446
|
-
|
447
|
-
end # module TMail
|
data/vendor/tmail/header.rb
DELETED
@@ -1,895 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# header.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/encode'
|
12
|
-
require 'tmail/address'
|
13
|
-
require 'tmail/parser'
|
14
|
-
require 'tmail/config'
|
15
|
-
require 'tmail/textutils'
|
16
|
-
|
17
|
-
|
18
|
-
module TMail
|
19
|
-
|
20
|
-
class HeaderField
|
21
|
-
|
22
|
-
include TextUtils
|
23
|
-
|
24
|
-
class << self
|
25
|
-
|
26
|
-
alias newobj new
|
27
|
-
|
28
|
-
def new(name, body, conf = DEFAULT_CONFIG)
|
29
|
-
klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
|
30
|
-
klass.newobj body, conf
|
31
|
-
end
|
32
|
-
|
33
|
-
def new_from_port(port, name, conf = DEFAULT_CONFIG)
|
34
|
-
re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i')
|
35
|
-
str = nil
|
36
|
-
port.ropen {|f|
|
37
|
-
f.each do |line|
|
38
|
-
if m = re.match(line) then str = m.post_match.strip
|
39
|
-
elsif str and /\A[\t ]/ =~ line then str << ' ' << line.strip
|
40
|
-
elsif /\A-*\s*\z/ =~ line then break
|
41
|
-
elsif str then break
|
42
|
-
end
|
43
|
-
end
|
44
|
-
}
|
45
|
-
new(name, str, Config.to_config(conf))
|
46
|
-
end
|
47
|
-
|
48
|
-
def internal_new(name, conf)
|
49
|
-
FNAME_TO_CLASS[name].newobj('', conf, true)
|
50
|
-
end
|
51
|
-
|
52
|
-
end # class << self
|
53
|
-
|
54
|
-
def initialize(body, conf, intern = false)
|
55
|
-
@body = body
|
56
|
-
@config = conf
|
57
|
-
|
58
|
-
@illegal = false
|
59
|
-
@parsed = false
|
60
|
-
if intern
|
61
|
-
@parsed = true
|
62
|
-
parse_init
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def inspect
|
67
|
-
"#<#{self.class} #{@body.inspect}>"
|
68
|
-
end
|
69
|
-
|
70
|
-
def illegal?
|
71
|
-
@illegal
|
72
|
-
end
|
73
|
-
|
74
|
-
def empty?
|
75
|
-
ensure_parsed
|
76
|
-
return true if @illegal
|
77
|
-
isempty?
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
|
82
|
-
def ensure_parsed
|
83
|
-
return if @parsed
|
84
|
-
@parsed = true
|
85
|
-
parse
|
86
|
-
end
|
87
|
-
|
88
|
-
# defabstract parse
|
89
|
-
# end
|
90
|
-
|
91
|
-
def clear_parse_status
|
92
|
-
@parsed = false
|
93
|
-
@illegal = false
|
94
|
-
end
|
95
|
-
|
96
|
-
public
|
97
|
-
|
98
|
-
def body
|
99
|
-
ensure_parsed
|
100
|
-
v = Decoder.new(s = '')
|
101
|
-
do_accept v
|
102
|
-
v.terminate
|
103
|
-
s
|
104
|
-
end
|
105
|
-
|
106
|
-
def body=(str)
|
107
|
-
@body = str
|
108
|
-
clear_parse_status
|
109
|
-
end
|
110
|
-
|
111
|
-
include StrategyInterface
|
112
|
-
|
113
|
-
def accept(strategy, dummy1 = nil, dummy2 = nil)
|
114
|
-
ensure_parsed
|
115
|
-
do_accept strategy
|
116
|
-
strategy.terminate
|
117
|
-
end
|
118
|
-
|
119
|
-
# abstract do_accept
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
class UnstructuredHeader < HeaderField
|
125
|
-
|
126
|
-
def body
|
127
|
-
ensure_parsed
|
128
|
-
@body
|
129
|
-
end
|
130
|
-
|
131
|
-
def body=(arg)
|
132
|
-
ensure_parsed
|
133
|
-
@body = arg
|
134
|
-
end
|
135
|
-
|
136
|
-
private
|
137
|
-
|
138
|
-
def parse_init
|
139
|
-
end
|
140
|
-
|
141
|
-
def parse
|
142
|
-
@body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
|
143
|
-
end
|
144
|
-
|
145
|
-
def isempty?
|
146
|
-
not @body
|
147
|
-
end
|
148
|
-
|
149
|
-
def do_accept(strategy)
|
150
|
-
strategy.text @body
|
151
|
-
end
|
152
|
-
|
153
|
-
end
|
154
|
-
|
155
|
-
|
156
|
-
class StructuredHeader < HeaderField
|
157
|
-
|
158
|
-
def comments
|
159
|
-
ensure_parsed
|
160
|
-
@comments
|
161
|
-
end
|
162
|
-
|
163
|
-
private
|
164
|
-
|
165
|
-
def parse
|
166
|
-
save = nil
|
167
|
-
|
168
|
-
begin
|
169
|
-
parse_init
|
170
|
-
do_parse
|
171
|
-
rescue SyntaxError
|
172
|
-
if not save and mime_encoded? @body
|
173
|
-
save = @body
|
174
|
-
@body = Decoder.decode(save)
|
175
|
-
retry
|
176
|
-
elsif save
|
177
|
-
@body = save
|
178
|
-
end
|
179
|
-
|
180
|
-
@illegal = true
|
181
|
-
raise if @config.strict_parse?
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def parse_init
|
186
|
-
@comments = []
|
187
|
-
init
|
188
|
-
end
|
189
|
-
|
190
|
-
def do_parse
|
191
|
-
obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
|
192
|
-
set obj if obj
|
193
|
-
end
|
194
|
-
|
195
|
-
end
|
196
|
-
|
197
|
-
|
198
|
-
class DateTimeHeader < StructuredHeader
|
199
|
-
|
200
|
-
PARSE_TYPE = :DATETIME
|
201
|
-
|
202
|
-
def date
|
203
|
-
ensure_parsed
|
204
|
-
@date
|
205
|
-
end
|
206
|
-
|
207
|
-
def date=(arg)
|
208
|
-
ensure_parsed
|
209
|
-
@date = arg
|
210
|
-
end
|
211
|
-
|
212
|
-
private
|
213
|
-
|
214
|
-
def init
|
215
|
-
@date = nil
|
216
|
-
end
|
217
|
-
|
218
|
-
def set(t)
|
219
|
-
@date = t
|
220
|
-
end
|
221
|
-
|
222
|
-
def isempty?
|
223
|
-
not @date
|
224
|
-
end
|
225
|
-
|
226
|
-
def do_accept(strategy)
|
227
|
-
strategy.meta time2str(@date)
|
228
|
-
end
|
229
|
-
|
230
|
-
end
|
231
|
-
|
232
|
-
|
233
|
-
class AddressHeader < StructuredHeader
|
234
|
-
|
235
|
-
PARSE_TYPE = :MADDRESS
|
236
|
-
|
237
|
-
def addrs
|
238
|
-
ensure_parsed
|
239
|
-
@addrs
|
240
|
-
end
|
241
|
-
|
242
|
-
private
|
243
|
-
|
244
|
-
def init
|
245
|
-
@addrs = []
|
246
|
-
end
|
247
|
-
|
248
|
-
def set(a)
|
249
|
-
@addrs = a
|
250
|
-
end
|
251
|
-
|
252
|
-
def isempty?
|
253
|
-
@addrs.empty?
|
254
|
-
end
|
255
|
-
|
256
|
-
def do_accept(strategy)
|
257
|
-
first = true
|
258
|
-
@addrs.each do |a|
|
259
|
-
if first
|
260
|
-
first = false
|
261
|
-
else
|
262
|
-
strategy.meta ','
|
263
|
-
strategy.space
|
264
|
-
end
|
265
|
-
a.accept strategy
|
266
|
-
end
|
267
|
-
|
268
|
-
@comments.each do |c|
|
269
|
-
strategy.space
|
270
|
-
strategy.meta '('
|
271
|
-
strategy.text c
|
272
|
-
strategy.meta ')'
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
end
|
277
|
-
|
278
|
-
|
279
|
-
class ReturnPathHeader < AddressHeader
|
280
|
-
|
281
|
-
PARSE_TYPE = :RETPATH
|
282
|
-
|
283
|
-
def addr
|
284
|
-
addrs()[0]
|
285
|
-
end
|
286
|
-
|
287
|
-
def spec
|
288
|
-
a = addr() or return nil
|
289
|
-
a.spec
|
290
|
-
end
|
291
|
-
|
292
|
-
def routes
|
293
|
-
a = addr() or return nil
|
294
|
-
a.routes
|
295
|
-
end
|
296
|
-
|
297
|
-
private
|
298
|
-
|
299
|
-
def do_accept(strategy)
|
300
|
-
a = addr()
|
301
|
-
|
302
|
-
strategy.meta '<'
|
303
|
-
unless a.routes.empty?
|
304
|
-
strategy.meta a.routes.map {|i| '@' + i }.join(',')
|
305
|
-
strategy.meta ':'
|
306
|
-
end
|
307
|
-
spec = a.spec
|
308
|
-
strategy.meta spec if spec
|
309
|
-
strategy.meta '>'
|
310
|
-
end
|
311
|
-
|
312
|
-
end
|
313
|
-
|
314
|
-
|
315
|
-
class SingleAddressHeader < AddressHeader
|
316
|
-
|
317
|
-
def addr
|
318
|
-
addrs()[0]
|
319
|
-
end
|
320
|
-
|
321
|
-
private
|
322
|
-
|
323
|
-
def do_accept(strategy)
|
324
|
-
a = addr()
|
325
|
-
a.accept strategy
|
326
|
-
@comments.each do |c|
|
327
|
-
strategy.space
|
328
|
-
strategy.meta '('
|
329
|
-
strategy.text c
|
330
|
-
strategy.meta ')'
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
end
|
335
|
-
|
336
|
-
|
337
|
-
class MessageIdHeader < StructuredHeader
|
338
|
-
|
339
|
-
def id
|
340
|
-
ensure_parsed
|
341
|
-
@id
|
342
|
-
end
|
343
|
-
|
344
|
-
def id=(arg)
|
345
|
-
ensure_parsed
|
346
|
-
@id = arg
|
347
|
-
end
|
348
|
-
|
349
|
-
private
|
350
|
-
|
351
|
-
def init
|
352
|
-
@id = nil
|
353
|
-
end
|
354
|
-
|
355
|
-
def isempty?
|
356
|
-
not @id
|
357
|
-
end
|
358
|
-
|
359
|
-
def do_parse
|
360
|
-
@id = @body.slice(MESSAGE_ID) or
|
361
|
-
raise SyntaxError, "wrong Message-ID format: #{@body}"
|
362
|
-
end
|
363
|
-
|
364
|
-
def do_accept(strategy)
|
365
|
-
strategy.meta @id
|
366
|
-
end
|
367
|
-
|
368
|
-
end
|
369
|
-
|
370
|
-
|
371
|
-
class ReferencesHeader < StructuredHeader
|
372
|
-
|
373
|
-
def refs
|
374
|
-
ensure_parsed
|
375
|
-
@refs
|
376
|
-
end
|
377
|
-
|
378
|
-
def each_id
|
379
|
-
self.refs.each do |i|
|
380
|
-
yield i if MESSAGE_ID =~ i
|
381
|
-
end
|
382
|
-
end
|
383
|
-
|
384
|
-
def ids
|
385
|
-
ensure_parsed
|
386
|
-
@ids
|
387
|
-
end
|
388
|
-
|
389
|
-
def each_phrase
|
390
|
-
self.refs.each do |i|
|
391
|
-
yield i unless MESSAGE_ID =~ i
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
def phrases
|
396
|
-
result = []
|
397
|
-
each_phrase do |i|
|
398
|
-
result.push i
|
399
|
-
end
|
400
|
-
result
|
401
|
-
end
|
402
|
-
|
403
|
-
private
|
404
|
-
|
405
|
-
def init
|
406
|
-
@refs = []
|
407
|
-
@ids = []
|
408
|
-
end
|
409
|
-
|
410
|
-
def isempty?
|
411
|
-
@ids.empty?
|
412
|
-
end
|
413
|
-
|
414
|
-
def do_parse
|
415
|
-
str = @body
|
416
|
-
while m = MESSAGE_ID.match(str)
|
417
|
-
pre = m.pre_match.strip
|
418
|
-
@refs.push pre unless pre.empty?
|
419
|
-
@refs.push s = m[0]
|
420
|
-
@ids.push s
|
421
|
-
str = m.post_match
|
422
|
-
end
|
423
|
-
str = str.strip
|
424
|
-
@refs.push str unless str.empty?
|
425
|
-
end
|
426
|
-
|
427
|
-
def do_accept(strategy)
|
428
|
-
first = true
|
429
|
-
@ids.each do |i|
|
430
|
-
if first
|
431
|
-
first = false
|
432
|
-
else
|
433
|
-
strategy.space
|
434
|
-
end
|
435
|
-
strategy.meta i
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
end
|
440
|
-
|
441
|
-
|
442
|
-
class ReceivedHeader < StructuredHeader
|
443
|
-
|
444
|
-
PARSE_TYPE = :RECEIVED
|
445
|
-
|
446
|
-
def from
|
447
|
-
ensure_parsed
|
448
|
-
@from
|
449
|
-
end
|
450
|
-
|
451
|
-
def from=(arg)
|
452
|
-
ensure_parsed
|
453
|
-
@from = arg
|
454
|
-
end
|
455
|
-
|
456
|
-
def by
|
457
|
-
ensure_parsed
|
458
|
-
@by
|
459
|
-
end
|
460
|
-
|
461
|
-
def by=(arg)
|
462
|
-
ensure_parsed
|
463
|
-
@by = arg
|
464
|
-
end
|
465
|
-
|
466
|
-
def via
|
467
|
-
ensure_parsed
|
468
|
-
@via
|
469
|
-
end
|
470
|
-
|
471
|
-
def via=(arg)
|
472
|
-
ensure_parsed
|
473
|
-
@via = arg
|
474
|
-
end
|
475
|
-
|
476
|
-
def with
|
477
|
-
ensure_parsed
|
478
|
-
@with
|
479
|
-
end
|
480
|
-
|
481
|
-
def id
|
482
|
-
ensure_parsed
|
483
|
-
@id
|
484
|
-
end
|
485
|
-
|
486
|
-
def id=(arg)
|
487
|
-
ensure_parsed
|
488
|
-
@id = arg
|
489
|
-
end
|
490
|
-
|
491
|
-
def _for
|
492
|
-
ensure_parsed
|
493
|
-
@_for
|
494
|
-
end
|
495
|
-
|
496
|
-
def _for=(arg)
|
497
|
-
ensure_parsed
|
498
|
-
@_for = arg
|
499
|
-
end
|
500
|
-
|
501
|
-
def date
|
502
|
-
ensure_parsed
|
503
|
-
@date
|
504
|
-
end
|
505
|
-
|
506
|
-
def date=(arg)
|
507
|
-
ensure_parsed
|
508
|
-
@date = arg
|
509
|
-
end
|
510
|
-
|
511
|
-
private
|
512
|
-
|
513
|
-
def init
|
514
|
-
@from = @by = @via = @with = @id = @_for = nil
|
515
|
-
@with = []
|
516
|
-
@date = nil
|
517
|
-
end
|
518
|
-
|
519
|
-
def set(args)
|
520
|
-
@from, @by, @via, @with, @id, @_for, @date = *args
|
521
|
-
end
|
522
|
-
|
523
|
-
def isempty?
|
524
|
-
@with.empty? and not (@from or @by or @via or @id or @_for or @date)
|
525
|
-
end
|
526
|
-
|
527
|
-
def do_accept(strategy)
|
528
|
-
list = []
|
529
|
-
list.push 'from ' + @from if @from
|
530
|
-
list.push 'by ' + @by if @by
|
531
|
-
list.push 'via ' + @via if @via
|
532
|
-
@with.each do |i|
|
533
|
-
list.push 'with ' + i
|
534
|
-
end
|
535
|
-
list.push 'id ' + @id if @id
|
536
|
-
list.push 'for <' + @_for + '>' if @_for
|
537
|
-
|
538
|
-
first = true
|
539
|
-
list.each do |i|
|
540
|
-
strategy.space unless first
|
541
|
-
strategy.meta i
|
542
|
-
first = false
|
543
|
-
end
|
544
|
-
if @date
|
545
|
-
strategy.meta ';'
|
546
|
-
strategy.space
|
547
|
-
strategy.meta time2str(@date)
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
end
|
552
|
-
|
553
|
-
|
554
|
-
class KeywordsHeader < StructuredHeader
|
555
|
-
|
556
|
-
PARSE_TYPE = :KEYWORDS
|
557
|
-
|
558
|
-
def keys
|
559
|
-
ensure_parsed
|
560
|
-
@keys
|
561
|
-
end
|
562
|
-
|
563
|
-
private
|
564
|
-
|
565
|
-
def init
|
566
|
-
@keys = []
|
567
|
-
end
|
568
|
-
|
569
|
-
def set(a)
|
570
|
-
@keys = a
|
571
|
-
end
|
572
|
-
|
573
|
-
def isempty?
|
574
|
-
@keys.empty?
|
575
|
-
end
|
576
|
-
|
577
|
-
def do_accept(strategy)
|
578
|
-
first = true
|
579
|
-
@keys.each do |i|
|
580
|
-
if first
|
581
|
-
first = false
|
582
|
-
else
|
583
|
-
strategy.meta ','
|
584
|
-
end
|
585
|
-
strategy.meta i
|
586
|
-
end
|
587
|
-
end
|
588
|
-
|
589
|
-
end
|
590
|
-
|
591
|
-
|
592
|
-
class EncryptedHeader < StructuredHeader
|
593
|
-
|
594
|
-
PARSE_TYPE = :ENCRYPTED
|
595
|
-
|
596
|
-
def encrypter
|
597
|
-
ensure_parsed
|
598
|
-
@encrypter
|
599
|
-
end
|
600
|
-
|
601
|
-
def encrypter=(arg)
|
602
|
-
ensure_parsed
|
603
|
-
@encrypter = arg
|
604
|
-
end
|
605
|
-
|
606
|
-
def keyword
|
607
|
-
ensure_parsed
|
608
|
-
@keyword
|
609
|
-
end
|
610
|
-
|
611
|
-
def keyword=(arg)
|
612
|
-
ensure_parsed
|
613
|
-
@keyword = arg
|
614
|
-
end
|
615
|
-
|
616
|
-
private
|
617
|
-
|
618
|
-
def init
|
619
|
-
@encrypter = nil
|
620
|
-
@keyword = nil
|
621
|
-
end
|
622
|
-
|
623
|
-
def set(args)
|
624
|
-
@encrypter, @keyword = args
|
625
|
-
end
|
626
|
-
|
627
|
-
def isempty?
|
628
|
-
not (@encrypter or @keyword)
|
629
|
-
end
|
630
|
-
|
631
|
-
def do_accept(strategy)
|
632
|
-
if @key
|
633
|
-
strategy.meta @encrypter + ','
|
634
|
-
strategy.space
|
635
|
-
strategy.meta @keyword
|
636
|
-
else
|
637
|
-
strategy.meta @encrypter
|
638
|
-
end
|
639
|
-
end
|
640
|
-
|
641
|
-
end
|
642
|
-
|
643
|
-
|
644
|
-
class MimeVersionHeader < StructuredHeader
|
645
|
-
|
646
|
-
PARSE_TYPE = :MIMEVERSION
|
647
|
-
|
648
|
-
def major
|
649
|
-
ensure_parsed
|
650
|
-
@major
|
651
|
-
end
|
652
|
-
|
653
|
-
def major=(arg)
|
654
|
-
ensure_parsed
|
655
|
-
@major = arg
|
656
|
-
end
|
657
|
-
|
658
|
-
def minor
|
659
|
-
ensure_parsed
|
660
|
-
@minor
|
661
|
-
end
|
662
|
-
|
663
|
-
def minor=(arg)
|
664
|
-
ensure_parsed
|
665
|
-
@minor = arg
|
666
|
-
end
|
667
|
-
|
668
|
-
def version
|
669
|
-
sprintf('%d.%d', major, minor)
|
670
|
-
end
|
671
|
-
|
672
|
-
private
|
673
|
-
|
674
|
-
def init
|
675
|
-
@major = nil
|
676
|
-
@minor = nil
|
677
|
-
end
|
678
|
-
|
679
|
-
def set(args)
|
680
|
-
@major, @minor = *args
|
681
|
-
end
|
682
|
-
|
683
|
-
def isempty?
|
684
|
-
not (@major or @minor)
|
685
|
-
end
|
686
|
-
|
687
|
-
def do_accept(strategy)
|
688
|
-
strategy.meta sprintf('%d.%d', @major, @minor)
|
689
|
-
end
|
690
|
-
|
691
|
-
end
|
692
|
-
|
693
|
-
|
694
|
-
class ContentTypeHeader < StructuredHeader
|
695
|
-
|
696
|
-
PARSE_TYPE = :CTYPE
|
697
|
-
|
698
|
-
def main_type
|
699
|
-
ensure_parsed
|
700
|
-
@main
|
701
|
-
end
|
702
|
-
|
703
|
-
def main_type=(arg)
|
704
|
-
ensure_parsed
|
705
|
-
@main = arg.downcase
|
706
|
-
end
|
707
|
-
|
708
|
-
def sub_type
|
709
|
-
ensure_parsed
|
710
|
-
@sub
|
711
|
-
end
|
712
|
-
|
713
|
-
def sub_type=(arg)
|
714
|
-
ensure_parsed
|
715
|
-
@sub = arg.downcase
|
716
|
-
end
|
717
|
-
|
718
|
-
def content_type
|
719
|
-
ensure_parsed
|
720
|
-
@sub ? sprintf('%s/%s', @main, @sub) : @main
|
721
|
-
end
|
722
|
-
|
723
|
-
def params
|
724
|
-
ensure_parsed
|
725
|
-
@params
|
726
|
-
end
|
727
|
-
|
728
|
-
def [](key)
|
729
|
-
ensure_parsed
|
730
|
-
@params and @params[key]
|
731
|
-
end
|
732
|
-
|
733
|
-
def []=(key, val)
|
734
|
-
ensure_parsed
|
735
|
-
(@params ||= {})[key] = val
|
736
|
-
end
|
737
|
-
|
738
|
-
private
|
739
|
-
|
740
|
-
def init
|
741
|
-
@main = @sub = @params = nil
|
742
|
-
end
|
743
|
-
|
744
|
-
def set(args)
|
745
|
-
@main, @sub, @params = *args
|
746
|
-
end
|
747
|
-
|
748
|
-
def isempty?
|
749
|
-
not (@main or @sub)
|
750
|
-
end
|
751
|
-
|
752
|
-
def do_accept(strategy)
|
753
|
-
if @sub
|
754
|
-
strategy.meta sprintf('%s/%s', @main, @sub)
|
755
|
-
else
|
756
|
-
strategy.meta @main
|
757
|
-
end
|
758
|
-
@params.each do |k,v|
|
759
|
-
strategy.meta ';'
|
760
|
-
strategy.space
|
761
|
-
strategy.kv_pair k, v
|
762
|
-
end
|
763
|
-
end
|
764
|
-
|
765
|
-
end
|
766
|
-
|
767
|
-
|
768
|
-
class ContentTransferEncodingHeader < StructuredHeader
|
769
|
-
|
770
|
-
PARSE_TYPE = :CENCODING
|
771
|
-
|
772
|
-
def encoding
|
773
|
-
ensure_parsed
|
774
|
-
@encoding
|
775
|
-
end
|
776
|
-
|
777
|
-
def encoding=(arg)
|
778
|
-
ensure_parsed
|
779
|
-
@encoding = arg
|
780
|
-
end
|
781
|
-
|
782
|
-
private
|
783
|
-
|
784
|
-
def init
|
785
|
-
@encoding = nil
|
786
|
-
end
|
787
|
-
|
788
|
-
def set(s)
|
789
|
-
@encoding = s
|
790
|
-
end
|
791
|
-
|
792
|
-
def isempty?
|
793
|
-
not @encoding
|
794
|
-
end
|
795
|
-
|
796
|
-
def do_accept(strategy)
|
797
|
-
strategy.meta @encoding.capitalize
|
798
|
-
end
|
799
|
-
|
800
|
-
end
|
801
|
-
|
802
|
-
|
803
|
-
class ContentDispositionHeader < StructuredHeader
|
804
|
-
|
805
|
-
PARSE_TYPE = :CDISPOSITION
|
806
|
-
|
807
|
-
def disposition
|
808
|
-
ensure_parsed
|
809
|
-
@disposition
|
810
|
-
end
|
811
|
-
|
812
|
-
def disposition=(str)
|
813
|
-
ensure_parsed
|
814
|
-
@disposition = str.downcase
|
815
|
-
end
|
816
|
-
|
817
|
-
def params
|
818
|
-
ensure_parsed
|
819
|
-
@params
|
820
|
-
end
|
821
|
-
|
822
|
-
def [](key)
|
823
|
-
ensure_parsed
|
824
|
-
@params and @params[key]
|
825
|
-
end
|
826
|
-
|
827
|
-
def []=(key, val)
|
828
|
-
ensure_parsed
|
829
|
-
(@params ||= {})[key] = val
|
830
|
-
end
|
831
|
-
|
832
|
-
private
|
833
|
-
|
834
|
-
def init
|
835
|
-
@disposition = @params = nil
|
836
|
-
end
|
837
|
-
|
838
|
-
def set(args)
|
839
|
-
@disposition, @params = *args
|
840
|
-
end
|
841
|
-
|
842
|
-
def isempty?
|
843
|
-
not @disposition and (not @params or @params.empty?)
|
844
|
-
end
|
845
|
-
|
846
|
-
def do_accept(strategy)
|
847
|
-
strategy.meta @disposition
|
848
|
-
@params.each do |k,v|
|
849
|
-
strategy.meta ';'
|
850
|
-
strategy.space
|
851
|
-
strategy.kv_pair k, v
|
852
|
-
end
|
853
|
-
end
|
854
|
-
|
855
|
-
end
|
856
|
-
|
857
|
-
|
858
|
-
class HeaderField # redefine
|
859
|
-
|
860
|
-
FNAME_TO_CLASS = {
|
861
|
-
'date' => DateTimeHeader,
|
862
|
-
'resent-date' => DateTimeHeader,
|
863
|
-
'to' => AddressHeader,
|
864
|
-
'cc' => AddressHeader,
|
865
|
-
'bcc' => AddressHeader,
|
866
|
-
'from' => AddressHeader,
|
867
|
-
'reply-to' => AddressHeader,
|
868
|
-
'resent-to' => AddressHeader,
|
869
|
-
'resent-cc' => AddressHeader,
|
870
|
-
'resent-bcc' => AddressHeader,
|
871
|
-
'resent-from' => AddressHeader,
|
872
|
-
'resent-reply-to' => AddressHeader,
|
873
|
-
'sender' => SingleAddressHeader,
|
874
|
-
'resent-sender' => SingleAddressHeader,
|
875
|
-
'return-path' => ReturnPathHeader,
|
876
|
-
'message-id' => MessageIdHeader,
|
877
|
-
'resent-message-id' => MessageIdHeader,
|
878
|
-
'in-reply-to' => ReferencesHeader,
|
879
|
-
'received' => ReceivedHeader,
|
880
|
-
'references' => ReferencesHeader,
|
881
|
-
'keywords' => KeywordsHeader,
|
882
|
-
'encrypted' => EncryptedHeader,
|
883
|
-
'mime-version' => MimeVersionHeader,
|
884
|
-
'content-type' => ContentTypeHeader,
|
885
|
-
'content-transfer-encoding' => ContentTransferEncodingHeader,
|
886
|
-
'content-disposition' => ContentDispositionHeader,
|
887
|
-
'content-id' => MessageIdHeader,
|
888
|
-
'subject' => UnstructuredHeader,
|
889
|
-
'comments' => UnstructuredHeader,
|
890
|
-
'content-description' => UnstructuredHeader
|
891
|
-
}
|
892
|
-
|
893
|
-
end
|
894
|
-
|
895
|
-
end # module TMail
|