summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin v. Löwis <martin@v.loewis.de>2002-11-04 09:56:00 (GMT)
committerMartin v. Löwis <martin@v.loewis.de>2002-11-04 09:56:00 (GMT)
commit1da9c57c740544fa0d2bc93949aa577b6184c8d2 (patch)
tree9e918767d7aae036025ba9636bfeb54b8f2d3add
parenta178cff979b37771b3da710c23d579cd4abf0f00 (diff)
downloadcpython-1da9c57c740544fa0d2bc93949aa577b6184c8d2.zip
cpython-1da9c57c740544fa0d2bc93949aa577b6184c8d2.tar.gz
cpython-1da9c57c740544fa0d2bc93949aa577b6184c8d2.tar.bz2
Patch #630829: Don't block on IAC, process suboptions.
-rw-r--r--Doc/lib/libtelnetlib.tex8
-rw-r--r--Lib/telnetlib.py113
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS3
4 files changed, 93 insertions, 32 deletions
diff --git a/Doc/lib/libtelnetlib.tex b/Doc/lib/libtelnetlib.tex
index fe56f72..83f6b43 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 4c806db..fe0672a 100644
--- a/Lib/telnetlib.py
+++ b/Lib/telnetlib.py
@@ -25,9 +25,6 @@ EOFError is needed in some cases 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
- timeout should be intrinsic to the connection object instead of an
@@ -56,6 +53,8 @@ DO = chr(253)
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 @@ PRAGMA_LOGON = chr(138) # TELOPT PRAGMA LOGON
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 @@ class Telnet:
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 @@ class Telnet:
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 @@ class Telnet:
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 @@ class Telnet:
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 901bb45..4b496f2 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 71eacc8..c8334d7 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