Skip to content

Commit c240fe3

Browse files
committed
Fix resolve_relative_url to handle multi-level relative paths
Signed-off-by: Kai Hodžić <hodzic.e.k@outlook.com>
1 parent 7b61dde commit c240fe3

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

src/python_inspector/utils_pypi.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import Union
2727
from urllib.parse import quote_plus
2828
from urllib.parse import unquote
29+
from urllib.parse import urljoin
2930
from urllib.parse import urlparse
3031
from urllib.parse import urlunparse
3132

@@ -1631,25 +1632,27 @@ async def fetch_links(
16311632

16321633
def resolve_relative_url(package_url, url):
16331634
"""
1634-
Return the resolved `url` URLstring given a `package_url` base URL string
1635+
Return the resolved `url` URL string given a `package_url` base URL string
16351636
of a package.
16361637
16371638
For example:
16381639
>>> resolve_relative_url("https://example.com/package", "../path/file.txt")
16391640
'https://example.com/path/file.txt'
1641+
>>> resolve_relative_url("https://example.com/simple/pkg/", "../../packages/file.whl")
1642+
'https://example.com/packages/file.whl'
16401643
"""
16411644
if not url.startswith(("http://", "https://")):
16421645
base_url_parts = urlparse(package_url)
16431646
url_parts = urlparse(url)
1644-
# If the relative URL starts with '..', remove the last directory from the base URL
1647+
# If the relative URL starts with '..', use urljoin to handle multi-level '../'
16451648
if url_parts.path.startswith(".."):
1646-
path = base_url_parts.path.rstrip("/").rsplit("/", 1)[0] + url_parts.path[2:]
1649+
url = urljoin(package_url, url)
16471650
else:
16481651
path = urlunparse(
16491652
("", "", url_parts.path, url_parts.params, url_parts.query, url_parts.fragment)
16501653
)
1651-
resolved_url_parts = base_url_parts._replace(path=path)
1652-
url = urlunparse(resolved_url_parts)
1654+
resolved_url_parts = base_url_parts._replace(path=path)
1655+
url = urlunparse(resolved_url_parts)
16531656
return url
16541657

16551658

tests/test_utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from python_inspector.resolution import fetch_and_extract_sdist
2424
from python_inspector.utils import get_netrc_auth
2525
from python_inspector.utils_pypi import PypiSimpleRepository
26+
from python_inspector.utils_pypi import resolve_relative_url
2627
from python_inspector.utils_pypi import valid_python_version
2728

2829
test_env = FileDrivenTesting()
@@ -164,3 +165,28 @@ def test_parse_reqs_with_setup_requires_and_python_requires():
164165
def test_valid_python_version():
165166
assert valid_python_version("3.8", ">3.1")
166167
assert not valid_python_version("3.8.1", ">3.9")
168+
169+
170+
def test_resolve_relative_url_multi_level():
171+
base = "https://example.com/api/pypi/repo/simple/pkg/"
172+
rel = "../../packages/packages/d9/0b/hash/file-1.0-cp310-linux.whl"
173+
result = resolve_relative_url(base, rel)
174+
assert (
175+
result
176+
== "https://example.com/api/pypi/repo/packages/packages/d9/0b/hash/file-1.0-cp310-linux.whl"
177+
)
178+
assert "/../" not in result
179+
180+
181+
def test_resolve_relative_url_single_level():
182+
base = "https://example.com/simple/pkg/"
183+
rel = "../other/file.whl"
184+
result = resolve_relative_url(base, rel)
185+
assert result == "https://example.com/simple/other/file.whl"
186+
187+
188+
def test_resolve_relative_url_absolute():
189+
base = "https://example.com/simple/pkg/"
190+
rel = "https://files.pythonhosted.org/packages/file.whl"
191+
result = resolve_relative_url(base, rel)
192+
assert result == "https://files.pythonhosted.org/packages/file.whl"

0 commit comments

Comments
 (0)