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

Fix root url resolution from x_forwarded_host headers #7787

Merged
merged 15 commits into from Mar 22, 2024
5 changes: 5 additions & 0 deletions .changeset/ripe-coins-poke.md
@@ -0,0 +1,5 @@
---
"gradio": patch
---

fix:Fix root url resolution from `x_forwarded_host` headers
16 changes: 11 additions & 5 deletions gradio/route_utils.py
Expand Up @@ -294,10 +294,16 @@ def get_root_url(
request: fastapi.Request, route_path: str, root_path: str | None
) -> str:
"""
Gets the root url of the request, stripping off any query parameters, the route_path, and trailing slashes.
Also ensures that the root url is https if the request is https. If an absolute root_path is provided,
it is returned directly. If a relative root_path is provided, and it is not already the subpath of the URL,
it is appended to the root url. The final root url will not have a trailing slash.
Gets the root url of the Gradio app (i.e. the public url of the app) without a trailing slash.

This is how the root_url is resolved:
1. If a user provides a `root_path` manually that is a full URL, it is returned directly.
2. If the request has an x-forwarded-host header (e.g. because it is behind a proxy), the root url is
constructed from the x-forwarded-host header. In this case, `route_path` is not used to construct the root url.
3. Otherwise, the root url is constructed from the request url. The query parameters and `route_path` are stripped off.
And if a relative `root_path` is provided, and it is not already the subpath of the URL, it is appended to the root url.

In cases (2) and (3), We also check to see if the x-forwarded-proto header is present, and if so, convert the root url to https.
"""
if root_path and client_utils.is_http_url_like(root_path):
return root_path.rstrip("/")
Expand All @@ -311,7 +317,7 @@ def get_root_url(
root_url = root_url.replace("http://", "https://")

route_path = route_path.rstrip("/")
if len(route_path) > 0:
if len(route_path) > 0 and not x_forwarded_host:
root_url = root_url[: -len(route_path)]
root_url = root_url.rstrip("/")

Expand Down
27 changes: 20 additions & 7 deletions test/test_routes.py
Expand Up @@ -1072,34 +1072,47 @@ def test_get_root_url(


@pytest.mark.parametrize(
"headers, root_path, expected_root_url",
"headers, root_path, route_path, expected_root_url",
[
({}, "/gradio/", "http://gradio.app/gradio"),
({"x-forwarded-proto": "http"}, "/gradio/", "http://gradio.app/gradio"),
({"x-forwarded-proto": "https"}, "/gradio/", "https://gradio.app/gradio"),
({"x-forwarded-host": "gradio.dev"}, "/gradio/", "http://gradio.dev/gradio"),
({}, "/gradio/", "/", "http://gradio.app/gradio"),
({"x-forwarded-proto": "http"}, "/gradio/", "/", "http://gradio.app/gradio"),
({"x-forwarded-proto": "https"}, "/gradio/", "/", "https://gradio.app/gradio"),
(
{"x-forwarded-host": "gradio.dev"},
"/gradio/",
"/",
"http://gradio.dev/gradio",
),
(
{"x-forwarded-host": "gradio.dev"},
"/gradio/",
"/config",
"http://gradio.dev/gradio",
),
(
{"x-forwarded-host": "gradio.dev", "x-forwarded-proto": "https"},
"/",
"/",
"https://gradio.dev",
),
(
{"x-forwarded-host": "gradio.dev", "x-forwarded-proto": "https"},
"http://google.com",
"/",
"http://google.com",
),
],
)
def test_get_root_url_headers(
headers: Dict[str, str], root_path: str, expected_root_url: str
headers: Dict[str, str], root_path: str, route_path: str, expected_root_url: str
):
scope = {
"type": "http",
"headers": [(k.encode(), v.encode()) for k, v in headers.items()],
"path": "http://gradio.app",
}
request = Request(scope)
assert get_root_url(request, "/", root_path) == expected_root_url
assert get_root_url(request, route_path, root_path) == expected_root_url


class TestSimpleAPIRoutes:
Expand Down