Skip to content

Commit e3593c5

Browse files
authored
Let requests handle url encoding (#610)
Previously, if a plugin url was like "https://mypluginrepo.example.com/download.php?plugin=MyPlugin", it raises an error as "?" and "=" where encoded `Trace: 404 Client Error: Not Found for url: https://mypluginrepo.example.com/download.php%3Fplugin%3DMyPlugin` The issue is that quote() would encode the entire URL including the protocol and special characters that should remain unencoded (like ://?=). This would break the URL structure. `download_remote_file_to_local` use `requests`. Let it handle the encoding part and provide instead the base url and query params.
2 parents a66a069 + a30f748 commit e3593c5

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

qgis_deployment_toolbelt/plugins/plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from enum import Enum
2323
from os.path import expanduser, expandvars
2424
from pathlib import Path
25-
from urllib.parse import quote, urlsplit, urlunsplit
25+
from urllib.parse import urlsplit, urlunsplit
2626

2727
# 3rd party
2828
from packaging.version import InvalidVersion, Version
@@ -200,7 +200,7 @@ def download_url(self) -> str:
200200
str: download URL
201201
"""
202202
if self.url:
203-
return quote(self.url, safe="/:")
203+
return self.url
204204
elif self.repository_url_xml and self.folder_name and self.version:
205205
split_url = urlsplit(self.repository_url_xml)
206206
new_url = split_url._replace(path=split_url.path.replace("plugins.xml", ""))

qgis_deployment_toolbelt/utils/file_downloader.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import warnings
1212
from os import getenv
1313
from pathlib import Path
14+
from urllib.parse import parse_qs, unquote, urlparse
1415

1516
# 3rd party
1617
import truststore
@@ -131,8 +132,19 @@ def download_remote_file_to_local(
131132
)
132133
dl_session.mount("https://", TruststoreAdapter())
133134

135+
# Clean url
136+
parsed_url = urlparse(unquote(remote_url_to_download))
137+
# Reconstruct base URL
138+
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
139+
# Get existing params if any exist
140+
params = (
141+
parse_qs(parsed_url.query, keep_blank_values=True)
142+
if parsed_url.query
143+
else {}
144+
)
134145
with dl_session.get(
135-
url=requote_uri(remote_url_to_download),
146+
url=base_url,
147+
params=params,
136148
stream=use_stream,
137149
timeout=timeout,
138150
) as req:

tests/test_utils_file_downloader.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
# standard library
14+
import filecmp
1415
import tempfile
1516
import unittest
1617
from pathlib import Path
@@ -57,6 +58,30 @@ def test_download_file_exists(self):
5758
self.assertTrue(downloaded_file.exists())
5859
self.assertTrue(downloaded_file.is_file())
5960

61+
def test_download_url_with_params(self):
62+
"""Test download remote file locally from url with params"""
63+
with tempfile.TemporaryDirectory(
64+
prefix="qdt_test_downloader_", ignore_cleanup_errors=True
65+
) as tmpdirname:
66+
# file that already exist locally
67+
downloaded_file_one = download_remote_file_to_local(
68+
remote_url_to_download="https://raw.githubusercontent.com/qgis-deployment/qgis-deployment-toolbelt-cli/main/README.md",
69+
local_file_path=Path(tmpdirname).joinpath("README1.md"),
70+
)
71+
self.assertIsInstance(downloaded_file_one, Path)
72+
self.assertTrue(downloaded_file_one.exists())
73+
self.assertTrue(downloaded_file_one.is_file())
74+
75+
downloaded_file_two = download_remote_file_to_local(
76+
remote_url_to_download="https://github.com/qgis-deployment/qgis-deployment-toolbelt-cli/blob/main/README.md?raw=true",
77+
local_file_path=Path(tmpdirname).joinpath("README2.md"),
78+
)
79+
self.assertIsInstance(downloaded_file_two, Path)
80+
self.assertTrue(downloaded_file_two.exists())
81+
self.assertTrue(downloaded_file_two.is_file())
82+
83+
self.assertTrue(filecmp.cmp(downloaded_file_one, downloaded_file_two))
84+
6085
def test_download_file_raise_http_error(self):
6186
"""Test download handling an HTTP error."""
6287
with tempfile.TemporaryDirectory(

0 commit comments

Comments
 (0)