eventmachine 0.12.10 → 1.0.0.beta.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 +2 -0
- data/Gemfile +1 -0
- data/README +1 -2
- data/Rakefile +4 -76
- data/docs/DEFERRABLES +183 -70
- data/docs/KEYBOARD +15 -11
- data/docs/LIGHTWEIGHT_CONCURRENCY +84 -24
- data/docs/SMTP +3 -1
- data/docs/SPAWNED_PROCESSES +84 -25
- data/eventmachine.gemspec +19 -26
- data/examples/ex_tick_loop_array.rb +15 -0
- data/examples/ex_tick_loop_counter.rb +32 -0
- data/ext/binder.cpp +0 -1
- data/ext/cmain.cpp +36 -11
- data/ext/cplusplus.cpp +1 -1
- data/ext/ed.cpp +104 -113
- data/ext/ed.h +24 -30
- data/ext/em.cpp +347 -248
- data/ext/em.h +23 -16
- data/ext/eventmachine.h +5 -3
- data/ext/extconf.rb +5 -3
- data/ext/fastfilereader/extconf.rb +5 -3
- data/ext/fastfilereader/mapper.cpp +1 -1
- data/ext/kb.cpp +1 -3
- data/ext/pipe.cpp +9 -11
- data/ext/project.h +12 -4
- data/ext/rubymain.cpp +138 -89
- data/java/src/com/rubyeventmachine/EmReactor.java +1 -0
- data/lib/em/channel.rb +1 -1
- data/lib/em/connection.rb +6 -1
- data/lib/em/deferrable.rb +16 -2
- data/lib/em/iterator.rb +270 -0
- data/lib/em/protocols.rb +1 -1
- data/lib/em/protocols/httpclient.rb +5 -0
- data/lib/em/protocols/line_protocol.rb +28 -0
- data/lib/em/protocols/smtpserver.rb +101 -8
- data/lib/em/protocols/stomp.rb +1 -1
- data/lib/{pr_eventmachine.rb → em/pure_ruby.rb} +1 -11
- data/lib/em/queue.rb +1 -0
- data/lib/em/streamer.rb +1 -1
- data/lib/em/tick_loop.rb +85 -0
- data/lib/em/timers.rb +2 -1
- data/lib/em/version.rb +1 -1
- data/lib/eventmachine.rb +38 -84
- data/lib/jeventmachine.rb +1 -0
- data/tests/test_attach.rb +13 -3
- data/tests/test_basic.rb +60 -95
- data/tests/test_channel.rb +3 -2
- data/tests/test_defer.rb +14 -12
- data/tests/test_deferrable.rb +35 -0
- data/tests/test_file_watch.rb +1 -1
- data/tests/test_futures.rb +1 -1
- data/tests/test_hc.rb +40 -68
- data/tests/test_httpclient.rb +15 -6
- data/tests/test_httpclient2.rb +3 -2
- data/tests/test_inactivity_timeout.rb +3 -3
- data/tests/test_ltp.rb +13 -5
- data/tests/test_next_tick.rb +1 -1
- data/tests/test_pending_connect_timeout.rb +2 -2
- data/tests/test_process_watch.rb +36 -34
- data/tests/test_proxy_connection.rb +52 -0
- data/tests/test_pure.rb +10 -1
- data/tests/test_sasl.rb +1 -1
- data/tests/test_send_file.rb +16 -7
- data/tests/test_servers.rb +1 -1
- data/tests/test_tick_loop.rb +59 -0
- data/tests/test_timers.rb +13 -15
- metadata +45 -17
- data/web/whatis +0 -7
@@ -48,6 +48,7 @@ public class EmReactor {
|
|
48
48
|
public final int EM_SSL_HANDSHAKE_COMPLETED = 108;
|
49
49
|
public final int EM_SSL_VERIFY = 109;
|
50
50
|
public final int EM_PROXY_TARGET_UNBOUND = 110;
|
51
|
+
public final int EM_PROXY_COMPLETED = 111;
|
51
52
|
|
52
53
|
private Selector mySelector;
|
53
54
|
private TreeMap<Long, ArrayList<Long>> Timers;
|
data/lib/em/channel.rb
CHANGED
@@ -35,7 +35,7 @@ module EventMachine
|
|
35
35
|
# Add items to the channel, which are pushed out to all subscribers.
|
36
36
|
def push(*items)
|
37
37
|
items = items.dup
|
38
|
-
EM.schedule {
|
38
|
+
EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } }
|
39
39
|
end
|
40
40
|
alias << push
|
41
41
|
|
data/lib/em/connection.rb
CHANGED
@@ -36,7 +36,7 @@ module EventMachine
|
|
36
36
|
allocate.instance_eval do
|
37
37
|
# Store signature
|
38
38
|
@signature = sig
|
39
|
-
associate_callback_target sig
|
39
|
+
# associate_callback_target sig
|
40
40
|
|
41
41
|
# Call a superclass's #initialize if it has one
|
42
42
|
initialize(*args)
|
@@ -134,6 +134,11 @@ module EventMachine
|
|
134
134
|
def proxy_target_unbound
|
135
135
|
end
|
136
136
|
|
137
|
+
# EventMachine::Connection#proxy_completed is called when the reactor finished proxying all
|
138
|
+
# of the requested bytes.
|
139
|
+
def proxy_completed
|
140
|
+
end
|
141
|
+
|
137
142
|
# EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up
|
138
143
|
# a low-level proxy relay for all data inbound for this connection, to the connection given
|
139
144
|
# as the argument. This is essentially just a helper method for enable_proxy.
|
data/lib/em/deferrable.rb
CHANGED
@@ -51,6 +51,13 @@ module EventMachine
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
# Cancels an outstanding callback to &block if any. Undoes the action of #callback.
|
55
|
+
#
|
56
|
+
def cancel_callback block
|
57
|
+
@callbacks ||= []
|
58
|
+
@callbacks.delete block
|
59
|
+
end
|
60
|
+
|
54
61
|
# Specify a block to be executed if and when the Deferrable object receives
|
55
62
|
# a status of :failed. See #set_deferred_status for more information.
|
56
63
|
#--
|
@@ -69,6 +76,13 @@ module EventMachine
|
|
69
76
|
end
|
70
77
|
end
|
71
78
|
|
79
|
+
# Cancels an outstanding errback to &block if any. Undoes the action of #errback.
|
80
|
+
#
|
81
|
+
def cancel_errback block
|
82
|
+
@errbacks ||= []
|
83
|
+
@errbacks.delete block
|
84
|
+
end
|
85
|
+
|
72
86
|
# Sets the "disposition" (status) of the Deferrable object. See also the large set of
|
73
87
|
# sugarings for this method.
|
74
88
|
# Note that if you call this method without arguments,
|
@@ -150,10 +164,10 @@ module EventMachine
|
|
150
164
|
# the Timeout expires (passing no arguments to the object's errbacks).
|
151
165
|
# Setting the status at any time prior to a call to the expiration of the timeout
|
152
166
|
# will cause the timer to be cancelled.
|
153
|
-
def timeout seconds
|
167
|
+
def timeout seconds, *args
|
154
168
|
cancel_timeout
|
155
169
|
me = self
|
156
|
-
@deferred_timeout = EventMachine::Timer.new(seconds) {me.fail}
|
170
|
+
@deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)}
|
157
171
|
end
|
158
172
|
|
159
173
|
# Cancels an outstanding timeout if any. Undoes the action of #timeout.
|
data/lib/em/iterator.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
module EventMachine
|
2
|
+
# A simple iterator for concurrent asynchronous work.
|
3
|
+
#
|
4
|
+
# Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually,
|
5
|
+
# instead of happening automatically after the yielded block finishes executing. For example:
|
6
|
+
#
|
7
|
+
# (0..10).each{ |num| }
|
8
|
+
#
|
9
|
+
# becomes:
|
10
|
+
#
|
11
|
+
# EM::Iterator.new(0..10).each{ |num,iter| iter.next }
|
12
|
+
#
|
13
|
+
# This is especially useful when doing asynchronous work via reactor libraries and
|
14
|
+
# functions. For example, given a sync and async http api:
|
15
|
+
#
|
16
|
+
# response = sync_http_get(url); ...
|
17
|
+
# async_http_get(url){ |response| ... }
|
18
|
+
#
|
19
|
+
# a synchronous iterator such as:
|
20
|
+
#
|
21
|
+
# responses = urls.map{ |url| sync_http_get(url) }
|
22
|
+
# ...
|
23
|
+
# puts 'all done!'
|
24
|
+
#
|
25
|
+
# could be written as:
|
26
|
+
#
|
27
|
+
# EM::Iterator.new(urls).map(proc{ |url,iter|
|
28
|
+
# async_http_get(url){ |res|
|
29
|
+
# iter.return(res)
|
30
|
+
# }
|
31
|
+
# }, proc{ |responses|
|
32
|
+
# ...
|
33
|
+
# puts 'all done!'
|
34
|
+
# })
|
35
|
+
#
|
36
|
+
# Now, you can take advantage of the asynchronous api to issue requests in parallel. For example,
|
37
|
+
# to fetch 10 urls at a time, simply pass in a concurrency of 10:
|
38
|
+
#
|
39
|
+
# EM::Iterator.new(urls, 10).each do |url,iter|
|
40
|
+
# async_http_get(url){ iter.next }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
class Iterator
|
44
|
+
# Create a new parallel async iterator with specified concurrency.
|
45
|
+
#
|
46
|
+
# i = EM::Iterator.new(1..100, 10)
|
47
|
+
#
|
48
|
+
# will create an iterator over the range that processes 10 items at a time. Iteration
|
49
|
+
# is started via #each, #map or #inject
|
50
|
+
#
|
51
|
+
def initialize(list, concurrency = 1)
|
52
|
+
raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
|
53
|
+
@list = list.to_a.dup
|
54
|
+
@concurrency = concurrency
|
55
|
+
|
56
|
+
@started = false
|
57
|
+
@ended = false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Change the concurrency of this iterator. Workers will automatically be spawned or destroyed
|
61
|
+
# to accomodate the new concurrency level.
|
62
|
+
#
|
63
|
+
def concurrency=(val)
|
64
|
+
old = @concurrency
|
65
|
+
@concurrency = val
|
66
|
+
|
67
|
+
spawn_workers if val > old and @started and !@ended
|
68
|
+
end
|
69
|
+
attr_reader :concurrency
|
70
|
+
|
71
|
+
# Iterate over a set of items using the specified block or proc.
|
72
|
+
#
|
73
|
+
# EM::Iterator.new(1..100).each do |num, iter|
|
74
|
+
# puts num
|
75
|
+
# iter.next
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# An optional second proc is invoked after the iteration is complete.
|
79
|
+
#
|
80
|
+
# EM::Iterator.new(1..100).each(
|
81
|
+
# proc{ |num,iter| iter.next },
|
82
|
+
# proc{ puts 'all done' }
|
83
|
+
# )
|
84
|
+
#
|
85
|
+
def each(foreach=nil, after=nil, &blk)
|
86
|
+
raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk
|
87
|
+
raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended
|
88
|
+
|
89
|
+
@started = true
|
90
|
+
@pending = 0
|
91
|
+
@workers = 0
|
92
|
+
|
93
|
+
all_done = proc{
|
94
|
+
after.call if after and @ended and @pending == 0
|
95
|
+
}
|
96
|
+
|
97
|
+
@process_next = proc{
|
98
|
+
# p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list]
|
99
|
+
unless @ended or @workers > @concurrency
|
100
|
+
if @list.empty?
|
101
|
+
@ended = true
|
102
|
+
@workers -= 1
|
103
|
+
all_done.call
|
104
|
+
else
|
105
|
+
item = @list.shift
|
106
|
+
@pending += 1
|
107
|
+
|
108
|
+
is_done = false
|
109
|
+
on_done = proc{
|
110
|
+
raise RuntimeError, 'already completed this iteration' if is_done
|
111
|
+
is_done = true
|
112
|
+
|
113
|
+
@pending -= 1
|
114
|
+
|
115
|
+
if @ended
|
116
|
+
all_done.call
|
117
|
+
else
|
118
|
+
EM.next_tick(@process_next)
|
119
|
+
end
|
120
|
+
}
|
121
|
+
class << on_done
|
122
|
+
alias :next :call
|
123
|
+
end
|
124
|
+
|
125
|
+
foreach.call(item, on_done)
|
126
|
+
end
|
127
|
+
else
|
128
|
+
@workers -= 1
|
129
|
+
end
|
130
|
+
}
|
131
|
+
|
132
|
+
spawn_workers
|
133
|
+
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
# Collect the results of an asynchronous iteration into an array.
|
138
|
+
#
|
139
|
+
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter|
|
140
|
+
# EM.system(cmd){ |output,status|
|
141
|
+
# iter.return(output)
|
142
|
+
# }
|
143
|
+
# }, proc{ |results|
|
144
|
+
# p results
|
145
|
+
# })
|
146
|
+
#
|
147
|
+
def map(foreach, after)
|
148
|
+
index = 0
|
149
|
+
|
150
|
+
inject([], proc{ |results,item,iter|
|
151
|
+
i = index
|
152
|
+
index += 1
|
153
|
+
|
154
|
+
is_done = false
|
155
|
+
on_done = proc{ |res|
|
156
|
+
raise RuntimeError, 'already returned a value for this iteration' if is_done
|
157
|
+
is_done = true
|
158
|
+
|
159
|
+
results[i] = res
|
160
|
+
iter.return(results)
|
161
|
+
}
|
162
|
+
class << on_done
|
163
|
+
alias :return :call
|
164
|
+
def next
|
165
|
+
raise NoMethodError, 'must call #return on a map iterator'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
foreach.call(item, on_done)
|
170
|
+
}, proc{ |results|
|
171
|
+
after.call(results)
|
172
|
+
})
|
173
|
+
end
|
174
|
+
|
175
|
+
# Inject the results of an asynchronous iteration onto a given object.
|
176
|
+
#
|
177
|
+
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
|
178
|
+
# EM.system(cmd){ |output,status|
|
179
|
+
# hash[cmd] = status.exitstatus == 0 ? output.strip : nil
|
180
|
+
# iter.return(hash)
|
181
|
+
# }
|
182
|
+
# }, proc{ |results|
|
183
|
+
# p results
|
184
|
+
# })
|
185
|
+
#
|
186
|
+
def inject(obj, foreach, after)
|
187
|
+
each(proc{ |item,iter|
|
188
|
+
is_done = false
|
189
|
+
on_done = proc{ |res|
|
190
|
+
raise RuntimeError, 'already returned a value for this iteration' if is_done
|
191
|
+
is_done = true
|
192
|
+
|
193
|
+
obj = res
|
194
|
+
iter.next
|
195
|
+
}
|
196
|
+
class << on_done
|
197
|
+
alias :return :call
|
198
|
+
def next
|
199
|
+
raise NoMethodError, 'must call #return on an inject iterator'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
foreach.call(obj, item, on_done)
|
204
|
+
}, proc{
|
205
|
+
after.call(obj)
|
206
|
+
})
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
# Spawn workers to consume items from the iterator's enumerator based on the current concurrency level.
|
212
|
+
#
|
213
|
+
def spawn_workers
|
214
|
+
EM.next_tick(start_worker = proc{
|
215
|
+
if @workers < @concurrency and !@ended
|
216
|
+
# p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended]
|
217
|
+
@workers += 1
|
218
|
+
@process_next.call
|
219
|
+
EM.next_tick(start_worker)
|
220
|
+
end
|
221
|
+
})
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
if __FILE__ == $0
|
228
|
+
$:.unshift File.join(File.dirname(__FILE__), '..')
|
229
|
+
require 'eventmachine'
|
230
|
+
|
231
|
+
# TODO: real tests
|
232
|
+
# TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next }
|
233
|
+
# TODO: support iter.pause/resume/stop/break/continue?
|
234
|
+
# TODO: create some exceptions instead of using RuntimeError
|
235
|
+
# TODO: support proc instead of enumerable? EM::Iterator.new(proc{ return queue.pop })
|
236
|
+
|
237
|
+
EM.run{
|
238
|
+
EM::Iterator.new(1..50).each{ |num,iter| p num; iter.next }
|
239
|
+
EM::Iterator.new([1,2,3], 10).each{ |num,iter| p num; iter.next }
|
240
|
+
|
241
|
+
i = EM::Iterator.new(1..100, 5)
|
242
|
+
i.each(proc{|num,iter|
|
243
|
+
p num.to_s
|
244
|
+
iter.next
|
245
|
+
}, proc{
|
246
|
+
p :done
|
247
|
+
})
|
248
|
+
EM.add_timer(0.03){
|
249
|
+
i.concurrency = 1
|
250
|
+
}
|
251
|
+
EM.add_timer(0.04){
|
252
|
+
i.concurrency = 3
|
253
|
+
}
|
254
|
+
|
255
|
+
EM::Iterator.new(100..150).map(proc{ |num,iter|
|
256
|
+
EM.add_timer(0.01){ iter.return(num) }
|
257
|
+
}, proc{ |results|
|
258
|
+
p results
|
259
|
+
})
|
260
|
+
|
261
|
+
EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
|
262
|
+
EM.system(cmd){ |output,status|
|
263
|
+
hash[cmd] = status.exitstatus == 0 ? output.strip : nil
|
264
|
+
iter.return(hash)
|
265
|
+
}
|
266
|
+
}, proc{ |results|
|
267
|
+
p results
|
268
|
+
})
|
269
|
+
}
|
270
|
+
end
|
data/lib/em/protocols.rb
CHANGED
@@ -142,6 +142,11 @@ module EventMachine
|
|
142
142
|
req << "Cookie: #{args[:cookie]}"
|
143
143
|
end
|
144
144
|
|
145
|
+
# Allow custom HTTP headers, e.g. SOAPAction
|
146
|
+
args[:custom_headers].each do |k,v|
|
147
|
+
req << "#{k}: #{v}"
|
148
|
+
end if args[:custom_headers]
|
149
|
+
|
145
150
|
# Basic-auth stanza contributed by Matt Murphy.
|
146
151
|
if args[:basic_auth]
|
147
152
|
basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Protocols
|
3
|
+
# LineProtocol will parse out newline terminated strings from a receive_data stream
|
4
|
+
#
|
5
|
+
# module Server
|
6
|
+
# include EM::P::LineProtocol
|
7
|
+
#
|
8
|
+
# def receive_line(line)
|
9
|
+
# send_data("you said: #{line}")
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
module LineProtocol
|
14
|
+
def receive_data data # :nodoc:
|
15
|
+
(@buf ||= '') << data
|
16
|
+
|
17
|
+
while line = @buf.slice!(/(.*)\r?\n/)
|
18
|
+
receive_line(line)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Invoked with lines received over the network
|
23
|
+
def receive_line(line)
|
24
|
+
# stub
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -32,6 +32,86 @@ module EventMachine
|
|
32
32
|
# and data to user code. User code is responsible for doing the right things with the
|
33
33
|
# data in order to get complete and correct SMTP server behavior.
|
34
34
|
#
|
35
|
+
# Simple SMTP server example:
|
36
|
+
#
|
37
|
+
# class EmailServer < EM::P::SmtpServer
|
38
|
+
# def receive_plain_auth(user, pass)
|
39
|
+
# true
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def get_server_domain
|
43
|
+
# "mock.smtp.server.local"
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def get_server_greeting
|
47
|
+
# "mock smtp server greets you with impunity"
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def receive_sender(sender)
|
51
|
+
# current.sender = sender
|
52
|
+
# true
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def receive_recipient(recipient)
|
56
|
+
# current.recipient = recipient
|
57
|
+
# true
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# def receive_message
|
61
|
+
# current.received = true
|
62
|
+
# current.completed_at = Time.now
|
63
|
+
#
|
64
|
+
# p [:received_email, current]
|
65
|
+
# @current = OpenStruct.new
|
66
|
+
# true
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# def receive_ehlo_domain(domain)
|
70
|
+
# @ehlo_domain = domain
|
71
|
+
# true
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# def receive_data_command
|
75
|
+
# current.data = ""
|
76
|
+
# true
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def receive_data_chunk(data)
|
80
|
+
# current.data << data.join("\n")
|
81
|
+
# true
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# def receive_transaction
|
85
|
+
# if @ehlo_domain
|
86
|
+
# current.ehlo_domain = @ehlo_domain
|
87
|
+
# @ehlo_domain = nil
|
88
|
+
# end
|
89
|
+
# true
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# def current
|
93
|
+
# @current ||= OpenStruct.new
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# def self.start(host = 'localhost', port = 1025)
|
97
|
+
# require 'ostruct'
|
98
|
+
# @server = EM.start_server host, port, self
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# def self.stop
|
102
|
+
# if @server
|
103
|
+
# EM.stop_server @server
|
104
|
+
# @server = nil
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# def self.running?
|
109
|
+
# !!@server
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# EM.run{ EmailServer.start }
|
114
|
+
#
|
35
115
|
#--
|
36
116
|
# Useful paragraphs in RFC-2821:
|
37
117
|
# 4.3.2: Concise list of command-reply sequences, in essence a text representation
|
@@ -114,6 +194,7 @@ module EventMachine
|
|
114
194
|
@@parms[:verbose] and $>.puts ">>> #{ln}"
|
115
195
|
|
116
196
|
return process_data_line(ln) if @state.include?(:data)
|
197
|
+
return process_auth_line(ln) if @state.include?(:auth_incomplete)
|
117
198
|
|
118
199
|
case ln
|
119
200
|
when EhloRegex
|
@@ -216,7 +297,7 @@ module EventMachine
|
|
216
297
|
send_data "250-STARTTLS\r\n"
|
217
298
|
end
|
218
299
|
if @@parms[:auth]
|
219
|
-
send_data "250-AUTH PLAIN
|
300
|
+
send_data "250-AUTH PLAIN\r\n"
|
220
301
|
end
|
221
302
|
send_data "250-NO-SOLICITING\r\n"
|
222
303
|
# TODO, size needs to be configurable.
|
@@ -259,14 +340,14 @@ module EventMachine
|
|
259
340
|
def process_auth str
|
260
341
|
if @state.include?(:auth)
|
261
342
|
send_data "503 auth already issued\r\n"
|
262
|
-
elsif str =~ /\APLAIN\s
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
send_data
|
267
|
-
@state << :auth
|
343
|
+
elsif str =~ /\APLAIN\s?/i
|
344
|
+
if $'.length == 0
|
345
|
+
# we got a partial response, so let the client know to send the rest
|
346
|
+
@state << :auth_incomplete
|
347
|
+
send_data("334 \r\n")
|
268
348
|
else
|
269
|
-
|
349
|
+
# we got the initial response, so go ahead & process it
|
350
|
+
process_auth_line($')
|
270
351
|
end
|
271
352
|
#elsif str =~ /\ALOGIN\s+/i
|
272
353
|
else
|
@@ -274,6 +355,18 @@ module EventMachine
|
|
274
355
|
end
|
275
356
|
end
|
276
357
|
|
358
|
+
def process_auth_line(line)
|
359
|
+
plain = line.unpack("m").first
|
360
|
+
discard,user,psw = plain.split("\000")
|
361
|
+
if receive_plain_auth user,psw
|
362
|
+
send_data "235 authentication ok\r\n"
|
363
|
+
@state << :auth
|
364
|
+
else
|
365
|
+
send_data "535 invalid authentication\r\n"
|
366
|
+
end
|
367
|
+
@state.delete :auth_incomplete
|
368
|
+
end
|
369
|
+
|
277
370
|
#--
|
278
371
|
# Unusually, we can deal with a Deferrable returned from the user application.
|
279
372
|
# This was added to deal with a special case in a particular application, but
|