Skip to content

Commit b751a4f

Browse files
committed
fix(wsgi): Respect HTTP_X_FORWARDED_PROTO in request.url construction
1 parent 9c360eb commit b751a4f

File tree

3 files changed

+90
-4
lines changed

3 files changed

+90
-4
lines changed

sentry_sdk/_werkzeug.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ def get_host(environ: "Dict[str, str]", use_x_forwarded_for: bool = False) -> st
7272
"""
7373
if use_x_forwarded_for and "HTTP_X_FORWARDED_HOST" in environ:
7474
rv = environ["HTTP_X_FORWARDED_HOST"]
75-
if environ["wsgi.url_scheme"] == "http" and rv.endswith(":80"):
75+
scheme = environ.get("HTTP_X_FORWARDED_PROTO", environ["wsgi.url_scheme"])
76+
if scheme == "http" and rv.endswith(":80"):
7677
rv = rv[:-3]
77-
elif environ["wsgi.url_scheme"] == "https" and rv.endswith(":443"):
78+
elif scheme == "https" and rv.endswith(":443"):
7879
rv = rv[:-4]
7980
elif environ.get("HTTP_HOST"):
8081
rv = environ["HTTP_HOST"]

sentry_sdk/integrations/wsgi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ def get_request_url(
5757
path_info = environ.get("PATH_INFO", "").lstrip("/")
5858
path = f"{script_name}/{path_info}"
5959

60+
scheme = environ.get("wsgi.url_scheme")
61+
if use_x_forwarded_for:
62+
scheme = environ.get("HTTP_X_FORWARDED_PROTO", scheme)
63+
6064
return "%s://%s/%s" % (
61-
environ.get("wsgi.url_scheme"),
65+
scheme,
6266
get_host(environ, use_x_forwarded_for),
6367
wsgi_decoding_dance(path).lstrip("/"),
6468
)

tests/integrations/wsgi/test_wsgi.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
import sentry_sdk
88
from sentry_sdk import capture_message
9-
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware, _ScopedResponse
9+
from sentry_sdk.integrations.wsgi import (
10+
SentryWsgiMiddleware,
11+
_ScopedResponse,
12+
get_request_url,
13+
)
1014

1115

1216
@pytest.fixture
@@ -547,3 +551,80 @@ def app(environ, start_response):
547551
assert isinstance(result, _ScopedResponse)
548552
else:
549553
assert result is response_mock
554+
555+
556+
@pytest.mark.parametrize(
557+
"environ,use_x_forwarded_for,expected_url",
558+
[
559+
# Without use_x_forwarded_for, wsgi.url_scheme is used
560+
(
561+
{
562+
"wsgi.url_scheme": "http",
563+
"SERVER_NAME": "example.com",
564+
"SERVER_PORT": "80",
565+
"PATH_INFO": "/test",
566+
"HTTP_X_FORWARDED_PROTO": "https",
567+
},
568+
False,
569+
"http://example.com/test",
570+
),
571+
# With use_x_forwarded_for, HTTP_X_FORWARDED_PROTO is respected
572+
(
573+
{
574+
"wsgi.url_scheme": "http",
575+
"SERVER_NAME": "example.com",
576+
"SERVER_PORT": "80",
577+
"PATH_INFO": "/test",
578+
"HTTP_X_FORWARDED_PROTO": "https",
579+
},
580+
True,
581+
"https://example.com/test",
582+
),
583+
# With use_x_forwarded_for but no forwarded proto, wsgi.url_scheme is used
584+
(
585+
{
586+
"wsgi.url_scheme": "http",
587+
"SERVER_NAME": "example.com",
588+
"SERVER_PORT": "80",
589+
"PATH_INFO": "/test",
590+
},
591+
True,
592+
"http://example.com/test",
593+
),
594+
# Forwarded host with default https port is stripped using forwarded proto
595+
(
596+
{
597+
"wsgi.url_scheme": "http",
598+
"SERVER_NAME": "internal",
599+
"SERVER_PORT": "80",
600+
"PATH_INFO": "/test",
601+
"HTTP_X_FORWARDED_PROTO": "https",
602+
"HTTP_X_FORWARDED_HOST": "example.com:443",
603+
},
604+
True,
605+
"https://example.com/test",
606+
),
607+
# Forwarded host with non-default port is preserved
608+
(
609+
{
610+
"wsgi.url_scheme": "http",
611+
"SERVER_NAME": "internal",
612+
"SERVER_PORT": "80",
613+
"PATH_INFO": "/test",
614+
"HTTP_X_FORWARDED_PROTO": "https",
615+
"HTTP_X_FORWARDED_HOST": "example.com:8443",
616+
},
617+
True,
618+
"https://example.com:8443/test",
619+
),
620+
],
621+
ids=[
622+
"ignores_forwarded_proto_when_disabled",
623+
"respects_forwarded_proto_when_enabled",
624+
"falls_back_to_url_scheme_when_no_forwarded_proto",
625+
"strips_default_https_port_from_forwarded_host",
626+
"preserves_non_default_port_on_forwarded_host",
627+
],
628+
)
629+
def test_get_request_url_x_forwarded_proto(environ, use_x_forwarded_for, expected_url):
630+
assert get_request_url(environ, use_x_forwarded_for) == expected_url

0 commit comments

Comments
 (0)