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.
- data/COPYING +515 -0
- data/README +56 -0
- data/Rakefile +12 -0
- data/lib/message_observers.rb +40 -0
- data/lib/rumbster.rb +24 -0
- data/lib/smtp_protocol.rb +42 -0
- data/lib/smtp_states.rb +159 -0
- data/test/message_observers_test.rb +102 -0
- data/test/rumbster_test.rb +69 -0
- data/test/smtp_protocol_test.rb +64 -0
- data/test/smtp_states_test.rb +217 -0
- data/vendor/tmail.rb +4 -0
- data/vendor/tmail/.cvsignore +3 -0
- data/vendor/tmail/Makefile +19 -0
- data/vendor/tmail/address.rb +222 -0
- data/vendor/tmail/base64.rb +52 -0
- data/vendor/tmail/compat.rb +39 -0
- data/vendor/tmail/config.rb +50 -0
- data/vendor/tmail/encode.rb +447 -0
- data/vendor/tmail/header.rb +895 -0
- data/vendor/tmail/info.rb +14 -0
- data/vendor/tmail/loader.rb +1 -0
- data/vendor/tmail/mail.rb +869 -0
- data/vendor/tmail/mailbox.rb +386 -0
- data/vendor/tmail/mbox.rb +1 -0
- data/vendor/tmail/net.rb +260 -0
- data/vendor/tmail/obsolete.rb +122 -0
- data/vendor/tmail/parser.rb +1475 -0
- data/vendor/tmail/parser.y +372 -0
- data/vendor/tmail/port.rb +356 -0
- data/vendor/tmail/scanner.rb +22 -0
- data/vendor/tmail/scanner_r.rb +243 -0
- data/vendor/tmail/stringio.rb +256 -0
- data/vendor/tmail/textutils.rb +197 -0
- data/vendor/tmail/tmail.rb +1 -0
- data/vendor/tmail/utils.rb +23 -0
- metadata +88 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# scanner.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/textutils'
|
12
|
+
|
13
|
+
module TMail
|
14
|
+
require 'tmail/scanner_r.rb'
|
15
|
+
begin
|
16
|
+
raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
|
17
|
+
require 'tmail/scanner_c.so'
|
18
|
+
Scanner = Scanner_C
|
19
|
+
rescue LoadError
|
20
|
+
Scanner = Scanner_R
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
#
|
2
|
+
# scanner_r.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/config'
|
12
|
+
|
13
|
+
module TMail
|
14
|
+
|
15
|
+
class Scanner_R
|
16
|
+
|
17
|
+
Version = '0.10.8'
|
18
|
+
Version.freeze
|
19
|
+
|
20
|
+
MIME_HEADERS = {
|
21
|
+
:CTYPE => true,
|
22
|
+
:CENCODING => true,
|
23
|
+
:CDISPOSITION => true
|
24
|
+
}
|
25
|
+
|
26
|
+
alnum = 'a-zA-Z0-9'
|
27
|
+
atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip
|
28
|
+
tokensyms = %q[ _#!$%&`'*+-{|}~^. ].strip
|
29
|
+
|
30
|
+
atomchars = alnum + Regexp.quote(atomsyms)
|
31
|
+
tokenchars = alnum + Regexp.quote(tokensyms)
|
32
|
+
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
|
33
|
+
|
34
|
+
eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
|
35
|
+
sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
|
36
|
+
utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
|
37
|
+
|
38
|
+
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
|
39
|
+
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
|
40
|
+
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
|
41
|
+
|
42
|
+
quoted_without_iso2022 = /\A[^\\"]+/n
|
43
|
+
domlit_without_iso2022 = /\A[^\\\]]+/n
|
44
|
+
comment_without_iso2022 = /\A[^\\()]+/n
|
45
|
+
|
46
|
+
PATTERN_TABLE = {}
|
47
|
+
PATTERN_TABLE['EUC'] =
|
48
|
+
[
|
49
|
+
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
|
50
|
+
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
|
51
|
+
quoted_with_iso2022,
|
52
|
+
domlit_with_iso2022,
|
53
|
+
comment_with_iso2022
|
54
|
+
]
|
55
|
+
PATTERN_TABLE['SJIS'] =
|
56
|
+
[
|
57
|
+
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
58
|
+
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
59
|
+
quoted_with_iso2022,
|
60
|
+
domlit_with_iso2022,
|
61
|
+
comment_with_iso2022
|
62
|
+
]
|
63
|
+
PATTERN_TABLE['UTF8'] =
|
64
|
+
[
|
65
|
+
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
|
66
|
+
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
|
67
|
+
quoted_without_iso2022,
|
68
|
+
domlit_without_iso2022,
|
69
|
+
comment_without_iso2022
|
70
|
+
]
|
71
|
+
PATTERN_TABLE['NONE'] =
|
72
|
+
[
|
73
|
+
/\A[#{atomchars}]+/n,
|
74
|
+
/\A[#{tokenchars}]+/n,
|
75
|
+
quoted_without_iso2022,
|
76
|
+
domlit_without_iso2022,
|
77
|
+
comment_without_iso2022
|
78
|
+
]
|
79
|
+
|
80
|
+
|
81
|
+
def initialize(str, scantype, comments)
|
82
|
+
init_scanner str
|
83
|
+
@comments = comments || []
|
84
|
+
@debug = false
|
85
|
+
|
86
|
+
# fix scanner mode
|
87
|
+
@received = (scantype == :RECEIVED)
|
88
|
+
@is_mime_header = MIME_HEADERS[scantype]
|
89
|
+
|
90
|
+
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
|
91
|
+
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_accessor :debug
|
95
|
+
|
96
|
+
def scan(&block)
|
97
|
+
if @debug
|
98
|
+
scan_main do |arr|
|
99
|
+
s, v = arr
|
100
|
+
printf "%7d %-10s %s\n",
|
101
|
+
rest_size(),
|
102
|
+
s.respond_to?(:id2name) ? s.id2name : s.inspect,
|
103
|
+
v.inspect
|
104
|
+
yield arr
|
105
|
+
end
|
106
|
+
else
|
107
|
+
scan_main(&block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
RECV_TOKEN = {
|
114
|
+
'from' => :FROM,
|
115
|
+
'by' => :BY,
|
116
|
+
'via' => :VIA,
|
117
|
+
'with' => :WITH,
|
118
|
+
'id' => :ID,
|
119
|
+
'for' => :FOR
|
120
|
+
}
|
121
|
+
|
122
|
+
def scan_main
|
123
|
+
until eof?
|
124
|
+
if skip(/\A[\n\r\t ]+/n) # LWSP
|
125
|
+
break if eof?
|
126
|
+
end
|
127
|
+
|
128
|
+
if s = readstr(@word_re)
|
129
|
+
if @is_mime_header
|
130
|
+
yield :TOKEN, s
|
131
|
+
else
|
132
|
+
# atom
|
133
|
+
if /\A\d+\z/ =~ s
|
134
|
+
yield :DIGIT, s
|
135
|
+
elsif @received
|
136
|
+
yield RECV_TOKEN[s.downcase] || :ATOM, s
|
137
|
+
else
|
138
|
+
yield :ATOM, s
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
elsif skip(/\A"/)
|
143
|
+
yield :QUOTED, scan_quoted_word()
|
144
|
+
|
145
|
+
elsif skip(/\A\[/)
|
146
|
+
yield :DOMLIT, scan_domain_literal()
|
147
|
+
|
148
|
+
elsif skip(/\A\(/)
|
149
|
+
@comments.push scan_comment()
|
150
|
+
|
151
|
+
else
|
152
|
+
c = readchar()
|
153
|
+
yield c, c
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
yield false, '$'
|
158
|
+
end
|
159
|
+
|
160
|
+
def scan_quoted_word
|
161
|
+
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
|
162
|
+
end
|
163
|
+
|
164
|
+
def scan_domain_literal
|
165
|
+
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
|
166
|
+
end
|
167
|
+
|
168
|
+
def scan_qstr(pattern, terminal, type)
|
169
|
+
result = ''
|
170
|
+
until eof?
|
171
|
+
if s = readstr(pattern) then result << s
|
172
|
+
elsif skip(terminal) then return result
|
173
|
+
elsif skip(/\A\\/) then result << readchar()
|
174
|
+
else
|
175
|
+
raise "TMail FATAL: not match in #{type}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
scan_error! "found unterminated #{type}"
|
179
|
+
end
|
180
|
+
|
181
|
+
def scan_comment
|
182
|
+
result = ''
|
183
|
+
nest = 1
|
184
|
+
content = @comment_re
|
185
|
+
|
186
|
+
until eof?
|
187
|
+
if s = readstr(content) then result << s
|
188
|
+
elsif skip(/\A\)/) then nest -= 1
|
189
|
+
return result if nest == 0
|
190
|
+
result << ')'
|
191
|
+
elsif skip(/\A\(/) then nest += 1
|
192
|
+
result << '('
|
193
|
+
elsif skip(/\A\\/) then result << readchar()
|
194
|
+
else
|
195
|
+
raise 'TMail FATAL: not match in comment'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
scan_error! 'found unterminated comment'
|
199
|
+
end
|
200
|
+
|
201
|
+
# string scanner
|
202
|
+
|
203
|
+
def init_scanner(str)
|
204
|
+
@src = str
|
205
|
+
end
|
206
|
+
|
207
|
+
def eof?
|
208
|
+
@src.empty?
|
209
|
+
end
|
210
|
+
|
211
|
+
def rest_size
|
212
|
+
@src.size
|
213
|
+
end
|
214
|
+
|
215
|
+
def readstr(re)
|
216
|
+
if m = re.match(@src)
|
217
|
+
@src = m.post_match
|
218
|
+
m[0]
|
219
|
+
else
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def readchar
|
225
|
+
readstr(/\A./)
|
226
|
+
end
|
227
|
+
|
228
|
+
def skip(re)
|
229
|
+
if m = re.match(@src)
|
230
|
+
@src = m.post_match
|
231
|
+
true
|
232
|
+
else
|
233
|
+
false
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def scan_error!(msg)
|
238
|
+
raise SyntaxError, msg
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
end # module TMail
|
@@ -0,0 +1,256 @@
|
|
1
|
+
#
|
2
|
+
# stringio.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 1999-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
|
+
# $amstdId: stringio.rb,v 1.12 2004/02/20 00:31:14 aamine Exp $
|
11
|
+
#
|
12
|
+
|
13
|
+
class StringInput
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def new(str)
|
19
|
+
if block_given?
|
20
|
+
begin
|
21
|
+
f = super
|
22
|
+
yield f
|
23
|
+
ensure
|
24
|
+
f.close if f
|
25
|
+
end
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias open new
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(str)
|
35
|
+
@src = str
|
36
|
+
@pos = 0
|
37
|
+
@closed = false
|
38
|
+
@lineno = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :lineno
|
42
|
+
|
43
|
+
def string
|
44
|
+
@src
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
stream_check!
|
53
|
+
@pos = nil
|
54
|
+
@closed = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def closed?
|
58
|
+
@closed
|
59
|
+
end
|
60
|
+
|
61
|
+
def pos
|
62
|
+
stream_check!
|
63
|
+
[@pos, @src.size].min
|
64
|
+
end
|
65
|
+
|
66
|
+
alias tell pos
|
67
|
+
|
68
|
+
def seek(offset, whence = IO::SEEK_SET)
|
69
|
+
stream_check!
|
70
|
+
case whence
|
71
|
+
when IO::SEEK_SET
|
72
|
+
@pos = offset
|
73
|
+
when IO::SEEK_CUR
|
74
|
+
@pos += offset
|
75
|
+
when IO::SEEK_END
|
76
|
+
@pos = @src.size - offset
|
77
|
+
else
|
78
|
+
raise ArgumentError, "unknown seek flag: #{whence}"
|
79
|
+
end
|
80
|
+
@pos = 0 if @pos < 0
|
81
|
+
@pos = [@pos, @src.size + 1].min
|
82
|
+
offset
|
83
|
+
end
|
84
|
+
|
85
|
+
def rewind
|
86
|
+
stream_check!
|
87
|
+
@pos = 0
|
88
|
+
end
|
89
|
+
|
90
|
+
def eof?
|
91
|
+
stream_check!
|
92
|
+
@pos > @src.size
|
93
|
+
end
|
94
|
+
|
95
|
+
def each(&block)
|
96
|
+
stream_check!
|
97
|
+
begin
|
98
|
+
@src.each(&block)
|
99
|
+
ensure
|
100
|
+
@pos = 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def gets
|
105
|
+
stream_check!
|
106
|
+
if idx = @src.index(?\n, @pos)
|
107
|
+
idx += 1 # "\n".size
|
108
|
+
line = @src[ @pos ... idx ]
|
109
|
+
@pos = idx
|
110
|
+
@pos += 1 if @pos == @src.size
|
111
|
+
else
|
112
|
+
line = @src[ @pos .. -1 ]
|
113
|
+
@pos = @src.size + 1
|
114
|
+
end
|
115
|
+
@lineno += 1
|
116
|
+
|
117
|
+
line
|
118
|
+
end
|
119
|
+
|
120
|
+
def getc
|
121
|
+
stream_check!
|
122
|
+
ch = @src[@pos]
|
123
|
+
@pos += 1
|
124
|
+
@pos += 1 if @pos == @src.size
|
125
|
+
ch
|
126
|
+
end
|
127
|
+
|
128
|
+
def read(len = nil)
|
129
|
+
stream_check!
|
130
|
+
return read_all() unless len
|
131
|
+
str = @src[@pos, len]
|
132
|
+
@pos += len
|
133
|
+
@pos += 1 if @pos == @src.size
|
134
|
+
str
|
135
|
+
end
|
136
|
+
|
137
|
+
alias sysread read
|
138
|
+
|
139
|
+
def read_all
|
140
|
+
stream_check!
|
141
|
+
return nil if eof?
|
142
|
+
rest = @src[@pos ... @src.size]
|
143
|
+
@pos = @src.size + 1
|
144
|
+
rest
|
145
|
+
end
|
146
|
+
|
147
|
+
def stream_check!
|
148
|
+
@closed and raise IOError, 'closed stream'
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class StringOutput
|
155
|
+
|
156
|
+
class << self
|
157
|
+
def new(str = '')
|
158
|
+
if block_given?
|
159
|
+
begin
|
160
|
+
f = super
|
161
|
+
yield f
|
162
|
+
ensure
|
163
|
+
f.close if f
|
164
|
+
end
|
165
|
+
else
|
166
|
+
super
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
alias open new
|
171
|
+
end
|
172
|
+
|
173
|
+
def initialize(str = '')
|
174
|
+
@dest = str
|
175
|
+
@closed = false
|
176
|
+
end
|
177
|
+
|
178
|
+
def close
|
179
|
+
@closed = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def closed?
|
183
|
+
@closed
|
184
|
+
end
|
185
|
+
|
186
|
+
def string
|
187
|
+
@dest
|
188
|
+
end
|
189
|
+
|
190
|
+
alias value string
|
191
|
+
alias to_str string
|
192
|
+
|
193
|
+
def size
|
194
|
+
@dest.size
|
195
|
+
end
|
196
|
+
|
197
|
+
alias pos size
|
198
|
+
|
199
|
+
def inspect
|
200
|
+
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
|
201
|
+
end
|
202
|
+
|
203
|
+
def print(*args)
|
204
|
+
stream_check!
|
205
|
+
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
|
206
|
+
args.each do |s|
|
207
|
+
raise ArgumentError, 'nil not allowed' if s.nil?
|
208
|
+
@dest << s.to_s
|
209
|
+
end
|
210
|
+
nil
|
211
|
+
end
|
212
|
+
|
213
|
+
def puts(*args)
|
214
|
+
stream_check!
|
215
|
+
args.each do |str|
|
216
|
+
@dest << (s = str.to_s)
|
217
|
+
@dest << "\n" unless s[-1] == ?\n
|
218
|
+
end
|
219
|
+
@dest << "\n" if args.empty?
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
|
223
|
+
def putc(ch)
|
224
|
+
stream_check!
|
225
|
+
@dest << ch.chr
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
def printf(*args)
|
230
|
+
stream_check!
|
231
|
+
@dest << sprintf(*args)
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def write(str)
|
236
|
+
stream_check!
|
237
|
+
s = str.to_s
|
238
|
+
@dest << s
|
239
|
+
s.size
|
240
|
+
end
|
241
|
+
|
242
|
+
alias syswrite write
|
243
|
+
|
244
|
+
def <<(str)
|
245
|
+
stream_check!
|
246
|
+
@dest << str.to_s
|
247
|
+
self
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def stream_check!
|
253
|
+
@closed and raise IOError, 'closed stream'
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|