Class: Muby::Connection

Inherits:
Object
  • Object
show all
Includes:
Configurable, Displayer, Logger
Defined in:
lib/muby/connection.rb

Overview

The class that encapsulates the actual Connection

Constant Summary collapse

TELNET_COMMANDS =
{
  240 => :end_of_sub_negotiation,
  241 => :noop,
  242 => :data_mark,
  243 => :break,
  249 => :go_ahead,
  250 => :sub_negotiation,
  251 => :will,
  252 => :wont,
  253 => :do,
  254 => :dont,
  255 => :iac
}

Instance Method Summary collapse

Methods included from Displayer

debug, error, exception, info, trace, warn

Methods included from Configurable

#conf

Methods included from Logger

#log_input, #log_output

Constructor Details

#initialize(inputWindow, outputWindow, host, port) ⇒ Connection

Set it up with windows, handle and host info.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/muby/connection.rb', line 16

def initialize(inputWindow, outputWindow, host, port)
  @inputWindow = inputWindow
  @outputWindow = outputWindow
  status("Connecting to " + host.inspect + ":" + port.inspect)
  @host = host
  @port = port
  connect!
  conf.connect_triggers.each do |command|
    @inputWindow.execute(command, @inputWindow, @outputWindow)
  end
  status("Connected to " + host.inspect + ":" + port.inspect)
  @matchBuffer = ""
  @showBuffer = []
  @used_triggers = {}
  @listener = Thread.new do
    begin
      status("Listening thread started")
      listen
      status("Listening thread finished")
    rescue Exception => e
      exception(e)
    ensure
      close
    end
  end if @socket
end

Instance Method Details

#append_show_buffer(c) ⇒ Object



293
294
295
296
297
298
299
# File 'lib/muby/connection.rb', line 293

def append_show_buffer(c)
  if String === @showBuffer.last
    @showBuffer.last << c
  else
    @showBuffer << c
  end
end

#closeObject



316
317
318
319
320
321
322
323
324
325
# File 'lib/muby/connection.rb', line 316

def close
  if Thread.current != @listener && @listener && @listener.alive?
    status("Killing listening thread")
    @listener.kill
  end
  if @socket && !@socket.closed?
    status("Disconnecting our end")
    @socket.close if @socket
  end
end

#connect!Object

Almost ripped from the documentation: www.ruby-doc.org/core/classes/Socket.src/M002114.html



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/muby/connection.rb', line 46

def connect!
  @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  addr = Socket.sockaddr_in(@port, @host)
  begin
    @socket.connect_nonblock(addr)
  rescue Errno::EINPROGRESS
    IO.select(nil, [@socket], nil, conf.connect_timeout)
    begin
      @socket.connect_nonblock(addr)
    rescue Errno::EISCONN
    rescue Errno::EALREADY
      raise "Connection timed out"
    end
  end
end

#display_bufferObject



374
375
376
377
378
379
380
381
382
383
# File 'lib/muby/connection.rb', line 374

def display_buffer
  unless @showBuffer.empty?
    if !gag(@matchBuffer) || nongag(@matchBuffer)
      @showBuffer = substitute(@showBuffer, conf.remote_substitutions)
      Muby::Completer.get_instance.store(@matchBuffer) if conf.feed_completer_with_input
      @outputWindow.print(*@showBuffer)
    end
    @showBuffer = []
  end
end

#feed(s) ⇒ Object



140
141
142
143
144
145
# File 'lib/muby/connection.rb', line 140

def feed(s)
  s.unpack("C*").each do |c|
    handle(c)
  end
  nil
end

#gag(matchBuffer) ⇒ Object

Check if we want to gag the current matchBuffer and debug about it.

Remove the gag regexp if it is broken.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/muby/connection.rb', line 76

def gag(matchBuffer)
  returnValue = false
  conf.gags.each do |gag|
    begin
      if matchBuffer.match(gag)
        trace(matchBuffer + " matches " + gag.inspect + ": gag active")
        returnValue = true
      end
    rescue RegexpError => error
      conf.gags.delete(key)
      exception(e)
    end
  end
  returnValue
end

#getcObject



147
148
149
150
151
# File 'lib/muby/connection.rb', line 147

def getc
  c = @socket.getc
  log_input(c.chr) if c
  c
end

#handle(c) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/muby/connection.rb', line 179

def handle(c)
  #
  # The telnet support (just so we dont explode or something)
  #
  # We are just plain ignoring most of it atm.
  #
  if TELNET_COMMANDS[c] == :iac # this is an "interpret as command"
    c = getc
    if (next_command = TELNET_COMMANDS[c]) == :iac # if the next one as well is an iac, we actually want to display a 255
      handle_regular_character(c)
    else
      case next_command
      when :do # ignore the option negotiation
        c = getc
      when :dont # ignore the option negotiation
        c = getc
      when :will # ignore the option negotiation
        c = getc
      when :wont # ignore the option negotiation
        c = getc
      when :noop # waf?
        # do nothing
      when :data_mark
        warn("Got a data_mark (TELNET protocol) which I don't know how to handle. Expect strange behaviour...")
      when :break
        warn("Got a break (TELNET protocol) which I don't know how to handle. Expect strange behaviour...")
      when :go_ahead
        display_buffer # on a go ahead the server obviously wants us to display the @displayBuffer regardless of our conf.flush setting
      when :sub_negotiation # we just skip ahead to the end of the sub negotiation
        while TELNET_COMMANDS[c] != :end_of_sub_negotiation
          c = getc
        end
      else
        warn("Got an unknown TELNET command (#{c.chr}) which I don't know how to handle. Expect strange behaviour...")
      end
    end
  elsif c == 27 # escape char! this is probably some ANSI color or shite
    c = getc
    if c.chr == "[" # this is an ansi-something that is more than one char long
      ansiString = ""
      while !"cnRhl()HABCfsurhgKJipm".include?((c = getc).chr)
        ansiString << c.chr
      end
      if c.chr == "m" && Ncurses.has_colors? # ah, text property! i understand this!
        properties = ansiString.split(";")
        attributes = 0
        bgcolor = false
        fgcolor = false
        reset = properties.index("0")
        if reset
          properties.delete("0")
        end
        properties.each do |property|
          case property.to_i
          when 1
            attributes = attributes | Ncurses.const_get("A_BOLD")
          when 2
            attributes = attributes | Ncurses.const_get("A_DIM")
          when 4
            attributes = attributes | Ncurses.const_get("A_UNDERLINE")
          when 5
            attributes = attributes | Ncurses.const_get("A_BLINK") unless conf.disable_blink
          when 7
            attributes = attributes | Ncurses.const_get("A_REVERSE")
          when 8
            attributes = attributes | Ncurses.const_get("A_INVIS")
          when 30
            fgcolor = Ncurses.const_get("COLOR_BLACK")
          when 31
            fgcolor = Ncurses.const_get("COLOR_RED")
          when 32
            fgcolor = Ncurses.const_get("COLOR_GREEN")
          when 33
            fgcolor = Ncurses.const_get("COLOR_YELLOW")
          when 34
            fgcolor = Ncurses.const_get("COLOR_BLUE")
          when 35
            fgcolor = Ncurses.const_get("COLOR_MAGENTA")
          when 36
            fgcolor = Ncurses.const_get("COLOR_CYAN")
          when 37
            fgcolor = Ncurses.const_get("COLOR_WHITE")
          when 40
            bgcolor = Ncurses.const_get("COLOR_BLACK")
          when 41
            bgcolor = Ncurses.const_get("COLOR_RED")
          when 42
            bgcolor = Ncurses.const_get("COLOR_GREEN")
          when 43
            bgcolor = Ncurses.const_get("COLOR_YELLOW")
          when 44
            bgcolor = Ncurses.const_get("COLOR_BLUE")
          when 45
            bgcolor = Ncurses.const_get("COLOR_MAGENTA")
          when 46
            bgcolor = Ncurses.const_get("COLOR_CYAN")
          when 47
            bgcolor = Ncurses.const_get("COLOR_WHITE")
          end
        end
        style = nil
        if reset
          style = Muby::Style.new(attributes, fgcolor, bgcolor, false)
        else
          style = Muby::Style.new(attributes, fgcolor, bgcolor, true)
        end
        @showBuffer.push(style)
      end
    end
  elsif !conf.broken_keycodes.include?(c)
    handle_regular_character(c)
  end
end

#handle_regular_character(c) ⇒ Object

This is not telnet command OR ansi, lets treat it as nice MUD text!

Debug about it, add it to match buffer, add it to show buffer and show if we want to show. Then check it for triggers.



173
174
175
176
177
# File 'lib/muby/connection.rb', line 173

def handle_regular_character(c)
  @matchBuffer = @matchBuffer + c.chr
  append_show_buffer(c.chr)
  manage_buffers(c)
end

#homemade_split(s, r) ⇒ Object



339
340
341
342
343
344
345
346
347
348
# File 'lib/muby/connection.rb', line 339

def homemade_split(s, r)
  rval = []
  rest = s
  while match = rest.match(/(.*?)#{r.source}(.*)/)
    rval << match[1]
    rest = match[2]
  end
  rval << rest
  rval
end

#listenObject

Keep listening to the remote socket and react accordingly.



304
305
306
307
308
309
310
311
312
313
314
# File 'lib/muby/connection.rb', line 304

def listen
  c = true
  # We have to look at each byte for ncurses reasons as well as triggers etc.
  while c
    while select([@socket],nil,nil,1) && c = getc
      handle(c)
    end
  end
  status("Connection closed by remote end")
  @inputWindow.disconnect
end

#manage_buffers(c) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
# File 'lib/muby/connection.rb', line 327

def manage_buffers(c)
  trigger(@matchBuffer, conf.remote_character_triggers, true)
  if c == 10 || conf.flush
    display_buffer
    if c == 10
      trigger(@matchBuffer, conf.remote_triggers, false)
      @matchBuffer = ""
      @used_triggers = {}
    end
  end
end

#nongag(matchBuffer) ⇒ Object

Check if we should definitely NOT gag the current matchBuffer, and debug about it.

Remove the nongag regexp if it is broken.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/muby/connection.rb', line 97

def nongag(matchBuffer)
  returnValue = false
  conf.anti_gags.each do |nongag|
    begin
      if matchBuffer.match(nongag)
        trace(matchBuffer + " matches " + nongag.inspect + ": nongag active")
        returnValue = true
      end
    rescue RegexpError => error
      conf.anti_gags.delete(key)
      exception(e) 
    end
  end
  returnValue
end

#send(s) ⇒ Object

Just plain send the string we got.



388
389
390
391
392
393
394
395
396
# File 'lib/muby/connection.rb', line 388

def send(s)
  log_output(s)
  conf.local_substitutions.each do |key, value|
    # It might be useful to allow colour codes for highlighting local substitutions.. but only when echo is on.
    # No, cause this only gets sent to the server, it never shows up to the user.
    s.gsub!(key, value)
  end
  @socket.print(s) if @socket
end

#status(message) ⇒ Object

Display a status message in the outputwindow if wanted (conf.connection_status)



65
66
67
68
69
# File 'lib/muby/connection.rb', line 65

def status(message)
  if conf.connection_status
    @outputWindow.print_on_newline(*[message + "\n"])
  end
end

#substitute(buffer, hash) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/muby/connection.rb', line 350

def substitute(buffer, hash)
  return_value = []
  buffer.each do |part|
    if String === part
      part_to_append = [part]
      hash.each do |regexp, substitution|
        case substitution
        when String
          part_to_append = [part.gsub(regexp, substitution)]
        when Array
          if part.match(regexp)
            split_part = homemade_split(part, regexp)
            part_to_append = split_part.zip([substitution] * (split_part.size - 1)).flatten.compact
          end
        end
      end
      return_value += part_to_append
    else
      return_value << part
    end
  end
  return_value
end

#trigger(matchBuffer, hash, skip_used = true) ⇒ Object

Check if we should trigger a Proc on the current matchBuffer, and debug about it.

Remove the trigger regexp if it is broken.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/muby/connection.rb', line 118

def trigger(matchBuffer, hash, skip_used = true)
  hash.each do |key, value|
    if skip_used && @used_triggers.include?(key)
      trace("#{key.inspect} has already been matched on this line, skipping it")
    else
      trace("checking if " + matchBuffer + " matches " + key.inspect)
      begin
        if match = matchBuffer.match(key)
          trace(matchBuffer + " matches " + key.inspect + ": .call'ing Proc")
          # Run the procedure associated with that trigger:
          @inputWindow.execute(value, @inputWindow, @outputWindow, match)
          @used_triggers[key] = true if skip_used
        end
        # If we received an error with the regular expression:
      rescue RegexpError => error
        hash.delete(key)
        exception(error)
      end
    end
  end
end