remailer 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  *.gem
2
2
 
3
+ config.rb
4
+
3
5
  ## MAC OS
4
6
  .DS_Store
5
7
 
data/README.rdoc CHANGED
@@ -76,6 +76,20 @@ exhausted:
76
76
  STDERR.puts "Sending complete."
77
77
  end
78
78
 
79
+ The call to send a message can also take a callback method which must receive
80
+ one parameter that will be the numerical status code returned by the SMTP
81
+ server. Success is defined as 250, errors vary:
82
+
83
+ connection.send_email(
84
+ 'from@example.net',
85
+ 'to@example.com',
86
+ email_content
87
+ ) do |status_code|
88
+ puts "Message finished with status #{status_code}"
89
+ end
90
+
91
+ A status code of nil is sent if the server timed out or the connection failed.
92
+
79
93
  == Status
80
94
 
81
95
  This software is currently experimental and is not recommended for production
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.1
@@ -2,12 +2,55 @@ require 'socket'
2
2
  require 'eventmachine'
3
3
 
4
4
  class Remailer::Connection < EventMachine::Connection
5
+ # == Exceptions ===========================================================
6
+
7
+ class CallbackArgumentsRequired < Exception; end
8
+
5
9
  # == Constants ============================================================
6
10
 
7
11
  DEFAULT_TIMEOUT = 5
8
- SMTP_PORT = 25
9
12
  CRLF = "\r\n".freeze
10
13
  CRLF_LENGTH = CRLF.length
14
+
15
+ SMTP_PORT = 25
16
+ SOCKS5_PORT = 1080
17
+
18
+ SOCKS5_VERSION = 5
19
+
20
+ SOCKS5_METHOD = {
21
+ :no_auth => 0,
22
+ :gssapi => 1,
23
+ :username_password => 2
24
+ }.freeze
25
+
26
+ SOCKS5_COMMAND = {
27
+ :connect => 1,
28
+ :bind => 2
29
+ }.freeze
30
+
31
+ SOCKS5_REPLY = {
32
+ 0 => 'Succeeded',
33
+ 1 => 'General SOCKS server failure',
34
+ 2 => 'Connection not allowed',
35
+ 3 => 'Network unreachable',
36
+ 4 => 'Host unreachable',
37
+ 5 => 'Connection refused',
38
+ 6 => 'TTL expired',
39
+ 7 => 'Command not supported',
40
+ 8 => 'Address type not supported'
41
+ }.freeze
42
+
43
+ SOCKS5_ADDRESS_TYPE = {
44
+ :ipv4 => 1,
45
+ :domainname => 3,
46
+ :ipv6 => 4
47
+ }.freeze
48
+
49
+ NOTIFICATIONS = [
50
+ :debug,
51
+ :error,
52
+ :connect
53
+ ].freeze
11
54
 
12
55
  # == Properties ===========================================================
13
56
 
@@ -29,40 +72,65 @@ class Remailer::Connection < EventMachine::Connection
29
72
  # * use_tls => Will use TLS if availble (default is true)
30
73
  def self.open(smtp_server, options = nil)
31
74
  options ||= { }
75
+ options[:host] = smtp_server
32
76
  options[:port] ||= 25
33
77
  options[:use_tls] = true unless (options.key?(:use_tls))
34
78
 
35
- EventMachine.connect(smtp_server, options[:port], self, options)
79
+ host_name = smtp_server
80
+ host_port = options[:port]
81
+
82
+ if (proxy_options = options[:proxy])
83
+ host_name = proxy_options[:host]
84
+ host_port = proxy_options[:port] || SOCKS5_PORT
85
+ end
86
+
87
+ EventMachine.connect(host_name, host_port, self, options)
36
88
  end
37
89
 
38
- # EHLO address
39
- # MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
40
- # RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
41
- # DATA <CRLF>
42
- # NOOP
43
- # QUIT
44
-
45
- # 250-mx.google.com at your service, [99.231.152.248]
46
- # 250-SIZE 35651584
47
- # 250-8BITMIME
48
- # 250-STARTTLS
49
- # 250 ENHANCEDSTATUSCODES
50
-
51
90
  def self.encode_data(data)
52
91
  data.gsub(/((?:\r\n|\n)\.)/m, '\\1.')
53
92
  end
93
+
94
+ def self.base64(string)
95
+ [ string ].pack('m').chomp
96
+ end
97
+
98
+ def self.encode_authentication(username, password)
99
+ base64("\0#{username}\0#{password}")
100
+ end
101
+
102
+ def self.warn_about_arguments(proc, range)
103
+ unless (range.include?(proc.arity) or proc.arity == -1)
104
+ STDERR.puts "Callback must accept #{[ range.min, range.max ].uniq.join(' to ')} arguments but accepts #{proc.arity}"
105
+ end
106
+ end
54
107
 
55
108
  # == Instance Methods =====================================================
56
109
 
57
110
  def initialize(options)
58
- @options = options
111
+ # Throwing exceptions inside this block is going to cause EventMachine
112
+ # to malfunction in a spectacular way and hide the actual exception. To
113
+ # allow for debugging, exceptions are dumped to STDERR as a last resort.
114
+ begin
115
+ @options = options
59
116
 
60
- @options[:hostname] ||= Socket.gethostname
61
- @messages = [ ]
117
+ @options[:hostname] ||= Socket.gethostname
118
+ @messages = [ ]
62
119
 
63
- debug_notification(:options, @options.inspect)
120
+ NOTIFICATIONS.each do |type|
121
+ callback = @options[type]
122
+
123
+ if (callback.is_a?(Proc))
124
+ self.class.warn_about_arguments(callback, (2..2))
125
+ end
126
+ end
64
127
 
65
- @timeout_at = Time.now + (@timeout || DEFAULT_TIMEOUT)
128
+ debug_notification(:options, @options.inspect)
129
+
130
+ reset_timeout!
131
+ rescue Object => e
132
+ STDERR.puts "#{e.class}: #{e}"
133
+ end
66
134
  end
67
135
 
68
136
  # Returns true if the connection has advertised TLS support, or false if
@@ -71,6 +139,18 @@ class Remailer::Connection < EventMachine::Connection
71
139
  def tls_support?
72
140
  !!@tls_support
73
141
  end
142
+
143
+ # Returns true if the connection will be using a proxy to connect, false
144
+ # otherwise.
145
+ def using_proxy?
146
+ !!@options[:proxy]
147
+ end
148
+
149
+ # Returns true if the connection will require authentication to complete,
150
+ # that is a username has been supplied in the options, or false otherwise.
151
+ def requires_authentication?
152
+ @options[:username] and !@options[:username].empty?
153
+ end
74
154
 
75
155
  # This is used to create a callback that will be called if no more messages
76
156
  # are schedueld to be sent.
@@ -78,11 +158,19 @@ class Remailer::Connection < EventMachine::Connection
78
158
  @options[:after_complete] = block
79
159
  end
80
160
 
161
+ # Closes the connection after all of the queued messages have been sent.
81
162
  def close_when_complete!
82
163
  @options[:close] = true
83
164
  end
84
165
 
166
+ # Sends an email message through the connection at the earliest opportunity.
167
+ # A callback block can be supplied that will be executed when the message
168
+ # has been sent, an unexpected result occurred, or the send timed out.
85
169
  def send_email(from, to, data, &block)
170
+ if (block_given?)
171
+ self.class.warn_about_arguments(block, 1..2)
172
+ end
173
+
86
174
  message = {
87
175
  :from => from,
88
176
  :to => to,
@@ -92,7 +180,9 @@ class Remailer::Connection < EventMachine::Connection
92
180
 
93
181
  @messages << message
94
182
 
183
+ # If the connection is ready to send...
95
184
  if (@state == :ready)
185
+ # ...send the message right away.
96
186
  send_queued_message!
97
187
  end
98
188
  end
@@ -114,7 +204,14 @@ class Remailer::Connection < EventMachine::Connection
114
204
  # flagging the connection as estasblished.
115
205
  def connection_completed
116
206
  @timeout_at = nil
117
- @state = :connected
207
+
208
+ if (using_proxy?)
209
+ enter_proxy_init_state!
210
+ else
211
+ connect_notification(true, "Connection completed")
212
+ @state = :connected
213
+ @connected = true
214
+ end
118
215
  end
119
216
 
120
217
  # This implements the EventMachine::Connection#unbind method to capture
@@ -130,17 +227,48 @@ class Remailer::Connection < EventMachine::Connection
130
227
  end
131
228
 
132
229
  def receive_data(data)
133
- # Data is received in arbitrary sized chunks, so there is no guarantee
134
- # a whole line will be ready to process, or that there is only one line.
135
- @buffer ||= ''
136
- @buffer << data
230
+ # FIX: Buffer the data anyway.
137
231
 
138
- while (line_index = @buffer.index(CRLF))
139
- if (line_index > 0)
140
- receive_reply(@buffer[0, line_index])
232
+ case (state)
233
+ when :proxy_init
234
+ version, method = data.unpack('CC')
235
+
236
+ if (method == SOCKS5_METHOD[:username_password])
237
+ enter_proxy_authentication_state!
238
+ else
239
+ enter_proxy_connecting_state!
240
+ end
241
+ when :proxy_connecting
242
+ version, reply, reserved, address_type, address, port = data.unpack('CCCCNn')
243
+
244
+ case (reply)
245
+ when 0
246
+ @state = :connected
247
+ @connected = true
248
+ connect_notification(true, "Connection completed")
249
+ else
250
+ debug(:error, "Proxy server returned error code #{reply}: #{SOCKS5_REPLY[reply]}")
251
+ connect_notification(false, "Proxy server returned error code #{reply}: #{SOCKS5_REPLY[reply]}")
252
+ close_connection
253
+ @state = :failed
141
254
  end
255
+ when :proxy_authenticating
256
+ # Decode response of authentication request...
257
+
258
+ # ...
259
+ else
260
+ # Data is received in arbitrary sized chunks, so there is no guarantee
261
+ # a whole line will be ready to process, or that there is only one line.
262
+ @buffer ||= ''
263
+ @buffer << data
264
+
265
+ while (line_index = @buffer.index(CRLF))
266
+ if (line_index > 0)
267
+ receive_reply(@buffer[0, line_index])
268
+ end
142
269
 
143
- @buffer = (@buffer[line_index + CRLF_LENGTH, @buffer.length] || '')
270
+ @buffer = (@buffer[line_index + CRLF_LENGTH, @buffer.length] || '')
271
+ end
144
272
  end
145
273
  end
146
274
 
@@ -152,6 +280,7 @@ class Remailer::Connection < EventMachine::Connection
152
280
  end
153
281
  end
154
282
 
283
+ protected
155
284
  def send_line(line = '')
156
285
  send_data(line + CRLF)
157
286
 
@@ -164,6 +293,18 @@ class Remailer::Connection < EventMachine::Connection
164
293
  !!@reply_complete
165
294
  end
166
295
 
296
+ def resolve_hostname(hostname)
297
+ # FIXME: Elminitate this potentially blocking call by using an async
298
+ # resolver if available.
299
+ record = Socket.gethostbyname(hostname)
300
+
301
+ debug_notification(:resolved, record && record.last)
302
+
303
+ record and record.last
304
+ rescue
305
+ nil
306
+ end
307
+
167
308
  def receive_reply(reply)
168
309
  debug_notification(:reply, reply.inspect)
169
310
 
@@ -173,8 +314,11 @@ class Remailer::Connection < EventMachine::Connection
173
314
  reply_code = $1.to_i
174
315
  @reply_complete = $2 != '-'
175
316
  reply_message = $3
317
+
318
+ debug_notification(:recv, reply)
176
319
  end
177
320
 
321
+ # The connection itself will be in a particular state.
178
322
  case (state)
179
323
  when :connected
180
324
  case (reply_code)
@@ -192,7 +336,7 @@ class Remailer::Connection < EventMachine::Connection
192
336
  send_line("HELO #{@options[:hostname]}")
193
337
  end
194
338
  else
195
- fail_unanticipated_response!(reply)
339
+ fail_unanticipated_response!(reply_code, reply_message)
196
340
  end
197
341
  when :sent_ehlo
198
342
  case (reply_code)
@@ -206,27 +350,46 @@ class Remailer::Connection < EventMachine::Connection
206
350
  when 'STARTTLS'
207
351
  @tls_support = true
208
352
  end
209
-
210
- # FIX: Add TLS support
211
- # if (@tls_support and @options[:use_tls])
212
- # @state = :tls_init
213
- # end
214
353
 
215
354
  if (@reply_complete)
216
- # Add authentication hook
217
- @state = :ready
218
-
219
- send_queued_message!
355
+ if (@options[:use_tls])
356
+ send_line("STARTTLS")
357
+ @state = :sent_starttls
358
+ elsif (requires_authentication?)
359
+ enter_sent_auth_state!
360
+ else
361
+ enter_ready_state!
362
+ end
220
363
  end
221
364
  else
222
- fail_unanticipated_response!(reply)
365
+ fail_unanticipated_response!(reply_code, reply_message)
223
366
  end
224
367
  when :sent_helo
225
368
  case (reply_code)
226
369
  when 250
227
- @state = :ready
370
+ enter_ready_state!
228
371
  else
229
- fail_unanticipated_response!(reply)
372
+ fail_unanticipated_response!(reply_code, reply_message)
373
+ end
374
+ when :sent_starttls
375
+ case (reply_code)
376
+ when 220
377
+ start_tls
378
+
379
+ if (requires_authentication?)
380
+ enter_sent_auth_state!
381
+ else
382
+ enter_ready_state!
383
+ end
384
+ else
385
+ fail_unanticipated_response!(reply_code, reply_message)
386
+ end
387
+ when :sent_auth
388
+ case (reply_code)
389
+ when 235
390
+ enter_ready_state!
391
+ else
392
+ fail_unanticipated_response!(reply_code, reply_message)
230
393
  end
231
394
  when :sent_mail_from
232
395
  case (reply_code)
@@ -234,7 +397,7 @@ class Remailer::Connection < EventMachine::Connection
234
397
  @state = :sent_rcpt_to
235
398
  send_line("RCPT TO:#{@active_message[:to]}")
236
399
  else
237
- fail_unanticipated_response!(reply)
400
+ fail_unanticipated_response!(reply_code, reply_message)
238
401
  end
239
402
  when :sent_rcpt_to
240
403
  case (reply_code)
@@ -244,48 +407,121 @@ class Remailer::Connection < EventMachine::Connection
244
407
 
245
408
  @data_offset = 0
246
409
  else
247
- fail_unanticipated_response!(reply)
410
+ fail_unanticipated_response!(reply_code, reply_message)
248
411
  end
249
412
  when :sent_data
250
413
  case (reply_code)
251
414
  when 354
252
415
  @state = :data_sending
253
416
 
254
- transmit_data_chunk!
417
+ transmit_data!
255
418
  else
256
- fail_unanticipated_response!(reply)
419
+ fail_unanticipated_response!(reply_code, reply_message)
257
420
  end
258
421
  when :sent_data_content
259
- if (callback = @active_message[:callback])
260
- callback.call(reply_code)
261
- end
262
-
263
- @state = :ready
422
+ send_callback(reply_code, reply_message)
264
423
 
265
- send_queued_message!
424
+ enter_ready_state!
266
425
  when :sent_quit
267
426
  case (reply_code)
268
427
  when 221
269
428
  @state = :closed
270
429
  close_connection
271
430
  else
272
- fail_unanticipated_response!(reply)
431
+ fail_unanticipated_response!(reply_code, reply_message)
273
432
  end
274
433
  when :sent_reset
275
434
  case (reply_code)
276
435
  when 250
277
- @state = :ready
278
-
279
-
280
- send_queued_message!
436
+ enter_ready_state!
281
437
  end
282
438
  end
283
439
  end
284
440
 
285
- def transmit_data_chunk!(chunk_size = nil)
441
+ def enter_proxy_init_state!
442
+ debug_notification(:proxy, "Initiating proxy connection through #{@options[:proxy][:host]}")
443
+
444
+ socks_methods = [ ]
445
+
446
+ if (@options[:proxy][:username])
447
+ socks_methods << SOCKS5_METHOD[:username_password]
448
+ end
449
+
450
+ send_data(
451
+ [
452
+ SOCKS5_VERSION,
453
+ socks_methods.length,
454
+ socks_methods
455
+ ].flatten.pack('CCC*')
456
+ )
457
+
458
+ @state = :proxy_init
459
+ end
460
+
461
+ def enter_proxy_connecting_state!
462
+ # REFACTOR: Move the resolution of the hostname to an earlier point to
463
+ # avoid connecting needlessly.
464
+
465
+ debug_notification(:proxy, "Sending proxy connection request to #{@options[:host]}:#{@options[:port]}")
466
+
467
+ if (ip_address = resolve_hostname(@options[:host]))
468
+ send_data(
469
+ [
470
+ SOCKS5_VERSION,
471
+ SOCKS5_COMMAND[:connect],
472
+ 0,
473
+ SOCKS5_ADDRESS_TYPE[:ipv4],
474
+ ip_address,
475
+ @options[:port]
476
+ ].pack('CCCCA4n')
477
+ )
478
+
479
+ @state = :proxy_connecting
480
+ else
481
+ send_callback(:error_connecting, "Could not resolve hostname #{@options[:host]}")
482
+
483
+ @state = :failed
484
+ close_connection
485
+ end
486
+ end
487
+
488
+ def enter_proxy_authenticating_state!
489
+ debug_notification(:proxy, "Sending proxy authentication")
490
+
491
+ proxy_options = @options[:proxy]
492
+ username = proxy_options[:username]
493
+ password = proxy_options[:password]
494
+
495
+ send_data(
496
+ [
497
+ SOCKS5_VERSION,
498
+ username.length,
499
+ username,
500
+ password.length,
501
+ password
502
+ ].pack('CCA*CA*')
503
+ )
504
+
505
+ @state = :proxy_authenticating
506
+ end
507
+
508
+ def enter_ready_state!
509
+ @state = :ready
510
+
511
+ send_queued_message!
512
+ end
513
+
514
+ def enter_sent_auth_state!
515
+ send_line("AUTH PLAIN #{self.class.encode_authentication(@options[:username], @options[:password])}")
516
+ @state = :sent_auth
517
+ end
518
+
519
+ def transmit_data!(chunk_size = nil)
286
520
  data = @active_message[:data]
287
521
  chunk_size ||= data.length
288
522
 
523
+ # This chunk-based sending will work better when/if EventMachine can be
524
+ # configured to support 'writable' notifications on the active socket.
289
525
  chunk = data[@data_offset, chunk_size]
290
526
  debug_notification(:send, chunk.inspect)
291
527
  send_data(self.class.encode_data(data))
@@ -301,12 +537,14 @@ class Remailer::Connection < EventMachine::Connection
301
537
  end
302
538
  end
303
539
 
304
- def notify_writable
305
- # FIXME: Get EventMachine to trigger this
540
+ def reset_timeout!
541
+ @timeout_at = Time.now + (@options[:timeout] || DEFAULT_TIMEOUT)
306
542
  end
307
543
 
308
544
  def send_queued_message!
309
545
  return if (@active_message)
546
+
547
+ reset_timeout!
310
548
 
311
549
  if (@active_message = @messages.shift)
312
550
  @state = :sent_mail_from
@@ -324,30 +562,61 @@ class Remailer::Connection < EventMachine::Connection
324
562
  def check_for_timeouts!
325
563
  return if (!@timeout_at or Time.now < @timeout_at)
326
564
 
327
- callback(nil)
565
+ error_notification(:timeout, "Connection timed out")
566
+ debug_notification(:timeout, "Connection timed out")
567
+ send_callback(:timeout, "Connection timed out before send could complete")
568
+
328
569
  @state = :timeout
570
+
571
+ unless (@connected)
572
+ connect_notification(false, "Connection timed out")
573
+ end
574
+
329
575
  close_connection
330
576
  end
331
577
 
332
- def debug_notification(type, message)
333
- case (@options[:debug])
578
+ def send_notification(type, code, message)
579
+ case (callback = @options[type])
334
580
  when nil, false
335
- # No debugging in this case
581
+ # No notification in this case
336
582
  when Proc
337
- @options[:debug].call(type, message)
583
+ callback.call(code, message)
338
584
  when IO
339
- @options[:debug].puts("%s: %s" % [ type, message ])
585
+ callback.puts("%s: %s" % [ code.to_s, message ])
340
586
  else
341
- STDERR.puts("%s: %s" % [ type, message ])
587
+ STDERR.puts("%s: %s" % [ code.to_s, message ])
342
588
  end
343
589
  end
344
590
 
345
- def fail_unanticipated_response!(reply)
346
- if (@active_message)
347
- if (callback = @active_message[:callback])
348
- callback.call(nil)
591
+ def connect_notification(code, message)
592
+ send_notification(:connect, code, message)
593
+ end
594
+
595
+ def error_notification(code, message)
596
+ send_notification(:error, code, message)
597
+ end
598
+
599
+ def debug_notification(code, message)
600
+ send_notification(:debug, code, message)
601
+ end
602
+
603
+ def send_callback(reply_code, reply_message)
604
+ if (callback = (@active_message and @active_message[:callback]))
605
+ # The callback is screened in advance when assigned to ensure that it
606
+ # has only 1 or 2 arguments. There should be no else here.
607
+ case (callback.arity)
608
+ when 2
609
+ callback.call(reply_code, reply_message)
610
+ when 1
611
+ callback.call(reply_code)
349
612
  end
350
613
  end
614
+ end
615
+
616
+ def fail_unanticipated_response!(reply_code, reply_message)
617
+ send_callback(reply_code, reply_message)
618
+ debug_notification(:error, "[#{@state}] #{reply_code} #{reply_message}")
619
+ error_notification(reply_code, reply_message)
351
620
 
352
621
  @active_message = nil
353
622
 
data/remailer.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{remailer}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Scott Tadman"]
12
- s.date = %q{2010-11-17}
12
+ s.date = %q{2010-11-22}
13
13
  s.description = %q{EventMachine capable SMTP engine}
14
14
  s.email = %q{scott@twg.ca}
15
15
  s.extra_rdoc_files = [
data/test/helper.rb CHANGED
@@ -62,3 +62,15 @@ class Test::Unit::TestCase
62
62
  end
63
63
  end
64
64
  end
65
+
66
+ require 'ostruct'
67
+
68
+ TestConfig = OpenStruct.new
69
+
70
+ config_file = File.expand_path("config.rb", File.dirname(__FILE__))
71
+
72
+ if (File.exist?(config_file))
73
+ require config_file
74
+ else
75
+ raise "No test/config.rb file found. Copy and modify test/config.example.rb"
76
+ end
@@ -1,8 +1,6 @@
1
1
  require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
2
 
3
3
  class RemailerTest < Test::Unit::TestCase
4
- TEST_SMTP_SERVER = 'mail.postageapp.com'.freeze
5
-
6
4
  def test_encode_data
7
5
  sample_data = "Line 1\r\nLine 2\r\n.\r\nLine 3\r\n.Line 4\r\n"
8
6
 
@@ -14,7 +12,7 @@ class RemailerTest < Test::Unit::TestCase
14
12
  debug = { }
15
13
 
16
14
  connection = Remailer::Connection.open(
17
- TEST_SMTP_SERVER,
15
+ TestConfig.smtp_server[:host],
18
16
  :debug => STDERR
19
17
  )
20
18
 
@@ -32,10 +30,104 @@ class RemailerTest < Test::Unit::TestCase
32
30
  connection.state == :closed
33
31
  end
34
32
 
35
- assert_equal TEST_SMTP_SERVER, connection.remote
33
+ assert_equal TestConfig.smtp_server[:host], connection.remote
34
+
35
+ assert_equal true, after_complete_trigger
36
+
37
+ assert_equal 52428800, connection.max_size
38
+ assert_equal :esmtp, connection.protocol
39
+ assert_equal true, connection.tls_support?
40
+ end
41
+ end
42
+
43
+ def test_failed_connect
44
+ engine do
45
+ error_received = nil
46
+
47
+ connection = Remailer::Connection.open(
48
+ 'example.com',
49
+ :debug => STDERR,
50
+ :error => lambda { |code, message|
51
+ error_received = [ code, message ]
52
+ },
53
+ :timeout => 1
54
+ )
55
+
56
+ assert_eventually(3) do
57
+ error_received
58
+ end
59
+
60
+ assert_equal :timeout, error_received[0]
61
+ end
62
+ end
63
+
64
+ def test_connect_with_auth
65
+ engine do
66
+ debug = { }
67
+
68
+ connection = Remailer::Connection.open(
69
+ TestConfig.public_smtp_server[:host],
70
+ :port => 587,
71
+ :debug => STDERR,
72
+ :username => TestConfig.public_smtp_server[:username],
73
+ :password => TestConfig.public_smtp_server[:password]
74
+ )
75
+
76
+ after_complete_trigger = false
77
+
78
+ connection.close_when_complete!
79
+ connection.after_complete do
80
+ after_complete_trigger = true
81
+ end
82
+
83
+ assert_equal :connecting, connection.state
84
+ assert !connection.error?
85
+
86
+ assert_eventually(15) do
87
+ connection.state == :closed
88
+ end
89
+
90
+ assert_equal TestConfig.public_smtp_server[:identifier], connection.remote
36
91
 
37
92
  assert_equal true, after_complete_trigger
38
93
 
94
+ assert_equal 35651584, connection.max_size
95
+ assert_equal :esmtp, connection.protocol
96
+ assert_equal true, connection.tls_support?
97
+ end
98
+ end
99
+
100
+ def test_connect_via_proxy
101
+ engine do
102
+ debug = { }
103
+
104
+ connection = Remailer::Connection.open(
105
+ TestConfig.smtp_server[:host],
106
+ :debug => STDERR,
107
+ :proxy => {
108
+ :proto => :socks5,
109
+ :host => TestConfig.proxy_server
110
+ }
111
+ )
112
+
113
+ after_complete_trigger = false
114
+
115
+ connection.close_when_complete!
116
+ connection.after_complete do
117
+ after_complete_trigger = true
118
+ end
119
+
120
+ assert_equal :connecting, connection.state
121
+ assert !connection.error?
122
+
123
+ assert_eventually(15) do
124
+ connection.state == :closed
125
+ end
126
+
127
+ assert_equal TestConfig.smtp_server[:identifier], connection.remote
128
+
129
+ assert_equal true, after_complete_trigger
130
+
39
131
  assert_equal 52428800, connection.max_size
40
132
  assert_equal :esmtp, connection.protocol
41
133
  assert_equal true, connection.tls_support?
@@ -45,7 +137,7 @@ class RemailerTest < Test::Unit::TestCase
45
137
  def test_connect_and_send_after_start
46
138
  engine do
47
139
  connection = Remailer::Connection.open(
48
- TEST_SMTP_SERVER,
140
+ TestConfig.smtp_server[:host],
49
141
  :debug => STDERR
50
142
  )
51
143
 
@@ -73,7 +165,7 @@ class RemailerTest < Test::Unit::TestCase
73
165
  def test_connect_and_send_dotted_message
74
166
  engine do
75
167
  connection = Remailer::Connection.open(
76
- TEST_SMTP_SERVER,
168
+ TestConfig.smtp_server[:host],
77
169
  :debug => STDERR
78
170
  )
79
171
 
@@ -97,15 +189,14 @@ class RemailerTest < Test::Unit::TestCase
97
189
 
98
190
  def test_connect_and_long_send
99
191
  engine do
100
- connection = Remailer::Connection.open('twgmail.twg.ca')
192
+ connection = Remailer::Connection.open(TestConfig.smtp_server[:host])
101
193
 
102
194
  assert_equal :connecting, connection.state
103
- assert !connection.error?
104
195
 
105
196
  result_code = nil
106
197
  connection.send_email(
107
- 'sender@postageapp.com',
108
- 'remailer+test@example.postageapp.com',
198
+ TestConfig.sender,
199
+ TestConfig.receiver,
109
200
  example_message + 'a' * 100000
110
201
  ) do |c|
111
202
  result_code = c
@@ -121,9 +212,9 @@ protected
121
212
  def example_message
122
213
  example = <<__END__
123
214
  Date: Sat, 13 Nov 2010 02:25:24 +0000
124
- From: sender@postageapp.com
125
- To: Remailer Test <remailer@twg.ca>
126
- Message-Id: <hfLkcIByfjYoNIxCO7DMsxBTX9svsFHikIOfAiYy@twg.ca>
215
+ From: #{TestConfig.sender}
216
+ To: Remailer Test <#{TestConfig.receiver}>
217
+ Message-Id: <hfLkcIByfjYoNIxCO7DMsxBTX9svsFHikIOfAiYy@#{TestConfig.sender.split(/@/).last}>
127
218
  Subject: Example Subject
128
219
  Mime-Version: 1.0
129
220
  Content-Type: text/plain
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 2
7
8
  - 1
8
- - 0
9
- version: 0.1.0
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Scott Tadman
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-17 00:00:00 -05:00
17
+ date: 2010-11-22 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies: []
20
20