From 5483bd300421f56c62047197e8f543b0ab2ceb34 Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Mon, 26 Oct 2026 16:02:20 +0530 Subject: [PATCH 1/2] fix: resolve relative download redirect URLs --- databusclient/api/download.py | 4 +-- tests/test_download_redirects.py | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/test_download_redirects.py diff --git a/databusclient/api/download.py b/databusclient/api/download.py index 356a2dd..5b8abfc 100644 --- a/databusclient/api/download.py +++ b/databusclient/api/download.py @@ -7,7 +7,7 @@ import re import shutil import tempfile -from urllib.parse import urlparse +from urllib.parse import urljoin, urlparse import requests from SPARQLWrapper import JSON, SPARQLWrapper @@ -467,7 +467,7 @@ def _download_file( 307, 308, ]: - url = response.headers.get("Location") + url = urljoin(url, response.headers["Location"]) print("Redirects url: ", url) # Re-do HEAD request on redirect URL response = requests.head(url, timeout=30) diff --git a/tests/test_download_redirects.py b/tests/test_download_redirects.py new file mode 100644 index 0000000..07f91f8 --- /dev/null +++ b/tests/test_download_redirects.py @@ -0,0 +1,49 @@ +"""Exercise download redirects against a local HTTP server.""" + +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from threading import Thread + +import pytest + +from databusclient.api.download import _download_file + + +@pytest.mark.parametrize( + "location", + ["/data.ttl", "../data.ttl", "data.ttl", "{base}/data.ttl", "//{host}/data.ttl"], +) +def test_download_resolves_redirect_location(tmp_path, location): + content = b" .\n" + + class Handler(BaseHTTPRequestHandler): + def do_HEAD(self): + if self.path == "/entry/file.ttl": + self.send_response(302) + self.send_header("Location", redirect_location) + else: + self.send_response(200) + self.end_headers() + + def do_GET(self): + assert self.path in ("/data.ttl", "/entry/data.ttl") + self.send_response(200) + self.send_header("Content-Length", str(len(content))) + self.end_headers() + self.wfile.write(content) + + def log_message(self, format, *args): + pass + + with ThreadingHTTPServer(("127.0.0.1", 0), Handler) as server: + host = f"127.0.0.1:{server.server_port}" + base = f"http://{host}" + redirect_location = location.format(base=base, host=host) + thread = Thread(target=server.serve_forever, kwargs={"poll_interval": 0.01}) + thread.start() + try: + _download_file(f"{base}/entry/file.ttl", localDir=str(tmp_path)) + finally: + server.shutdown() + thread.join() + + assert (tmp_path / "file.ttl").read_bytes() == content From 1715222c27ef313069021b4a5d6a9efdd9563a51 Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Mon, 26 Oct 2026 17:14:05 +0530 Subject: [PATCH 2/2] test: assert the exact path for each download redirect --- tests/test_download_redirects.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test_download_redirects.py b/tests/test_download_redirects.py index 07f91f8..8a5b456 100644 --- a/tests/test_download_redirects.py +++ b/tests/test_download_redirects.py @@ -9,11 +9,18 @@ @pytest.mark.parametrize( - "location", - ["/data.ttl", "../data.ttl", "data.ttl", "{base}/data.ttl", "//{host}/data.ttl"], + ("location", "expected_path"), + [ + ("/data.ttl", "/data.ttl"), + ("../data.ttl", "/data.ttl"), + ("data.ttl", "/entry/data.ttl"), + ("{base}/data.ttl", "/data.ttl"), + ("//{host}/data.ttl", "/data.ttl"), + ], ) -def test_download_resolves_redirect_location(tmp_path, location): +def test_download_resolves_redirect_location(tmp_path, location, expected_path): content = b" .\n" + requested_paths = [] class Handler(BaseHTTPRequestHandler): def do_HEAD(self): @@ -25,7 +32,7 @@ def do_HEAD(self): self.end_headers() def do_GET(self): - assert self.path in ("/data.ttl", "/entry/data.ttl") + requested_paths.append(self.path) self.send_response(200) self.send_header("Content-Length", str(len(content))) self.end_headers() @@ -46,4 +53,5 @@ def log_message(self, format, *args): server.shutdown() thread.join() + assert requested_paths == [expected_path] assert (tmp_path / "file.ttl").read_bytes() == content