Kaydet (Commit) e36f7356 authored tarafından Guido van Rossum's avatar Guido van Rossum

Major rewrite with new read_* interfaces

üst 0b26a19a
# Telnet client library # A TELNET client class. Based on RFC 854: TELNET Protocol
# Specification, by J. Postel and J. Reynolds
# Example:
#
# >>> from telnetlib import Telnet
# >>> tn = Telnet('voorn.cwi.nl', 79) # connect to finger port
# >>> tn.write('guido\r\n')
# >>> print tn.read_all()
# Login name: guido In real life: Guido van Rossum
# Office: M353, x4127 Home phone: 020-6225521
# Directory: /ufs/guido Shell: /usr/local/bin/esh
# On since Oct 28 11:02:16 on ttyq1
# Project: Multimedia Kernel Systems
# No Plan.
# >>>
#
# Note that read() won't read until eof -- it just reads some data
# (but it guarantees to read at least one byte unless EOF is hit).
#
# It is possible to pass a Telnet object to select.select() in order
# to wait until more data is available. Note that in this case,
# read_eager() may return '' even if there was data on the socket,
# because the protocol negotiation may have eaten the data.
# This is why EOFError is needed to distinguish between "no data"
# and "connection closed" (since the socket also appears ready for
# reading when it is closed).
#
# Bugs:
# - may hang when connection is slow in the middle of an IAC sequence
#
# To do:
# - option negotiation
# Imported modules
import socket import socket
import select import select
import string import string
import regsub import regsub
# Tunable parameters # Tunable parameters
TIMEOUT = 30.0 DEBUGLEVEL = 0
DEBUGLEVEL = 1
# Telnet protocol defaults # Telnet protocol defaults
TELNET_PORT = 23 TELNET_PORT = 23
...@@ -24,158 +58,264 @@ WILL = chr(251) ...@@ -24,158 +58,264 @@ WILL = chr(251)
class Telnet: class Telnet:
# Constructor # Constructor
def __init__(self, host, port): def __init__(self, host, *args):
self.debuglevel = DEBUGLEVEL if not args:
self.host = host port = TELNET_PORT
if not port: port = TELNET_PORT else:
self.port = port if len(args) > 1: raise TypeError, 'too many args'
self.timeout = TIMEOUT port = args[0]
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if not port: port = TELNET_PORT
self.sock.connect((self.host, self.port)) self.debuglevel = DEBUGLEVEL
self.rawq = '' self.host = host
self.irawq = 0 self.port = port
self.cookedq = '' self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.host, self.port))
# Destructor self.rawq = ''
def __del__(self): self.irawq = 0
self.close() self.cookedq = ''
self.eof = 0
# Print debug message
def msg(self, msg, *args): # Destructor
if self.debuglevel > 0: def __del__(self):
print 'TELNET:', msg%args self.close()
# Set debug level # Debug message
def set_debuglevel(self, debuglevel): def msg(self, msg, *args):
self.debuglevel = debuglevel if self.debuglevel > 0:
print 'Telnet(%s,%d):' % (self.host, self.port), msg % args
# Set time-out on certain reads
def set_timeout(self, timeout): # Set debug level
self.timeout = float(timeout) def set_debuglevel(self, debuglevel):
self.debuglevel = debuglevel
# Explicit close
def close(self): # Explicit close
if self.sock: def close(self):
self.sock.close() if self.sock:
self.sock = None self.sock.close()
self.sock = None
# Return socket (e.g. for select) self.eof = 1
def get_socket(self):
return self.sock # Return socket (e.g. for select)
def get_socket(self):
# Return socket's fileno (e.g. for select) return self.sock
def fileno(self):
return self.sock.fileno() # Return socket's fileno (e.g. for select)
def fileno(self):
# Write a string to the socket, doubling any IAC characters return self.sock.fileno()
def write(self, buffer):
if IAC in buffer: # Write a string to the socket, doubling any IAC characters
buffer = regsub.gsub(IAC, IAC+IAC, buffer) # Might block if the connection is blocked
self.sock.send(buffer) # May raise socket.error if the connection is closed
def write(self, buffer):
# Read until a given string is encountered or until timeout if IAC in buffer:
def read_until(self, match): buffer = regsub.gsub(IAC, IAC+IAC, buffer)
## self.msg('read_until(%s)' % `match`) self.sock.send(buffer)
n = len(match)
self.process_rawq() # The following read_* methods exist:
i = string.find(self.cookedq, match) # Special case:
if i < 0: # - read_until() reads until a string is encountered or a timeout is hit
i = max(0, len(self.cookedq)-n) # These may block:
self.fill_cookedq() # - read_all() reads all data until EOF
i = string.find(self.cookedq, match, i) # - read_some() reads at least one byte until EOF
if i >= 0: # These may do I/O but won't block doing it:
i = i+n # - read_very_eager() reads all data available on the socket
buf = self.cookedq[:i] # - read_eager() reads either data already queued or some data
self.cookedq = self.cookedq[i:] # available on the socket
## self.msg('read_until(%s) -> %s' % (`match`, `buf`)) # These don't do I/O:
return buf # - read_lazy() reads all data in the raw queue (processing it first)
while select.select([self], [], [], self.timeout) == \ # - read_very_lazy() reads all data in the cooked queue
([self], [], []):
i = max(0, len(self.cookedq)-n)
self.fill_rawq()
self.process_rawq()
i = string.find(self.cookedq, match, i)
if i >= 0:
i = i+n
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
## self.msg('read_until(%s) -> %s' %
## (`match`, `buf`))
return buf
buf = self.cookedq
self.cookedq = ''
## self.msg('read_until(%s) -> %s' % (`match`, `buf`))
return buf
# Read everything that's possible without really blocking # Read until a given string is encountered or until timeout
def read_now(self): # Raise EOFError if connection closed and no cooked data available
self.fill_cookedq() # Return '' if no cooked data available otherwise
buf = self.cookedq def read_until(self, match, *args):
self.cookedq = '' if not args:
## self.msg('read_now() --> %s' % `buf`) timeout = None
else:
if len(args) > 1: raise TypeError, 'too many args'
timeout = args[0]
n = len(match)
self.process_rawq()
i = string.find(self.cookedq, match)
if i >= 0:
i = i+n
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
return buf
s_reply = ([self], [], [])
s_args = s_reply
if timeout is not None:
s_args = s_args + (timeout,)
while not self.eof and apply(select.select, s_args) == s_reply:
i = max(0, len(self.cookedq)-n)
self.fill_rawq()
self.process_rawq()
i = string.find(self.cookedq, match, i)
if i >= 0:
i = i+n
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
return buf return buf
return self.read_very_lazy()
# Read all data until EOF
# Block until connection closed
def read_all(self):
self.process_rawq()
while not self.eof:
self.fill_rawq()
self.process_rawq()
buf = self.cookedq
self.cookedq = ''
return buf
# Read at least one byte of cooked data unless EOF is hit
# Return '' if EOF is hit
# Block if no data is immediately available
def read_some(self):
self.process_rawq()
while not self.cookedq and not self.eof:
self.fill_rawq()
self.process_rawq()
buf = self.cookedq
self.cookedq = ''
return buf
# Read everything that's possible without blocking in I/O (eager)
# Raise EOFError if connection closed and no cooked data available
# Return '' if no cooked data available otherwise
# Don't block unless in the midst of an IAC sequence
def read_very_eager(self):
self.process_rawq()
while not self.eof and self.sock_avail():
self.fill_rawq()
self.process_rawq()
return self.read_very_lazy()
# Read readily available data
# Raise EOFError if connection closed and no cooked data available
# Return '' if no cooked data available otherwise
# Don't block unless in the midst of an IAC sequence
def read_eager(self):
self.process_rawq()
while not self.cookedq and not self.eof and self.sock_avail():
self.fill_rawq()
self.process_rawq()
return self.read_very_lazy()
# Process and return data that's already in the queues (lazy)
# Raise EOFError if connection closed and no data available
# Return '' if no cooked data available otherwise
# Don't block unless in the midst of an IAC sequence
def read_lazy(self):
self.process_rawq()
return self.read_very_lazy()
# Return any data available in the cooked queue (very lazy)
# Raise EOFError if connection closed and no data available
# Return '' if no cooked data available otherwise
# Don't block
def read_very_lazy(self):
buf = self.cookedq
self.cookedq = ''
if not buf and self.eof and not self.rawq:
raise EOFError, 'telnet connection closed'
return buf
# Transfer from raw queue to cooked queue
# Set self.eof when connection is closed
# Don't block unless in the midst of an IAC sequence
def process_rawq(self):
buf = ''
try:
while self.rawq:
c = self.rawq_getchar()
if c != IAC:
buf = buf + c
continue
c = self.rawq_getchar()
if c == IAC:
buf = buf + c
elif c in (DO, DONT):
opt = self.rawq_getchar()
self.msg('IAC %s %d', c == DO and 'DO' or 'DONT', ord(c))
self.sock.send(IAC + WONT + opt)
elif c in (WILL, WONT):
opt = self.rawq_getchar()
self.msg('IAC %s %d',
c == WILL and 'WILL' or 'WONT', ord(c))
else:
self.msg('IAC %s not recognized' % `c`)
except EOFError: # raised by self.rawq_getchar()
pass
self.cookedq = self.cookedq + buf
# Get next char from raw queue
# Block if no data is immediately available
# Raise EOFError when connection is closed
def rawq_getchar(self):
if not self.rawq:
self.fill_rawq()
if self.eof:
raise EOFError
c = self.rawq[self.irawq]
self.irawq = self.irawq + 1
if self.irawq >= len(self.rawq):
self.rawq = ''
self.irawq = 0
return c
# Fill raw queue from exactly one recv() system call
# Block if no data is immediately available
# Set self.eof when connection is closed
def fill_rawq(self):
if self.irawq >= len(self.rawq):
self.rawq = ''
self.irawq = 0
# The buffer size should be fairly small so as to avoid quadratic
# behavior in process_rawq() above
buf = self.sock.recv(50)
self.eof = (not buf)
self.rawq = self.rawq + buf
# Test whether data is available on the socket
def sock_avail(self):
return select.select([self], [], [], 0) == ([self], [], [])
# Fill cooked queue without blocking # Test program
def fill_cookedq(self): # Usage: test [-d] ... [host [port]]
self.process_rawq() def test():
while select.select([self], [], [], 0) == ([self], [], []): import sys, string, socket, select
self.fill_rawq() debuglevel = 0
if not self.rawq: while sys.argv[1:] and sys.argv[1] == '-d':
raise EOFError debuglevel = debuglevel+1
self.process_rawq() del sys.argv[1]
host = 'localhost'
# Transfer from raw queue to cooked queue if sys.argv[1:]:
def process_rawq(self): host = sys.argv[1]
# There is some silliness going on here in an attempt port = 0
# to avoid quadratic behavior with large inputs... if sys.argv[2:]:
buf = '' portstr = sys.argv[2]
while self.rawq: try:
c = self.rawq_getchar() port = string.atoi(portstr)
if c != IAC: except string.atoi_error:
buf = buf + c port = socket.getservbyname(portstr, 'tcp')
if len(buf) >= 44: tn = Telnet(host, port)
## self.msg('transfer: %s' % `buf`) tn.set_debuglevel(debuglevel)
self.cookedq = self.cookedq + buf while 1:
buf = '' rfd, wfd, xfd = select.select([tn, sys.stdin], [], [])
continue if sys.stdin in rfd:
c = self.rawq_getchar() line = sys.stdin.readline()
if c == IAC: tn.write(line)
buf = buf + c if tn in rfd:
elif c in (DO, DONT): try:
opt = self.rawq_getchar() text = tn.read_eager()
self.msg('IAC %s %d', except EOFError:
c == DO and 'DO' or 'DONT', print '*** Connection closed by remote host ***'
ord(c)) break
self.sock.send(IAC + WONT + opt) if text:
elif c in (WILL, WONT): sys.stdout.write(text)
opt = self.rawq_getchar() sys.stdout.flush()
self.msg('IAC %s %d', tn.close()
c == WILL and 'WILL' or 'WONT',
ord(c))
else:
self.msg('IAC %s not recognized' % `c`)
## self.msg('transfer: %s' % `buf`)
self.cookedq = self.cookedq + buf
# Get next char from raw queue, blocking if necessary
def rawq_getchar(self):
if not self.rawq:
self.fill_rawq()
if self.irawq >= len(self.rawq):
raise EOFError
c = self.rawq[self.irawq]
self.irawq = self.irawq + 1
if self.irawq >= len(self.rawq):
self.rawq = ''
self.irawq = 0
return c
# Fill raw queue
def fill_rawq(self):
if self.irawq >= len(self.rawq):
self.rawq = ''
self.irawq = 0
buf = self.sock.recv(50)
## self.msg('fill_rawq(): %s' % `buf`)
self.rawq = self.rawq + buf
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment