remailer 0.4.8 → 0.4.10

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.8
1
+ 0.4.10
@@ -77,9 +77,28 @@ class Remailer::Connection < EventMachine::Connection
77
77
  host_port = proxy_options[:port] || SOCKS5_PORT
78
78
  end
79
79
 
80
+ establish!(host_name, host_port, options)
81
+ end
82
+
83
+ # Warns about supplying a Proc which does not appear to accept the required
84
+ # number of arguments.
85
+ def self.warn_about_arguments(proc, range)
86
+ unless (range.include?(proc.arity) or proc.arity == -1)
87
+ STDERR.puts "Callback must accept #{[ range.min, range.max ].uniq.join(' to ')} arguments but accepts #{proc.arity}"
88
+ end
89
+ end
90
+
91
+ def self.establish!(host_name, host_port, options)
80
92
  EventMachine.connect(host_name, host_port, self, options)
81
93
 
82
94
  rescue EventMachine::ConnectionError => e
95
+ report_exception(e, options)
96
+
97
+ false
98
+ end
99
+
100
+ # Handles callbacks driven by exceptions before an instance could be created.
101
+ def self.report_exception(e)
83
102
  case (options[:connect])
84
103
  when Proc
85
104
  options[:connect].call(false, e.to_s)
@@ -111,14 +130,6 @@ class Remailer::Connection < EventMachine::Connection
111
130
  false
112
131
  end
113
132
 
114
- # Warns about supplying a Proc which does not appear to accept the required
115
- # number of arguments.
116
- def self.warn_about_arguments(proc, range)
117
- unless (range.include?(proc.arity) or proc.arity == -1)
118
- STDERR.puts "Callback must accept #{[ range.min, range.max ].uniq.join(' to ')} arguments but accepts #{proc.arity}"
119
- end
120
- end
121
-
122
133
  # == Instance Methods =====================================================
123
134
 
124
135
  # EventMachine will call this constructor and it is not to be called
@@ -264,17 +275,27 @@ class Remailer::Connection < EventMachine::Connection
264
275
  # This implements the EventMachine::Connection#unbind method to capture
265
276
  # a connection closed event.
266
277
  def unbind
278
+ return if (@unbound)
279
+
267
280
  @connected = false
281
+ @unbound = true
268
282
  @interpreter = nil
269
283
 
270
284
  if (@active_message)
271
285
  if (callback = @active_message[:callback])
272
286
  callback.call(nil)
287
+ @active_message = nil
273
288
  end
274
289
  end
275
290
 
276
291
  send_callback(:on_disconnect)
277
292
  end
293
+
294
+ # Returns true if the connection has been unbound by EventMachine, false
295
+ # otherwise.
296
+ def unbound?
297
+ !!@unbound
298
+ end
278
299
 
279
300
  # This implements the EventMachine::Connection#receive_data method that
280
301
  # is called each time new data is received from the socket.
@@ -284,9 +305,9 @@ class Remailer::Connection < EventMachine::Connection
284
305
  @buffer ||= ''
285
306
  @buffer << data
286
307
 
287
- if (@interpreter)
288
- @interpreter.process(@buffer) do |reply|
289
- debug_notification(:receive, "[#{@interpreter.label}] #{reply.inspect}")
308
+ if (interpreter = @interpreter)
309
+ interpreter.process(@buffer) do |reply|
310
+ debug_notification(:receive, "[#{interpreter.label}] #{reply.inspect}")
290
311
  end
291
312
  else
292
313
  error_notification(:out_of_band, "Receiving data before a protocol has been established.")
@@ -308,7 +329,11 @@ class Remailer::Connection < EventMachine::Connection
308
329
  # Returns the current state of the active interpreter, or nil if no state
309
330
  # is assigned.
310
331
  def state
311
- @interpreter and @interpreter.state
332
+ if (interpreter = @interpreter)
333
+ @interpreter.state
334
+ else
335
+ nil
336
+ end
312
337
  end
313
338
 
314
339
  # Sends a single line to the remote host with the appropriate CR+LF
@@ -322,14 +347,20 @@ class Remailer::Connection < EventMachine::Connection
322
347
  end
323
348
 
324
349
  def resolve_hostname(hostname)
325
- # FIXME: Elminitate this potentially blocking call by using an async
326
- # resolver if available.
327
350
  record = Socket.gethostbyname(hostname)
328
351
 
329
352
  # FIXME: IPv6 Support here
330
- debug_notification(:resolved, record && record.last.unpack('CCCC').join('.'))
353
+ address = (record and record[3])
354
+
355
+ if (address)
356
+ debug_notification(:resolver, "Address #{hostname} resolved as #{address.unpack('CCCC').join('.')}")
357
+ else
358
+ debug_notification(:resolver, "Address #{hostname} could not be resolved")
359
+ end
360
+
361
+ yield(address) if (block_given?)
331
362
 
332
- record and record.last
363
+ address
333
364
  rescue
334
365
  nil
335
366
  end
@@ -339,6 +370,12 @@ class Remailer::Connection < EventMachine::Connection
339
370
  @timeout_at = Time.now + @timeout
340
371
  end
341
372
 
373
+ # Returns the number of seconds remaining until a timeout will occur, or
374
+ # nil if no time-out is pending.
375
+ def time_remaning
376
+ @timeout_at and (@timeout_at.to_i - Time.now.to_i)
377
+ end
378
+
342
379
  # Checks for a timeout condition, and if one is detected, will close the
343
380
  # connection and send appropriate callbacks.
344
381
  def check_for_timeouts!
@@ -362,8 +399,8 @@ class Remailer::Connection < EventMachine::Connection
362
399
 
363
400
  message = "Timed out before a connection could be established to #{remote_options[:host]}:#{remote_options[:port]}"
364
401
 
365
- if (@interpreter)
366
- message << " using #{@interpreter.label}"
402
+ if (interpreter = @interpreter)
403
+ message << " using #{interpreter.label}"
367
404
  end
368
405
 
369
406
  connect_notification(false, message)
@@ -408,13 +445,20 @@ class Remailer::Connection < EventMachine::Connection
408
445
 
409
446
  # EventMachine: Closes down the connection.
410
447
  def close_connection
411
- send_callback(:on_disconnect)
448
+ return if (@closed)
449
+
450
+ unless (@timed_out)
451
+ send_callback(:on_disconnect)
452
+ end
453
+
412
454
  debug_notification(:closed, "Connection closed")
455
+
413
456
  super
414
457
 
415
458
  @connected = false
416
459
  @closed = true
417
460
  @timeout_at = nil
461
+ @interpreter = nil
418
462
  end
419
463
  alias_method :close, :close_connection
420
464
 
@@ -493,7 +537,9 @@ class Remailer::Connection < EventMachine::Connection
493
537
  end
494
538
 
495
539
  def message_callback(reply_code, reply_message)
496
- if (callback = (@active_message and @active_message[:callback]))
540
+ active_message = @active_message
541
+
542
+ if (callback = (active_message and active_message[:callback]))
497
543
  # The callback is screened in advance when assigned to ensure that it
498
544
  # has only 1 or 2 arguments. There should be no else here.
499
545
  case (callback.arity)
@@ -121,11 +121,7 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
121
121
  interpret(220) do
122
122
  delegate.start_tls
123
123
 
124
- if (delegate.requires_authentication?)
125
- enter_state(:auth)
126
- else
127
- enter_state(:established)
128
- end
124
+ enter_state(:helo)
129
125
  end
130
126
  end
131
127
 
@@ -138,20 +134,10 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
138
134
  enter_state(:established)
139
135
  end
140
136
 
141
- interpret(535) do |message, continues|
142
- if (@error)
143
- @error << ' '
144
-
145
- if (message.match(/^(\S+)/).to_s == @error.match(/^(\S+)/).to_s)
146
- @error << message.sub(/^\S+/, '')
147
- else
148
- @error << message
149
- end
150
- else
137
+ interpret(535) do |reply_message, continues|
138
+ handle_reply_continuation(535, reply_message, continues) do |reply_code, reply_message|
151
139
  @error = message
152
- end
153
140
 
154
- unless (continues)
155
141
  enter_state(:quit)
156
142
  end
157
143
  end
@@ -218,11 +204,23 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
218
204
  end
219
205
  end
220
206
 
221
- interpret(250) do
222
- if (delegate.active_message[:test])
207
+ interpret(250) do |reply_message, continues|
208
+ handle_reply_continuation(250, reply_message, continues) do |reply_code, reply_message|
209
+ if (delegate.active_message[:test])
210
+ delegate_call(:after_message_sent, reply_code, reply_message)
211
+
212
+ enter_state(:reset)
213
+ else
214
+ enter_state(:data)
215
+ end
216
+ end
217
+ end
218
+
219
+ interpret(500..599) do |reply_code, reply_message, continues|
220
+ handle_reply_continuation(reply_code, reply_message, continues) do |reply_code, reply_message|
221
+ delegate_call(:after_message_sent, reply_code, reply_message)
222
+
223
223
  enter_state(:reset)
224
- else
225
- enter_state(:data)
226
224
  end
227
225
  end
228
226
  end
@@ -251,8 +249,10 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
251
249
  delegate.send_line(".")
252
250
  end
253
251
 
254
- default do |reply_code, reply_message|
255
- delegate_call(:after_message_sent, reply_code, reply_message)
252
+ default do |reply_code, reply_message, continues|
253
+ handle_reply_continuation(reply_code, reply_message, continues) do |reply_code, reply_message|
254
+ delegate_call(:after_message_sent, reply_code, reply_message)
255
+ end
256
256
 
257
257
  enter_state(:sent)
258
258
  end
@@ -300,14 +300,16 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
300
300
  end
301
301
  end
302
302
 
303
- on_error do |reply_code, reply_message|
304
- delegate.message_callback(reply_code, reply_message)
305
- delegate.debug_notification(:error, "[#{@state}] #{reply_code} #{reply_message}")
306
- delegate.error_notification(reply_code, reply_message)
307
-
308
- delegate.active_message = nil
309
-
310
- enter_state(delegate.protocol ? :reset : :terminated)
303
+ on_error do |reply_code, reply_message, continues|
304
+ handle_reply_continuation(reply_code, reply_message, continues) do |reply_code, reply_message|
305
+ delegate.message_callback(reply_code, reply_message)
306
+ delegate.debug_notification(:error, "[#{@state}] #{reply_code} #{reply_message}")
307
+ delegate.error_notification(reply_code, reply_message)
308
+
309
+ delegate.active_message = nil
310
+
311
+ enter_state(@state == :initialized ? :terminated : :reset)
312
+ end
311
313
  end
312
314
 
313
315
  # == Instance Methods =====================================================
@@ -315,6 +317,22 @@ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
315
317
  def label
316
318
  'SMTP'
317
319
  end
320
+
321
+ def handle_reply_continuation(reply_code, reply_message, continues)
322
+ @reply_message ||= ''
323
+
324
+ if (preamble = @reply_message.split(/\s/).first)
325
+ reply_message.sub!(/^#{preamble}/, '')
326
+ end
327
+
328
+ @reply_message << reply_message.gsub(/\s+/, ' ')
329
+
330
+ unless (continues)
331
+ yield(reply_code, @reply_message)
332
+
333
+ @reply_message = nil
334
+ end
335
+ end
318
336
 
319
337
  def will_interpret?(proc, args)
320
338
  # Can only interpret blocks if the last part of the message has been
@@ -82,9 +82,10 @@ class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
82
82
 
83
83
  state :resolving_destination do
84
84
  enter do
85
- # FIX: Use an async resolver here
86
- @destination_address = delegate.resolve_hostname(delegate.options[:host])
87
- enter_state(:connect_through_proxy)
85
+ delegate.resolve_hostname(delegate.options[:host]) do |address|
86
+ @destination_address = address
87
+ enter_state(:connect_through_proxy)
88
+ end
88
89
  end
89
90
  end
90
91
 
@@ -104,7 +105,7 @@ class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
104
105
  ].pack('CCCCA4n')
105
106
  )
106
107
  else
107
- delegate.send_callback(:error_connecting, "Could not resolve hostname #{delegate.options[:host]}")
108
+ @error_message = "Could not resolve hostname #{delegate.options[:host]}"
108
109
  enter_state(:failed)
109
110
  end
110
111
  end
@@ -169,10 +170,15 @@ class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
169
170
 
170
171
  state :failed do
171
172
  enter do
172
- message = "Proxy server returned error code #{@reply}: #{SOCKS5_REPLY[@reply]}"
173
- delegate.debug_notification(:error, message)
173
+ if (@error_message)
174
+ delegate.debug_notification(:error, @error_message)
175
+ delegate.error_notification("SOCKS5", @error_message)
176
+ else
177
+ message = "Proxy server returned error code #{@reply}: #{SOCKS5_REPLY[@reply]}"
178
+ delegate.debug_notification(:error, message)
179
+ delegate.error_notification("SOCKS5_#{@reply}", message)
180
+ end
174
181
  delegate.connect_notification(false, message)
175
- delegate.error_notification("SOCKS5_#{@reply}", message)
176
182
  delegate.close_connection
177
183
  end
178
184
 
@@ -231,6 +231,8 @@ class Remailer::Interpreter
231
231
  case (response)
232
232
  when Regexp
233
233
  match_result = response.match(object)
234
+ when Range
235
+ response.include?(object)
234
236
  else
235
237
  response === object
236
238
  end
@@ -249,6 +251,8 @@ class Remailer::Interpreter
249
251
  end
250
252
  when String
251
253
  args[0].sub!(matched, '')
254
+ when Range
255
+ # Keep as-is
252
256
  else
253
257
  args.shift
254
258
  end
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.4.8"
8
+ s.version = "0.4.10"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = [%q{Scott Tadman}]
12
- s.date = %q{2011-05-15}
12
+ s.date = %q{2011-07-05}
13
13
  s.description = %q{EventMachine SMTP Mail User Agent}
14
14
  s.email = %q{scott@twg.ca}
15
15
  s.extra_rdoc_files = [
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
38
38
  ]
39
39
  s.homepage = %q{http://github.com/twg/remailer}
40
40
  s.require_paths = [%q{lib}]
41
- s.rubygems_version = %q{1.8.2}
41
+ s.rubygems_version = %q{1.8.5}
42
42
  s.summary = %q{Reactor-Ready SMTP Mailer}
43
43
  s.test_files = [
44
44
  "test/config.example.rb",
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: remailer
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.4.8
5
+ version: 0.4.10
6
6
  platform: ruby
7
7
  authors:
8
8
  - Scott Tadman
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-15 00:00:00 Z
13
+ date: 2011-07-05 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: eventmachine
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
74
  requirements: []
75
75
 
76
76
  rubyforge_project:
77
- rubygems_version: 1.8.2
77
+ rubygems_version: 1.8.5
78
78
  signing_key:
79
79
  specification_version: 3
80
80
  summary: Reactor-Ready SMTP Mailer