From b4dac5ce33843cf52badeb9faf0f7f52f20a9a6a Mon Sep 17 00:00:00 2001 From: Emanuele Aina Date: Thu, 25 Feb 2021 22:53:11 +0100 Subject: [PATCH] fix: handle tags like debian/2%2.6-21 as identifiers Git refnames are relatively free-form and can contain all sort for special characters, not just `/` and `#`, see http://git-scm.com/docs/git-check-ref-format In particular, Debian's DEP-14 standard for storing packaging in git repositories mandates the use of the `%` character in tags in some cases like `debian/2%2.6-21`. Unfortunately python-gitlab currently only escapes `/` to `%2F` and in some cases `#` to `%23`. This means that when using the commit API to retrieve information about the `debian/2%2.6-21` tag only the slash is escaped before being inserted in the URL path and the `%` is left untouched, resulting in something like `/api/v4/projects/123/repository/commits/debian%2F2%2.6-21`. When urllib3 seees that it detects the invalid `%` escape and then urlencodes the whole string, resulting in `/api/v4/projects/123/repository/commits/debian%252F2%252.6-21`, where the original `/` got escaped twice and produced `%252F`. To avoid the issue, fully urlencode identifiers and parameters to avoid the urllib3 auto-escaping in all cases. Signed-off-by: Emanuele Aina --- gitlab/tests/test_utils.py | 8 ++++++++ gitlab/utils.py | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/gitlab/tests/test_utils.py b/gitlab/tests/test_utils.py index 50aaecf2a..5a8148c12 100644 --- a/gitlab/tests/test_utils.py +++ b/gitlab/tests/test_utils.py @@ -27,6 +27,10 @@ def test_clean_str_id(): dest = "foo%23bar%2Fbaz%2F" assert dest == utils.clean_str_id(src) + src = "foo%bar/baz/" + dest = "foo%25bar%2Fbaz%2F" + assert dest == utils.clean_str_id(src) + def test_sanitized_url(): src = "http://localhost/foo/bar" @@ -48,6 +52,10 @@ def test_sanitize_parameters_slash(): assert "foo%2Fbar" == utils.sanitize_parameters("foo/bar") +def test_sanitize_parameters_slash_and_percent(): + assert "foo%2Fbar%25quuz" == utils.sanitize_parameters("foo/bar%quuz") + + def test_sanitize_parameters_dict(): source = {"url": "foo/bar", "id": 1} expected = {"url": "foo%2Fbar", "id": 1} diff --git a/gitlab/utils.py b/gitlab/utils.py index 987f1d375..45a4af8f1 100644 --- a/gitlab/utils.py +++ b/gitlab/utils.py @@ -16,7 +16,7 @@ # along with this program. If not, see . from typing import Any, Callable, Dict, Optional -from urllib.parse import urlparse +from urllib.parse import quote, urlparse import requests @@ -57,14 +57,14 @@ def copy_dict(dest: Dict[str, Any], src: Dict[str, Any]) -> None: def clean_str_id(id: str) -> str: - return id.replace("/", "%2F").replace("#", "%23") + return quote(id, safe="") def sanitize_parameters(value): if isinstance(value, dict): return dict((k, sanitize_parameters(v)) for k, v in value.items()) if isinstance(value, str): - return value.replace("/", "%2F") + return quote(value, safe="") return value