diff --git a/news/5892.bugfix b/news/5892.bugfix new file mode 100644 index 00000000000..4f25ee08e9b --- /dev/null +++ b/news/5892.bugfix @@ -0,0 +1 @@ +Improve handling of file URIs: correctly handle `file://localhost/...` and don't try to use UNC paths on Unix. diff --git a/src/pip/_internal/download.py b/src/pip/_internal/download.py index 2bbe1762cda..22eaa6b246c 100644 --- a/src/pip/_internal/download.py +++ b/src/pip/_internal/download.py @@ -473,9 +473,17 @@ def url_to_path(url): _, netloc, path, _, _ = urllib_parse.urlsplit(url) - # if we have a UNC path, prepend UNC share notation - if netloc: + if not netloc or netloc == 'localhost': + # According to RFC 8089, same as empty authority. + netloc = '' + elif sys.platform == 'win32': + # If we have a UNC path, prepend UNC share notation. netloc = '\\\\' + netloc + else: + raise ValueError( + 'non-local file URIs are not supported on this platform: %r' + % url + ) path = urllib_request.url2pathname(netloc + path) return path diff --git a/tests/unit/test_download.py b/tests/unit/test_download.py index 532eb3fc3eb..2391f426022 100644 --- a/tests/unit/test_download.py +++ b/tests/unit/test_download.py @@ -1,5 +1,6 @@ import hashlib import os +import sys from io import BytesIO from shutil import copy, rmtree from tempfile import mkdtemp @@ -125,11 +126,6 @@ def test_path_to_url_unix(): assert path_to_url('file') == 'file://' + urllib_request.pathname2url(path) -@pytest.mark.skipif("sys.platform == 'win32'") -def test_url_to_path_unix(): - assert url_to_path('file:///tmp/file') == '/tmp/file' - - @pytest.mark.skipif("sys.platform != 'win32'") def test_path_to_url_win(): assert path_to_url('c:/tmp/file') == 'file:///C:/tmp/file' @@ -139,10 +135,27 @@ def test_path_to_url_win(): assert path_to_url('file') == 'file:' + urllib_request.pathname2url(path) -@pytest.mark.skipif("sys.platform != 'win32'") -def test_url_to_path_win(): - assert url_to_path('file:///c:/tmp/file') == 'C:\\tmp\\file' - assert url_to_path('file://unc/as/path') == r'\\unc\as\path' +@pytest.mark.parametrize("url,win_expected,non_win_expected", [ + ('file:tmp', 'tmp', 'tmp'), + ('file:c:/path/to/file', r'C:\path\to\file', 'c:/path/to/file'), + ('file:/path/to/file', r'\path\to\file', '/path/to/file'), + ('file://localhost/tmp/file', r'\tmp\file', '/tmp/file'), + ('file://localhost/c:/tmp/file', r'C:\tmp\file', '/c:/tmp/file'), + ('file://somehost/tmp/file', r'\\somehost\tmp\file', None), + ('file:///tmp/file', r'\tmp\file', '/tmp/file'), + ('file:///c:/tmp/file', r'C:\tmp\file', '/c:/tmp/file'), +]) +def test_url_to_path(url, win_expected, non_win_expected): + if sys.platform == 'win32': + expected_path = win_expected + else: + expected_path = non_win_expected + + if expected_path is None: + with pytest.raises(ValueError): + url_to_path(url) + else: + assert url_to_path(url) == expected_path @pytest.mark.skipif("sys.platform != 'win32'")