summaryrefslogtreecommitdiff
path: root/gitlab/utils.py
diff options
context:
space:
mode:
authorJohn L. Villalovos <john@sodarock.com>2022-01-09 00:53:12 -0800
committerJohn L. Villalovos <john@sodarock.com>2022-01-09 00:53:12 -0800
commit99e1f8bad3e52ca6fb2dc6b876a262c6fee39e41 (patch)
tree6a11bdaf3e49a3dec844255d74a860798e8acc68 /gitlab/utils.py
parentac812727c26c9bde4ee5c1115029f2ff4ab1964b (diff)
downloadgitlab-jlvillal/encoded_id_alt.tar.gz
fix: use url-encoded ID in all paths ALTERNATE METHODjlvillal/encoded_id_alt
An alternative to https://github.com/python-gitlab/python-gitlab/pull/1819 Make sure all usage of the ID in the URL path is encoded. Normally it isn't an issue as most IDs are integers or strings which don't contain a slash ('/'). But when the ID is a string with a slash character it will break things. Add a test case that shows this fixes wikis issue with subpages which use the slash character. Closes: #1079
Diffstat (limited to 'gitlab/utils.py')
-rw-r--r--gitlab/utils.py63
1 files changed, 54 insertions, 9 deletions
diff --git a/gitlab/utils.py b/gitlab/utils.py
index 1f29104..5444e28 100644
--- a/gitlab/utils.py
+++ b/gitlab/utils.py
@@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib.parse
-from typing import Any, Callable, Dict, Optional
+from typing import Any, Callable, Dict, Optional, Union
import requests
@@ -56,8 +56,13 @@ def copy_dict(dest: Dict[str, Any], src: Dict[str, Any]) -> None:
dest[k] = v
-def _url_encode(id: str) -> str:
- """Encode/quote the characters in the string so that they can be used in a path.
+class EncodedId(str):
+ """A custom `str` class that will return the URL-encoded value of the string.
+
+ Features:
+ * Using it recursively will only url-encode the value once.
+ * Can accept either `str` or `int` as input value.
+ * Can be used in an f-string and output the URL-encoded string.
Reference to documentation on why this is necessary.
@@ -69,12 +74,52 @@ def _url_encode(id: str) -> str:
https://docs.gitlab.com/ee/api/index.html#path-parameters
Path parameters that are required to be URL-encoded must be followed. If not, it
- doesn’t match an API endpoint and responds with a 404. If there’s something in front
- of the API (for example, Apache), ensure that it doesn’t decode the URL-encoded path
- parameters.
-
- """
- return urllib.parse.quote(id, safe="")
+ doesn’t match an API endpoint and responds with a 404. If there’s something in
+ front of the API (for example, Apache), ensure that it doesn’t decode the
+ URL-encoded path parameters."""
+
+ # `original_str` will contain the original string value that was used to create the
+ # first instance of EncodedId. We will use this original value to generate the
+ # URL-encoded value each time.
+ original_str: str
+
+ def __init__(self, value: Union[int, str]) -> None:
+ # At this point `super().__str__()` returns the URL-encoded value. Which means
+ # when using this as a `str` it will return the URL-encoded value.
+ #
+ # But `value` contains the original value passed in `EncodedId(value)`. We use
+ # this to always keep the original string that was received so that no matter
+ # how many times we recurse we only URL-encode our original string once.
+ if isinstance(value, int):
+ value = str(value)
+ # Make sure isinstance() for `EncodedId` comes before check for `str` as
+ # `EncodedId` is an instance of `str` and would pass that check.
+ elif isinstance(value, EncodedId):
+ # This is the key part as we are always keeping the original string even
+ # through multiple recursions.
+ value = value.original_str
+ elif isinstance(value, str):
+ pass
+ else:
+ raise ValueError(f"Unsupported type received: {type(value)}")
+ self.original_str = value
+ super().__init__()
+
+ def __new__(cls, value: Union[str, int, "EncodedId"]) -> "EncodedId":
+ if isinstance(value, int):
+ value = str(value)
+ # Make sure isinstance() for `EncodedId` comes before check for `str` as
+ # `EncodedId` is an instance of `str` and would pass that check.
+ elif isinstance(value, EncodedId):
+ # We use the original string value to URL-encode
+ value = value.original_str
+ elif isinstance(value, str):
+ pass
+ else:
+ raise ValueError(f"Unsupported type received: {type(value)}")
+ # Set the value our string will return
+ value = urllib.parse.quote(value, safe="")
+ return super().__new__(cls, value)
def remove_none_from_dict(data: Dict[str, Any]) -> Dict[str, Any]: