mirror of
https://github.com/python/cpython.git
synced 2025-11-25 21:11:09 +00:00
Issue #10711: Remove HTTP 0.9 support from http.client. The strict
parameter to HTTPConnection and friends is deprecated.
This commit is contained in:
parent
7cb3051dc3
commit
988dbd7bc2
4 changed files with 63 additions and 120 deletions
|
|
@ -23,16 +23,13 @@ HTTPS protocols. It is normally not used directly --- the module
|
|||
The module provides the following classes:
|
||||
|
||||
|
||||
.. class:: HTTPConnection(host, port=None, strict=None[, timeout[, source_address]])
|
||||
.. class:: HTTPConnection(host, port=None[, strict[, timeout[, source_address]]])
|
||||
|
||||
An :class:`HTTPConnection` instance represents one transaction with an HTTP
|
||||
server. It should be instantiated passing it a host and optional port
|
||||
number. If no port number is passed, the port is extracted from the host
|
||||
string if it has the form ``host:port``, else the default HTTP port (80) is
|
||||
used. When True, the optional parameter *strict* (which defaults to a false
|
||||
value) causes ``BadStatusLine`` to
|
||||
be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1
|
||||
status line. If the optional *timeout* parameter is given, blocking
|
||||
used. If the optional *timeout* parameter is given, blocking
|
||||
operations (like connection attempts) will timeout after that many seconds
|
||||
(if it is not given, the global default timeout setting is used).
|
||||
The optional *source_address* parameter may be a typle of a (host, port)
|
||||
|
|
@ -49,8 +46,12 @@ The module provides the following classes:
|
|||
.. versionchanged:: 3.2
|
||||
*source_address* was added.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
The *strict* parameter is deprecated. HTTP 0.9-style "Simple Responses"
|
||||
are not supported anymore.
|
||||
|
||||
.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None, strict=None[, timeout[, source_address]], *, context=None, check_hostname=None)
|
||||
|
||||
.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None[, strict[, timeout[, source_address]]], *, context=None, check_hostname=None)
|
||||
|
||||
A subclass of :class:`HTTPConnection` that uses SSL for communication with
|
||||
secure servers. Default port is ``443``. If *context* is specified, it
|
||||
|
|
@ -80,12 +81,20 @@ The module provides the following classes:
|
|||
This class now supports HTTPS virtual hosts if possible (that is,
|
||||
if :data:`ssl.HAS_SNI` is true).
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
The *strict* parameter is deprecated. HTTP 0.9-style "Simple Responses"
|
||||
are not supported anymore.
|
||||
|
||||
.. class:: HTTPResponse(sock, debuglevel=0, strict=0, method=None, url=None)
|
||||
|
||||
.. class:: HTTPResponse(sock, debuglevel=0[, strict], method=None, url=None)
|
||||
|
||||
Class whose instances are returned upon successful connection. Not
|
||||
instantiated directly by user.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
The *strict* parameter is deprecated. HTTP 0.9-style "Simple Responses"
|
||||
are not supported anymore.
|
||||
|
||||
|
||||
The following exceptions are raised as appropriate:
|
||||
|
||||
|
|
|
|||
|
|
@ -252,13 +252,10 @@ def parse_headers(fp, _class=HTTPMessage):
|
|||
hstring = b''.join(headers).decode('iso-8859-1')
|
||||
return email.parser.Parser(_class=_class).parsestr(hstring)
|
||||
|
||||
class HTTPResponse(io.RawIOBase):
|
||||
|
||||
# strict: If true, raise BadStatusLine if the status line can't be
|
||||
# parsed as a valid HTTP/1.0 or 1.1 status line. By default it is
|
||||
# false because it prevents clients from talking to HTTP/0.9
|
||||
# servers. Note that a response with a sufficiently corrupted
|
||||
# status line will look like an HTTP/0.9 response.
|
||||
_strict_sentinel = object()
|
||||
|
||||
class HTTPResponse(io.RawIOBase):
|
||||
|
||||
# See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
|
||||
|
||||
|
|
@ -267,7 +264,7 @@ class HTTPResponse(io.RawIOBase):
|
|||
# text following RFC 2047. The basic status line parsing only
|
||||
# accepts iso-8859-1.
|
||||
|
||||
def __init__(self, sock, debuglevel=0, strict=0, method=None, url=None):
|
||||
def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None):
|
||||
# If the response includes a content-length header, we need to
|
||||
# make sure that the client doesn't read more than the
|
||||
# specified number of bytes. If it does, it will block until
|
||||
|
|
@ -277,7 +274,10 @@ class HTTPResponse(io.RawIOBase):
|
|||
# clients unless they know what they are doing.
|
||||
self.fp = sock.makefile("rb")
|
||||
self.debuglevel = debuglevel
|
||||
self.strict = strict
|
||||
if strict is not _strict_sentinel:
|
||||
warnings.warn("the 'strict' argument isn't supported anymore; "
|
||||
"http.client now always assumes HTTP/1.x compliant servers.",
|
||||
DeprecationWarning, 2)
|
||||
self._method = method
|
||||
|
||||
# The HTTPResponse object is returned via urllib. The clients
|
||||
|
|
@ -299,7 +299,6 @@ class HTTPResponse(io.RawIOBase):
|
|||
self.will_close = _UNKNOWN # conn will close at end of response
|
||||
|
||||
def _read_status(self):
|
||||
# Initialize with Simple-Response defaults.
|
||||
line = str(self.fp.readline(), "iso-8859-1")
|
||||
if self.debuglevel > 0:
|
||||
print("reply:", repr(line))
|
||||
|
|
@ -308,25 +307,17 @@ class HTTPResponse(io.RawIOBase):
|
|||
# sending a valid response.
|
||||
raise BadStatusLine(line)
|
||||
try:
|
||||
[version, status, reason] = line.split(None, 2)
|
||||
version, status, reason = line.split(None, 2)
|
||||
except ValueError:
|
||||
try:
|
||||
[version, status] = line.split(None, 1)
|
||||
version, status = line.split(None, 1)
|
||||
reason = ""
|
||||
except ValueError:
|
||||
# empty version will cause next test to fail and status
|
||||
# will be treated as 0.9 response.
|
||||
# empty version will cause next test to fail.
|
||||
version = ""
|
||||
if not version.startswith("HTTP/"):
|
||||
if self.strict:
|
||||
self.close()
|
||||
raise BadStatusLine(line)
|
||||
else:
|
||||
# Assume it's a Simple-Response from an 0.9 server.
|
||||
# We have to convert the first line back to raw bytes
|
||||
# because self.fp.readline() needs to return bytes.
|
||||
self.fp = LineAndFileWrapper(bytes(line, "ascii"), self.fp)
|
||||
return "HTTP/0.9", 200, ""
|
||||
self.close()
|
||||
raise BadStatusLine(line)
|
||||
|
||||
# The status code is a three-digit number
|
||||
try:
|
||||
|
|
@ -357,22 +348,14 @@ class HTTPResponse(io.RawIOBase):
|
|||
|
||||
self.code = self.status = status
|
||||
self.reason = reason.strip()
|
||||
if version == "HTTP/1.0":
|
||||
if version in ("HTTP/1.0", "HTTP/0.9"):
|
||||
# Some servers might still return "0.9", treat it as 1.0 anyway
|
||||
self.version = 10
|
||||
elif version.startswith("HTTP/1."):
|
||||
self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
|
||||
elif version == "HTTP/0.9":
|
||||
self.version = 9
|
||||
else:
|
||||
raise UnknownProtocol(version)
|
||||
|
||||
if self.version == 9:
|
||||
self.length = None
|
||||
self.chunked = False
|
||||
self.will_close = True
|
||||
self.headers = self.msg = email.message_from_string('')
|
||||
return
|
||||
|
||||
self.headers = self.msg = parse_headers(self.fp)
|
||||
|
||||
if self.debuglevel > 0:
|
||||
|
|
@ -639,10 +622,13 @@ class HTTPConnection:
|
|||
default_port = HTTP_PORT
|
||||
auto_open = 1
|
||||
debuglevel = 0
|
||||
strict = 0
|
||||
|
||||
def __init__(self, host, port=None, strict=None,
|
||||
def __init__(self, host, port=None, strict=_strict_sentinel,
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
|
||||
if strict is not _strict_sentinel:
|
||||
warnings.warn("the 'strict' argument isn't supported anymore; "
|
||||
"http.client now always assumes HTTP/1.x compliant servers.",
|
||||
DeprecationWarning, 2)
|
||||
self.timeout = timeout
|
||||
self.source_address = source_address
|
||||
self.sock = None
|
||||
|
|
@ -654,8 +640,6 @@ class HTTPConnection:
|
|||
self._tunnel_port = None
|
||||
|
||||
self._set_hostport(host, port)
|
||||
if strict is not None:
|
||||
self.strict = strict
|
||||
|
||||
def set_tunnel(self, host, port=None, headers=None):
|
||||
""" Sets up the host and the port for the HTTP CONNECT Tunnelling.
|
||||
|
|
@ -700,8 +684,7 @@ class HTTPConnection:
|
|||
header_bytes = header_str.encode("ascii")
|
||||
self.send(header_bytes)
|
||||
|
||||
response = self.response_class(self.sock, strict = self.strict,
|
||||
method = self._method)
|
||||
response = self.response_class(self.sock, method = self._method)
|
||||
(version, code, message) = response._read_status()
|
||||
|
||||
if code != 200:
|
||||
|
|
@ -1025,11 +1008,9 @@ class HTTPConnection:
|
|||
|
||||
if self.debuglevel > 0:
|
||||
response = self.response_class(self.sock, self.debuglevel,
|
||||
strict=self.strict,
|
||||
method=self._method)
|
||||
else:
|
||||
response = self.response_class(self.sock, strict=self.strict,
|
||||
method=self._method)
|
||||
response = self.response_class(self.sock, method=self._method)
|
||||
|
||||
response.begin()
|
||||
assert response.will_close != _UNKNOWN
|
||||
|
|
@ -1057,7 +1038,7 @@ else:
|
|||
# XXX Should key_file and cert_file be deprecated in favour of context?
|
||||
|
||||
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
||||
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
strict=_strict_sentinel, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None, *, context=None, check_hostname=None):
|
||||
super(HTTPSConnection, self).__init__(host, port, strict, timeout,
|
||||
source_address)
|
||||
|
|
@ -1158,71 +1139,3 @@ class BadStatusLine(HTTPException):
|
|||
|
||||
# for backwards compatibility
|
||||
error = HTTPException
|
||||
|
||||
class LineAndFileWrapper:
|
||||
"""A limited file-like object for HTTP/0.9 responses."""
|
||||
|
||||
# The status-line parsing code calls readline(), which normally
|
||||
# get the HTTP status line. For a 0.9 response, however, this is
|
||||
# actually the first line of the body! Clients need to get a
|
||||
# readable file object that contains that line.
|
||||
|
||||
def __init__(self, line, file):
|
||||
self._line = line
|
||||
self._file = file
|
||||
self._line_consumed = 0
|
||||
self._line_offset = 0
|
||||
self._line_left = len(line)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self._file, attr)
|
||||
|
||||
def _done(self):
|
||||
# called when the last byte is read from the line. After the
|
||||
# call, all read methods are delegated to the underlying file
|
||||
# object.
|
||||
self._line_consumed = 1
|
||||
self.read = self._file.read
|
||||
self.readline = self._file.readline
|
||||
self.readlines = self._file.readlines
|
||||
|
||||
def read(self, amt=None):
|
||||
if self._line_consumed:
|
||||
return self._file.read(amt)
|
||||
assert self._line_left
|
||||
if amt is None or amt > self._line_left:
|
||||
s = self._line[self._line_offset:]
|
||||
self._done()
|
||||
if amt is None:
|
||||
return s + self._file.read()
|
||||
else:
|
||||
return s + self._file.read(amt - len(s))
|
||||
else:
|
||||
assert amt <= self._line_left
|
||||
i = self._line_offset
|
||||
j = i + amt
|
||||
s = self._line[i:j]
|
||||
self._line_offset = j
|
||||
self._line_left -= amt
|
||||
if self._line_left == 0:
|
||||
self._done()
|
||||
return s
|
||||
|
||||
def readline(self):
|
||||
if self._line_consumed:
|
||||
return self._file.readline()
|
||||
assert self._line_left
|
||||
s = self._line[self._line_offset:]
|
||||
self._done()
|
||||
return s
|
||||
|
||||
def readlines(self, size=None):
|
||||
if self._line_consumed:
|
||||
return self._file.readlines(size)
|
||||
assert self._line_left
|
||||
L = [self._line[self._line_offset:]]
|
||||
self._done()
|
||||
if size is None:
|
||||
return L + self._file.readlines()
|
||||
else:
|
||||
return L + self._file.readlines(size)
|
||||
|
|
|
|||
|
|
@ -139,8 +139,10 @@ class urlopen_HttpTests(unittest.TestCase):
|
|||
|
||||
def fakehttp(self, fakedata):
|
||||
class FakeSocket(io.BytesIO):
|
||||
io_refs = 1
|
||||
def sendall(self, str): pass
|
||||
def makefile(self, *args, **kwds):
|
||||
self.io_refs += 1
|
||||
return self
|
||||
def read(self, amt=None):
|
||||
if self.closed: return b""
|
||||
|
|
@ -148,6 +150,10 @@ class urlopen_HttpTests(unittest.TestCase):
|
|||
def readline(self, length=None):
|
||||
if self.closed: return b""
|
||||
return io.BytesIO.readline(self, length)
|
||||
def close(self):
|
||||
self.io_refs -= 1
|
||||
if self.io_refs == 0:
|
||||
io.BytesIO.close(self)
|
||||
class FakeHTTPConnection(http.client.HTTPConnection):
|
||||
def connect(self):
|
||||
self.sock = FakeSocket(fakedata)
|
||||
|
|
@ -157,8 +163,8 @@ class urlopen_HttpTests(unittest.TestCase):
|
|||
def unfakehttp(self):
|
||||
http.client.HTTPConnection = self._connection_class
|
||||
|
||||
def test_read(self):
|
||||
self.fakehttp(b"Hello!")
|
||||
def check_read(self, ver):
|
||||
self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!")
|
||||
try:
|
||||
fp = urlopen("http://python.org/")
|
||||
self.assertEqual(fp.readline(), b"Hello!")
|
||||
|
|
@ -168,6 +174,17 @@ class urlopen_HttpTests(unittest.TestCase):
|
|||
finally:
|
||||
self.unfakehttp()
|
||||
|
||||
def test_read_0_9(self):
|
||||
# "0.9" response accepted (but not "simple responses" without
|
||||
# a status line)
|
||||
self.check_read(b"0.9")
|
||||
|
||||
def test_read_1_0(self):
|
||||
self.check_read(b"1.0")
|
||||
|
||||
def test_read_1_1(self):
|
||||
self.check_read(b"1.1")
|
||||
|
||||
def test_read_bogus(self):
|
||||
# urlopen() should raise IOError for many error codes.
|
||||
self.fakehttp(b'''HTTP/1.1 401 Authentication Required
|
||||
|
|
@ -191,7 +208,7 @@ Content-Type: text/html; charset=iso-8859-1
|
|||
self.unfakehttp()
|
||||
|
||||
def test_userpass_inurl(self):
|
||||
self.fakehttp(b"Hello!")
|
||||
self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!")
|
||||
try:
|
||||
fp = urlopen("http://user:pass@python.org/")
|
||||
self.assertEqual(fp.readline(), b"Hello!")
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ Core and Builtins
|
|||
|
||||
Library
|
||||
-------
|
||||
|
||||
- Issue #10711: Remove HTTP 0.9 support from http.client. The ``strict``
|
||||
parameter to HTTPConnection and friends is deprecated.
|
||||
|
||||
- Issue #9721: Fix the behavior of urljoin when the relative url starts with a
|
||||
';' character. Patch by Wes Chow.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue