#!/usr/bin/python
#
# Copyright (c) 2008, Thomas Hurst <tom@hur.st>
#
# Use of this file is unrestricted provided this notice is retained.
# If you use it, it'd be nice if you dropped me a note.  Also beer.
from terminatorlib.util import dbg, err
from terminatorlib.version import APP_NAME, APP_VERSION
import socket
import threading
import SocketServer
import code
import sys
import readline
import rlcompleter
import re
[docs]def ddbg(msg):
  # uncomment this to get lots of spam from debugserver
  return
  dbg(msg)
 
[docs]class PythonConsoleServer(SocketServer.BaseRequestHandler):
  env = None
[docs]  def setup(self):
    dbg('debugserver: connect from %s' % str(self.client_address))
    ddbg('debugserver: env=%r' % PythonConsoleServer.env)
    self.console = TerminatorConsole(PythonConsoleServer.env)
 
[docs]  def handle(self):
    ddbg("debugserver: handling")
    try:
      self.socketio = self.request.makefile()
      sys.stdout = self.socketio
      sys.stdin = self.socketio
      sys.stderr = self.socketio
      self.console.run(self)
    finally:
      sys.stdout = sys.__stdout__
      sys.stdin = sys.__stdin__
      sys.stderr = sys.__stderr__
      self.socketio.close()
      ddbg("debugserver: done handling")
 
[docs]  def verify_request(self, request, client_address):
    return True
 
[docs]  def finish(self):
    ddbg('debugserver: disconnect from %s' % str(self.client_address))
# rfc1116/rfc1184  
LINEMODE = chr(34) # Linemode negotiation
NULL = chr(0)
ECHO = chr(1)
CR   = chr(13)
LF   = chr(10)
SE   = chr(240) # End subnegotiation
NOP  = chr(241)
DM   = chr(242) # Data Mark
BRK  = chr(243) # Break
IP   = chr(244) # Interrupt Process
AO   = chr(245) # Abort Output
AYT  = chr(246) # Are You There
EC   = chr(247) # Erase Character
EL   = chr(248) # Erase Line
GA   = chr(249) # Go Ahead
SB   = chr(250) # Subnegotiation follows
WILL = chr(251) # Subnegotiation commands
WONT = chr(252)
DO   = chr(253)
DONT = chr(254)
IAC  = chr(255) # Interpret As Command
UIAC        = '(^|[^' + IAC + '])' + IAC # Unescaped IAC
BareLF      = re.compile('([^' + CR + '])' + CR)
DoDont      = re.compile(UIAC +'[' + DO + DONT + '](.)')
WillWont    = re.compile(UIAC + '[' + WILL + WONT + '](.)')
AreYouThere = re.compile(UIAC + AYT)
IpTelnet    = re.compile(UIAC + IP)
OtherTelnet = re.compile(UIAC + '[^' + IAC + ']')
# See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/205335 for telnet bits
# Python doesn't make this an especially neat conversion :(
[docs]class TerminatorConsole(code.InteractiveConsole):
[docs]  def parse_telnet(self, data):
    odata = data
    data = re.sub(BareLF, '\\1', data)
    data = data.replace(CR + NULL, '')
    data = data.replace(NULL, '')
    bits = re.findall(DoDont, data)
    ddbg("bits = %r" % bits)
    if bits:
      data = re.sub(DoDont, '\\1', data)
      ddbg("telnet: DO/DON'T answer")
      # answer DO and DON'T with WON'T
      for bit in bits:
        self.write(IAC + WONT + bit[1])
    bits = re.findall(WillWont, data)
    if bits:
      data = re.sub(WillWont, '\\1', data)
      ddbg("telnet: WILL/WON'T answer")
      for bit in bits:
        # answer WILLs and WON'T with DON'Ts
        self.write(IAC + DONT + bit[1])
    bits = re.findall(AreYouThere, data)
    if bits:
      ddbg("telnet: am I there answer")
      data = re.sub(AreYouThere, '\\1', data)
      for bit in bits:
        self.write("Yes, I'm still here, I think.\n")
    (data, interrupts) = re.subn(IpTelnet, '\\1', data)
    if interrupts:
      ddbg("debugserver: Ctrl-C detected")
      raise KeyboardInterrupt
    data = re.sub(OtherTelnet, '\\1', data) # and any other Telnet codes
    data = data.replace(IAC + IAC, IAC) # and handle escapes
    if data != odata:
      ddbg("debugserver: Replaced %r with %r" % (odata, data))
    return data
 
[docs]  def write(self, data):
    ddbg("debugserver: write %r" % data)
    self.server.socketio.write(data)
    self.server.socketio.flush()
 
[docs]  def run(self, server):
    self.server = server
    self.write("Welcome to the %s-%s debug server, have a nice stay\n" % (APP_NAME, APP_VERSION))
    self.interact()
    try:
      self.write("Time to go.  Bye!\n")
    except:
      pass
  
[docs]def spawn(env):
  PythonConsoleServer.env = env
  tcpserver = SocketServer.TCPServer(('127.0.0.1', 0), PythonConsoleServer)
  dbg("debugserver: listening on %s" % str(tcpserver.server_address))
  debugserver = threading.Thread(target=tcpserver.serve_forever, name="DebugServer")
  debugserver.setDaemon(True)
  debugserver.start()
  return(debugserver, tcpserver)