From e36f7356161dfe6dd108b21f379fa3fec76eeb3c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 1 Nov 1993 14:49:37 +0000 Subject: [PATCH] Major rewrite with new read_* interfaces --- Demo/cwilib/telnetlib.py | 426 ++++++++++++++++++++++++++------------- 1 file changed, 283 insertions(+), 143 deletions(-) diff --git a/Demo/cwilib/telnetlib.py b/Demo/cwilib/telnetlib.py index 5c862e78de8..3a48a79b75b 100755 --- a/Demo/cwilib/telnetlib.py +++ b/Demo/cwilib/telnetlib.py @@ -1,13 +1,47 @@ -# 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 select import string import regsub # Tunable parameters -TIMEOUT = 30.0 -DEBUGLEVEL = 1 +DEBUGLEVEL = 0 # Telnet protocol defaults TELNET_PORT = 23 @@ -24,158 +58,264 @@ WILL = chr(251) class Telnet: - # Constructor - def __init__(self, host, port): - self.debuglevel = DEBUGLEVEL - self.host = host - if not port: port = TELNET_PORT - self.port = port - self.timeout = TIMEOUT - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.host, self.port)) - self.rawq = '' - self.irawq = 0 - self.cookedq = '' + # Constructor + def __init__(self, host, *args): + if not args: + port = TELNET_PORT + else: + if len(args) > 1: raise TypeError, 'too many args' + port = args[0] + if not port: port = TELNET_PORT + self.debuglevel = DEBUGLEVEL + self.host = host + self.port = port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + self.rawq = '' + self.irawq = 0 + self.cookedq = '' + self.eof = 0 - # Destructor - def __del__(self): - self.close() + # Destructor + def __del__(self): + self.close() - # Print debug message - def msg(self, msg, *args): - if self.debuglevel > 0: - print 'TELNET:', msg%args + # Debug message + def msg(self, msg, *args): + if self.debuglevel > 0: + print 'Telnet(%s,%d):' % (self.host, self.port), msg % args - # Set debug level - def set_debuglevel(self, debuglevel): - self.debuglevel = debuglevel + # Set debug level + def set_debuglevel(self, debuglevel): + self.debuglevel = debuglevel - # Set time-out on certain reads - def set_timeout(self, timeout): - self.timeout = float(timeout) + # Explicit close + def close(self): + if self.sock: + self.sock.close() + self.sock = None + self.eof = 1 - # Explicit close - def close(self): - if self.sock: - self.sock.close() - self.sock = None + # Return socket (e.g. for select) + def get_socket(self): + return self.sock - # Return socket (e.g. for select) - def get_socket(self): - return self.sock + # Return socket's fileno (e.g. for select) + def fileno(self): + return self.sock.fileno() - # Return socket's fileno (e.g. for select) - def fileno(self): - return self.sock.fileno() + # Write a string to the socket, doubling any IAC characters + # Might block if the connection is blocked + # May raise socket.error if the connection is closed + def write(self, buffer): + if IAC in buffer: + buffer = regsub.gsub(IAC, IAC+IAC, buffer) + self.sock.send(buffer) - # Write a string to the socket, doubling any IAC characters - def write(self, buffer): - if IAC in buffer: - buffer = regsub.gsub(IAC, IAC+IAC, buffer) - self.sock.send(buffer) + # The following read_* methods exist: + # Special case: + # - read_until() reads until a string is encountered or a timeout is hit + # These may block: + # - read_all() reads all data until EOF + # - read_some() reads at least one byte until EOF + # These may do I/O but won't block doing it: + # - read_very_eager() reads all data available on the socket + # - read_eager() reads either data already queued or some data + # available on the socket + # These don't do I/O: + # - read_lazy() reads all data in the raw queue (processing it first) + # - read_very_lazy() reads all data in the cooked queue - # Read until a given string is encountered or until timeout - def read_until(self, match): -## self.msg('read_until(%s)' % `match`) - n = len(match) - self.process_rawq() - i = string.find(self.cookedq, match) - if i < 0: - i = max(0, len(self.cookedq)-n) - self.fill_cookedq() - 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 - while select.select([self], [], [], self.timeout) == \ - ([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`)) + # Read until a given string is encountered or until timeout + # Raise EOFError if connection closed and no cooked data available + # Return '' if no cooked data available otherwise + def read_until(self, match, *args): + if not args: + 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 self.read_very_lazy() - # Read everything that's possible without really blocking - def read_now(self): - self.fill_cookedq() - buf = self.cookedq - self.cookedq = '' -## self.msg('read_now() --> %s' % `buf`) - return buf + # 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 - # Fill cooked queue without blocking - def fill_cookedq(self): - self.process_rawq() - while select.select([self], [], [], 0) == ([self], [], []): - self.fill_rawq() - if not self.rawq: - raise EOFError - self.process_rawq() + # 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 - # Transfer from raw queue to cooked queue - def process_rawq(self): - # There is some silliness going on here in an attempt - # to avoid quadratic behavior with large inputs... - buf = '' - while self.rawq: - c = self.rawq_getchar() - if c != IAC: - buf = buf + c - if len(buf) >= 44: -## self.msg('transfer: %s' % `buf`) - self.cookedq = self.cookedq + buf - buf = '' - 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`) -## self.msg('transfer: %s' % `buf`) - self.cookedq = self.cookedq + 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() - # 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 + # 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() - # 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 + # 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], [], []) + + +# Test program +# Usage: test [-d] ... [host [port]] +def test(): + import sys, string, socket, select + debuglevel = 0 + while sys.argv[1:] and sys.argv[1] == '-d': + debuglevel = debuglevel+1 + del sys.argv[1] + host = 'localhost' + if sys.argv[1:]: + host = sys.argv[1] + port = 0 + if sys.argv[2:]: + portstr = sys.argv[2] + try: + port = string.atoi(portstr) + except string.atoi_error: + port = socket.getservbyname(portstr, 'tcp') + tn = Telnet(host, port) + tn.set_debuglevel(debuglevel) + while 1: + rfd, wfd, xfd = select.select([tn, sys.stdin], [], []) + if sys.stdin in rfd: + line = sys.stdin.readline() + tn.write(line) + if tn in rfd: + try: + text = tn.read_eager() + except EOFError: + print '*** Connection closed by remote host ***' + break + if text: + sys.stdout.write(text) + sys.stdout.flush() + tn.close()