diff --git a/Doc/lib/libtelnetlib.tex b/Doc/lib/libtelnetlib.tex index fe56f72126c..83f6b43e4dc 100644 --- a/Doc/lib/libtelnetlib.tex +++ b/Doc/lib/libtelnetlib.tex @@ -96,6 +96,14 @@ Return \code{''} if no cooked data available otherwise. This method never blocks. \end{methoddesc} +\begin{methoddesc}{read_sb_data}{} +Return the data collected between a SB/SE pair (suboption begin/end). +The callback should access these data when it was invoked with a +\code{SE} command. This method never blocks. + +\versionadded{2.3} +\end{methoddesc} + \begin{methoddesc}{open}{host\optional{, port}} Connect to a host. The optional second argument is the port number, which diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py index 4c806db22e0..fe0672a9a1a 100644 --- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -25,9 +25,6 @@ "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 - timeout should be intrinsic to the connection object instead of an @@ -56,6 +53,8 @@ WONT = chr(252) WILL = chr(251) theNULL = chr(0) +SB = chr(250) +SE = chr(240) # Telnet protocol options code (don't change) # These ones all come from arpa/telnet.h @@ -117,6 +116,7 @@ SSPI_LOGON = chr(139) # TELOPT SSPI LOGON PRAGMA_HEARTBEAT = chr(140) # TELOPT PRAGMA HEARTBEAT EXOPL = chr(255) # Extended-Options-List +NOOPT = chr(0) class Telnet: @@ -161,10 +161,14 @@ class Telnet: Reads all data in the cooked queue, without doing any socket I/O. + read_sb_data() + Reads available data between SB ... SE sequence. Don't block. + set_option_negotiation_callback(callback) Each time a telnet option is read on the input flow, this callback (if set) is called with the following parameters : - callback(telnet socket, command (DO/DONT/WILL/WONT), option) + callback(telnet socket, command, option) + option will be chr(0) when there is no option. No other action is done afterwards by telnetlib. """ @@ -185,6 +189,9 @@ def __init__(self, host=None, port=0): self.irawq = 0 self.cookedq = '' self.eof = 0 + self.iacseq = '' # Buffer for IAC sequence. + self.sb = 0 # flag for SB and SE sequence. + self.sbdataq = '' self.option_callback = None if host is not None: self.open(host, port) @@ -250,6 +257,8 @@ def close(self): self.sock.close() self.sock = 0 self.eof = 1 + self.iacseq = '' + self.sb = 0 def get_socket(self): """Return the socket object used internally.""" @@ -379,6 +388,18 @@ def read_very_lazy(self): if not buf and self.eof and not self.rawq: raise EOFError, 'telnet connection closed' return buf + + def read_sb_data(self): + """Return any data available in the SB ... SE queue. + + Return '' if no SB ... SE available. Should only be called + after seeing a SB or SE command. When a new SB command is + found, old unread SB data will be discarded. Don't block. + + """ + buf = self.sbdataq + self.sbdataq = '' + return buf def set_option_negotiation_callback(self, callback): """Provide a callback function called after each receipt of a telnet option.""" @@ -391,40 +412,70 @@ def process_rawq(self): the midst of an IAC sequence. """ - buf = '' + buf = ['', ''] try: while self.rawq: c = self.rawq_getchar() - if c == theNULL: - continue - if c == "\021": - continue - 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(opt)) - if self.option_callback: - self.option_callback(self.sock, c, opt) + if not self.iacseq: + if c == theNULL: + continue + if c == "\021": + continue + if c != IAC: + buf[self.sb] = buf[self.sb] + c + continue else: - self.sock.sendall(IAC + WONT + opt) - elif c in (WILL, WONT): - opt = self.rawq_getchar() - self.msg('IAC %s %d', - c == WILL and 'WILL' or 'WONT', ord(opt)) - if self.option_callback: - self.option_callback(self.sock, c, opt) + self.iacseq += c + elif len(self.iacseq) == 1: + 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]' + if c in (DO, DONT, WILL, WONT): + self.iacseq += c + continue + + self.iacseq = '' + if c == IAC: + buf[self.sb] = buf[self.sb] + c else: - self.sock.sendall(IAC + DONT + opt) - else: - self.msg('IAC %d not recognized' % ord(c)) + if c == SB: # SB ... SE start. + self.sb = 1 + self.sbdataq = '' + elif c == SE: + self.sb = 0 + self.sbdataq = self.sbdataq + buf[1] + buf[1] = '' + if self.option_callback: + # Callback is supposed to look into + # the sbdataq + self.option_callback(self.sock, c, NOOPT) + else: + # We can't offer automatic processing of + # suboptions. Alas, we should not get any + # unless we did a WILL/DO before. + self.msg('IAC %d not recognized' % ord(c)) + elif len(self.iacseq) == 2: + cmd = self.iacseq[1] + self.iacseq = '' + opt = c + if cmd in (DO, DONT): + self.msg('IAC %s %d', + cmd == DO and 'DO' or 'DONT', ord(opt)) + if self.option_callback: + self.option_callback(self.sock, cmd, opt) + else: + self.sock.sendall(IAC + WONT + opt) + elif cmd in (WILL, WONT): + self.msg('IAC %s %d', + cmd == WILL and 'WILL' or 'WONT', ord(opt)) + if self.option_callback: + self.option_callback(self.sock, cmd, opt) + else: + self.sock.sendall(IAC + DONT + opt) except EOFError: # raised by self.rawq_getchar() + self.iacseq = '' # Reset on EOF + self.sb = 0 pass - self.cookedq = self.cookedq + buf + self.cookedq = self.cookedq + buf[0] + self.sbdataq = self.sbdataq + buf[1] def rawq_getchar(self): """Get next char from raw queue. diff --git a/Misc/ACKS b/Misc/ACKS index 901bb45970b..4b496f25161 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -465,6 +465,7 @@ Steven Scott Nick Seidenman Fred Sells Denis Severson +Ha Shao Bruce Sherwood Pete Shinners Michael Shiplett diff --git a/Misc/NEWS b/Misc/NEWS index 71eacc84d4c..c8334d70c4e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1707,7 +1707,8 @@ Library ------- - telnetlib includes symbolic names for the options, and support for - setting an option negotiation callback. + setting an option negotiation callback. It also supports processing + of suboptions. - The new C standard no longer requires that math libraries set errno to ERANGE on overflow. For platform libraries that exploit this new