Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly handle Upgrade-requests for HTTP/2 by ignoring them #1642

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.cfg
Expand Up @@ -82,7 +82,7 @@ plugins =

[coverage:report]
precision = 2
fail_under = 97.69
fail_under = 97.63
show_missing = true
skip_covered = true
exclude_lines =
Expand Down
10 changes: 8 additions & 2 deletions uvicorn/protocols/http/h11_impl.py
Expand Up @@ -204,12 +204,18 @@ def handle_events(self) -> None:
"headers": self.headers,
}

is_upgrade, is_http2 = False, False
for name, value in self.headers:
if name == b"connection":
tokens = [token.lower().strip() for token in value.split(b",")]
if b"upgrade" in tokens:
self.handle_upgrade(event)
return
is_upgrade = True
elif name == b"upgrade" and value == b"h2c":
is_http2 = True

if is_upgrade and not is_http2:
self.handle_upgrade(event)
return

# Handle 503 responses when 'limit_concurrency' is exceeded.
if self.limit_concurrency is not None and (
Expand Down
17 changes: 13 additions & 4 deletions uvicorn/protocols/http/httptools_impl.py
Expand Up @@ -104,6 +104,7 @@ def __init__(
self.scope: HTTPScope = None # type: ignore[assignment]
self.headers: List[Tuple[bytes, bytes]] = None # type: ignore[assignment]
self.expect_100_continue = False
self.is_http2 = False
self.cycle: RequestResponseCycle = None # type: ignore[assignment]

# Protocol interface
Expand Down Expand Up @@ -168,7 +169,9 @@ def handle_upgrade(self) -> None:
if name == b"upgrade":
upgrade_value = value.lower()

if upgrade_value != b"websocket" or self.ws_protocol_class is None:
if upgrade_value == b"h2c":
return
elif upgrade_value != b"websocket" or self.ws_protocol_class is None:
msg = "Unsupported upgrade request."
self.logger.warning(msg)
from uvicorn.protocols.websockets.auto import AutoWebSocketsProtocol
Expand Down Expand Up @@ -236,6 +239,8 @@ def on_header(self, name: bytes, value: bytes) -> None:
name = name.lower()
if name == b"expect" and value.lower() == b"100-continue":
self.expect_100_continue = True
elif name == b"upgrade" and value == b"h2c":
self.is_http2 = True
self.headers.append((name, value))

def on_headers_complete(self) -> None:
Expand All @@ -244,7 +249,7 @@ def on_headers_complete(self) -> None:
self.scope["method"] = method.decode("ascii")
if http_version != "1.1":
self.scope["http_version"] = http_version
if self.parser.should_upgrade():
if self.parser.should_upgrade() and not self.is_http2:
return
parsed_url = httptools.parse_url(self.url)
raw_path = parsed_url.path
Expand Down Expand Up @@ -291,15 +296,19 @@ def on_headers_complete(self) -> None:
self.pipeline.appendleft((self.cycle, app))

def on_body(self, body: bytes) -> None:
if self.parser.should_upgrade() or self.cycle.response_complete:
if (
self.parser.should_upgrade() and not self.is_http2
) or self.cycle.response_complete:
return
self.cycle.body += body
if len(self.cycle.body) > HIGH_WATER_LIMIT:
self.flow.pause_reading()
self.cycle.message_event.set()

def on_message_complete(self) -> None:
if self.parser.should_upgrade() or self.cycle.response_complete:
if (
self.parser.should_upgrade() and not self.is_http2
) or self.cycle.response_complete:
return
self.cycle.more_body = False
self.cycle.message_event.set()
Expand Down