From d4be68e361f4258cf0848fc70018dfe37a2acc24 Mon Sep 17 00:00:00 2001 From: shadchin Date: Mon, 18 Apr 2022 12:39:32 +0300 Subject: IGNIETFERRO-1816 Update Python 3 from 3.9.12 to 3.10.4 ref:9f96be6d02ee8044fdd6f124b799b270c20ce641 --- contrib/tools/python3/src/Lib/http/__init__.py | 1 + contrib/tools/python3/src/Lib/http/client.py | 84 ++++++++++++------------- contrib/tools/python3/src/Lib/http/cookiejar.py | 40 +++++++----- contrib/tools/python3/src/Lib/http/server.py | 5 +- 4 files changed, 71 insertions(+), 59 deletions(-) (limited to 'contrib/tools/python3/src/Lib/http') diff --git a/contrib/tools/python3/src/Lib/http/__init__.py b/contrib/tools/python3/src/Lib/http/__init__.py index 37be765349e..bf8d7d68868 100644 --- a/contrib/tools/python3/src/Lib/http/__init__.py +++ b/contrib/tools/python3/src/Lib/http/__init__.py @@ -2,6 +2,7 @@ from enum import IntEnum __all__ = ['HTTPStatus'] + class HTTPStatus(IntEnum): """HTTP status codes and reason phrases diff --git a/contrib/tools/python3/src/Lib/http/client.py b/contrib/tools/python3/src/Lib/http/client.py index a98432e5685..a6ab135b2c3 100644 --- a/contrib/tools/python3/src/Lib/http/client.py +++ b/contrib/tools/python3/src/Lib/http/client.py @@ -75,6 +75,7 @@ import http import io import re import socket +import sys import collections.abc from urllib.parse import urlsplit @@ -106,9 +107,6 @@ globals().update(http.HTTPStatus.__members__) # Mapping status codes to official W3C names responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()} -# maximal amount of data to read at one time in _safe_read -MAXAMOUNT = 1048576 - # maximal line length when calling readline(). _MAXLINE = 65536 _MAXHEADERS = 100 @@ -457,18 +455,25 @@ class HTTPResponse(io.BufferedIOBase): self._close_conn() return b"" + if self.chunked: + return self._read_chunked(amt) + if amt is not None: - # Amount is given, implement using readinto - b = bytearray(amt) - n = self.readinto(b) - return memoryview(b)[:n].tobytes() + if self.length is not None and amt > self.length: + # clip the read to the "end of response" + amt = self.length + s = self.fp.read(amt) + if not s and amt: + # Ideally, we would raise IncompleteRead if the content-length + # wasn't satisfied, but it might break compatibility. + self._close_conn() + elif self.length is not None: + self.length -= len(s) + if not self.length: + self._close_conn() + return s else: # Amount is not given (unbounded read) so we must check self.length - # and self.chunked - - if self.chunked: - return self._readall_chunked() - if self.length is None: s = self.fp.read() else: @@ -569,7 +574,7 @@ class HTTPResponse(io.BufferedIOBase): self.chunk_left = chunk_left return chunk_left - def _readall_chunked(self): + def _read_chunked(self, amt=None): assert self.chunked != _UNKNOWN value = [] try: @@ -577,7 +582,15 @@ class HTTPResponse(io.BufferedIOBase): chunk_left = self._get_chunk_left() if chunk_left is None: break + + if amt is not None and amt <= chunk_left: + value.append(self._safe_read(amt)) + self.chunk_left = chunk_left - amt + break + value.append(self._safe_read(chunk_left)) + if amt is not None: + amt -= chunk_left self.chunk_left = 0 return b''.join(value) except IncompleteRead: @@ -608,43 +621,24 @@ class HTTPResponse(io.BufferedIOBase): raise IncompleteRead(bytes(b[0:total_bytes])) def _safe_read(self, amt): - """Read the number of bytes requested, compensating for partial reads. - - Normally, we have a blocking socket, but a read() can be interrupted - by a signal (resulting in a partial read). - - Note that we cannot distinguish between EOF and an interrupt when zero - bytes have been read. IncompleteRead() will be raised in this - situation. + """Read the number of bytes requested. This function should be used when bytes "should" be present for reading. If the bytes are truly not available (due to EOF), then the IncompleteRead exception can be used to detect the problem. """ - s = [] - while amt > 0: - chunk = self.fp.read(min(amt, MAXAMOUNT)) - if not chunk: - raise IncompleteRead(b''.join(s), amt) - s.append(chunk) - amt -= len(chunk) - return b"".join(s) + data = self.fp.read(amt) + if len(data) < amt: + raise IncompleteRead(data, amt-len(data)) + return data def _safe_readinto(self, b): """Same as _safe_read, but for reading into a buffer.""" - total_bytes = 0 - mvb = memoryview(b) - while total_bytes < len(b): - if MAXAMOUNT < len(mvb): - temp_mvb = mvb[0:MAXAMOUNT] - n = self.fp.readinto(temp_mvb) - else: - n = self.fp.readinto(mvb) - if not n: - raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b)) - mvb = mvb[n:] - total_bytes += n - return total_bytes + amt = len(b) + n = self.fp.readinto(b) + if n < amt: + raise IncompleteRead(bytes(b[:n]), amt-n) + return n def read1(self, n=-1): """Read with at most one underlying system call. If at least one @@ -943,6 +937,7 @@ class HTTPConnection: def connect(self): """Connect to the host and port specified in __init__.""" + sys.audit("http.client.connect", self, self.host, self.port) self.sock = self._create_connection( (self.host,self.port), self.timeout, self.source_address) # Might fail in OSs that don't implement TCP_NODELAY @@ -995,8 +990,10 @@ class HTTPConnection: break if encode: datablock = datablock.encode("iso-8859-1") + sys.audit("http.client.send", self, datablock) self.sock.sendall(datablock) return + sys.audit("http.client.send", self, data) try: self.sock.sendall(data) except TypeError: @@ -1422,6 +1419,9 @@ else: 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 diff --git a/contrib/tools/python3/src/Lib/http/cookiejar.py b/contrib/tools/python3/src/Lib/http/cookiejar.py index 47ed5c3d64a..eaa76c26b9c 100644 --- a/contrib/tools/python3/src/Lib/http/cookiejar.py +++ b/contrib/tools/python3/src/Lib/http/cookiejar.py @@ -50,10 +50,18 @@ def _debug(*args): logger = logging.getLogger("http.cookiejar") return logger.debug(*args) - +HTTPONLY_ATTR = "HTTPOnly" +HTTPONLY_PREFIX = "#HttpOnly_" DEFAULT_HTTP_PORT = str(http.client.HTTP_PORT) +NETSCAPE_MAGIC_RGX = re.compile("#( Netscape)? HTTP Cookie File") MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar " "instance initialised with one)") +NETSCAPE_HEADER_TEXT = """\ +# Netscape HTTP Cookie File +# http://curl.haxx.se/rfc/cookie_spec.html +# This is a generated file! Do not edit. + +""" def _warn_unhandled_exception(): # There are a few catch-all except: statements in this module, for @@ -2004,19 +2012,11 @@ class MozillaCookieJar(FileCookieJar): header by default (Mozilla can cope with that). """ - magic_re = re.compile("#( Netscape)? HTTP Cookie File") - header = """\ -# Netscape HTTP Cookie File -# http://curl.haxx.se/rfc/cookie_spec.html -# This is a generated file! Do not edit. - -""" def _really_load(self, f, filename, ignore_discard, ignore_expires): now = time.time() - magic = f.readline() - if not self.magic_re.search(magic): + if not NETSCAPE_MAGIC_RGX.match(f.readline()): raise LoadError( "%r does not look like a Netscape format cookies file" % filename) @@ -2024,8 +2024,17 @@ class MozillaCookieJar(FileCookieJar): try: while 1: 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_" + if line.startswith(HTTPONLY_PREFIX): + rest[HTTPONLY_ATTR] = "" + line = line[len(HTTPONLY_PREFIX):] + # last field may be absent, so keep any trailing tab if line.endswith("\n"): line = line[:-1] @@ -2063,7 +2072,7 @@ class MozillaCookieJar(FileCookieJar): discard, None, None, - {}) + rest) if not ignore_discard and c.discard: continue if not ignore_expires and c.is_expired(now): @@ -2083,16 +2092,17 @@ class MozillaCookieJar(FileCookieJar): else: raise ValueError(MISSING_FILENAME_TEXT) with open(filename, "w") as f: - f.write(self.header) + f.write(NETSCAPE_HEADER_TEXT) now = time.time() for cookie in self: + domain = cookie.domain if not ignore_discard and cookie.discard: continue if not ignore_expires and cookie.is_expired(now): continue if cookie.secure: secure = "TRUE" else: secure = "FALSE" - if cookie.domain.startswith("."): initial_dot = "TRUE" + if domain.startswith("."): initial_dot = "TRUE" else: initial_dot = "FALSE" if cookie.expires is not None: expires = str(cookie.expires) @@ -2107,7 +2117,9 @@ class MozillaCookieJar(FileCookieJar): else: name = cookie.name value = cookie.value + if cookie.has_nonstandard_attr(HTTPONLY_ATTR): + domain = HTTPONLY_PREFIX + domain f.write( - "\t".join([cookie.domain, initial_dot, cookie.path, + "\t".join([domain, initial_dot, cookie.path, secure, expires, name, value])+ "\n") diff --git a/contrib/tools/python3/src/Lib/http/server.py b/contrib/tools/python3/src/Lib/http/server.py index 2d2300c2aea..58abadf7377 100644 --- a/contrib/tools/python3/src/Lib/http/server.py +++ b/contrib/tools/python3/src/Lib/http/server.py @@ -412,7 +412,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): method = getattr(self, mname) method() self.wfile.flush() #actually send the response if not already done. - except socket.timeout as e: + except TimeoutError as e: #a read or a write timed out. Discard this connection self.log_error("Request timed out: %r", e) self.close_connection = True @@ -1091,8 +1091,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): env['PATH_INFO'] = uqrest env['PATH_TRANSLATED'] = self.translate_path(uqrest) env['SCRIPT_NAME'] = scriptname - if query: - env['QUERY_STRING'] = query + env['QUERY_STRING'] = query env['REMOTE_ADDR'] = self.client_address[0] authorization = self.headers.get("authorization") if authorization: -- cgit v1.3