diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/tools/python3/src/Lib/http | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/python3/src/Lib/http')
-rw-r--r-- | contrib/tools/python3/src/Lib/http/__init__.py | 26 | ||||
-rw-r--r-- | contrib/tools/python3/src/Lib/http/client.py | 272 | ||||
-rw-r--r-- | contrib/tools/python3/src/Lib/http/cookiejar.py | 36 | ||||
-rw-r--r-- | contrib/tools/python3/src/Lib/http/cookies.py | 10 | ||||
-rw-r--r-- | contrib/tools/python3/src/Lib/http/server.py | 130 |
5 files changed, 237 insertions, 237 deletions
diff --git a/contrib/tools/python3/src/Lib/http/__init__.py b/contrib/tools/python3/src/Lib/http/__init__.py index 37be765349..2efca879a9 100644 --- a/contrib/tools/python3/src/Lib/http/__init__.py +++ b/contrib/tools/python3/src/Lib/http/__init__.py @@ -15,11 +15,11 @@ class HTTPStatus(IntEnum): * RFC 7238: Permanent Redirect * RFC 2295: Transparent Content Negotiation in HTTP * RFC 2774: An HTTP Extension Framework - * RFC 7725: An HTTP Status Code to Report Legal Obstacles + * RFC 7725: An HTTP Status Code to Report Legal Obstacles * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2) - * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) - * RFC 8297: An HTTP Status Code for Indicating Hints - * RFC 8470: Using Early Data in HTTP + * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) + * RFC 8297: An HTTP Status Code for Indicating Hints + * RFC 8470: Using Early Data in HTTP """ def __new__(cls, value, phrase, description=''): obj = int.__new__(cls, value) @@ -34,7 +34,7 @@ class HTTPStatus(IntEnum): SWITCHING_PROTOCOLS = (101, 'Switching Protocols', 'Switching to new protocol; obey Upgrade header') PROCESSING = 102, 'Processing' - EARLY_HINTS = 103, 'Early Hints' + EARLY_HINTS = 103, 'Early Hints' # success OK = 200, 'OK', 'Request fulfilled, document follows' @@ -64,7 +64,7 @@ class HTTPStatus(IntEnum): TEMPORARY_REDIRECT = (307, 'Temporary Redirect', 'Object moved temporarily -- see URI list') PERMANENT_REDIRECT = (308, 'Permanent Redirect', - 'Object moved permanently -- see URI list') + 'Object moved permanently -- see URI list') # client error BAD_REQUEST = (400, 'Bad Request', @@ -104,14 +104,14 @@ class HTTPStatus(IntEnum): 'Cannot satisfy request range') EXPECTATION_FAILED = (417, 'Expectation Failed', 'Expect condition could not be satisfied') - IM_A_TEAPOT = (418, 'I\'m a Teapot', - 'Server refuses to brew coffee because it is a teapot.') + IM_A_TEAPOT = (418, 'I\'m a Teapot', + 'Server refuses to brew coffee because it is a teapot.') MISDIRECTED_REQUEST = (421, 'Misdirected Request', 'Server is not able to produce a response') UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity' LOCKED = 423, 'Locked' FAILED_DEPENDENCY = 424, 'Failed Dependency' - TOO_EARLY = 425, 'Too Early' + TOO_EARLY = 425, 'Too Early' UPGRADE_REQUIRED = 426, 'Upgrade Required' PRECONDITION_REQUIRED = (428, 'Precondition Required', 'The origin server requires the request to be conditional') @@ -122,10 +122,10 @@ class HTTPStatus(IntEnum): 'Request Header Fields Too Large', 'The server is unwilling to process the request because its header ' 'fields are too large') - UNAVAILABLE_FOR_LEGAL_REASONS = (451, - 'Unavailable For Legal Reasons', - 'The server is denying access to the ' - 'resource as a consequence of a legal demand') + UNAVAILABLE_FOR_LEGAL_REASONS = (451, + 'Unavailable For Legal Reasons', + 'The server is denying access to the ' + 'resource as a consequence of a legal demand') # server errors INTERNAL_SERVER_ERROR = (500, 'Internal Server Error', diff --git a/contrib/tools/python3/src/Lib/http/client.py b/contrib/tools/python3/src/Lib/http/client.py index a98432e568..f0ce40cdc0 100644 --- a/contrib/tools/python3/src/Lib/http/client.py +++ b/contrib/tools/python3/src/Lib/http/client.py @@ -70,7 +70,7 @@ Req-sent-unread-response _CS_REQ_SENT <response_class> import email.parser import email.message -import errno +import errno import http import io import re @@ -106,9 +106,9 @@ 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 amount of data to read at one time in _safe_read +MAXAMOUNT = 1048576 + # maximal line length when calling readline(). _MAXLINE = 65536 _MAXHEADERS = 100 @@ -141,20 +141,20 @@ _MAXHEADERS = 100 _is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch _is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search -# These characters are not allowed within HTTP URL paths. -# See https://tools.ietf.org/html/rfc3986#section-3.3 and the -# https://tools.ietf.org/html/rfc3986#appendix-A pchar definition. -# Prevents CVE-2019-9740. Includes control characters such as \r\n. -# We don't restrict chars above \x7f as putrequest() limits us to ASCII. -_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') -# Arguably only these _should_ allowed: -# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") -# We are more lenient for assumed real world compatibility purposes. - -# These characters are not allowed within HTTP method names -# to prevent http header injection. -_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') - +# These characters are not allowed within HTTP URL paths. +# See https://tools.ietf.org/html/rfc3986#section-3.3 and the +# https://tools.ietf.org/html/rfc3986#appendix-A pchar definition. +# Prevents CVE-2019-9740. Includes control characters such as \r\n. +# We don't restrict chars above \x7f as putrequest() limits us to ASCII. +_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') +# Arguably only these _should_ allowed: +# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") +# We are more lenient for assumed real world compatibility purposes. + +# These characters are not allowed within HTTP method names +# to prevent http header injection. +_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') + # We always set the Content-Length header for these methods because some # servers will otherwise respond with a 411 _METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} @@ -205,11 +205,11 @@ class HTTPMessage(email.message.Message): lst.append(line) return lst -def _read_headers(fp): - """Reads potential header lines into a list from a file pointer. +def _read_headers(fp): + """Reads potential header lines into a list from a file pointer. - Length of line is limited by _MAXLINE, and number of - headers is limited by _MAXHEADERS. + Length of line is limited by _MAXLINE, and number of + headers is limited by _MAXHEADERS. """ headers = [] while True: @@ -221,19 +221,19 @@ def _read_headers(fp): raise HTTPException("got more than %d headers" % _MAXHEADERS) if line in (b'\r\n', b'\n', b''): break - return headers - -def parse_headers(fp, _class=HTTPMessage): - """Parses only RFC2822 headers from a file pointer. - - email Parser wants to see strings rather than bytes. - But a TextIOWrapper around self.rfile would buffer too many bytes - from the stream, bytes which we later need to read as bytes. - So we read the correct bytes here, as bytes, for email Parser - to parse. - - """ - headers = _read_headers(fp) + return headers + +def parse_headers(fp, _class=HTTPMessage): + """Parses only RFC2822 headers from a file pointer. + + email Parser wants to see strings rather than bytes. + But a TextIOWrapper around self.rfile would buffer too many bytes + from the stream, bytes which we later need to read as bytes. + So we read the correct bytes here, as bytes, for email Parser + to parse. + + """ + headers = _read_headers(fp) hstring = b''.join(headers).decode('iso-8859-1') return email.parser.Parser(_class=_class).parsestr(hstring) @@ -321,10 +321,10 @@ class HTTPResponse(io.BufferedIOBase): if status != CONTINUE: break # skip the header from the 100 response - skipped_headers = _read_headers(self.fp) - if self.debuglevel > 0: - print("headers:", skipped_headers) - del skipped_headers + skipped_headers = _read_headers(self.fp) + if self.debuglevel > 0: + print("headers:", skipped_headers) + del skipped_headers self.code = self.status = status self.reason = reason.strip() @@ -339,8 +339,8 @@ class HTTPResponse(io.BufferedIOBase): self.headers = self.msg = parse_headers(self.fp) if self.debuglevel > 0: - for hdr, val in self.headers.items(): - print("header:", hdr + ":", val) + for hdr, val in self.headers.items(): + print("header:", hdr + ":", val) # are we using the chunked-style of transfer encoding? tr_enc = self.headers.get("transfer-encoding") @@ -608,43 +608,43 @@ 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, 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. + This function should be used when <amt> 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) + 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) 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 + 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 def read1(self, n=-1): """Read with at most one underlying system call. If at least one @@ -856,8 +856,8 @@ class HTTPConnection: (self.host, self.port) = self._get_hostport(host, port) - self._validate_host(self.host) - + self._validate_host(self.host) + # This is stored as an instance variable to allow unit # tests to replace it with a suitable mockup self._create_connection = socket.create_connection @@ -870,7 +870,7 @@ class HTTPConnection: 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 + This method must be called before the HTTP connection has been established. The headers argument should be a mapping of extra HTTP headers to send @@ -910,24 +910,24 @@ 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) - headers = [connect] + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( + self._tunnel_host.encode("ascii"), self._tunnel_port) + headers = [connect] for header, value in self._tunnel_headers.items(): - headers.append(f"{header}: {value}\r\n".encode("latin-1")) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of - # potentially emitting a series of small packets. - self.send(b"".join(headers)) - del headers + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers response = self.response_class(self.sock, method=self._method) (version, code, message) = response._read_status() if code != http.HTTPStatus.OK: self.close() - raise OSError(f"Tunnel connection failed: {code} {message.strip()}") + raise OSError(f"Tunnel connection failed: {code} {message.strip()}") while True: line = response.fp.readline(_MAXLINE + 1) if len(line) > _MAXLINE: @@ -945,12 +945,12 @@ class HTTPConnection: """Connect to the host and port specified in __init__.""" self.sock = self._create_connection( (self.host,self.port), self.timeout, self.source_address) - # Might fail in OSs that don't implement TCP_NODELAY - try: - self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - except OSError as e: - if e.errno != errno.ENOPROTOOPT: - raise + # Might fail in OSs that don't implement TCP_NODELAY + try: + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError as e: + if e.errno != errno.ENOPROTOOPT: + raise if self._tunnel_host: self._tunnel() @@ -1121,17 +1121,17 @@ class HTTPConnection: else: raise CannotSendRequest(self.__state) - self._validate_method(method) - - # Save the method for use later in the response phase + self._validate_method(method) + + # Save the method for use later in the response phase self._method = method - - url = url or '/' - self._validate_path(url) - + + url = url or '/' + self._validate_path(url) + request = '%s %s %s' % (method, url, self._http_vsn_str) - self._output(self._encode_request(request)) + self._output(self._encode_request(request)) if self._http_vsn == 11: # Issue some standard headers for better HTTP/1.1 compliance @@ -1209,35 +1209,35 @@ class HTTPConnection: # For HTTP/1.0, the server will assume "not chunked" pass - def _encode_request(self, request): - # ASCII also helps prevent CVE-2019-9740. - return request.encode('ascii') - - def _validate_method(self, method): - """Validate a method name for putrequest.""" - # prevent http header injection - match = _contains_disallowed_method_pchar_re.search(method) - if match: - raise ValueError( - f"method can't contain control characters. {method!r} " - f"(found at least {match.group()!r})") - - def _validate_path(self, url): - """Validate a url for putrequest.""" - # Prevent CVE-2019-9740. - match = _contains_disallowed_url_pchar_re.search(url) - if match: - raise InvalidURL(f"URL can't contain control characters. {url!r} " - f"(found at least {match.group()!r})") - - def _validate_host(self, host): - """Validate a host so it doesn't contain control characters.""" - # Prevent CVE-2019-18348. - match = _contains_disallowed_url_pchar_re.search(host) - if match: - raise InvalidURL(f"URL can't contain control characters. {host!r} " - f"(found at least {match.group()!r})") - + def _encode_request(self, request): + # ASCII also helps prevent CVE-2019-9740. + return request.encode('ascii') + + def _validate_method(self, method): + """Validate a method name for putrequest.""" + # prevent http header injection + match = _contains_disallowed_method_pchar_re.search(method) + if match: + raise ValueError( + f"method can't contain control characters. {method!r} " + f"(found at least {match.group()!r})") + + def _validate_path(self, url): + """Validate a url for putrequest.""" + # Prevent CVE-2019-9740. + match = _contains_disallowed_url_pchar_re.search(url) + if match: + raise InvalidURL(f"URL can't contain control characters. {url!r} " + f"(found at least {match.group()!r})") + + def _validate_host(self, host): + """Validate a host so it doesn't contain control characters.""" + # Prevent CVE-2019-18348. + match = _contains_disallowed_url_pchar_re.search(host) + if match: + raise InvalidURL(f"URL can't contain control characters. {host!r} " + f"(found at least {match.group()!r})") + def putheader(self, header, *values): """Send a request header line to the server. @@ -1422,9 +1422,9 @@ else: self.cert_file = cert_file if context is None: context = ssl._create_default_https_context() - # enable PHA for TLS 1.3 connections if available - if context.post_handshake_auth is not None: - context.post_handshake_auth = True + # 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 @@ -1433,10 +1433,10 @@ else: "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 + # 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 self._context = context if check_hostname is not None: self._context.check_hostname = check_hostname @@ -1490,7 +1490,7 @@ class IncompleteRead(HTTPException): e = '' return '%s(%i bytes read%s)' % (self.__class__.__name__, len(self.partial), e) - __str__ = object.__str__ + __str__ = object.__str__ class ImproperConnectionState(HTTPException): pass diff --git a/contrib/tools/python3/src/Lib/http/cookiejar.py b/contrib/tools/python3/src/Lib/http/cookiejar.py index 47ed5c3d64..27debead36 100644 --- a/contrib/tools/python3/src/Lib/http/cookiejar.py +++ b/contrib/tools/python3/src/Lib/http/cookiejar.py @@ -28,7 +28,7 @@ http://wwwsearch.sf.net/): __all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy', 'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar'] -import os +import os import copy import datetime import re @@ -214,14 +214,14 @@ LOOSE_HTTP_DATE_RE = re.compile( (?::(\d\d))? # optional seconds )? # optional clock \s* - (?: - ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone - \s* - )? - (?: - \(\w+\) # ASCII representation of timezone in parens. + (?: + ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone \s* - )?$""", re.X | re.ASCII) + )? + (?: + \(\w+\) # ASCII representation of timezone in parens. + \s* + )?$""", re.X | re.ASCII) def http2time(text): """Returns time in seconds since epoch of time represented by a string. @@ -291,11 +291,11 @@ ISO_DATE_RE = re.compile( (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) )? # optional clock \s* - (?: - ([-+]?\d\d?:?(:?\d\d)? - |Z|z) # timezone (Z is "zero meridian", i.e. GMT) - \s* - )?$""", re.X | re. ASCII) + (?: + ([-+]?\d\d?:?(:?\d\d)? + |Z|z) # timezone (Z is "zero meridian", i.e. GMT) + \s* + )?$""", re.X | re. ASCII) def iso2time(text): """ As for http2time, but parses the ISO 8601 formats: @@ -885,7 +885,7 @@ class DefaultCookiePolicy(CookiePolicy): strict_ns_domain=DomainLiberal, strict_ns_set_initial_dollar=False, strict_ns_set_path=False, - secure_protocols=("https", "wss") + secure_protocols=("https", "wss") ): """Constructor arguments should be passed as keyword arguments only.""" self.netscape = netscape @@ -898,7 +898,7 @@ class DefaultCookiePolicy(CookiePolicy): self.strict_ns_domain = strict_ns_domain self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar self.strict_ns_set_path = strict_ns_set_path - self.secure_protocols = secure_protocols + self.secure_protocols = secure_protocols if blocked_domains is not None: self._blocked_domains = tuple(blocked_domains) @@ -1125,7 +1125,7 @@ class DefaultCookiePolicy(CookiePolicy): return True def return_ok_secure(self, cookie, request): - if cookie.secure and request.type not in self.secure_protocols: + if cookie.secure and request.type not in self.secure_protocols: _debug(" secure cookie with non-secure request") return False return True @@ -1599,7 +1599,7 @@ class CookieJar: headers = response.info() rfc2965_hdrs = headers.get_all("Set-Cookie2", []) ns_hdrs = headers.get_all("Set-Cookie", []) - self._policy._now = self._now = int(time.time()) + self._policy._now = self._now = int(time.time()) rfc2965 = self._policy.rfc2965 netscape = self._policy.netscape @@ -1781,7 +1781,7 @@ class FileCookieJar(CookieJar): """ CookieJar.__init__(self, policy) if filename is not None: - filename = os.fspath(filename) + filename = os.fspath(filename) self.filename = filename self.delayload = bool(delayload) diff --git a/contrib/tools/python3/src/Lib/http/cookies.py b/contrib/tools/python3/src/Lib/http/cookies.py index 35ac2dc6ae..efdd950d07 100644 --- a/contrib/tools/python3/src/Lib/http/cookies.py +++ b/contrib/tools/python3/src/Lib/http/cookies.py @@ -131,7 +131,7 @@ Finis. # import re import string -import types +import types __all__ = ["CookieError", "BaseCookie", "SimpleCookie"] @@ -257,7 +257,7 @@ class Morsel(dict): In a cookie, each such pair may have several attributes, so this class is used to keep the attributes associated with the appropriate key,value pair. This class also includes a coded_value attribute, which is used to hold - the network representation of the value. + the network representation of the value. """ # RFC 2109 lists these attributes as reserved: # path comment domain @@ -281,7 +281,7 @@ class Morsel(dict): "secure" : "Secure", "httponly" : "HttpOnly", "version" : "Version", - "samesite" : "SameSite", + "samesite" : "SameSite", } _flags = {'secure', 'httponly'} @@ -420,9 +420,9 @@ class Morsel(dict): # Return the result return _semispacejoin(result) - __class_getitem__ = classmethod(types.GenericAlias) - + __class_getitem__ = classmethod(types.GenericAlias) + # # Pattern for finding cookie # diff --git a/contrib/tools/python3/src/Lib/http/server.py b/contrib/tools/python3/src/Lib/http/server.py index d7cce20432..ca40a0f1f1 100644 --- a/contrib/tools/python3/src/Lib/http/server.py +++ b/contrib/tools/python3/src/Lib/http/server.py @@ -103,7 +103,7 @@ import socketserver import sys import time import urllib.parse -import contextlib +import contextlib from functools import partial from http import HTTPStatus @@ -639,17 +639,17 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): """ server_version = "SimpleHTTP/" + __version__ - extensions_map = _encodings_map_default = { - '.gz': 'application/gzip', - '.Z': 'application/octet-stream', - '.bz2': 'application/x-bzip2', - '.xz': 'application/x-xz', - } + extensions_map = _encodings_map_default = { + '.gz': 'application/gzip', + '.Z': 'application/octet-stream', + '.bz2': 'application/x-bzip2', + '.xz': 'application/x-xz', + } def __init__(self, *args, directory=None, **kwargs): if directory is None: directory = os.getcwd() - self.directory = os.fspath(directory) + self.directory = os.fspath(directory) super().__init__(*args, **kwargs) def do_GET(self): @@ -689,7 +689,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): parts[3], parts[4]) new_url = urllib.parse.urlunsplit(new_parts) self.send_header("Location", new_url) - self.send_header("Content-Length", "0") + self.send_header("Content-Length", "0") self.end_headers() return None for index in "index.html", "index.htm": @@ -700,14 +700,14 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): else: return self.list_directory(path) ctype = self.guess_type(path) - # check for trailing "/" which should return 404. See Issue17324 - # The test for this was added in test_httpserver.py - # However, some OS platforms accept a trailingSlash as a filename - # See discussion on python-dev and Issue34711 regarding - # parseing and rejection of filenames with a trailing slash - if path.endswith("/"): - self.send_error(HTTPStatus.NOT_FOUND, "File not found") - return None + # check for trailing "/" which should return 404. See Issue17324 + # The test for this was added in test_httpserver.py + # However, some OS platforms accept a trailingSlash as a filename + # See discussion on python-dev and Issue34711 regarding + # parseing and rejection of filenames with a trailing slash + if path.endswith("/"): + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return None try: f = open(path, 'rb') except OSError: @@ -879,10 +879,10 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): ext = ext.lower() if ext in self.extensions_map: return self.extensions_map[ext] - guess, _ = mimetypes.guess_type(path) - if guess: - return guess - return 'application/octet-stream' + guess, _ = mimetypes.guess_type(path) + if guess: + return guess + return 'application/octet-stream' # Utilities for CGIHTTPRequestHandler @@ -1013,10 +1013,10 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): """ collapsed_path = _url_collapse_path(self.path) dir_sep = collapsed_path.find('/', 1) - while dir_sep > 0 and not collapsed_path[:dir_sep] in self.cgi_directories: - dir_sep = collapsed_path.find('/', dir_sep+1) - if dir_sep > 0: - head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:] + while dir_sep > 0 and not collapsed_path[:dir_sep] in self.cgi_directories: + dir_sep = collapsed_path.find('/', dir_sep+1) + if dir_sep > 0: + head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:] self.cgi_info = head, tail return True return False @@ -1124,7 +1124,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): referer = self.headers.get('referer') if referer: env['HTTP_REFERER'] = referer - accept = self.headers.get_all('accept', ()) + accept = self.headers.get_all('accept', ()) env['HTTP_ACCEPT'] = ','.join(accept) ua = self.headers.get('user-agent') if ua: @@ -1160,9 +1160,9 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): while select.select([self.rfile], [], [], 0)[0]: if not self.rfile.read(1): break - exitcode = os.waitstatus_to_exitcode(sts) - if exitcode: - self.log_error(f"CGI script exit code {exitcode}") + exitcode = os.waitstatus_to_exitcode(sts) + if exitcode: + self.log_error(f"CGI script exit code {exitcode}") return # Child try: @@ -1221,34 +1221,34 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): self.log_message("CGI script exited OK") -def _get_best_family(*address): - infos = socket.getaddrinfo( - *address, - type=socket.SOCK_STREAM, - flags=socket.AI_PASSIVE, - ) - family, type, proto, canonname, sockaddr = next(iter(infos)) - return family, sockaddr - - +def _get_best_family(*address): + infos = socket.getaddrinfo( + *address, + type=socket.SOCK_STREAM, + flags=socket.AI_PASSIVE, + ) + family, type, proto, canonname, sockaddr = next(iter(infos)) + return family, sockaddr + + def test(HandlerClass=BaseHTTPRequestHandler, ServerClass=ThreadingHTTPServer, - protocol="HTTP/1.0", port=8000, bind=None): + protocol="HTTP/1.0", port=8000, bind=None): """Test the HTTP request handler class. This runs an HTTP server on port 8000 (or the port argument). """ - ServerClass.address_family, addr = _get_best_family(bind, port) + ServerClass.address_family, addr = _get_best_family(bind, port) HandlerClass.protocol_version = protocol - with ServerClass(addr, HandlerClass) as httpd: - host, port = httpd.socket.getsockname()[:2] - url_host = f'[{host}]' if ':' in host else host - print( - f"Serving HTTP on {host} port {port} " - f"(http://{url_host}:{port}/) ..." - ) + with ServerClass(addr, HandlerClass) as httpd: + host, port = httpd.socket.getsockname()[:2] + url_host = f'[{host}]' if ':' in host else host + print( + f"Serving HTTP on {host} port {port} " + f"(http://{url_host}:{port}/) ..." + ) try: httpd.serve_forever() except KeyboardInterrupt: @@ -1261,7 +1261,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='Run as CGI Server') - parser.add_argument('--bind', '-b', metavar='ADDRESS', + parser.add_argument('--bind', '-b', metavar='ADDRESS', help='Specify alternate bind address ' '[default: all interfaces]') parser.add_argument('--directory', '-d', default=os.getcwd(), @@ -1277,19 +1277,19 @@ if __name__ == '__main__': else: handler_class = partial(SimpleHTTPRequestHandler, directory=args.directory) - - # ensure dual-stack is not disabled; ref #38907 - class DualStackServer(ThreadingHTTPServer): - def server_bind(self): - # suppress exception when protocol is IPv4 - with contextlib.suppress(Exception): - self.socket.setsockopt( - socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - return super().server_bind() - - test( - HandlerClass=handler_class, - ServerClass=DualStackServer, - port=args.port, - bind=args.bind, - ) + + # ensure dual-stack is not disabled; ref #38907 + class DualStackServer(ThreadingHTTPServer): + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + test( + HandlerClass=handler_class, + ServerClass=DualStackServer, + port=args.port, + bind=args.bind, + ) |