remailer 0.5.2 → 0.9.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.
- checksums.yaml +7 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +19 -0
- data/{README.rdoc → README.md} +12 -11
- data/Rakefile +21 -11
- data/VERSION +1 -1
- data/lib/remailer/abstract_connection.rb +36 -20
- data/lib/remailer/imap/client.rb +1 -1
- data/lib/remailer/interpreter.rb +40 -19
- data/lib/remailer/interpreter/state_proxy.rb +1 -1
- data/lib/remailer/smtp/client.rb +20 -26
- data/lib/remailer/smtp/client/interpreter.rb +85 -12
- data/lib/remailer/smtp/server.rb +9 -4
- data/lib/remailer/smtp/server/interpreter.rb +16 -15
- data/lib/remailer/smtp/server/transaction.rb +9 -2
- data/lib/remailer/socks5/client/interpreter.rb +18 -13
- data/remailer.gemspec +33 -23
- data/test/bin/exercise +115 -0
- data/test/config.example.yml +15 -0
- data/test/config.rb +41 -0
- data/test/helper.rb +42 -23
- data/test/unit/remailer_imap_client_interpreter_test.rb +2 -2
- data/test/unit/remailer_imap_client_test.rb +10 -12
- data/test/unit/remailer_interpreter_state_proxy_test.rb +14 -14
- data/test/unit/remailer_interpreter_test.rb +36 -28
- data/test/unit/remailer_smtp_client_interpreter_test.rb +80 -33
- data/test/unit/remailer_smtp_client_test.rb +53 -55
- data/test/unit/remailer_smtp_server_test.rb +24 -18
- data/test/unit/remailer_socks5_client_interpreter_test.rb +71 -10
- data/test/unit/remailer_test.rb +2 -2
- metadata +66 -24
- data/test/config.example.rb +0 -17
data/test/bin/exercise
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'optparse'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
$LOAD_PATH << File.expand_path('../../lib', File.dirname(__FILE__))
|
8
|
+
|
9
|
+
require 'remailer'
|
10
|
+
|
11
|
+
# == Support Methods ========================================================
|
12
|
+
|
13
|
+
class Exerciser
|
14
|
+
attr_reader :report
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
@options = options
|
18
|
+
@pending = [ ]
|
19
|
+
@report = { }
|
20
|
+
end
|
21
|
+
|
22
|
+
def test(server)
|
23
|
+
results = @report[server] = {
|
24
|
+
success: 0,
|
25
|
+
timeout: 0,
|
26
|
+
error: 0
|
27
|
+
}
|
28
|
+
|
29
|
+
@options[:count].times do
|
30
|
+
uuid = SecureRandom.uuid
|
31
|
+
|
32
|
+
@pending << uuid
|
33
|
+
|
34
|
+
connection_options = {
|
35
|
+
close: true,
|
36
|
+
proxy: {
|
37
|
+
host: @options[:proxy_host],
|
38
|
+
port: @options[:proxy_port]
|
39
|
+
},
|
40
|
+
connect: lambda do |success, host|
|
41
|
+
results[success ? :success : :error] += 1
|
42
|
+
@pending.delete(uuid)
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
if (@options[:verbose])
|
47
|
+
connection_options[:debug] = STDOUT
|
48
|
+
end
|
49
|
+
|
50
|
+
Remailer::SMTP::Client.open(server, connection_options)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def done?
|
55
|
+
@pending.empty?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# == Main ===================================================================
|
60
|
+
|
61
|
+
options = {
|
62
|
+
count: 1,
|
63
|
+
concurrency: 1,
|
64
|
+
proxy_host: 'localhost',
|
65
|
+
proxy_port: 1080
|
66
|
+
}
|
67
|
+
|
68
|
+
opts = OptionParser.new do |parser|
|
69
|
+
parser.banner = "Usage: exerciser [options] server [server [...]]"
|
70
|
+
parser.on('-n', '--count=n') do |n|
|
71
|
+
options[:count] = n.to_i
|
72
|
+
end
|
73
|
+
parser.on('-c', '--concurrency=n') do |n|
|
74
|
+
options[:concurrency] = n.to_i
|
75
|
+
end
|
76
|
+
parser.on('-X', '--proxy=s') do |s|
|
77
|
+
server, port = s.split(/:/)
|
78
|
+
|
79
|
+
options[:proxy_host] = server
|
80
|
+
|
81
|
+
if (port)
|
82
|
+
options[:proxy_port] = port.to_i
|
83
|
+
end
|
84
|
+
end
|
85
|
+
parser.on('-v', '--verbose') do
|
86
|
+
options[:verbose] = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
servers = opts.parse(*ARGV)
|
91
|
+
|
92
|
+
unless (servers.any?)
|
93
|
+
puts opts
|
94
|
+
exit(0)
|
95
|
+
end
|
96
|
+
|
97
|
+
EventMachine.run do
|
98
|
+
exerciser = Exerciser.new(options)
|
99
|
+
|
100
|
+
servers.each do |server|
|
101
|
+
exerciser.test(server)
|
102
|
+
end
|
103
|
+
|
104
|
+
EventMachine.add_periodic_timer(0.1) do
|
105
|
+
if (exerciser.done?)
|
106
|
+
puts '%-40s %-6s %-6s' % [ 'Server', 'Success', 'Fail' ]
|
107
|
+
puts '-' * 58
|
108
|
+
exerciser.report.each do |server, results|
|
109
|
+
puts '%-40s %6d %6d' % [ server, results[:success], results[:error] ]
|
110
|
+
end
|
111
|
+
|
112
|
+
EventMachine.stop_event_loop
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
smtp_server:
|
2
|
+
host: "smtp.gmail.com"
|
3
|
+
identifier: "smtp.gmail.com"
|
4
|
+
|
5
|
+
public_smtp_server:
|
6
|
+
host: "smtp.gmail.com"
|
7
|
+
identifier: "mx.google.com"
|
8
|
+
username: "<account>@gmail.com"
|
9
|
+
password: "<password>"
|
10
|
+
port: 587
|
11
|
+
|
12
|
+
proxy_server: "<proxy.server>"
|
13
|
+
|
14
|
+
recipient: "<account>@gmail.com"
|
15
|
+
sender: "<account>@gmail.com"
|
data/test/config.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class TestConfig < Hash
|
4
|
+
CONFIG_FILE = 'config.yml'
|
5
|
+
|
6
|
+
def self.options
|
7
|
+
@options ||= begin
|
8
|
+
config_path = File.expand_path(CONFIG_FILE, File.dirname(__FILE__))
|
9
|
+
|
10
|
+
unless (File.exist?(config_path))
|
11
|
+
raise "Config #{CONFIG_FILE} not found. Copy test/config.example.yml and fill in appropriate test settings."
|
12
|
+
end
|
13
|
+
|
14
|
+
new(config_path)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(path)
|
19
|
+
merge!(symbolized_keys(YAML.load(File.open(path))))
|
20
|
+
end
|
21
|
+
|
22
|
+
def symbolized_keys(object)
|
23
|
+
case (object)
|
24
|
+
when Hash
|
25
|
+
Hash[
|
26
|
+
object.collect do |key, value|
|
27
|
+
[
|
28
|
+
key ? key.to_sym : key,
|
29
|
+
symbolized_keys(value)
|
30
|
+
]
|
31
|
+
end
|
32
|
+
]
|
33
|
+
when Array
|
34
|
+
object.collect do |value|
|
35
|
+
symbolized_keys(value)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
object
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
2
|
+
|
3
|
+
gem 'minitest'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/reporters'
|
6
|
+
|
7
|
+
Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
|
3
8
|
|
4
9
|
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
5
10
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
11
|
|
7
12
|
require 'timeout'
|
8
|
-
require 'thwait'
|
9
13
|
require 'rubygems'
|
10
14
|
|
11
15
|
begin
|
@@ -14,8 +18,6 @@ rescue => e
|
|
14
18
|
raise "EventMachine gem could not be loaded: #{e.class}: #{e}"
|
15
19
|
end
|
16
20
|
|
17
|
-
puts $LOAD_PATH.inspect
|
18
|
-
|
19
21
|
require 'remailer'
|
20
22
|
|
21
23
|
class Proc
|
@@ -38,8 +40,6 @@ end
|
|
38
40
|
module TestTriggerHelper
|
39
41
|
def self.included(base)
|
40
42
|
base.class_eval do
|
41
|
-
attr_reader :triggered
|
42
|
-
|
43
43
|
def triggered
|
44
44
|
@triggered ||= Hash.new(false)
|
45
45
|
end
|
@@ -51,36 +51,64 @@ module TestTriggerHelper
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
if (ENV['DEBUG'])
|
55
|
+
STDERR.sync = true
|
56
|
+
end
|
57
|
+
|
58
|
+
class MiniTest::Test
|
59
|
+
def debug_channel
|
60
|
+
ENV['DEBUG'] ? STDERR : nil
|
61
|
+
end
|
62
|
+
|
55
63
|
def engine
|
56
64
|
exception = nil
|
65
|
+
test_thread = nil
|
57
66
|
|
58
|
-
|
67
|
+
engine_thread =
|
59
68
|
Thread.new do
|
60
69
|
Thread.abort_on_exception = true
|
61
70
|
|
62
71
|
# Create a thread for the engine to run on
|
63
72
|
begin
|
64
73
|
EventMachine.run
|
74
|
+
|
65
75
|
rescue Object => exception
|
66
76
|
end
|
67
|
-
end
|
77
|
+
end
|
78
|
+
|
79
|
+
test_thread =
|
68
80
|
Thread.new do
|
69
81
|
# Execute the test code in a separate thread to avoid blocking
|
70
82
|
# the EventMachine loop.
|
71
83
|
begin
|
84
|
+
while (!EventMachine.reactor_running?)
|
85
|
+
# Wait impatiently.
|
86
|
+
end
|
87
|
+
|
72
88
|
yield
|
73
89
|
rescue Object => exception
|
74
90
|
ensure
|
75
91
|
begin
|
76
92
|
EventMachine.stop_event_loop
|
77
93
|
rescue Object
|
94
|
+
STDERR.puts("[#{exception.class}] #{exception}")
|
78
95
|
# Shutting down may trigger an exception from time to time
|
79
96
|
# if the engine itself has failed.
|
80
97
|
end
|
81
98
|
end
|
82
99
|
end
|
83
|
-
|
100
|
+
|
101
|
+
test_thread.join
|
102
|
+
|
103
|
+
begin
|
104
|
+
Timeout.timeout(1) do
|
105
|
+
engine_thread.join
|
106
|
+
end
|
107
|
+
rescue Timeout::Error
|
108
|
+
engine_thread.kill
|
109
|
+
|
110
|
+
fail 'Execution timed out'
|
111
|
+
end
|
84
112
|
|
85
113
|
if (exception)
|
86
114
|
raise exception
|
@@ -89,6 +117,7 @@ class Test::Unit::TestCase
|
|
89
117
|
|
90
118
|
def assert_timeout(time, message = nil, &block)
|
91
119
|
Timeout::timeout(time, &block)
|
120
|
+
|
92
121
|
rescue Timeout::Error
|
93
122
|
flunk(message || 'assert_timeout timed out')
|
94
123
|
end
|
@@ -106,12 +135,12 @@ class Test::Unit::TestCase
|
|
106
135
|
end
|
107
136
|
|
108
137
|
def assert_mapping(map, &block)
|
109
|
-
result_map = map.inject({ }) do |h, (k,
|
138
|
+
result_map = map.inject({ }) do |h, (k, _v)|
|
110
139
|
h[k] = yield(k)
|
111
140
|
h
|
112
141
|
end
|
113
142
|
|
114
|
-
differences = result_map.inject([ ]) do |a, (k,v)|
|
143
|
+
differences = result_map.inject([ ]) do |a, (k, v)|
|
115
144
|
if (v != map[k])
|
116
145
|
a << k
|
117
146
|
end
|
@@ -123,14 +152,4 @@ class Test::Unit::TestCase
|
|
123
152
|
end
|
124
153
|
end
|
125
154
|
|
126
|
-
|
127
|
-
|
128
|
-
TestConfig = OpenStruct.new
|
129
|
-
|
130
|
-
config_file = File.expand_path("config.rb", File.dirname(__FILE__))
|
131
|
-
|
132
|
-
if (File.exist?(config_file))
|
133
|
-
require config_file
|
134
|
-
else
|
135
|
-
raise "No test/config.rb file found. Copy and modify test/config.example.rb"
|
136
|
-
end
|
155
|
+
require_relative 'config'
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
require_relative '../helper'
|
2
2
|
|
3
|
-
class RemailerIMAPClientInterpreterTest < Test
|
3
|
+
class RemailerIMAPClientInterpreterTest < MiniTest::Test
|
4
4
|
def test_split_list_definition
|
5
5
|
assert_mapping(
|
6
6
|
'(\HasChildren \HasNoChildren) "/" "[Gmail]/All Mail"' =>
|
@@ -1,18 +1,16 @@
|
|
1
|
-
|
1
|
+
require_relative '../helper'
|
2
2
|
|
3
|
-
class RemailerIMAPClientTest < Test
|
4
|
-
def setup
|
5
|
-
STDERR.sync = true
|
6
|
-
end
|
7
|
-
|
3
|
+
class RemailerIMAPClientTest < MiniTest::Test
|
8
4
|
def test_connect
|
5
|
+
skip
|
6
|
+
|
7
|
+
debug = { }
|
8
|
+
|
9
9
|
engine do
|
10
|
-
debug = { }
|
11
|
-
|
12
10
|
client = Remailer::IMAP::Client.open(
|
13
|
-
TestConfig.imap_server[:host],
|
14
|
-
:
|
15
|
-
:
|
11
|
+
TestConfig.options[:imap_server][:host],
|
12
|
+
debug: self.debug_channel,
|
13
|
+
connect: lambda { |_success, host| debug[:connected_host] = host }
|
16
14
|
)
|
17
15
|
|
18
16
|
assert client
|
@@ -37,7 +35,7 @@ class RemailerIMAPClientTest < Test::Unit::TestCase
|
|
37
35
|
|
38
36
|
login_status = nil
|
39
37
|
|
40
|
-
client.login(TestConfig.smtp_server[:username], TestConfig.smtp_server[:password]) do |status, message|
|
38
|
+
client.login(TestConfig.options[:smtp_server][:username], TestConfig.options[:smtp_server][:password]) do |status, message|
|
41
39
|
login_status = status
|
42
40
|
end
|
43
41
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
require_relative '../helper'
|
2
2
|
|
3
|
-
class RemailerInterpreterStateTest < Test
|
3
|
+
class RemailerInterpreterStateTest < MiniTest::Test
|
4
4
|
def test_defaults
|
5
5
|
options = { }
|
6
6
|
|
@@ -15,9 +15,9 @@ class RemailerInterpreterStateTest < Test::Unit::TestCase
|
|
15
15
|
proxy = Remailer::Interpreter::StateProxy.new(options)
|
16
16
|
|
17
17
|
expected = {
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
18
|
+
enter: [ lambda { } ],
|
19
|
+
default: [ lambda { } ],
|
20
|
+
leave: [ lambda { } ]
|
21
21
|
}.freeze
|
22
22
|
|
23
23
|
proxy.enter(&expected[:enter][0])
|
@@ -31,9 +31,9 @@ class RemailerInterpreterStateTest < Test::Unit::TestCase
|
|
31
31
|
options = { }
|
32
32
|
|
33
33
|
expected = {
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
34
|
+
enter: [ lambda { } ],
|
35
|
+
terminate: [ lambda { } ],
|
36
|
+
leave: [ lambda { } ]
|
37
37
|
}.freeze
|
38
38
|
|
39
39
|
Remailer::Interpreter::StateProxy.new(options) do
|
@@ -49,10 +49,10 @@ class RemailerInterpreterStateTest < Test::Unit::TestCase
|
|
49
49
|
options = { }
|
50
50
|
|
51
51
|
expected = {
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
52
|
+
enter: [ lambda { } ],
|
53
|
+
interpret: [ [ 10, lambda { } ], [ 1, lambda { } ] ],
|
54
|
+
default: [ lambda { } ],
|
55
|
+
leave: [ lambda { } ]
|
56
56
|
}.freeze
|
57
57
|
|
58
58
|
Remailer::Interpreter::StateProxy.new(options) do
|
@@ -80,7 +80,7 @@ class RemailerInterpreterStateTest < Test::Unit::TestCase
|
|
80
80
|
|
81
81
|
proxy.leave(&proc[1])
|
82
82
|
|
83
|
-
assert_equal({ :
|
84
|
-
assert_equal({ :
|
83
|
+
assert_equal({ enter: [ proc[0] ] }, options_a)
|
84
|
+
assert_equal({ leave: [ proc[1] ] }, options_b)
|
85
85
|
end
|
86
86
|
end
|
@@ -1,4 +1,18 @@
|
|
1
|
-
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class RegexpInterpreter < Remailer::Interpreter
|
4
|
+
attr_reader :received
|
5
|
+
|
6
|
+
state :initialized do
|
7
|
+
interpret(/^HELO\s+/) do |line|
|
8
|
+
@received = [ :helo, line ]
|
9
|
+
end
|
10
|
+
|
11
|
+
interpret(/^MAIL FROM:<([^>]+)>/) do |line|
|
12
|
+
@received = [ :mail_from, line ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
2
16
|
|
3
17
|
class ExampleDelegate
|
4
18
|
include TestTriggerHelper
|
@@ -46,20 +60,6 @@ class LineInterpreterSubclass < LineInterpreter
|
|
46
60
|
end
|
47
61
|
end
|
48
62
|
|
49
|
-
class RegexpInterpreter < Remailer::Interpreter
|
50
|
-
attr_reader :received
|
51
|
-
|
52
|
-
state :initialized do
|
53
|
-
interpret(/^HELO\s+/) do |line|
|
54
|
-
@received = [ :helo, line ]
|
55
|
-
end
|
56
|
-
|
57
|
-
interpret(/^MAIL FROM:<([^>]+)>/) do |line|
|
58
|
-
@received = [ :mail_from, line ]
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
63
|
class ExampleInterpreter < Remailer::Interpreter
|
64
64
|
include TestTriggerHelper
|
65
65
|
|
@@ -105,15 +105,21 @@ class InterpreterWithAccessor < Remailer::Interpreter
|
|
105
105
|
attr_accessor :example
|
106
106
|
end
|
107
107
|
|
108
|
-
class RemailerInterpreterTest < Test
|
108
|
+
class RemailerInterpreterTest < MiniTest::Test
|
109
109
|
def test_default_state
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
test_interpreter_class = Class.new(Remailer::Interpreter)
|
111
|
+
|
112
|
+
assert test_interpreter_class.states_empty?
|
113
|
+
|
114
|
+
assert_equal [ :initialized, :terminated ], test_interpreter_class.states_defined.collect { |s| s.to_s }.sort.collect { |s| s.to_sym }
|
115
|
+
assert_equal true, test_interpreter_class.state_defined?(:initialized)
|
116
|
+
assert_equal true, test_interpreter_class.state_defined?(:terminated)
|
117
|
+
assert_equal false, test_interpreter_class.state_defined?(:unknown)
|
118
|
+
|
119
|
+
assert_equal [ :initialized, :terminated ], test_interpreter_class.states_defined
|
120
|
+
|
121
|
+
interpreter = test_interpreter_class.new
|
114
122
|
|
115
|
-
interpreter = Remailer::Interpreter.new
|
116
|
-
|
117
123
|
assert_equal :initialized, interpreter.state
|
118
124
|
|
119
125
|
buffer = 'a'
|
@@ -124,13 +130,15 @@ class RemailerInterpreterTest < Test::Unit::TestCase
|
|
124
130
|
end
|
125
131
|
|
126
132
|
def test_delegate
|
133
|
+
test_interpreter_class = Class.new(Remailer::Interpreter)
|
134
|
+
|
127
135
|
delegate = ExampleDelegate.new
|
128
136
|
|
129
137
|
assert delegate.triggered
|
130
138
|
|
131
|
-
interpreter =
|
139
|
+
interpreter = test_interpreter_class.new(delegate: delegate)
|
132
140
|
|
133
|
-
|
141
|
+
assert_nil delegate.attribute
|
134
142
|
assert_equal false, delegate.triggered[:method_no_args]
|
135
143
|
assert_equal false, delegate.triggered[:method_with_args]
|
136
144
|
|
@@ -228,7 +236,7 @@ class RemailerInterpreterTest < Test::Unit::TestCase
|
|
228
236
|
|
229
237
|
interpreter.process(line)
|
230
238
|
|
231
|
-
|
239
|
+
assert_nil interpreter.lines[-1]
|
232
240
|
assert_equal "TEST", line
|
233
241
|
|
234
242
|
line << "\0"
|
@@ -242,7 +250,7 @@ class RemailerInterpreterTest < Test::Unit::TestCase
|
|
242
250
|
def test_regexp_interpreter
|
243
251
|
interpreter = RegexpInterpreter.new
|
244
252
|
|
245
|
-
|
253
|
+
assert_nil interpreter.received
|
246
254
|
|
247
255
|
line = "HELO example.com"
|
248
256
|
|
@@ -269,7 +277,7 @@ class RemailerInterpreterTest < Test::Unit::TestCase
|
|
269
277
|
assert_equal true, interpreter.interpret(:random)
|
270
278
|
|
271
279
|
assert_equal :branch, interpreter.state
|
272
|
-
|
280
|
+
assert_nil interpreter.error
|
273
281
|
|
274
282
|
assert_equal :random, interpreter.reply
|
275
283
|
end
|
@@ -291,7 +299,7 @@ class RemailerInterpreterTest < Test::Unit::TestCase
|
|
291
299
|
def test_new_with_block
|
292
300
|
interpreter = InterpreterWithAccessor.new
|
293
301
|
|
294
|
-
|
302
|
+
assert_nil interpreter.example
|
295
303
|
|
296
304
|
interpreter = InterpreterWithAccessor.new do |interpreter|
|
297
305
|
interpreter.example = 'example'
|