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,64 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'smtp_protocol'
|
5
|
+
|
6
|
+
class SmtpProtocolTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_protocol_sets_protocol_property_on_each_state
|
9
|
+
init_state = TestState.new
|
10
|
+
|
11
|
+
protocol = SmtpProtocol.new(:init, {:init => init_state})
|
12
|
+
|
13
|
+
assert_same protocol, init_state.protocol
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_protocol_starts_in_the_initial_state
|
17
|
+
init_state = TestState.new
|
18
|
+
|
19
|
+
protocol = SmtpProtocol.new(:init, {:init => init_state})
|
20
|
+
protocol.serve(nil)
|
21
|
+
|
22
|
+
assert init_state.called
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_state_is_changed_when_a_new_state_is_set
|
26
|
+
next_state = TestState.new
|
27
|
+
|
28
|
+
protocol = SmtpProtocol.new(:init, {:init => TestState.new, :next => next_state})
|
29
|
+
protocol.state = :next
|
30
|
+
protocol.serve(nil)
|
31
|
+
|
32
|
+
assert next_state.called
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_listeners_are_notified_when_a_new_message_is_received
|
36
|
+
protocol = SmtpProtocol.new(:init, {:init => TestState.new})
|
37
|
+
observer = TestObserver.new
|
38
|
+
message = 'hi'
|
39
|
+
|
40
|
+
protocol.add_observer(observer)
|
41
|
+
|
42
|
+
protocol.new_message_received(message)
|
43
|
+
|
44
|
+
assert_same message, observer.received_message
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestObserver
|
50
|
+
attr_accessor :received_message
|
51
|
+
|
52
|
+
def update(message)
|
53
|
+
@received_message = message
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class TestState
|
58
|
+
attr_accessor :called, :protocol
|
59
|
+
|
60
|
+
def serve(io)
|
61
|
+
@called = true
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'stringio'
|
5
|
+
require 'smtp_protocol'
|
6
|
+
|
7
|
+
class SmtpStatesTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
buffer = ''
|
11
|
+
@server_stream = StringIO.new(buffer)
|
12
|
+
@client_stream = StringIO.new(buffer)
|
13
|
+
@protocol = TestProtocol.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_greeting_is_returned_upon_initial_client_connection
|
17
|
+
init_state = InitState.new(@protocol)
|
18
|
+
init_state.serve(@server_stream)
|
19
|
+
|
20
|
+
assert_equal "220 ruby ESMTP\n", @client_stream.readline
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_initial_state_passes_protocol_connect_as_the_next_state_in_the_chain
|
24
|
+
init_state = InitState.new(@protocol)
|
25
|
+
init_state.serve(@server_stream)
|
26
|
+
|
27
|
+
assert_equal :connect, @protocol.state
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_initial_state_passes_protocol_the_same_io_as_it_received
|
31
|
+
init_state = InitState.new(@protocol)
|
32
|
+
init_state.serve(@server_stream)
|
33
|
+
|
34
|
+
assert_same @server_stream, @protocol.io
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_initial_state_raises_not_initialized_when_protocol_is_not_set
|
38
|
+
init_state = InitState.new
|
39
|
+
|
40
|
+
assert_raise NotInitializedError do
|
41
|
+
init_state.serve(@server_stream)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_helo_is_accepted_while_in_connect_state
|
46
|
+
@client_stream.puts "HELO test.client"
|
47
|
+
connect_state = ConnectState.new(@protocol)
|
48
|
+
|
49
|
+
connect_state.serve(@server_stream)
|
50
|
+
|
51
|
+
assert_equal "250 ruby\n", @client_stream.readline
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_connect_state_passes_protocol_connected_as_the_next_state_in_the_chain
|
55
|
+
@client_stream.puts "HELO test.client"
|
56
|
+
connect_state = ConnectState.new(@protocol)
|
57
|
+
|
58
|
+
connect_state.serve(@server_stream)
|
59
|
+
|
60
|
+
assert_equal :connected, @protocol.state
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_connect_state_passes_protocol_the_same_io_as_it_received
|
64
|
+
@client_stream.puts "HELO test.client"
|
65
|
+
connect_state = ConnectState.new(@protocol)
|
66
|
+
|
67
|
+
connect_state.serve(@server_stream)
|
68
|
+
|
69
|
+
assert_same @server_stream, @protocol.io
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_connect_state_raises_not_initialized_when_protocol_is_not_set
|
73
|
+
connect_state = ConnectState.new
|
74
|
+
|
75
|
+
assert_raise NotInitializedError do
|
76
|
+
connect_state.serve(@server_stream)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_from_okayed_while_in_connected_state
|
81
|
+
@client_stream.puts "MAIL FROM:<adam@esterlines.com>"
|
82
|
+
connected_state = ConnectedState.new(@protocol)
|
83
|
+
|
84
|
+
connected_state.serve(@server_stream)
|
85
|
+
|
86
|
+
assert_equal "250 ok\n", @client_stream.readline
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_connected_state_passes_protocol_connected_as_the_next_state_when_client_sends_from_request
|
90
|
+
@client_stream.puts "MAIL FROM:<junk@junkster.com>"
|
91
|
+
connected_state = ConnectedState.new(@protocol)
|
92
|
+
|
93
|
+
connected_state.serve(@server_stream)
|
94
|
+
|
95
|
+
assert_equal :connected, @protocol.state
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_rcpt_okayed_while_in_connected_state
|
99
|
+
@client_stream.puts "RCPT TO:<junk@junkster.com>"
|
100
|
+
connected_state = ConnectedState.new(@protocol)
|
101
|
+
|
102
|
+
connected_state.serve(@server_stream)
|
103
|
+
|
104
|
+
assert_equal "250 ok\n", @client_stream.readline
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_connected_state_passes_protocol_connected_as_the_next_state_when_client_sends_rcpt_request
|
108
|
+
@client_stream.puts "RCPT TO:<foo@foo.com>"
|
109
|
+
connected_state = ConnectedState.new(@protocol)
|
110
|
+
|
111
|
+
connected_state.serve(@server_stream)
|
112
|
+
|
113
|
+
assert_equal :connected, @protocol.state
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_connected_state_passes_protocol_the_same_io_as_it_received
|
117
|
+
@client_stream.puts "MAIL FROM:<foo@foo.com>"
|
118
|
+
connected_state = ConnectedState.new(@protocol)
|
119
|
+
|
120
|
+
connected_state.serve(@server_stream)
|
121
|
+
|
122
|
+
assert_same @server_stream, @protocol.io
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_data_request_given_the_go_ahead_while_in_connected_state
|
126
|
+
@client_stream.puts "DATA"
|
127
|
+
connected_state = ConnectedState.new(@protocol)
|
128
|
+
|
129
|
+
connected_state.serve(@server_stream)
|
130
|
+
|
131
|
+
assert_equal "354 go ahead\n", @client_stream.readline
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_connected_state_passes_protocol_read_mail_as_the_next_state_when_client_sends_data_request
|
135
|
+
@client_stream.puts "DATA"
|
136
|
+
connected_state = ConnectedState.new(@protocol)
|
137
|
+
|
138
|
+
connected_state.serve(@server_stream)
|
139
|
+
|
140
|
+
assert_equal :read_mail, @protocol.state
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_connected_state_raises_not_initialized_when_protocol_is_not_set
|
144
|
+
connected_state = ConnectedState.new
|
145
|
+
|
146
|
+
assert_raise NotInitializedError do
|
147
|
+
connected_state.serve(@server_stream)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_read_mail_state_reads_until_a_single_dot_is_found_on_a_line_then_returns_an_ok_message
|
152
|
+
@client_stream.puts "To: junk@junk.com\nFrom: junk2@junk2.com\n\nHi\n.\n"
|
153
|
+
read_mail_state = ReadMailState.new(@protocol)
|
154
|
+
|
155
|
+
read_mail_state.serve(@server_stream)
|
156
|
+
|
157
|
+
assert_equal "250 ok\n", @client_stream.readline
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_read_mail_state_passes_read_message_to_protocol
|
161
|
+
message = "To: junk@junk.com\nFrom: junk2@junk2.com\n\nHi\n"
|
162
|
+
@client_stream.puts "#{message}.\n"
|
163
|
+
read_mail_state = ReadMailState.new(@protocol)
|
164
|
+
|
165
|
+
read_mail_state.serve(@server_stream)
|
166
|
+
|
167
|
+
assert_equal message, @protocol.new_message
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_read_mail_state_passes_protocol_quit_as_the_next_state_when_mail_message_is_read
|
171
|
+
@client_stream.puts "To: junk@junk.com\nFrom: junk2@junk2.com\n\nHi\n.\n"
|
172
|
+
read_mail_state = ReadMailState.new(@protocol)
|
173
|
+
|
174
|
+
read_mail_state.serve(@server_stream)
|
175
|
+
|
176
|
+
assert_equal :quit, @protocol.state
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_read_mail_state_raises_not_initialized_when_protocol_is_not_set
|
180
|
+
read_mail_state = ReadMailState.new
|
181
|
+
|
182
|
+
assert_raise NotInitializedError do
|
183
|
+
read_mail_state.serve(@server_stream)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_quit_state_reads_client_quit_and_says_goodbye
|
188
|
+
@client_stream.puts "QUIT"
|
189
|
+
quit_state = QuitState.new(@protocol)
|
190
|
+
|
191
|
+
quit_state.serve(@server_stream)
|
192
|
+
|
193
|
+
assert_equal "221 ruby goodbye\n", @client_stream.readline
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_quit_state_raises_not_initialized_when_protocol_is_not_set
|
197
|
+
quit_state = QuitState.new
|
198
|
+
|
199
|
+
assert_raise NotInitializedError do
|
200
|
+
quit_state.serve(@server_stream)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
class TestProtocol
|
207
|
+
attr_accessor :state, :io
|
208
|
+
attr_reader :new_message
|
209
|
+
|
210
|
+
def serve (io)
|
211
|
+
@io = io
|
212
|
+
end
|
213
|
+
|
214
|
+
def new_message_received(message)
|
215
|
+
@new_message = message
|
216
|
+
end
|
217
|
+
end
|
data/vendor/tmail.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# lib/tmail/Makefile
|
3
|
+
#
|
4
|
+
|
5
|
+
debug:
|
6
|
+
rm -f parser.rb
|
7
|
+
make parser.rb DEBUG=true
|
8
|
+
|
9
|
+
parser.rb: parser.y
|
10
|
+
if [ "$(DEBUG)" = true ]; then \
|
11
|
+
racc -v -g -o$@ parser.y ;\
|
12
|
+
else \
|
13
|
+
racc -E -o$@ parser.y ;\
|
14
|
+
fi
|
15
|
+
|
16
|
+
clean:
|
17
|
+
rm -f parser.rb parser.output
|
18
|
+
|
19
|
+
distclean: clean
|
@@ -0,0 +1,222 @@
|
|
1
|
+
#
|
2
|
+
# address.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/parser'
|
13
|
+
|
14
|
+
module TMail
|
15
|
+
|
16
|
+
class Address
|
17
|
+
|
18
|
+
include TextUtils
|
19
|
+
|
20
|
+
def Address.parse(str)
|
21
|
+
Parser.parse :ADDRESS, str
|
22
|
+
end
|
23
|
+
|
24
|
+
def address_group?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(local, domain)
|
29
|
+
if domain
|
30
|
+
domain.each do |s|
|
31
|
+
raise SyntaxError, 'empty word in domain' if s.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@local = local
|
35
|
+
@domain = domain
|
36
|
+
@name = nil
|
37
|
+
@routes = []
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
def name=(str)
|
43
|
+
@name = str
|
44
|
+
@name = nil if str and str.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
alias phrase name
|
48
|
+
alias phrase= name=
|
49
|
+
|
50
|
+
attr_reader :routes
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<#{self.class} #{address()}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
def local
|
57
|
+
return nil unless @local
|
58
|
+
return '""' if @local.size == 1 and @local[0].empty?
|
59
|
+
@local.map {|i| quote_atom(i) }.join('.')
|
60
|
+
end
|
61
|
+
|
62
|
+
def domain
|
63
|
+
return nil unless @domain
|
64
|
+
join_domain(@domain)
|
65
|
+
end
|
66
|
+
|
67
|
+
def spec
|
68
|
+
s = self.local
|
69
|
+
d = self.domain
|
70
|
+
if s and d
|
71
|
+
s + '@' + d
|
72
|
+
else
|
73
|
+
s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
alias address spec
|
78
|
+
|
79
|
+
|
80
|
+
def ==(other)
|
81
|
+
other.respond_to? :spec and self.spec == other.spec
|
82
|
+
end
|
83
|
+
|
84
|
+
alias eql? ==
|
85
|
+
|
86
|
+
def hash
|
87
|
+
@local.hash ^ @domain.hash
|
88
|
+
end
|
89
|
+
|
90
|
+
def dup
|
91
|
+
obj = self.class.new(@local.dup, @domain.dup)
|
92
|
+
obj.name = @name.dup if @name
|
93
|
+
obj.routes.replace @routes
|
94
|
+
obj
|
95
|
+
end
|
96
|
+
|
97
|
+
include StrategyInterface
|
98
|
+
|
99
|
+
def accept(strategy, dummy1 = nil, dummy2 = nil)
|
100
|
+
unless @local
|
101
|
+
strategy.meta '<>' # empty return-path
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
spec_p = (not @name and @routes.empty?)
|
106
|
+
if @name
|
107
|
+
strategy.phrase @name
|
108
|
+
strategy.space
|
109
|
+
end
|
110
|
+
tmp = spec_p ? '' : '<'
|
111
|
+
unless @routes.empty?
|
112
|
+
tmp << @routes.map {|i| '@' + i }.join(',') << ':'
|
113
|
+
end
|
114
|
+
tmp << self.spec
|
115
|
+
tmp << '>' unless spec_p
|
116
|
+
strategy.meta tmp
|
117
|
+
strategy.lwsp ''
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
class AddressGroup
|
124
|
+
|
125
|
+
include Enumerable
|
126
|
+
|
127
|
+
def address_group?
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize(name, addrs)
|
132
|
+
@name = name
|
133
|
+
@addresses = addrs
|
134
|
+
end
|
135
|
+
|
136
|
+
attr_reader :name
|
137
|
+
|
138
|
+
def ==(other)
|
139
|
+
other.respond_to? :to_a and @addresses == other.to_a
|
140
|
+
end
|
141
|
+
|
142
|
+
alias eql? ==
|
143
|
+
|
144
|
+
def hash
|
145
|
+
map {|i| i.hash }.hash
|
146
|
+
end
|
147
|
+
|
148
|
+
def [](idx)
|
149
|
+
@addresses[idx]
|
150
|
+
end
|
151
|
+
|
152
|
+
def size
|
153
|
+
@addresses.size
|
154
|
+
end
|
155
|
+
|
156
|
+
def empty?
|
157
|
+
@addresses.empty?
|
158
|
+
end
|
159
|
+
|
160
|
+
def each(&block)
|
161
|
+
@addresses.each(&block)
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_a
|
165
|
+
@addresses.dup
|
166
|
+
end
|
167
|
+
|
168
|
+
alias to_ary to_a
|
169
|
+
|
170
|
+
def include?(a)
|
171
|
+
@addresses.include? a
|
172
|
+
end
|
173
|
+
|
174
|
+
def flatten
|
175
|
+
set = []
|
176
|
+
@addresses.each do |a|
|
177
|
+
if a.respond_to? :flatten
|
178
|
+
set.concat a.flatten
|
179
|
+
else
|
180
|
+
set.push a
|
181
|
+
end
|
182
|
+
end
|
183
|
+
set
|
184
|
+
end
|
185
|
+
|
186
|
+
def each_address(&block)
|
187
|
+
flatten.each(&block)
|
188
|
+
end
|
189
|
+
|
190
|
+
def add(a)
|
191
|
+
@addresses.push a
|
192
|
+
end
|
193
|
+
|
194
|
+
alias push add
|
195
|
+
|
196
|
+
def delete(a)
|
197
|
+
@addresses.delete a
|
198
|
+
end
|
199
|
+
|
200
|
+
include StrategyInterface
|
201
|
+
|
202
|
+
def accept(strategy, dummy1 = nil, dummy2 = nil)
|
203
|
+
strategy.phrase @name
|
204
|
+
strategy.meta ':'
|
205
|
+
strategy.space
|
206
|
+
first = true
|
207
|
+
each do |mbox|
|
208
|
+
if first
|
209
|
+
first = false
|
210
|
+
else
|
211
|
+
strategy.meta ','
|
212
|
+
end
|
213
|
+
strategy.space
|
214
|
+
mbox.accept strategy
|
215
|
+
end
|
216
|
+
strategy.meta ';'
|
217
|
+
strategy.lwsp ''
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end # module TMail
|