diff --git a/h11/_abnf.py b/h11/_abnf.py index b706913..57e2804 100644 --- a/h11/_abnf.py +++ b/h11/_abnf.py @@ -93,8 +93,12 @@ r"{http_version}" r" " r"(?P{status_code})" - r" " - r"(?P{reason_phrase})" + # However, there are apparently a few too many servers out there that just + # leave out the reason phrase: + # https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036 + # https://github.com/seanmonstar/httparse/issues/29 + # so make it optional. ?: is a non-capturing group. + r"(?: (?P{reason_phrase}))?" .format(**globals())) HEXDIG = r"[0-9A-Fa-f]" diff --git a/h11/_readers.py b/h11/_readers.py index 40bfa19..9ed0a1c 100644 --- a/h11/_readers.py +++ b/h11/_readers.py @@ -74,6 +74,9 @@ def maybe_read_from_SEND_RESPONSE_server(buf): if not lines: raise LocalProtocolError("no response line received") matches = validate(status_line_re, lines[0]) + # Tolerate missing reason phrases + if matches["reason"] is None: + 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) diff --git a/h11/tests/test_io.py b/h11/tests/test_io.py index a816100..db05e47 100644 --- a/h11/tests/test_io.py +++ b/h11/tests/test_io.py @@ -142,6 +142,13 @@ def test_readers_unusual(): Response(status_code=200, headers=[("Foo", "")], http_version="1.0", reason=b"OK")) + # Tolerate broken servers that leave off the response code + tr(READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200\r\n" + b"Foo: bar\r\n\r\n", + Response(status_code=200, headers=[("Foo", "bar")], + http_version="1.0", reason=b"")) + # obsolete line folding tr(READERS[CLIENT, IDLE], b"HEAD /foo HTTP/1.1\r\n"