Commit 643c3d6f authored by Jeremy Hylton's avatar Jeremy Hylton

Fix for SF bug 579107.

The recent SSL changes resulted in important, but subtle changes to
close() semantics.  Since builtin socket makefile() is not called for
SSL connections, we don't get separately closeable fds for connection
and response.  Comments in the code explain how to restore makefile
semantics.

Bug fix candidate.
parent 5d941bc9
...@@ -754,13 +754,59 @@ class HTTPConnection: ...@@ -754,13 +754,59 @@ class HTTPConnection:
return response return response
class SSLFile: # The next several classes are used to define FakeSocket,a socket-like
# interface to an SSL connection.
# The primary complexity comes from faking a makefile() method. The
# standard socket makefile() implementation calls dup() on the socket
# file descriptor. As a consequence, clients can call close() on the
# parent socket and its makefile children in any order. The underlying
# socket isn't closed until they are all closed.
# The implementation uses reference counting to keep the socket open
# until the last client calls close(). SharedSocket keeps track of
# the reference counting and SharedSocketClient provides an constructor
# and close() method that call incref() and decref() correctly.
class SharedSocket:
def __init__(self, sock):
self.sock = sock
self._refcnt = 0
def incref(self):
self._refcnt += 1
def decref(self):
self._refcnt -= 1
assert self._refcnt >= 0
if self._refcnt == 0:
self.sock.close()
def __del__(self):
self.sock.close()
class SharedSocketClient:
def __init__(self, shared):
self._closed = 0
self._shared = shared
self._shared.incref()
self._sock = shared.sock
def close(self):
if not self._closed:
self._shared.decref()
self._closed = 1
self._shared = None
class SSLFile(SharedSocketClient):
"""File-like object wrapping an SSL socket.""" """File-like object wrapping an SSL socket."""
BUFSIZE = 8192 BUFSIZE = 8192
def __init__(self, sock, ssl, bufsize=None): def __init__(self, sock, ssl, bufsize=None):
self._sock = sock SharedSocketClient.__init__(self, sock)
self._ssl = ssl self._ssl = ssl
self._buf = '' self._buf = ''
self._bufsize = bufsize or self.__class__.BUFSIZE self._bufsize = bufsize or self.__class__.BUFSIZE
...@@ -829,30 +875,36 @@ class SSLFile: ...@@ -829,30 +875,36 @@ class SSLFile:
self._buf = all[i:] self._buf = all[i:]
return line return line
def close(self): class FakeSocket(SharedSocketClient):
self._sock.close()
class _closedsocket:
def __getattr__(self, name):
raise error(9, 'Bad file descriptor')
class FakeSocket:
def __init__(self, sock, ssl): def __init__(self, sock, ssl):
self.__sock = sock sock = SharedSocket(sock)
self.__ssl = ssl SharedSocketClient.__init__(self, sock)
self._ssl = ssl
def close(self):
SharedSocketClient.close(self)
self._sock = self.__class__._closedsocket()
def makefile(self, mode, bufsize=None): def makefile(self, mode, bufsize=None):
if mode != 'r' and mode != 'rb': if mode != 'r' and mode != 'rb':
raise UnimplementedFileMode() raise UnimplementedFileMode()
return SSLFile(self.__sock, self.__ssl, bufsize) return SSLFile(self._shared, self._ssl, bufsize)
def send(self, stuff, flags = 0): def send(self, stuff, flags = 0):
return self.__ssl.write(stuff) return self._ssl.write(stuff)
def sendall(self, stuff, flags = 0): sendall = send
return self.__ssl.write(stuff)
def recv(self, len = 1024, flags = 0): def recv(self, len = 1024, flags = 0):
return self.__ssl.read(len) return self._ssl.read(len)
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.__sock, attr) return getattr(self._sock, attr)
class HTTPSConnection(HTTPConnection): class HTTPSConnection(HTTPConnection):
...@@ -1101,15 +1153,11 @@ class LineAndFileWrapper: ...@@ -1101,15 +1153,11 @@ class LineAndFileWrapper:
else: else:
return L + self._file.readlines(size) return L + self._file.readlines(size)
#
# snarfed from httplib.py for now...
#
def test(): def test():
"""Test this module. """Test this module.
The test consists of retrieving and displaying the Python A hodge podge of tests collected here, because they have too many
home page, along with the error code and error string returned external dependencies for the regular test suite.
by the www.python.org server.
""" """
import sys import sys
...@@ -1130,11 +1178,11 @@ def test(): ...@@ -1130,11 +1178,11 @@ def test():
status, reason, headers = h.getreply() status, reason, headers = h.getreply()
print 'status =', status print 'status =', status
print 'reason =', reason print 'reason =', reason
print "read", len(h.getfile().read())
print print
if headers: if headers:
for header in headers.headers: print header.strip() for header in headers.headers: print header.strip()
print print
print "read", len(h.getfile().read())
# minimal test that code to extract host from url works # minimal test that code to extract host from url works
class HTTP11(HTTP): class HTTP11(HTTP):
...@@ -1148,22 +1196,26 @@ def test(): ...@@ -1148,22 +1196,26 @@ def test():
h.close() h.close()
if hasattr(socket, 'ssl'): if hasattr(socket, 'ssl'):
host = 'sourceforge.net'
selector = '/projects/python' for host, selector in (('sourceforge.net', '/projects/python'),
('dbserv2.theopalgroup.com', '/mediumfile'),
('dbserv2.theopalgroup.com', '/smallfile'),
):
print "https://%s%s" % (host, selector)
hs = HTTPS() hs = HTTPS()
hs.connect(host) hs.connect(host)
hs.putrequest('GET', selector) hs.putrequest('GET', selector)
hs.endheaders() hs.endheaders()
status, reason, headers = hs.getreply() status, reason, headers = hs.getreply()
# XXX why does this give a 302 response?
print 'status =', status print 'status =', status
print 'reason =', reason print 'reason =', reason
print "read", len(hs.getfile().read())
print print
if headers: if headers:
for header in headers.headers: print header.strip() for header in headers.headers: print header.strip()
print print
print "read", len(hs.getfile().read())
return
# Test a buggy server -- returns garbled status line. # Test a buggy server -- returns garbled status line.
# http://www.yahoo.com/promotions/mom_com97/supermom.html # http://www.yahoo.com/promotions/mom_com97/supermom.html
......
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