diff --git a/h11/_events.py b/h11/_events.py index 68f6034..e9c85b7 100644 --- a/h11/_events.py +++ b/h11/_events.py @@ -24,6 +24,7 @@ class _EventBundle(object): _defaults = {} def __init__(self, **kwargs): + _parsed = kwargs.pop("_parsed", False) allowed = set(self._fields) for kwarg in kwargs: if kwarg not in allowed: @@ -42,15 +43,17 @@ def __init__(self, **kwargs): # Special handling for some fields if "headers" in self.__dict__: - self.headers = _headers.normalize_and_validate(self.headers) + self.headers = _headers.normalize_and_validate( + self.headers, _parsed=_parsed) - for field in ["method", "target", "http_version", "reason"]: - if field in self.__dict__: - self.__dict__[field] = bytesify(self.__dict__[field]) + if not _parsed: + for field in ["method", "target", "http_version", "reason"]: + if field in self.__dict__: + self.__dict__[field] = bytesify(self.__dict__[field]) - if "status_code" in self.__dict__: - if not isinstance(self.status_code, int): - raise LocalProtocolError("status code must be integer") + if "status_code" in self.__dict__: + if not isinstance(self.status_code, int): + raise LocalProtocolError("status code must be integer") self._validate() diff --git a/h11/_headers.py b/h11/_headers.py index 47de8dc..ff3b153 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -60,16 +60,20 @@ _field_name_re = re.compile(field_name.encode("ascii")) _field_value_re = re.compile(field_value.encode("ascii")) -def normalize_and_validate(headers): +def normalize_and_validate(headers, _parsed=False): new_headers = [] saw_content_length = False saw_transfer_encoding = False for name, value in headers: - name = bytesify(name).lower() - value = bytesify(value) - validate(_field_name_re, name, "Illegal header name {!r}", name) - validate(_field_value_re, value, - "Illegal header value {!r}", value) + # For headers coming out of the parser, we can safely skip some steps, + # because it always returns bytes and has already run these regexes + # over the data: + if not _parsed: + name = bytesify(name) + value = bytesify(value) + validate(_field_name_re, name, "Illegal header name {!r}", name) + validate(_field_value_re, value, "Illegal header value {!r}", value) + name = name.lower() if name == b"content-length": if saw_content_length: raise LocalProtocolError("multiple Content-Length headers") diff --git a/h11/_readers.py b/h11/_readers.py index 9ed0a1c..c7248fd 100644 --- a/h11/_readers.py +++ b/h11/_readers.py @@ -63,7 +63,9 @@ def maybe_read_from_IDLE_client(buf): if not lines: raise LocalProtocolError("no request line received") matches = validate(request_line_re, lines[0]) - return Request(headers=list(_decode_header_lines(lines[1:])), **matches) + return Request(headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + **matches) status_line_re = re.compile(status_line.encode("ascii")) @@ -79,7 +81,9 @@ def maybe_read_from_SEND_RESPONSE_server(buf): matches["reason"] = b"" status_code = matches["status_code"] = int(matches["status_code"]) class_ = InformationalResponse if status_code < 200 else Response - return class_(headers=list(_decode_header_lines(lines[1:])), **matches) + return class_(headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + **matches) class ContentLengthReader: