summaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Lib/http
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/tools/python3/src/Lib/http')
-rw-r--r--contrib/tools/python3/src/Lib/http/__init__.py20
-rw-r--r--contrib/tools/python3/src/Lib/http/client.py140
-rw-r--r--contrib/tools/python3/src/Lib/http/cookiejar.py19
-rw-r--r--contrib/tools/python3/src/Lib/http/server.py3
4 files changed, 100 insertions, 82 deletions
diff --git a/contrib/tools/python3/src/Lib/http/__init__.py b/contrib/tools/python3/src/Lib/http/__init__.py
index cd2885dc775..e093a1fec4d 100644
--- a/contrib/tools/python3/src/Lib/http/__init__.py
+++ b/contrib/tools/python3/src/Lib/http/__init__.py
@@ -31,6 +31,26 @@ class HTTPStatus:
obj.description = description
return obj
+ @property
+ def is_informational(self):
+ return 100 <= self <= 199
+
+ @property
+ def is_success(self):
+ return 200 <= self <= 299
+
+ @property
+ def is_redirection(self):
+ return 300 <= self <= 399
+
+ @property
+ def is_client_error(self):
+ return 400 <= self <= 499
+
+ @property
+ def is_server_error(self):
+ return 500 <= self <= 599
+
# informational
CONTINUE = 100, 'Continue', 'Request received, please continue'
SWITCHING_PROTOCOLS = (101, 'Switching Protocols',
diff --git a/contrib/tools/python3/src/Lib/http/client.py b/contrib/tools/python3/src/Lib/http/client.py
index 1ee22989ae1..5eebfccafbc 100644
--- a/contrib/tools/python3/src/Lib/http/client.py
+++ b/contrib/tools/python3/src/Lib/http/client.py
@@ -228,8 +228,9 @@ def _read_headers(fp):
break
return headers
-def parse_headers(fp, _class=HTTPMessage):
- """Parses only RFC2822 headers from a file pointer.
+def _parse_header_lines(header_lines, _class=HTTPMessage):
+ """
+ Parses only RFC2822 headers from header lines.
email Parser wants to see strings rather than bytes.
But a TextIOWrapper around self.rfile would buffer too many bytes
@@ -238,10 +239,15 @@ def parse_headers(fp, _class=HTTPMessage):
to parse.
"""
- headers = _read_headers(fp)
- hstring = b''.join(headers).decode('iso-8859-1')
+ hstring = b''.join(header_lines).decode('iso-8859-1')
return email.parser.Parser(_class=_class).parsestr(hstring)
+def parse_headers(fp, _class=HTTPMessage):
+ """Parses only RFC2822 headers from a file pointer."""
+
+ headers = _read_headers(fp)
+ return _parse_header_lines(headers, _class)
+
class HTTPResponse(io.BufferedIOBase):
@@ -586,11 +592,7 @@ class HTTPResponse(io.BufferedIOBase):
assert self.chunked != _UNKNOWN
value = []
try:
- while True:
- chunk_left = self._get_chunk_left()
- if chunk_left is None:
- break
-
+ while (chunk_left := self._get_chunk_left()) is not None:
if amt is not None and amt <= chunk_left:
value.append(self._safe_read(amt))
self.chunk_left = chunk_left - amt
@@ -798,6 +800,20 @@ class HTTPResponse(io.BufferedIOBase):
'''
return self.status
+
+def _create_https_context(http_version):
+ # Function also used by urllib.request to be able to set the check_hostname
+ # attribute on a context object.
+ context = ssl._create_default_https_context()
+ # send ALPN extension to indicate HTTP/1.1 protocol
+ if http_version == 11:
+ context.set_alpn_protocols(['http/1.1'])
+ # enable PHA for TLS 1.3 connections if available
+ if context.post_handshake_auth is not None:
+ context.post_handshake_auth = True
+ return context
+
+
class HTTPConnection:
_http_vsn = 11
@@ -859,6 +875,7 @@ class HTTPConnection:
self._tunnel_host = None
self._tunnel_port = None
self._tunnel_headers = {}
+ self._raw_proxy_headers = None
(self.host, self.port) = self._get_hostport(host, port)
@@ -871,9 +888,9 @@ class HTTPConnection:
def set_tunnel(self, host, port=None, headers=None):
"""Set up host and port for HTTP CONNECT tunnelling.
- In a connection that uses HTTP CONNECT tunneling, the host passed to the
- constructor is used as a proxy server that relays all communication to
- the endpoint passed to `set_tunnel`. This done by sending an HTTP
+ In a connection that uses HTTP CONNECT tunnelling, the host passed to
+ the constructor is used as a proxy server that relays all communication
+ to the endpoint passed to `set_tunnel`. This done by sending an HTTP
CONNECT request to the proxy server when the connection is established.
This method must be called before the HTTP connection has been
@@ -881,6 +898,13 @@ class HTTPConnection:
The headers argument should be a mapping of extra HTTP headers to send
with the CONNECT request.
+
+ As HTTP/1.1 is used for HTTP CONNECT tunnelling request, as per the RFC
+ (https://tools.ietf.org/html/rfc7231#section-4.3.6), a HTTP Host:
+ header must be provided, matching the authority-form of the request
+ target provided as the destination for the CONNECT request. If a
+ HTTP Host: header is not provided via the headers argument, one
+ is generated and transmitted automatically.
"""
if self.sock:
@@ -888,10 +912,15 @@ class HTTPConnection:
self._tunnel_host, self._tunnel_port = self._get_hostport(host, port)
if headers:
- self._tunnel_headers = headers
+ self._tunnel_headers = headers.copy()
else:
self._tunnel_headers.clear()
+ if not any(header.lower() == "host" for header in self._tunnel_headers):
+ encoded_host = self._tunnel_host.encode("idna").decode("ascii")
+ self._tunnel_headers["Host"] = "%s:%d" % (
+ encoded_host, self._tunnel_port)
+
def _get_hostport(self, host, port):
if port is None:
i = host.rfind(':')
@@ -916,8 +945,9 @@ class HTTPConnection:
self.debuglevel = level
def _tunnel(self):
- connect = b"CONNECT %s:%d HTTP/1.0\r\n" % (
- self._tunnel_host.encode("ascii"), self._tunnel_port)
+ connect = b"CONNECT %s:%d %s\r\n" % (
+ self._tunnel_host.encode("idna"), self._tunnel_port,
+ self._http_vsn_str.encode("ascii"))
headers = [connect]
for header, value in self._tunnel_headers.items():
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
@@ -932,24 +962,33 @@ class HTTPConnection:
try:
(version, code, message) = response._read_status()
+ self._raw_proxy_headers = _read_headers(response.fp)
+
+ if self.debuglevel > 0:
+ for header in self._raw_proxy_headers:
+ print('header:', header.decode())
+
if code != http.HTTPStatus.OK:
self.close()
raise OSError(f"Tunnel connection failed: {code} {message.strip()}")
- while True:
- line = response.fp.readline(_MAXLINE + 1)
- if len(line) > _MAXLINE:
- raise LineTooLong("header line")
- if not line:
- # for sites which EOF without sending a trailer
- break
- if line in (b'\r\n', b'\n', b''):
- break
- if self.debuglevel > 0:
- print('header:', line.decode())
finally:
response.close()
+ def get_proxy_response_headers(self):
+ """
+ Returns a dictionary with the headers of the response
+ received from the proxy server to the CONNECT request
+ sent to set the tunnel.
+
+ If the CONNECT request was not sent, the method returns None.
+ """
+ return (
+ _parse_header_lines(self._raw_proxy_headers)
+ if self._raw_proxy_headers is not None
+ else None
+ )
+
def connect(self):
"""Connect to the host and port specified in __init__."""
sys.audit("http.client.connect", self, self.host, self.port)
@@ -999,10 +1038,7 @@ class HTTPConnection:
encode = self._is_textIO(data)
if encode and self.debuglevel > 0:
print("encoding file using iso-8859-1")
- while 1:
- datablock = data.read(self.blocksize)
- if not datablock:
- break
+ while datablock := data.read(self.blocksize):
if encode:
datablock = datablock.encode("iso-8859-1")
sys.audit("http.client.send", self, datablock)
@@ -1032,10 +1068,7 @@ class HTTPConnection:
encode = self._is_textIO(readable)
if encode and self.debuglevel > 0:
print("encoding file using iso-8859-1")
- while True:
- datablock = readable.read(self.blocksize)
- if not datablock:
- break
+ while datablock := readable.read(self.blocksize):
if encode:
datablock = datablock.encode("iso-8859-1")
yield datablock
@@ -1416,46 +1449,15 @@ else:
default_port = HTTPS_PORT
- # 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,
- timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None, *, context=None,
- check_hostname=None, blocksize=8192):
+ def __init__(self, host, port=None,
+ *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None, context=None, blocksize=8192):
super(HTTPSConnection, self).__init__(host, port, timeout,
source_address,
blocksize=blocksize)
- if (key_file is not None or cert_file is not None or
- check_hostname is not None):
- import warnings
- warnings.warn("key_file, cert_file and check_hostname are "
- "deprecated, use a custom context instead.",
- DeprecationWarning, 2)
- self.key_file = key_file
- self.cert_file = cert_file
if context is None:
- context = ssl._create_default_https_context()
- # send ALPN extension to indicate HTTP/1.1 protocol
- if self._http_vsn == 11:
- context.set_alpn_protocols(['http/1.1'])
- # enable PHA for TLS 1.3 connections if available
- if context.post_handshake_auth is not None:
- context.post_handshake_auth = True
- will_verify = context.verify_mode != ssl.CERT_NONE
- if check_hostname is None:
- check_hostname = context.check_hostname
- if check_hostname and not will_verify:
- raise ValueError("check_hostname needs a SSL context with "
- "either CERT_OPTIONAL or CERT_REQUIRED")
- if key_file or cert_file:
- context.load_cert_chain(cert_file, key_file)
- # cert and key file means the user wants to authenticate.
- # enable TLS 1.3 PHA implicitly even for custom contexts.
- if context.post_handshake_auth is not None:
- context.post_handshake_auth = True
+ context = _create_https_context(self._http_vsn)
self._context = context
- if check_hostname is not None:
- self._context.check_hostname = check_hostname
def connect(self):
"Connect to a host on a given (SSL) port."
diff --git a/contrib/tools/python3/src/Lib/http/cookiejar.py b/contrib/tools/python3/src/Lib/http/cookiejar.py
index e622fc36cbf..bd89370e168 100644
--- a/contrib/tools/python3/src/Lib/http/cookiejar.py
+++ b/contrib/tools/python3/src/Lib/http/cookiejar.py
@@ -104,9 +104,9 @@ def time2isoz(t=None):
"""
if t is None:
- dt = datetime.datetime.utcnow()
+ dt = datetime.datetime.now(tz=datetime.UTC)
else:
- dt = datetime.datetime.utcfromtimestamp(t)
+ dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC)
return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
@@ -122,9 +122,9 @@ def time2netscape(t=None):
"""
if t is None:
- dt = datetime.datetime.utcnow()
+ dt = datetime.datetime.now(tz=datetime.UTC)
else:
- dt = datetime.datetime.utcfromtimestamp(t)
+ dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC)
return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % (
DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1],
dt.year, dt.hour, dt.minute, dt.second)
@@ -640,7 +640,7 @@ def eff_request_host(request):
"""
erhn = req_host = request_host(request)
- if req_host.find(".") == -1 and not IPV4_RE.search(req_host):
+ if "." not in req_host:
erhn = req_host + ".local"
return req_host, erhn
@@ -1918,9 +1918,7 @@ class LWPCookieJar(FileCookieJar):
"comment", "commenturl")
try:
- while 1:
- line = f.readline()
- if line == "": break
+ while (line := f.readline()) != "":
if not line.startswith(header):
continue
line = line[len(header):].strip()
@@ -2020,12 +2018,9 @@ class MozillaCookieJar(FileCookieJar):
filename)
try:
- while 1:
- line = f.readline()
+ while (line := f.readline()) != "":
rest = {}
- if line == "": break
-
# httponly is a cookie flag as defined in rfc6265
# when encoded in a netscape cookie file,
# the line is prepended with "#HttpOnly_"
diff --git a/contrib/tools/python3/src/Lib/http/server.py b/contrib/tools/python3/src/Lib/http/server.py
index da07f110f17..ca6240d9a92 100644
--- a/contrib/tools/python3/src/Lib/http/server.py
+++ b/contrib/tools/python3/src/Lib/http/server.py
@@ -657,6 +657,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
"""
server_version = "SimpleHTTP/" + __version__
+ index_pages = ("index.html", "index.htm")
extensions_map = _encodings_map_default = {
'.gz': 'application/gzip',
'.Z': 'application/octet-stream',
@@ -710,7 +711,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
self.send_header("Content-Length", "0")
self.end_headers()
return None
- for index in "index.html", "index.htm":
+ for index in self.index_pages:
index = os.path.join(path, index)
if os.path.isfile(index):
path = index