remailer 0.1.0 → 0.2.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/.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