diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2020-08-15 22:29:01 -0700 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2020-08-16 16:49:06 -0700 |
commit | d347823b8eb5bdc9ccecc440168032658bd752fa (patch) | |
tree | 4a7bd6794c82a2e5b59c024e16020432fefca2d4 /src | |
parent | 1b4bcce97cceaae588b5508d42308f13be926ce2 (diff) | |
download | waitress-d347823b8eb5bdc9ccecc440168032658bd752fa.tar.gz |
Reduce compat.py to minimum size
Diffstat (limited to 'src')
-rw-r--r-- | src/waitress/adjustments.py | 4 | ||||
-rw-r--r-- | src/waitress/compat.py | 36 | ||||
-rw-r--r-- | src/waitress/parser.py | 52 | ||||
-rw-r--r-- | src/waitress/rfc7230.py | 6 | ||||
-rw-r--r-- | src/waitress/task.py | 7 |
5 files changed, 45 insertions, 60 deletions
diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py index f121b6e..145ac86 100644 --- a/src/waitress/adjustments.py +++ b/src/waitress/adjustments.py @@ -17,7 +17,7 @@ import getopt import socket import warnings -from .compat import HAS_IPV6, WIN, string_types +from .compat import HAS_IPV6, WIN from .proxy_headers import PROXY_HEADERS truthy = frozenset(("t", "true", "y", "yes", "on", "1")) @@ -47,7 +47,7 @@ def asoctal(s): def aslist_cronly(value): - if isinstance(value, string_types): + if isinstance(value, str): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) diff --git a/src/waitress/compat.py b/src/waitress/compat.py index 7c2630c..67543b9 100644 --- a/src/waitress/compat.py +++ b/src/waitress/compat.py @@ -1,50 +1,14 @@ -import _thread as thread -from http import client as httplib -from io import StringIO as NativeIO -import os import platform # Fix for issue reported in https://github.com/Pylons/waitress/issues/138, # Python on Windows may not define IPPROTO_IPV6 in socket. import socket import sys -from urllib import parse as urlparse -from urllib.parse import unquote_to_bytes import warnings # True if we are running on Windows WIN = platform.system() == "Windows" -string_types = (str,) -integer_types = (int,) -class_types = (type,) -text_type = str -binary_type = bytes -long = int - - -def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode("latin-1") - - -def text_(s, encoding="latin-1", errors="strict"): - """ If ``s`` is an instance of ``binary_type``, return - ``s.decode(encoding, errors)``, otherwise return ``s``""" - - if isinstance(s, binary_type): - return s.decode(encoding, errors) - - return s # pragma: no cover - - -def tostr(s): - return str(s, "latin-1", "strict") - - -def tobytes(s): - return bytes(s, "latin-1") - - MAXINT = sys.maxsize HAS_IPV6 = socket.has_ipv6 diff --git a/src/waitress/parser.py b/src/waitress/parser.py index 4530b23..3b99921 100644 --- a/src/waitress/parser.py +++ b/src/waitress/parser.py @@ -18,9 +18,10 @@ processing but threads to do work. """ from io import BytesIO import re +from urllib import parse +from urllib.parse import unquote_to_bytes from waitress.buffers import OverflowableBuffer -from waitress.compat import tostr, unquote_bytes_to_wsgi, urlparse from waitress.receiver import ChunkedReceiver, FixedStreamReceiver from waitress.utilities import ( BadRequest, @@ -33,6 +34,10 @@ from waitress.utilities import ( from .rfc7230 import HEADER_FIELD +def unquote_bytes_to_wsgi(bytestring): + return unquote_to_bytes(bytestring).decode("latin-1") + + class ParsingError(Exception): pass @@ -80,11 +85,13 @@ class HTTPRequestParser: bytes consumed. Sets the completed flag once both the header and the body have been received. """ + if self.completed: return 0 # Can't consume any more. datalen = len(data) br = self.body_rcv + if br is None: # In header. max_header = self.adj.max_request_header_size @@ -106,12 +113,14 @@ class HTTPRequestParser: # If the first line + headers is over the max length, we return a # RequestHeaderFieldsTooLarge error rather than continuing to # attempt to parse the headers. + if self.header_bytes_received >= max_header: self.parse_header(b"GET / HTTP/1.0\r\n") self.error = RequestHeaderFieldsTooLarge( "exceeds max_header of %s" % max_header ) self.completed = True + return consumed if index >= 0: @@ -195,6 +204,7 @@ class HTTPRequestParser: first line of the request). """ index = header_plus.find(b"\r\n") + if index >= 0: first_line = header_plus[:index].rstrip() header = header_plus[index + 2 :] @@ -209,6 +219,7 @@ class HTTPRequestParser: lines = get_header_lines(header) headers = self.headers + for line in lines: header = HEADER_FIELD.match(line) @@ -219,25 +230,26 @@ class HTTPRequestParser: if b"_" in key: # TODO(xistence): Should we drop this request instead? + continue # Only strip off whitespace that is considered valid whitespace by # RFC7230, don't strip the rest value = value.strip(b" \t") - key1 = tostr(key.upper().replace(b"-", b"_")) + key1 = key.upper().replace(b"-", b"_").decode("latin-1") # If a header already exists, we append subsequent values # separated by a comma. Applications already need to handle # the comma separated values, as HTTP front ends might do # the concatenation for you (behavior specified in RFC2616). try: - headers[key1] += tostr(b", " + value) + headers[key1] += (b", " + value).decode("latin-1") except KeyError: - headers[key1] = tostr(value) + headers[key1] = value.decode("latin-1") # command, uri, version will be bytes command, uri, version = crack_first_line(first_line) - version = tostr(version) - command = tostr(command) + version = version.decode("latin-1") + command = command.decode("latin-1") self.command = command self.version = version ( @@ -280,6 +292,7 @@ class HTTPRequestParser: # Note: the identity transfer-coding was removed in RFC7230: # https://tools.ietf.org/html/rfc7230#appendix-A.2 and is thus # not supported + if encoding not in {"chunked"}: raise TransferEncodingNotImplemented( "Transfer-Encoding requested is not supported." @@ -296,6 +309,7 @@ class HTTPRequestParser: expect = headers.get("EXPECT", "").lower() self.expect_continue = expect == "100-continue" + if connection.lower() == "close": self.connection_close = True @@ -306,12 +320,14 @@ class HTTPRequestParser: raise ParsingError("Content-Length is invalid") self.content_length = cl + if cl > 0: buf = OverflowableBuffer(self.adj.inbuf_overflow) self.body_rcv = FixedStreamReceiver(cl, buf) def get_body_stream(self): body_rcv = self.body_rcv + if body_rcv is not None: return body_rcv.getfile() else: @@ -319,6 +335,7 @@ class HTTPRequestParser: def close(self): body_rcv = self.body_rcv + if body_rcv is not None: body_rcv.getbuf().close() @@ -346,16 +363,16 @@ def split_uri(uri): path, query = path.split(b"?", 1) else: try: - scheme, netloc, path, query, fragment = urlparse.urlsplit(uri) + scheme, netloc, path, query, fragment = parse.urlsplit(uri) except UnicodeError: raise ParsingError("Bad URI") return ( - tostr(scheme), - tostr(netloc), + scheme.decode("latin-1"), + netloc.decode("latin-1"), unquote_bytes_to_wsgi(path), - tostr(query), - tostr(fragment), + query.decode("latin-1"), + fragment.decode("latin-1"), ) @@ -365,20 +382,24 @@ def get_header_lines(header): """ r = [] lines = header.split(b"\r\n") + for line in lines: if not line: continue if b"\r" in line or b"\n" in line: - raise ParsingError('Bare CR or LF found in header line "%s"' % tostr(line)) + raise ParsingError( + 'Bare CR or LF found in header line "%s"' % str(line, "latin-1") + ) if line.startswith((b" ", b"\t")): if not r: # https://corte.si/posts/code/pathod/pythonservers/index.html - raise ParsingError('Malformed header line "%s"' % tostr(line)) + raise ParsingError('Malformed header line "%s"' % str(line, "latin-1")) r[-1] += line else: r.append(line) + return r @@ -391,6 +412,7 @@ first_line_re = re.compile( def crack_first_line(line): m = first_line_re.match(line) + if m is not None and m.end() == len(line): if m.group(3): version = m.group(5) @@ -407,9 +429,11 @@ def crack_first_line(line): # unsuspecting souls from sending lowercase HTTP methods to waitress # and having the request complete, while servers like nginx drop the # request onto the floor. + if method != method.upper(): - raise ParsingError('Malformed HTTP method "%s"' % tostr(method)) + raise ParsingError('Malformed HTTP method "%s"' % str(method, "latin-1")) uri = m.group(2) + return method, uri, version else: return b"", b"", b"" diff --git a/src/waitress/rfc7230.py b/src/waitress/rfc7230.py index cd33c90..9b25fbd 100644 --- a/src/waitress/rfc7230.py +++ b/src/waitress/rfc7230.py @@ -5,8 +5,6 @@ needed to properly parse HTTP messages. import re -from .compat import tobytes - WS = "[ \t]" OWS = WS + "{0,}?" RWS = WS + "{1,}?" @@ -46,7 +44,7 @@ FIELD_CONTENT = FIELD_VCHAR + "+(?:[ \t]+" + FIELD_VCHAR + "+)*" FIELD_VALUE = "(?:" + FIELD_CONTENT + ")?" HEADER_FIELD = re.compile( - tobytes( + ( "^(?P<name>" + TOKEN + "):" + OWS + "(?P<value>" + FIELD_VALUE + ")" + OWS + "$" - ) + ).encode("latin-1") ) diff --git a/src/waitress/task.py b/src/waitress/task.py index b82109f..779f01e 100644 --- a/src/waitress/task.py +++ b/src/waitress/task.py @@ -19,7 +19,6 @@ import threading import time from .buffers import ReadOnlyFileBasedBuffer -from .compat import tobytes from .utilities import build_http_date, logger, queue_logger rename_headers = { # or keep them without the HTTP_ prefix added @@ -281,7 +280,7 @@ class Task: lines = [first_line] + next_lines res = "%s\r\n\r\n" % "\r\n".join(lines) - return tobytes(res) + return res.encode("latin-1") def remove_content_length_header(self): response_headers = [] @@ -317,7 +316,7 @@ class Task: cl = self.content_length if self.chunked_response: # use chunked encoding response - towrite = tobytes(hex(len(data))[2:].upper()) + b"\r\n" + towrite = hex(len(data))[2:].upper().encode("latin-1") + b"\r\n" towrite += data + b"\r\n" elif cl is not None: towrite = data[: cl - self.content_bytes_written] @@ -361,7 +360,7 @@ class ErrorTask(Task): self.response_headers.append(("Connection", "close")) self.close_on_finish = True self.content_length = len(body) - self.write(tobytes(body)) + self.write(body.encode("latin-1")) class WSGITask(Task): |