Skip to content

Commit cd1da78

Browse files
authored
Safely convert file uris to local paths (#1701)
The previous `url[8:]` is wrong as it strips the leading "/" on *nix filesystems. On modern python, we would use `Path.from_uri(url)` here, so let's backport that. We then see that we also forgot the important `is_absolute` check.
1 parent 904eb36 commit cd1da78

File tree

1 file changed

+30
-2
lines changed

1 file changed

+30
-2
lines changed

package_control/download_manager.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import os
12
import re
23
import socket
34
import sys
45
from threading import Lock, Timer
5-
from urllib.parse import unquote, urljoin, urlparse
6+
from urllib.parse import unquote_to_bytes, urljoin, urlparse
67

78
from . import __version__
89
from . import text
@@ -69,7 +70,7 @@ def http_get(url, settings, error_message='', prefer_cached=False):
6970

7071
if url[:8].lower() == "file:///":
7172
try:
72-
with open(unquote(url[8:]), "rb") as f:
73+
with open(from_uri(url), "rb") as f:
7374
return f.read()
7475
except OSError as e:
7576
raise DownloaderException(str(e))
@@ -88,6 +89,33 @@ def http_get(url, settings, error_message='', prefer_cached=False):
8889
return result
8990

9091

92+
def from_uri(uri: str) -> str: # roughly taken from Python 3.13
93+
"""Return a new path from the given 'file' URI."""
94+
if not uri.lower().startswith('file:'):
95+
raise ValueError("URI does not start with 'file:': {uri!r}".format(uri=uri))
96+
path = os.fsdecode(unquote_to_bytes(uri))
97+
path = path[5:]
98+
if path[:3] == '///':
99+
# Remove empty authority
100+
path = path[2:]
101+
elif path[:12].lower() == '//localhost/':
102+
# Remove 'localhost' authority
103+
path = path[11:]
104+
if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'):
105+
# Remove slash before DOS device/UNC path
106+
path = path[1:]
107+
path = path[0].upper() + path[1:]
108+
if path[1:2] == '|':
109+
# Replace bar with colon in DOS drive
110+
path = path[:1] + ':' + path[2:]
111+
if not os.path.isabs(path):
112+
raise ValueError(
113+
"URI is not absolute: {uri!r}. Parsed so far: {path!r}"
114+
.format(uri=uri, path=path)
115+
)
116+
return path
117+
118+
91119
def _grab(url, settings):
92120
global _http_cache, _managers, _lock, _in_use, _timer
93121

0 commit comments

Comments
 (0)