pry-remote-em 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,88 @@
1
+ require 'pry-remote-em/metrics'
2
+
3
+ module PryRemoteEm
4
+ # See Readme for Sandbox using guide
5
+ class Sandbox
6
+ attr_accessor :pry, :server
7
+
8
+ # Use output methods as expected
9
+
10
+ %w[puts putc print p pp].each do |method|
11
+ define_method method do |*arguments|
12
+ pry.output.puts(*arguments)
13
+ end
14
+ end
15
+
16
+ # Working with errors
17
+
18
+ @@last_errors = []
19
+ @@ignore_errors = []
20
+ @@error_classes = Hash.new { |hash, key| hash[key] = 0 }
21
+
22
+ def error_classes
23
+ return puts 'No errors, yay!' if @@error_classes.empty?
24
+ puts @@error_classes.map { |key, value| "#{key}: #{value}" }
25
+ end
26
+
27
+ def error_history
28
+ return puts 'No errors, yay!' unless any_errors?
29
+ puts @@last_errors.map { |error| "#{error.source_timestamp} #{"#{error.class}: #{error.message}".sub(/(?<=^.{51}).{4,}$/, '...')}" }
30
+ end
31
+
32
+ def self.add_error(exception, source_binding = nil)
33
+ unless exception.kind_of?(Exception) && exception.backtrace && (source_binding.nil? || source_binding.kind_of?(Binding))
34
+ raise ArgumentError, 'exception with backtrace and optional binding expected'
35
+ end
36
+
37
+ return if @@last_errors.map(&:object_id).include?(exception.object_id) || @@ignore_errors.include?(exception.class)
38
+
39
+ timestamp = Time.now
40
+ exception.define_singleton_method(:source_timestamp) { timestamp }
41
+
42
+ exception.define_singleton_method(:source_binding) { source_binding } if source_binding
43
+
44
+ @@last_errors.push(exception)
45
+ @@error_classes[exception.class] += 1
46
+ Metrics.add(:errors)
47
+
48
+ maximum_errors = ENV['PRYEMSANDBOXERRORS'].nil? || ENV['PRYEMSANDBOXERRORS'].empty? ? MAXIMUM_ERRORS_IN_SANDBOX : ENV['PRYEMSANDBOXERRORS'].to_i
49
+ @@last_errors.shift if @@last_errors.size > maximum_errors
50
+ end
51
+
52
+ def self.last_errors
53
+ @@last_errors
54
+ end
55
+
56
+ def self.any_errors?
57
+ @@last_errors.any?
58
+ end
59
+
60
+ def self.last_error
61
+ @@last_errors.last
62
+ end
63
+
64
+ def self.ignore_errors
65
+ @@ignore_errors
66
+ end
67
+
68
+ %w[any_errors? last_errors last_error ignore_errors].each do |method|
69
+ define_method(method) do |*arguments|
70
+ self.class.send(method, *arguments)
71
+ end
72
+ end
73
+
74
+ # Metrics related methods
75
+
76
+ def show_metrics
77
+ puts Metrics.list.map { |key, value| "#{key}: #{value}" }
78
+ end
79
+
80
+ # Safely show in Pry prompt
81
+
82
+ def inspect
83
+ 'sandbox'
84
+ end
85
+
86
+ Pry.config.prompt_safe_objects.push(self)
87
+ end
88
+ end
@@ -0,0 +1,511 @@
1
+ require 'pry'
2
+ require 'socket'
3
+ require 'logger'
4
+ require 'securerandom'
5
+ require 'pry-remote-em'
6
+ require 'pry-remote-em/broker'
7
+ require 'pry-remote-em/server/shell_cmd'
8
+ require 'pry-remote-em/sandbox'
9
+
10
+ # How it works with Pry
11
+ #
12
+ # When PryRemoteEm.run is called it registers with EventMachine for a given ip
13
+ # and port. When a connection is received EM yields an instance of PryRemoteEm,
14
+ # we start a Fiber (f1) then call Pry.start specifying the Server instance as the
15
+ # input and output object for Pry. The Pry instance that is created goes into
16
+ # its REPL. When it gets to the read part it calls @input.readline
17
+ # (PryRemoteEm#readline) and passes a prompt, e.g., [1] pry(#<Foo>)>.
18
+ #
19
+ # PryRemoteEm#readline calls #send_data with the prompt then yields from the
20
+ # current Fiber (f1); the one we started when EventMachine yielded to us. The root
21
+ # Fiber is now active again. At some point, the root Fiber receives data from
22
+ # the client. It calls #receive_data in our Server instance. That instance then
23
+ # resumes the Fiber (f1) that was waiting for #readline to finish.
24
+ #
25
+ # Inside the resumed Fiber (f1) PryRemoteEm#readline returns the recieved data
26
+ # to the instance of Pry, which continues its REPL. Pry calls #puts, or #print
27
+ # or #write on its output object (PryRemoteEm). We send that data out to the client
28
+ # and immediately return. Pry then calls PryRemoteEm#readline again and again
29
+ # we send the prompt then immediately yield back to the root Fiber.
30
+ #
31
+ # Pry just interacts with PryRemoteEm as if it were any other blocking Readline
32
+ # object. The important bit is making sure that it is started in a new Fiber that
33
+ # can be paused and resumed as needed. PryRemoteEm#readline pauses it and
34
+ # PryRemoteEm#receive_raw resumes it.
35
+ #
36
+ # Reference:
37
+ # http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/
38
+ module PryRemoteEm
39
+ class << self
40
+ # Local PryRemoteEm servers, including EM signatures, indexed by id. Each
41
+ # signature can be used with high level EM methods like EM.stop_server or
42
+ # EM.get_sockname. If a server has been stopped EM.get_sockname will return
43
+ # nil for that server's signature.
44
+ def servers
45
+ @servers ||= {}
46
+ end
47
+
48
+ # Safely stop one or more PryRemoteEm servers and remove them from the list
49
+ # of servers.
50
+ # @param [String, nil] argument id, url or name, use `nil` to stop them all
51
+ # @return [Hash] stopped servers if they were
52
+ def stop_server(argument = nil)
53
+ servers_to_stop = if argument
54
+ servers.select do |id, description|
55
+ argument == id || description[:urls].include?(argument) || argument == description[:name]
56
+ end
57
+ else
58
+ servers
59
+ end
60
+
61
+ servers_to_stop.each do |id, description|
62
+ EM.stop_server(description[:server]) if EM.get_sockname(description[:server])
63
+ Broker.unregister(id)
64
+ servers.delete(id)
65
+ end
66
+ end
67
+ end
68
+
69
+ module Server
70
+ include Proto
71
+
72
+ class << self
73
+ # Start a pry-remote-em server
74
+ # @param [Hash] options
75
+ # @option options [Object] :target Object to bind Pry session, default - PryRemoteEm::Sandbox instance
76
+ # @option options [String] :host The IP-address to listen on, default - 127.0.0.1 (same as PRYEMHOST environment variable)
77
+ # @option options [Fixnum, String, Symbol] :port The port to listen on - if :auto or 'auto' the next available port will be taken, default - 6463 (same as PRYEMPORT environment variable)
78
+ # @option options [String] :id Server's UUID, will be generated automatically unless you pass it explicitly
79
+ # @option options [Boolean] :tls require SSL encryption, default - false
80
+ # @option options [Logger] :logger Logger for Pry Server, default - STDERR
81
+ # @option options [Proc, Object] :auth require user authentication - see README
82
+ # @option options [Boolean] :allow_shell_cmds Allow shell commands or not, default - true
83
+ # @option options [Integer, Symbol] :port_fail set to :auto to search for available port in range from given port to port + 100, or pass explicit integer to use instaed of 100, default - 1
84
+ # @option options [String] :name Server name to show in broker list, default - object's inspect (same as PRYEMNAME environment variable)
85
+ # @option options [String] :external_url External URL to connect behind firewall, NAT, Docket etc. in form "pryem://my.host:6463", default - use given host and port and expand it to all interfaces in case of 0.0.0.0 (same as PRYEMURL environment variable)
86
+ # @option options [Integer] :heartbeat_interval Interval to send heartbeats and updated details to broker, default - 15 (same as PRYEMHBSEND environment variable)
87
+ # @option options [Boolean] :remote_broker Connect to remote broker instead of starting local one, default - false (same as PRYEMREMOTEBROKER environment variable)
88
+ # @option options [String] :broker_host Broker host to connect to, default - localhost
89
+ # @option options [String] :broker_port Broker port to connect to, default - 6462
90
+ # @option options [Hash] :details Optional details to pass to broker and show in table (should consist of string/symbol keys and simple serializable values)
91
+ def run(options = {}, &block)
92
+ description = options.dup
93
+ description[:target] ||= PryRemoteEm::Sandbox.new
94
+ description[:host] ||= ENV['PRYEMHOST'].nil? || ENV['PRYEMHOST'].empty? ? DEFAULT_SERVER_HOST : ENV['PRYEMHOST']
95
+ determine_port_and_tries(description)
96
+ determine_name(description)
97
+ description[:id] ||= SecureRandom.uuid
98
+ description[:logger] ||= ::Logger.new(STDERR)
99
+ description[:external_url] ||= ENV['PRYEMURL'] || "#{description[:tls] ? 'pryems' : 'pryem'}://#{description[:host]}:#{description[:port]}/"
100
+ description[:details] ||= {}
101
+ description[:allow_shell_cmds] = true if description[:allow_shell_cmds].nil?
102
+ description[:heartbeat_interval] ||= ENV['PRYEMHBSEND'].nil? || ENV['PRYEMHBSEND'].empty? ? HEARTBEAT_SEND_INTERVAL : ENV['PRYEMHBSEND']
103
+ description[:urls] = expand_url(description[:external_url])
104
+ description[:server] = start_server(description, &block)
105
+ description[:logger].info("[pry-remote-em] listening for connections on #{description[:external_url]}")
106
+ PryRemoteEm.servers[description[:id]] = description
107
+ register_in_broker(description)
108
+ return description
109
+ end
110
+
111
+ # The list of pry-remote-em connections for a given object, or the list of all pry-remote-em
112
+ # connections for this process.
113
+ # The peer list is used when broadcasting messages between connections.
114
+ def peers(obj = nil)
115
+ @peers ||= {}
116
+ obj.nil? ? @peers.values.flatten : (@peers[obj] ||= [])
117
+ end
118
+
119
+ # Record the association between a given object and a given pry-remote-em connection.
120
+ def register(obj, peer)
121
+ peers(obj).tap { |plist| plist.include?(peer) || plist.push(peer) }
122
+ end
123
+
124
+ # Remove the association between a given object and a given pry-remote-em connection.
125
+ def unregister(obj, peer)
126
+ peers(obj).tap {|plist| true while plist.delete(peer) }
127
+ end
128
+
129
+ private
130
+
131
+ def determine_port_and_tries(description)
132
+ description[:port] ||= ENV['PRYEMPORT'].nil? || ENV['PRYEMPORT'].empty? ? DEFAULT_SERVER_PORT : ENV['PRYEMPORT']
133
+ description[:port] = :auto if description[:port] == 'auto'
134
+ description[:port] = description[:port].to_i if description[:port].kind_of?(String)
135
+ description[:tries] = [description[:port], description[:port_fail]].include?(:auto) ? 100 : description[:port_fail] || 1
136
+ description[:port] = DEFAULT_SERVER_PORT if description[:port] == :auto
137
+ # TODO raise a useful exception not RuntimeError
138
+ raise "root permission required for port below 1024 (#{port})" if description[:port] < 1024 && Process.euid != 0
139
+ end
140
+
141
+ def determine_name(description)
142
+ description[:name] ||= ENV['PRYEMNAME']
143
+ if description[:name].nil?
144
+ object = description[:target]
145
+ inner_object = object.kind_of?(Binding) ? object.send(:eval, 'self') : object
146
+ description[:name] = Pry.view_clip(inner_object)
147
+ else
148
+ description[:custom_name] = true
149
+ end
150
+ description[:name].sub!(/(?<=^.{57}).{4,}$/, '...')
151
+ end
152
+
153
+ def expand_url(url)
154
+ return Array(url) if (uri = URI.parse(url)).host != '0.0.0.0'
155
+ Socket.ip_address_list.select(&:ipv4?).map(&:ip_address).map do |ip|
156
+ uri.clone.tap { |uri_copy| uri_copy.host = ip }.to_s
157
+ end
158
+ end
159
+
160
+ def start_server(description, &block)
161
+ EM.start_server(description[:host], description[:port], PryRemoteEm::Server, description) do |pre|
162
+ Fiber.new do
163
+ begin
164
+ yield pre if block_given?
165
+ Pry.hooks.add_hook :when_started, pre do |target, options, pry|
166
+ pry.pager = false
167
+ pry.config.prompt_name = description[:name] + ' ' if description[:custom_name]
168
+ if description[:target].is_a? PryRemoteEm::Sandbox
169
+ description[:target].pry = pry
170
+ description[:target].server = description
171
+ pry.last_exception = description[:target].last_error if description[:target].any_errors?
172
+ end
173
+ description[:pry] = pry
174
+ end
175
+ Pry.start(description[:target], input: pre, output: pre)
176
+ ensure
177
+ pre.close_connection
178
+ Pry.hooks.delete_hook :when_started, pre
179
+ end
180
+ end.resume
181
+ end
182
+ rescue => error
183
+ if error.message.include?('port is in use') && description[:tries] > 1
184
+ description[:tries] -= 1
185
+ description[:port] += 1
186
+ retry
187
+ end
188
+ raise "can't bind to #{description[:host]}:#{description[:port]} - #{error}"
189
+ end
190
+
191
+ def register_in_broker(description)
192
+ broker_description = {
193
+ id: description[:id],
194
+ urls: description[:urls],
195
+ name: description[:name],
196
+ details: description[:details],
197
+ metrics: PryRemoteEm::Metrics.list
198
+ }
199
+ broker_options = {
200
+ tls: description[:tls],
201
+ remote_broker: description[:remote_broker],
202
+ logger: description[:logger]
203
+ }
204
+ Broker.run(description[:broker_host], description[:broker_port], broker_options) do |broker|
205
+ broker.register(broker_description)
206
+
207
+ rereg = EM::PeriodicTimer.new(description[:heartbeat_interval]) do
208
+ EM.get_sockname(description[:server]) ? broker.register(broker_description) : nil #rereg.cancel
209
+ end
210
+ end
211
+ end
212
+ end # class << self
213
+
214
+ def initialize(opts = {})
215
+ @obj = opts[:target]
216
+ @opts = opts
217
+ @allow_shell_cmds = opts[:allow_shell_cmds]
218
+ @log = opts[:logger] || ::Logger.new(STDERR)
219
+ # Blocks that will be called on each authentication attempt, prior checking the credentials
220
+ @auth_attempt_cbs = []
221
+ # All authentication attempts that occured before an auth callback was registered
222
+ @auth_attempts = []
223
+ # Blocks that will be called on each failed authentication attempt
224
+ @auth_fail_cbs = []
225
+ # All failed authentication attempts that occured before an auth callback was reigstered
226
+ @auth_fails = []
227
+ # Blocks that will be called on successful authentication attempt
228
+ @auth_ok_cbs = []
229
+ # All successful authentication attemps that occured before the auth ok callbacks were registered
230
+ @auth_oks = []
231
+
232
+ # The number maximum number of authentication attempts that are permitted
233
+ @auth_tries = 5
234
+ # Data to be sent after the user successfully authenticates if authentication is required
235
+ @after_auth = []
236
+ return unless (a = opts[:auth])
237
+ if a.is_a?(Proc)
238
+ return fail("auth handler Procs must take two arguments not (#{a.arity})") unless a.arity == 2
239
+ @auth = a
240
+ elsif a.respond_to?(:call)
241
+ return fail("auth handler must take two arguments not (#{a.method(:call).arity})") unless a.method(:call).arity == 2
242
+ @auth = a
243
+ else
244
+ return error('auth handler objects must respond to :call, or :[]') unless a.respond_to?(:[])
245
+ @auth = lambda {|u,p| a[u] && a[u] == p }
246
+ end
247
+ end
248
+
249
+ def url
250
+ port, host = Socket.unpack_sockaddr_in(get_sockname)
251
+ "#{@opts[:tls] ? 'pryems' : 'pryem'}://#{host}:#{port}/"
252
+ end
253
+
254
+ def post_init
255
+ @lines = []
256
+ @auth_required = @auth
257
+ @port, @ip = Socket.unpack_sockaddr_in(get_peername)
258
+ @log.info("[pry-remote-em] received client connection from #{@ip}:#{@port}")
259
+ # TODO include first level prompt in banner
260
+ send_banner("PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}")
261
+ need_new_line = false
262
+ if @opts[:details].any?
263
+ send_raw("\nServer details:\n#{@opts[:details].map { |key, value| " #{key}: #{value}" } * "\n"}\n")
264
+ need_new_line = true
265
+ end
266
+ if PryRemoteEm::Metrics.any?
267
+ send_raw("\nServer metrics:\n#{PryRemoteEm::Metrics.list.map { |key, value| " #{key}: #{value}" } * "\n"}\n")
268
+ need_new_line = true
269
+ end
270
+ send_raw("\n") if need_new_line
271
+ @log.info("#{url} PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}")
272
+ @opts[:tls] ? start_tls : (@auth_required && send_auth(false))
273
+ PryRemoteEm::Server.register(@obj, self)
274
+ end
275
+
276
+ def start_tls
277
+ @log.debug("[pry-remote-em] starting TLS (#{peer_ip}:#{peer_port})")
278
+ super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
279
+ end
280
+
281
+ def ssl_handshake_completed
282
+ @log.info("[pry-remote-em] TLS connection established (#{peer_ip}:#{peer_port})")
283
+ send_auth(false) if @auth_required
284
+ end
285
+
286
+ def peer_ip
287
+ return @peer_ip if @peer_ip
288
+ return '' if get_peername.nil?
289
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
290
+ @peer_ip
291
+ end
292
+
293
+ def peer_port
294
+ return @peer_port if @peer_port
295
+ return '' if get_peername.nil?
296
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
297
+ @peer_port
298
+ end
299
+
300
+ def receive_clear_buffer
301
+ @opts[:pry].eval_string.replace('')
302
+ @last_prompt = @opts[:pry].select_prompt
303
+ send_last_prompt
304
+ end
305
+
306
+ def receive_raw(d)
307
+ return if require_auth
308
+
309
+ return send_last_prompt if d.nil?
310
+
311
+ if d.empty?
312
+ @lines.push('')
313
+ else
314
+ lines = d.split("\n")
315
+ @lines.push(*lines)
316
+ end
317
+
318
+ if @waiting
319
+ f, @waiting = @waiting, nil
320
+ f.resume(@lines.shift)
321
+ end
322
+ end
323
+
324
+ # tab completion request
325
+ def receive_completion(c)
326
+ return if require_auth
327
+ send_completion(@compl_proc.call(c))
328
+ end
329
+
330
+ def receive_auth(user, pass)
331
+ return send_auth(true) if !@auth || !@auth_required
332
+ return send_auth('auth data must include a user and pass') if user.nil? || pass.nil?
333
+ auth_attempt(user, peer_ip)
334
+ unless (@auth_required = !@auth.call(user, pass))
335
+ @user = user
336
+ auth_ok(user, peer_ip)
337
+ authenticated!
338
+ else
339
+ auth_fail(user, peer_ip)
340
+ if @auth_tries <= 0
341
+ msg = 'max authentication attempts reached'
342
+ send_auth(msg)
343
+ @log.debug("[pry-remote-em] #{msg} (#{peer_ip}:#{peer_port})")
344
+ return close_connection_after_writing
345
+ end
346
+ @auth_tries -= 1
347
+ end
348
+ return send_auth(!@auth_required)
349
+ end
350
+
351
+ def receive_msg(m)
352
+ return if require_auth
353
+ peers.each { |peer| peer.send_message(m, @user) }
354
+ send_last_prompt
355
+ end
356
+
357
+ def receive_msg_bcast(mb)
358
+ return if require_auth
359
+ peers(:all).each { |peer| peer.send_bmessage(mb, @user) }
360
+ send_last_prompt
361
+ end
362
+
363
+ def receive_shell_cmd(cmd)
364
+ return if require_auth
365
+ unless @allow_shell_cmds
366
+ puts "\033[1mshell commands are not allowed by this server\033[0m"
367
+ @log.error("refused to execute shell command '#{cmd}' for #{@user} (#{peer_ip}:#{peer_port})")
368
+ send_shell_result(-1)
369
+ send_last_prompt
370
+ else
371
+ @log.warn("executing shell command '#{cmd}' for #{@user} (#{peer_ip}:#{peer_port})")
372
+ @shell_cmd = EM.popen3(cmd, ShellCmd, self)
373
+ end
374
+ end
375
+
376
+ def receive_shell_data(d)
377
+ return if require_auth
378
+ @shell_cmd.send_data(d)
379
+ end
380
+
381
+ def receive_shell_sig(type)
382
+ return if require_auth
383
+ @shell_cmd.close_connection if type == :int
384
+ end
385
+
386
+ def receive_unknown(j)
387
+ return if require_auth
388
+ warn "received unexpected data: #{j.inspect}"
389
+ send_error("received unexpected data: #{j.inspect}")
390
+ send_last_prompt
391
+ end
392
+
393
+ def require_auth
394
+ return false if !@auth_required
395
+ send_auth(false)
396
+ true
397
+ end
398
+
399
+ def authenticated!
400
+ while (aa = @after_auth.shift)
401
+ aa.call
402
+ end
403
+ end
404
+
405
+ def unbind
406
+ PryRemoteEm::Server.unregister(@obj, self)
407
+ @log.debug("[pry-remote-em] remote session terminated (#{@ip}:#{@port})")
408
+ end
409
+
410
+ def peers(all = false)
411
+ plist = (all ? PryRemoteEm::Server.peers : PryRemoteEm::Server.peers(@obj)).clone
412
+ plist.delete(self)
413
+ plist
414
+ end
415
+
416
+ def send_last_prompt
417
+ @auth_required ? (after_auth { send_prompt(@last_prompt) }) : send_prompt(@last_prompt)
418
+ end
419
+
420
+ def after_auth(&blk)
421
+ # TODO perhaps replace with #auth_ok
422
+ @after_auth.push(blk)
423
+ end
424
+
425
+ # Sends a chat message to the client.
426
+ def send_message(msg, from = nil)
427
+ msg = "#{msg} (@#{from})" unless from.nil?
428
+ @auth_required ? (after_auth {send_msg(msg)}) : send_msg(msg)
429
+ end
430
+ #
431
+ # Sends a chat message to the client.
432
+ def send_bmessage(msg, from = nil)
433
+ msg = "#{msg} (@#{from})" unless from.nil?
434
+ @auth_required ? (after_auth {send_msg_bcast(msg)}) : send_msg_bcast(msg)
435
+ end
436
+
437
+ # Callbacks for events on the server
438
+
439
+ # Registers a block to call when authentication is attempted.
440
+ # @overload auth_attempt(&blk)
441
+ # @yield [user, ip] a block to call on each authentication attempt
442
+ # @yieldparam [String] user
443
+ # @yieldparam [String] ip
444
+ def auth_attempt(*args, &blk)
445
+ block_given? ? @auth_attempt_cbs << blk : @auth_attempts.push(args)
446
+ while (auth_data = @auth_attempts.shift)
447
+ @auth_attempt_cbs.each { |cb| cb.call(*auth_data) }
448
+ end
449
+ end # auth_attempt(*args, &blk)
450
+
451
+ # Registers a block to call when authentication fails.
452
+ # @overload auth_fail(&blk)
453
+ # @yield [user, ip] a block to call after each failed authentication attempt
454
+ # @yieldparam [String] user
455
+ # @yieldparam [String] ip
456
+ def auth_fail(*args, &blk)
457
+ block_given? ? @auth_fail_cbs << blk : @auth_fails.push(args)
458
+ while (fail_data = @auth_fails.shift)
459
+ @auth_fail_cbs.each { |cb| cb.call(*fail_data) }
460
+ end
461
+ end # auth_fail(*args, &blk)
462
+
463
+ # Registers a block to call when authentication succeeds.
464
+ # @overload auth_ok(&blk)
465
+ # @yield [user, ip] a block to call after each successful authentication attempt
466
+ # @yieldparam [String] user
467
+ # @yieldparam [String] ip
468
+ def auth_ok(*args, &blk)
469
+ block_given? ? @auth_ok_cbs << blk : @auth_oks.push(args)
470
+ while (ok_data = @auth_oks.shift)
471
+ @auth_ok_cbs.each { |cb| cb.call(*ok_data) }
472
+ end
473
+ end # auth_fail(*args, &blk)
474
+
475
+ def send_error(msg)
476
+ puts "\033[31m#{msg}\033[0m"
477
+ end
478
+
479
+ # Methods that make Server compatible with Pry
480
+
481
+ def readline(prompt)
482
+ @last_prompt = prompt
483
+ @auth_required ? (after_auth { send_prompt(prompt) }) : send_prompt(prompt)
484
+ return @lines.shift unless @lines.empty?
485
+ @waiting = Fiber.current
486
+ return Fiber.yield
487
+ end
488
+
489
+ def print(val)
490
+ @auth_required ? (after_auth { send_raw(val) }) : send_raw(val)
491
+ end
492
+ alias :write :print
493
+
494
+ def puts(data = '')
495
+ s = data.to_s
496
+ print(s[0] == "\n" ? s : s + "\n")
497
+ end
498
+
499
+ def completion_proc=(compl)
500
+ @compl_proc = compl
501
+ end
502
+
503
+ def tty?
504
+ true # might be a very bad idea ....
505
+ end
506
+
507
+ def flush
508
+ true
509
+ end
510
+ end # module::Server
511
+ end # module::PryRemoteEm