eventmachine 0.12.8-x86-mswin32-60 → 0.12.10-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +14 -13
  2. data/Rakefile +374 -264
  3. data/eventmachine.gemspec +4 -5
  4. data/ext/binder.cpp +125 -126
  5. data/ext/binder.h +46 -48
  6. data/ext/cmain.cpp +184 -42
  7. data/ext/cplusplus.cpp +202 -202
  8. data/ext/ed.cpp +242 -81
  9. data/ext/ed.h +39 -22
  10. data/ext/em.cpp +127 -108
  11. data/ext/em.h +27 -18
  12. data/ext/emwin.cpp +3 -3
  13. data/ext/eventmachine.h +49 -38
  14. data/ext/eventmachine_cpp.h +96 -96
  15. data/ext/extconf.rb +147 -132
  16. data/ext/fastfilereader/extconf.rb +82 -76
  17. data/ext/project.h +151 -140
  18. data/ext/rubymain.cpp +222 -103
  19. data/ext/ssl.cpp +460 -460
  20. data/ext/ssl.h +94 -94
  21. data/java/src/com/rubyeventmachine/EmReactor.java +570 -423
  22. data/java/src/com/rubyeventmachine/EventableChannel.java +69 -57
  23. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +189 -171
  24. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +364 -244
  25. data/java/src/com/rubyeventmachine/{Application.java → application/Application.java} +194 -200
  26. data/java/src/com/rubyeventmachine/{Connection.java → application/Connection.java} +74 -74
  27. data/java/src/com/rubyeventmachine/{ConnectionFactory.java → application/ConnectionFactory.java} +36 -36
  28. data/java/src/com/rubyeventmachine/{DefaultConnectionFactory.java → application/DefaultConnectionFactory.java} +46 -46
  29. data/java/src/com/rubyeventmachine/{PeriodicTimer.java → application/PeriodicTimer.java} +38 -38
  30. data/java/src/com/rubyeventmachine/{Timer.java → application/Timer.java} +54 -54
  31. data/java/src/com/rubyeventmachine/tests/ApplicationTest.java +109 -108
  32. data/java/src/com/rubyeventmachine/tests/ConnectTest.java +148 -146
  33. data/java/src/com/rubyeventmachine/tests/TestDatagrams.java +53 -53
  34. data/java/src/com/rubyeventmachine/tests/TestServers.java +75 -74
  35. data/java/src/com/rubyeventmachine/tests/TestTimers.java +90 -89
  36. data/lib/em/connection.rb +71 -12
  37. data/lib/em/deferrable.rb +191 -186
  38. data/lib/em/protocols.rb +36 -35
  39. data/lib/em/protocols/httpclient2.rb +590 -582
  40. data/lib/em/protocols/line_and_text.rb +125 -126
  41. data/lib/em/protocols/linetext2.rb +161 -160
  42. data/lib/em/protocols/object_protocol.rb +45 -39
  43. data/lib/em/protocols/smtpclient.rb +357 -331
  44. data/lib/em/protocols/socks4.rb +66 -0
  45. data/lib/em/queue.rb +60 -60
  46. data/lib/em/timers.rb +56 -55
  47. data/lib/em/version.rb +1 -1
  48. data/lib/eventmachine.rb +125 -169
  49. data/lib/jeventmachine.rb +257 -142
  50. data/tasks/{cpp.rake → cpp.rake_example} +76 -76
  51. data/tests/test_attach.rb +125 -100
  52. data/tests/test_basic.rb +1 -2
  53. data/tests/test_connection_count.rb +34 -44
  54. data/tests/test_epoll.rb +0 -2
  55. data/tests/test_get_sock_opt.rb +30 -0
  56. data/tests/test_httpclient2.rb +3 -3
  57. data/tests/test_inactivity_timeout.rb +21 -1
  58. data/tests/test_ltp.rb +182 -188
  59. data/tests/test_next_tick.rb +0 -2
  60. data/tests/test_pause.rb +70 -0
  61. data/tests/test_pending_connect_timeout.rb +48 -0
  62. data/tests/test_ssl_args.rb +78 -67
  63. data/tests/test_timers.rb +162 -141
  64. metadata +13 -11
  65. data/tasks/project.rake +0 -79
  66. data/tasks/tests.rake +0 -193
@@ -1,39 +1,45 @@
1
- module EventMachine
2
- module Protocols
3
- # ObjectProtocol allows for easy communication using marshaled ruby objects
4
- #
5
- # module RubyServer
6
- # include EM::P::ObjectProtocol
7
- #
8
- # def receive_object obj
9
- # send_object({'you said' => obj})
10
- # end
11
- # end
12
- #
13
- module ObjectProtocol
14
- def receive_data data # :nodoc:
15
- (@buf ||= '') << data
16
-
17
- while @buf.size >= 4
18
- if @buf.size >= 4+(size=@buf.unpack('N').first)
19
- @buf.slice!(0,4)
20
- receive_object Marshal.load(@buf.slice!(0,size))
21
- else
22
- break
23
- end
24
- end
25
- end
26
-
27
- # Invoked with ruby objects received over the network
28
- def receive_object obj
29
- # stub
30
- end
31
-
32
- # Sends a ruby object over the network
33
- def send_object obj
34
- data = Marshal.dump(obj)
35
- send_data [data.respond_to?(:bytesize) ? data.bytesize : data.size, data].pack('Na*')
36
- end
37
- end
38
- end
39
- end
1
+ module EventMachine
2
+ module Protocols
3
+ # ObjectProtocol allows for easy communication using marshaled ruby objects
4
+ #
5
+ # module RubyServer
6
+ # include EM::P::ObjectProtocol
7
+ #
8
+ # def receive_object obj
9
+ # send_object({'you said' => obj})
10
+ # end
11
+ # end
12
+ #
13
+ module ObjectProtocol
14
+ # By default returns Marshal, override to return JSON or YAML, or any
15
+ # other serializer/deserializer responding to #dump and #load.
16
+ def serializer
17
+ Marshal
18
+ end
19
+
20
+ def receive_data data # :nodoc:
21
+ (@buf ||= '') << data
22
+
23
+ while @buf.size >= 4
24
+ if @buf.size >= 4+(size=@buf.unpack('N').first)
25
+ @buf.slice!(0,4)
26
+ receive_object serializer.load(@buf.slice!(0,size))
27
+ else
28
+ break
29
+ end
30
+ end
31
+ end
32
+
33
+ # Invoked with ruby objects received over the network
34
+ def receive_object obj
35
+ # stub
36
+ end
37
+
38
+ # Sends a ruby object over the network
39
+ def send_object obj
40
+ data = serializer.dump(obj)
41
+ send_data [data.respond_to?(:bytesize) ? data.bytesize : data.size, data].pack('Na*')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,331 +1,357 @@
1
- #--
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 16 July 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
- require 'ostruct'
27
-
28
- module EventMachine
29
- module Protocols
30
-
31
- # Simple SMTP client
32
- #
33
- # email = EM::Protocols::SmtpClient.send(
34
- # :domain=>"example.com",
35
- # :host=>'localhost',
36
- # :port=>25, # optional, defaults 25
37
- # :starttls=>true, # use ssl
38
- # :from=>"sender@example.com",
39
- # :to=> ["to_1@example.com", "to_2@example.com"],
40
- # :header=> {"Subject" => "This is a subject line"},
41
- # :body=> "This is the body of the email"
42
- # )
43
- # email.callback{
44
- # puts 'Email sent!'
45
- # }
46
- # email.errback{ |e|
47
- # puts 'Email failed!'
48
- # }
49
- #
50
- class SmtpClient < Connection
51
- include EventMachine::Deferrable
52
- include EventMachine::Protocols::LineText2
53
-
54
- # :host => required String
55
- # a string containing the IP address or host name of the SMTP server to connect to.
56
- # :port => optional
57
- # defaults to 25.
58
- # :domain => required String
59
- # This is passed as the argument to the EHLO command.
60
- # :starttls => optional Boolean
61
- # If it evaluates true, then the client will initiate STARTTLS with
62
- # the server, and abort the connection if the negotiation doesn't succeed.
63
- # TODO, need to be able to pass certificate parameters with this option.
64
- # :auth => optional Hash of auth parameters
65
- # If not given, then no auth will be attempted.
66
- # (In that case, the connection will be aborted if the server requires auth.)
67
- # Specify the hash value :type to determine the auth type, along with additional parameters
68
- # depending on the type.
69
- # Currently only :type => :plain is supported. Pass additional parameters :username (String),
70
- # and :password (either a String or a Proc that will be called at auth-time).
71
- # Example: :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"}
72
- # :from => required String
73
- # Specifies the sender of the message. Will be passed as the argument
74
- # to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters.
75
- # The connection will abort if the server rejects the value.
76
- # :to => required String or Array of Strings
77
- # The recipient(s) of the message. Do NOT enclose
78
- # any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more
79
- # recipients are rejected by the server. (Of course, if ALL of them are, the server will most
80
- # likely trigger an error when we try to send data.) An array of codes containing the status
81
- # of each requested recipient is available after the call completes. TODO, we should define
82
- # an overridable stub that will be called on rejection of a recipient or a sender, giving
83
- # user code the chance to try again or abort the connection.
84
- # :header => Required hash of values to be transmitted in the header of the message.
85
- # The hash keys are the names of the headers (do NOT append a trailing colon), and the values are strings
86
- # containing the header values. TODO, support Arrays of header values, which would cause us to
87
- # send that specific header line more than once.
88
- #
89
- # Example: :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"}
90
- # :body => Optional string, defaults blank.
91
- # This will be passed as the body of the email message.
92
- # TODO, this needs to be significantly beefed up. As currently written, this requires the caller
93
- # to properly format the input into CRLF-delimited lines of 7-bit characters in the standard
94
- # SMTP transmission format. We need to be able to automatically convert binary data, and add
95
- # correct line-breaks to text data. I think the :body parameter should remain as it is, and we
96
- # should add a :content parameter that contains autoconversions and/or conversion parameters.
97
- # Then we can check if either :body or :content is present and do the right thing.
98
- # :verbose => Optional.
99
- # If true, will cause a lot of information (including the server-side of the
100
- # conversation) to be dumped to $>.
101
- #
102
- def self.send args={}
103
- args[:port] ||= 25
104
- args[:body] ||= ""
105
-
106
- =begin
107
- (I don't think it's possible for EM#connect to throw an exception under normal
108
- circumstances, so this original code is stubbed out. A connect-failure will result
109
- in the #unbind method being called without calling #connection_completed.)
110
- begin
111
- EventMachine.connect( args[:host], args[:port], self) {|c|
112
- # According to the EM docs, we will get here AFTER post_init is called.
113
- c.args = args
114
- c.set_comm_inactivity_timeout 60
115
- }
116
- rescue
117
- # We'll get here on a connect error. This code mimics the effect
118
- # of a call to invoke_internal_error. Would be great to DRY this up.
119
- # (Actually, it may be that we never get here, if EM#connect catches
120
- # its errors internally.)
121
- d = EM::DefaultDeferrable.new
122
- d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]})
123
- d
124
- end
125
- =end
126
- EventMachine.connect( args[:host], args[:port], self) {|c|
127
- # According to the EM docs, we will get here AFTER post_init is called.
128
- c.args = args
129
- c.set_comm_inactivity_timeout 60
130
- }
131
- end
132
-
133
- # :stopdoc:
134
-
135
- attr_writer :args
136
-
137
- def post_init
138
- @return_values = OpenStruct.new
139
- @return_values.start_time = Time.now
140
- end
141
-
142
- def connection_completed
143
- @responder = :receive_signon
144
- @msg = []
145
- end
146
-
147
- # We can get here in a variety of ways, all of them being failures unless
148
- # the @succeeded flag is set. If a protocol success was recorded, then don't
149
- # set a deferred success because the caller will already have done it
150
- # (no need to wait until the connection closes to invoke the callbacks).
151
- #
152
- def unbind
153
- unless @succeeded
154
- @return_values.elapsed_time = Time.now - @return_values.start_time
155
- @return_values.responder = @responder
156
- @return_values.code = @code
157
- @return_values.message = @msg
158
- set_deferred_status(:failed, @return_values)
159
- end
160
- end
161
-
162
- def receive_line ln
163
- $>.puts ln if @args[:verbose]
164
- @range = ln[0...1].to_i
165
- @code = ln[0...3].to_i
166
- @msg << ln[4..-1]
167
- unless ln[3...4] == '-'
168
- $>.puts @responder if @args[:verbose]
169
- send @responder
170
- @msg.clear
171
- end
172
- end
173
-
174
- # We encountered an error from the server and will close the connection.
175
- # Use the error and message the server returned.
176
- #
177
- def invoke_error
178
- @return_values.elapsed_time = Time.now - @return_values.start_time
179
- @return_values.responder = @responder
180
- @return_values.code = @code
181
- @return_values.message = @msg
182
- set_deferred_status :failed, @return_values
183
- send_data "QUIT\r\n"
184
- close_connection_after_writing
185
- end
186
-
187
- # We encountered an error on our side of the protocol and will close the connection.
188
- # Use an extra-protocol error code (900) and use the message from the caller.
189
- #
190
- def invoke_internal_error msg = "???"
191
- @return_values.elapsed_time = Time.now - @return_values.start_time
192
- @return_values.responder = @responder
193
- @return_values.code = 900
194
- @return_values.message = msg
195
- set_deferred_status :failed, @return_values
196
- send_data "QUIT\r\n"
197
- close_connection_after_writing
198
- end
199
-
200
- def receive_signon
201
- return invoke_error unless @range == 2
202
- send_data "EHLO #{@args[:domain]}\r\n"
203
- @responder = :receive_ehlo_response
204
- end
205
-
206
- def receive_ehlo_response
207
- return invoke_error unless @range == 2
208
- @server_caps = @msg
209
- invoke_starttls
210
- end
211
-
212
- def invoke_starttls
213
- if @args[:starttls]
214
- # It would be more sociable to first ask if @server_caps contains
215
- # the string "STARTTLS" before we invoke it, but hey, life's too short.
216
- send_data "STARTTLS\r\n"
217
- @responder = :receive_starttls_response
218
- else
219
- invoke_auth
220
- end
221
- end
222
- def receive_starttls_response
223
- return invoke_error unless @range == 2
224
- start_tls
225
- invoke_auth
226
- end
227
-
228
- # Perform an authentication. If the caller didn't request one, then fall through
229
- # to the mail-from state.
230
- def invoke_auth
231
- if @args[:auth]
232
- if @args[:auth][:type] == :plain
233
- psw = @args[:auth][:password]
234
- if psw.respond_to?(:call)
235
- psw = psw.call
236
- end
237
- #str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp
238
- str = ["\0#{@args[:auth][:username]}\0#{psw}"].pack("m").chomp
239
- send_data "AUTH PLAIN #{str}\r\n"
240
- @responder = :receive_auth_response
241
- else
242
- return invoke_internal_error("unsupported auth type")
243
- end
244
- else
245
- invoke_mail_from
246
- end
247
- end
248
- def receive_auth_response
249
- return invoke_error unless @range == 2
250
- invoke_mail_from
251
- end
252
-
253
- def invoke_mail_from
254
- send_data "MAIL FROM: <#{@args[:from]}>\r\n"
255
- @responder = :receive_mail_from_response
256
- end
257
- def receive_mail_from_response
258
- return invoke_error unless @range == 2
259
- invoke_rcpt_to
260
- end
261
-
262
- def invoke_rcpt_to
263
- @rcpt_responses ||= []
264
- l = @rcpt_responses.length
265
- to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s]
266
- if l < to.length
267
- send_data "RCPT TO: <#{to[l]}>\r\n"
268
- @responder = :receive_rcpt_to_response
269
- else
270
- e = @rcpt_responses.select {|rr| rr.last == 2}
271
- if e and e.length > 0
272
- invoke_data
273
- else
274
- invoke_error
275
- end
276
- end
277
- end
278
- def receive_rcpt_to_response
279
- @rcpt_responses << [@code, @msg, @range]
280
- invoke_rcpt_to
281
- end
282
-
283
- def invoke_data
284
- send_data "DATA\r\n"
285
- @responder = :receive_data_response
286
- end
287
- def receive_data_response
288
- return invoke_error unless @range == 3
289
-
290
- # The data to send can be given either in @args[:content] (an array or string of raw data
291
- # which MUST be in correct SMTP body format, including a trailing dot line), or a header and
292
- # body given in @args[:header] and @args[:body].
293
- #
294
- if @args[:content]
295
- send_data @args[:content].to_s
296
- else
297
- # The header can be a hash or an array.
298
- if @args[:header].is_a?(Hash)
299
- (@args[:header] || {}).each {|k,v| send_data "#{k}: #{v}\r\n" }
300
- else
301
- send_data @args[:header].to_s
302
- end
303
- send_data "\r\n"
304
-
305
- if @args[:body].is_a?(Array)
306
- @args[:body].each {|e| send_data e}
307
- else
308
- send_data @args[:body].to_s
309
- end
310
-
311
- send_data "\r\n.\r\n"
312
- end
313
-
314
- @responder = :receive_message_response
315
- end
316
- def receive_message_response
317
- return invoke_error unless @range == 2
318
- send_data "QUIT\r\n"
319
- close_connection_after_writing
320
- @succeeded = true
321
- @return_values.elapsed_time = Time.now - @return_values.start_time
322
- @return_values.responder = @responder
323
- @return_values.code = @code
324
- @return_values.message = @msg
325
- set_deferred_status :succeeded, @return_values
326
- end
327
-
328
- # :startdoc:
329
- end
330
- end
331
- end
1
+ #--
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 16 July 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+
26
+ require 'ostruct'
27
+
28
+ module EventMachine
29
+ module Protocols
30
+
31
+ # Simple SMTP client
32
+ #
33
+ # email = EM::Protocols::SmtpClient.send(
34
+ # :domain=>"example.com",
35
+ # :host=>'localhost',
36
+ # :port=>25, # optional, defaults 25
37
+ # :starttls=>true, # use ssl
38
+ # :from=>"sender@example.com",
39
+ # :to=> ["to_1@example.com", "to_2@example.com"],
40
+ # :header=> {"Subject" => "This is a subject line"},
41
+ # :body=> "This is the body of the email"
42
+ # )
43
+ # email.callback{
44
+ # puts 'Email sent!'
45
+ # }
46
+ # email.errback{ |e|
47
+ # puts 'Email failed!'
48
+ # }
49
+ #
50
+ # Sending generated emails (using mailfactory)
51
+ #
52
+ # mail = MailFactory.new
53
+ # mail.to = 'someone@site.co'
54
+ # mail.from = 'me@site.com'
55
+ # mail.subject = 'hi!'
56
+ # mail.text = 'hello world'
57
+ # mail.html = '<h1>hello world</h1>'
58
+ #
59
+ # email = EM::P::SmtpClient.send(
60
+ # :domain=>'site.com',
61
+ # :from=>mail.from,
62
+ # :to=>mail.to,
63
+ # :content=>"#{mail.to_s}\r\n.\r\n"
64
+ # )
65
+ #
66
+ class SmtpClient < Connection
67
+ include EventMachine::Deferrable
68
+ include EventMachine::Protocols::LineText2
69
+
70
+ def initialize
71
+ @succeeded = nil
72
+ @responder = nil
73
+ @code = nil
74
+ @msg = nil
75
+ end
76
+
77
+ # :host => required String
78
+ # a string containing the IP address or host name of the SMTP server to connect to.
79
+ # :port => optional
80
+ # defaults to 25.
81
+ # :domain => required String
82
+ # This is passed as the argument to the EHLO command.
83
+ # :starttls => optional Boolean
84
+ # If it evaluates true, then the client will initiate STARTTLS with
85
+ # the server, and abort the connection if the negotiation doesn't succeed.
86
+ # TODO, need to be able to pass certificate parameters with this option.
87
+ # :auth => optional Hash of auth parameters
88
+ # If not given, then no auth will be attempted.
89
+ # (In that case, the connection will be aborted if the server requires auth.)
90
+ # Specify the hash value :type to determine the auth type, along with additional parameters
91
+ # depending on the type.
92
+ # Currently only :type => :plain is supported. Pass additional parameters :username (String),
93
+ # and :password (either a String or a Proc that will be called at auth-time).
94
+ # Example: :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"}
95
+ # :from => required String
96
+ # Specifies the sender of the message. Will be passed as the argument
97
+ # to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters.
98
+ # The connection will abort if the server rejects the value.
99
+ # :to => required String or Array of Strings
100
+ # The recipient(s) of the message. Do NOT enclose
101
+ # any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more
102
+ # recipients are rejected by the server. (Of course, if ALL of them are, the server will most
103
+ # likely trigger an error when we try to send data.) An array of codes containing the status
104
+ # of each requested recipient is available after the call completes. TODO, we should define
105
+ # an overridable stub that will be called on rejection of a recipient or a sender, giving
106
+ # user code the chance to try again or abort the connection.
107
+ # :header => Required hash of values to be transmitted in the header of the message.
108
+ # The hash keys are the names of the headers (do NOT append a trailing colon), and the values are strings
109
+ # containing the header values. TODO, support Arrays of header values, which would cause us to
110
+ # send that specific header line more than once.
111
+ #
112
+ # Example: :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"}
113
+ # :body => Optional string, defaults blank.
114
+ # This will be passed as the body of the email message.
115
+ # TODO, this needs to be significantly beefed up. As currently written, this requires the caller
116
+ # to properly format the input into CRLF-delimited lines of 7-bit characters in the standard
117
+ # SMTP transmission format. We need to be able to automatically convert binary data, and add
118
+ # correct line-breaks to text data. I think the :body parameter should remain as it is, and we
119
+ # should add a :content parameter that contains autoconversions and/or conversion parameters.
120
+ # Then we can check if either :body or :content is present and do the right thing.
121
+ # :content => Optional array or string
122
+ # Alternative to providing header and body, an array or string of raw data which MUST be in
123
+ # correct SMTP body format, including a trailing dot line
124
+ # :verbose => Optional.
125
+ # If true, will cause a lot of information (including the server-side of the
126
+ # conversation) to be dumped to $>.
127
+ #
128
+ def self.send args={}
129
+ args[:port] ||= 25
130
+ args[:body] ||= ""
131
+
132
+ =begin
133
+ (I don't think it's possible for EM#connect to throw an exception under normal
134
+ circumstances, so this original code is stubbed out. A connect-failure will result
135
+ in the #unbind method being called without calling #connection_completed.)
136
+ begin
137
+ EventMachine.connect( args[:host], args[:port], self) {|c|
138
+ # According to the EM docs, we will get here AFTER post_init is called.
139
+ c.args = args
140
+ c.set_comm_inactivity_timeout 60
141
+ }
142
+ rescue
143
+ # We'll get here on a connect error. This code mimics the effect
144
+ # of a call to invoke_internal_error. Would be great to DRY this up.
145
+ # (Actually, it may be that we never get here, if EM#connect catches
146
+ # its errors internally.)
147
+ d = EM::DefaultDeferrable.new
148
+ d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]})
149
+ d
150
+ end
151
+ =end
152
+ EventMachine.connect( args[:host], args[:port], self) {|c|
153
+ # According to the EM docs, we will get here AFTER post_init is called.
154
+ c.args = args
155
+ c.set_comm_inactivity_timeout 60
156
+ }
157
+ end
158
+
159
+ # :stopdoc:
160
+
161
+ attr_writer :args
162
+
163
+ def post_init
164
+ @return_values = OpenStruct.new
165
+ @return_values.start_time = Time.now
166
+ end
167
+
168
+ def connection_completed
169
+ @responder = :receive_signon
170
+ @msg = []
171
+ end
172
+
173
+ # We can get here in a variety of ways, all of them being failures unless
174
+ # the @succeeded flag is set. If a protocol success was recorded, then don't
175
+ # set a deferred success because the caller will already have done it
176
+ # (no need to wait until the connection closes to invoke the callbacks).
177
+ #
178
+ def unbind
179
+ unless @succeeded
180
+ @return_values.elapsed_time = Time.now - @return_values.start_time
181
+ @return_values.responder = @responder
182
+ @return_values.code = @code
183
+ @return_values.message = @msg
184
+ set_deferred_status(:failed, @return_values)
185
+ end
186
+ end
187
+
188
+ def receive_line ln
189
+ $>.puts ln if @args[:verbose]
190
+ @range = ln[0...1].to_i
191
+ @code = ln[0...3].to_i
192
+ @msg << ln[4..-1]
193
+ unless ln[3...4] == '-'
194
+ $>.puts @responder if @args[:verbose]
195
+ send @responder
196
+ @msg.clear
197
+ end
198
+ end
199
+
200
+ # We encountered an error from the server and will close the connection.
201
+ # Use the error and message the server returned.
202
+ #
203
+ def invoke_error
204
+ @return_values.elapsed_time = Time.now - @return_values.start_time
205
+ @return_values.responder = @responder
206
+ @return_values.code = @code
207
+ @return_values.message = @msg
208
+ set_deferred_status :failed, @return_values
209
+ send_data "QUIT\r\n"
210
+ close_connection_after_writing
211
+ end
212
+
213
+ # We encountered an error on our side of the protocol and will close the connection.
214
+ # Use an extra-protocol error code (900) and use the message from the caller.
215
+ #
216
+ def invoke_internal_error msg = "???"
217
+ @return_values.elapsed_time = Time.now - @return_values.start_time
218
+ @return_values.responder = @responder
219
+ @return_values.code = 900
220
+ @return_values.message = msg
221
+ set_deferred_status :failed, @return_values
222
+ send_data "QUIT\r\n"
223
+ close_connection_after_writing
224
+ end
225
+
226
+ def receive_signon
227
+ return invoke_error unless @range == 2
228
+ send_data "EHLO #{@args[:domain]}\r\n"
229
+ @responder = :receive_ehlo_response
230
+ end
231
+
232
+ def receive_ehlo_response
233
+ return invoke_error unless @range == 2
234
+ @server_caps = @msg
235
+ invoke_starttls
236
+ end
237
+
238
+ def invoke_starttls
239
+ if @args[:starttls]
240
+ # It would be more sociable to first ask if @server_caps contains
241
+ # the string "STARTTLS" before we invoke it, but hey, life's too short.
242
+ send_data "STARTTLS\r\n"
243
+ @responder = :receive_starttls_response
244
+ else
245
+ invoke_auth
246
+ end
247
+ end
248
+ def receive_starttls_response
249
+ return invoke_error unless @range == 2
250
+ start_tls
251
+ invoke_auth
252
+ end
253
+
254
+ # Perform an authentication. If the caller didn't request one, then fall through
255
+ # to the mail-from state.
256
+ def invoke_auth
257
+ if @args[:auth]
258
+ if @args[:auth][:type] == :plain
259
+ psw = @args[:auth][:password]
260
+ if psw.respond_to?(:call)
261
+ psw = psw.call
262
+ end
263
+ #str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp
264
+ str = ["\0#{@args[:auth][:username]}\0#{psw}"].pack("m").chomp
265
+ send_data "AUTH PLAIN #{str}\r\n"
266
+ @responder = :receive_auth_response
267
+ else
268
+ return invoke_internal_error("unsupported auth type")
269
+ end
270
+ else
271
+ invoke_mail_from
272
+ end
273
+ end
274
+ def receive_auth_response
275
+ return invoke_error unless @range == 2
276
+ invoke_mail_from
277
+ end
278
+
279
+ def invoke_mail_from
280
+ send_data "MAIL FROM: <#{@args[:from]}>\r\n"
281
+ @responder = :receive_mail_from_response
282
+ end
283
+ def receive_mail_from_response
284
+ return invoke_error unless @range == 2
285
+ invoke_rcpt_to
286
+ end
287
+
288
+ def invoke_rcpt_to
289
+ @rcpt_responses ||= []
290
+ l = @rcpt_responses.length
291
+ to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s]
292
+ if l < to.length
293
+ send_data "RCPT TO: <#{to[l]}>\r\n"
294
+ @responder = :receive_rcpt_to_response
295
+ else
296
+ e = @rcpt_responses.select {|rr| rr.last == 2}
297
+ if e and e.length > 0
298
+ invoke_data
299
+ else
300
+ invoke_error
301
+ end
302
+ end
303
+ end
304
+ def receive_rcpt_to_response
305
+ @rcpt_responses << [@code, @msg, @range]
306
+ invoke_rcpt_to
307
+ end
308
+
309
+ def invoke_data
310
+ send_data "DATA\r\n"
311
+ @responder = :receive_data_response
312
+ end
313
+ def receive_data_response
314
+ return invoke_error unless @range == 3
315
+
316
+ # The data to send can be given either in @args[:content] (an array or string of raw data
317
+ # which MUST be in correct SMTP body format, including a trailing dot line), or a header and
318
+ # body given in @args[:header] and @args[:body].
319
+ #
320
+ if @args[:content]
321
+ send_data @args[:content].to_s
322
+ else
323
+ # The header can be a hash or an array.
324
+ if @args[:header].is_a?(Hash)
325
+ (@args[:header] || {}).each {|k,v| send_data "#{k}: #{v}\r\n" }
326
+ else
327
+ send_data @args[:header].to_s
328
+ end
329
+ send_data "\r\n"
330
+
331
+ if @args[:body].is_a?(Array)
332
+ @args[:body].each {|e| send_data e}
333
+ else
334
+ send_data @args[:body].to_s
335
+ end
336
+
337
+ send_data "\r\n.\r\n"
338
+ end
339
+
340
+ @responder = :receive_message_response
341
+ end
342
+ def receive_message_response
343
+ return invoke_error unless @range == 2
344
+ send_data "QUIT\r\n"
345
+ close_connection_after_writing
346
+ @succeeded = true
347
+ @return_values.elapsed_time = Time.now - @return_values.start_time
348
+ @return_values.responder = @responder
349
+ @return_values.code = @code
350
+ @return_values.message = @msg
351
+ set_deferred_status :succeeded, @return_values
352
+ end
353
+
354
+ # :startdoc:
355
+ end
356
+ end
357
+ end