Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit99e1f8b

Browse files
fix: use url-encoded ID in all paths ALTERNATE METHOD
An alternative to#1819Make sure all usage of the ID in the URL path is encoded. Normally itisn't an issue as most IDs are integers or strings which don't containa slash ('/'). But when the ID is a string with a slash character itwill break things.Add a test case that shows this fixes wikis issue with subpages whichuse the slash character.Closes:#1079
1 parentac81272 commit99e1f8b

21 files changed

+230
-133
lines changed

‎gitlab/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ def get_id(self) -> Any:
208208
returnNone
209209
returngetattr(self,self._id_attr)
210210

211+
@property
212+
defencoded_id(self)->Any:
213+
"""Ensure that the ID is url-encoded so that it can be safely used in a URL
214+
path"""
215+
obj_id=self.get_id()
216+
ifisinstance(obj_id,str):
217+
obj_id=gitlab.utils.EncodedId(obj_id)
218+
returnobj_id
219+
211220
@property
212221
defattributes(self)->Dict[str,Any]:
213222
d=self.__dict__["_updated_attrs"].copy()

‎gitlab/mixins.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ def get(
9999
GitlabAuthenticationError: If authentication is not correct
100100
GitlabGetError: If the server cannot perform the request
101101
"""
102-
ifnotisinstance(id,int):
103-
id=utils._url_encode(id)
104-
path=f"{self.path}/{id}"
102+
path=f"{self.path}/{utils.EncodedId(id)}"
105103
ifTYPE_CHECKING:
106104
assertself._obj_clsisnotNone
107105
iflazyisTrue:
@@ -173,7 +171,7 @@ def refresh(self, **kwargs: Any) -> None:
173171
GitlabGetError: If the server cannot perform the request
174172
"""
175173
ifself._id_attr:
176-
path=f"{self.manager.path}/{self.id}"
174+
path=f"{self.manager.path}/{self.encoded_id}"
177175
else:
178176
ifTYPE_CHECKING:
179177
assertself.manager.pathisnotNone
@@ -391,7 +389,7 @@ def update(
391389
ifidisNone:
392390
path=self.path
393391
else:
394-
path=f"{self.path}/{id}"
392+
path=f"{self.path}/{utils.EncodedId(id)}"
395393

396394
self._check_missing_update_attrs(new_data)
397395
files= {}
@@ -444,7 +442,7 @@ def set(self, key: str, value: str, **kwargs: Any) -> base.RESTObject:
444442
Returns:
445443
The created/updated attribute
446444
"""
447-
path=f"{self.path}/{utils._url_encode(key)}"
445+
path=f"{self.path}/{utils.EncodedId(key)}"
448446
data= {"value":value}
449447
server_data=self.gitlab.http_put(path,post_data=data,**kwargs)
450448
ifTYPE_CHECKING:
@@ -477,9 +475,7 @@ def delete(self, id: Union[str, int], **kwargs: Any) -> None:
477475
ifidisNone:
478476
path=self.path
479477
else:
480-
ifnotisinstance(id,int):
481-
id=utils._url_encode(id)
482-
path=f"{self.path}/{id}"
478+
path=f"{self.path}/{utils.EncodedId(id)}"
483479
self.gitlab.http_delete(path,**kwargs)
484480

485481

@@ -545,7 +541,7 @@ def save(self, **kwargs: Any) -> None:
545541
return
546542

547543
# call the manager
548-
obj_id=self.get_id()
544+
obj_id=self.encoded_id
549545
ifTYPE_CHECKING:
550546
assertisinstance(self.manager,UpdateMixin)
551547
server_data=self.manager.update(obj_id,updated_data,**kwargs)
@@ -575,7 +571,7 @@ def delete(self, **kwargs: Any) -> None:
575571
"""
576572
ifTYPE_CHECKING:
577573
assertisinstance(self.manager,DeleteMixin)
578-
self.manager.delete(self.get_id(),**kwargs)
574+
self.manager.delete(self.encoded_id,**kwargs)
579575

580576

581577
classUserAgentDetailMixin(_RestObjectBase):
@@ -598,7 +594,7 @@ def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]:
598594
GitlabAuthenticationError: If authentication is not correct
599595
GitlabGetError: If the server cannot perform the request
600596
"""
601-
path=f"{self.manager.path}/{self.get_id()}/user_agent_detail"
597+
path=f"{self.manager.path}/{self.encoded_id}/user_agent_detail"
602598
result=self.manager.gitlab.http_get(path,**kwargs)
603599
ifTYPE_CHECKING:
604600
assertnotisinstance(result,requests.Response)
@@ -631,7 +627,7 @@ def approve(
631627
GitlabUpdateError: If the server fails to perform the request
632628
"""
633629

634-
path=f"{self.manager.path}/{self.id}/approve"
630+
path=f"{self.manager.path}/{self.encoded_id}/approve"
635631
data= {"access_level":access_level}
636632
server_data=self.manager.gitlab.http_put(path,post_data=data,**kwargs)
637633
ifTYPE_CHECKING:
@@ -705,7 +701,7 @@ def subscribe(self, **kwargs: Any) -> None:
705701
GitlabAuthenticationError: If authentication is not correct
706702
GitlabSubscribeError: If the subscription cannot be done
707703
"""
708-
path=f"{self.manager.path}/{self.get_id()}/subscribe"
704+
path=f"{self.manager.path}/{self.encoded_id}/subscribe"
709705
server_data=self.manager.gitlab.http_post(path,**kwargs)
710706
ifTYPE_CHECKING:
711707
assertnotisinstance(server_data,requests.Response)
@@ -725,7 +721,7 @@ def unsubscribe(self, **kwargs: Any) -> None:
725721
GitlabAuthenticationError: If authentication is not correct
726722
GitlabUnsubscribeError: If the unsubscription cannot be done
727723
"""
728-
path=f"{self.manager.path}/{self.get_id()}/unsubscribe"
724+
path=f"{self.manager.path}/{self.encoded_id}/unsubscribe"
729725
server_data=self.manager.gitlab.http_post(path,**kwargs)
730726
ifTYPE_CHECKING:
731727
assertnotisinstance(server_data,requests.Response)
@@ -752,7 +748,7 @@ def todo(self, **kwargs: Any) -> None:
752748
GitlabAuthenticationError: If authentication is not correct
753749
GitlabTodoError: If the todo cannot be set
754750
"""
755-
path=f"{self.manager.path}/{self.get_id()}/todo"
751+
path=f"{self.manager.path}/{self.encoded_id}/todo"
756752
self.manager.gitlab.http_post(path,**kwargs)
757753

758754

@@ -781,7 +777,7 @@ def time_stats(self, **kwargs: Any) -> Dict[str, Any]:
781777
if"time_stats"inself.attributes:
782778
returnself.attributes["time_stats"]
783779

784-
path=f"{self.manager.path}/{self.get_id()}/time_stats"
780+
path=f"{self.manager.path}/{self.encoded_id}/time_stats"
785781
result=self.manager.gitlab.http_get(path,**kwargs)
786782
ifTYPE_CHECKING:
787783
assertnotisinstance(result,requests.Response)
@@ -800,7 +796,7 @@ def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
800796
GitlabAuthenticationError: If authentication is not correct
801797
GitlabTimeTrackingError: If the time tracking update cannot be done
802798
"""
803-
path=f"{self.manager.path}/{self.get_id()}/time_estimate"
799+
path=f"{self.manager.path}/{self.encoded_id}/time_estimate"
804800
data= {"duration":duration}
805801
result=self.manager.gitlab.http_post(path,post_data=data,**kwargs)
806802
ifTYPE_CHECKING:
@@ -819,7 +815,7 @@ def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]:
819815
GitlabAuthenticationError: If authentication is not correct
820816
GitlabTimeTrackingError: If the time tracking update cannot be done
821817
"""
822-
path=f"{self.manager.path}/{self.get_id()}/reset_time_estimate"
818+
path=f"{self.manager.path}/{self.encoded_id}/reset_time_estimate"
823819
result=self.manager.gitlab.http_post(path,**kwargs)
824820
ifTYPE_CHECKING:
825821
assertnotisinstance(result,requests.Response)
@@ -838,7 +834,7 @@ def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
838834
GitlabAuthenticationError: If authentication is not correct
839835
GitlabTimeTrackingError: If the time tracking update cannot be done
840836
"""
841-
path=f"{self.manager.path}/{self.get_id()}/add_spent_time"
837+
path=f"{self.manager.path}/{self.encoded_id}/add_spent_time"
842838
data= {"duration":duration}
843839
result=self.manager.gitlab.http_post(path,post_data=data,**kwargs)
844840
ifTYPE_CHECKING:
@@ -857,7 +853,7 @@ def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]:
857853
GitlabAuthenticationError: If authentication is not correct
858854
GitlabTimeTrackingError: If the time tracking update cannot be done
859855
"""
860-
path=f"{self.manager.path}/{self.get_id()}/reset_spent_time"
856+
path=f"{self.manager.path}/{self.encoded_id}/reset_spent_time"
861857
result=self.manager.gitlab.http_post(path,**kwargs)
862858
ifTYPE_CHECKING:
863859
assertnotisinstance(result,requests.Response)
@@ -893,7 +889,7 @@ def participants(self, **kwargs: Any) -> Dict[str, Any]:
893889
The list of participants
894890
"""
895891

896-
path=f"{self.manager.path}/{self.get_id()}/participants"
892+
path=f"{self.manager.path}/{self.encoded_id}/participants"
897893
result=self.manager.gitlab.http_get(path,**kwargs)
898894
ifTYPE_CHECKING:
899895
assertnotisinstance(result,requests.Response)
@@ -967,7 +963,7 @@ def promote(self, **kwargs: Any) -> Dict[str, Any]:
967963
The updated object data (*not* a RESTObject)
968964
"""
969965

970-
path=f"{self.manager.path}/{self.id}/promote"
966+
path=f"{self.manager.path}/{self.encoded_id}/promote"
971967
http_method=self._get_update_method()
972968
result=http_method(path,**kwargs)
973969
ifTYPE_CHECKING:

‎gitlab/utils.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

1818
importurllib.parse
19-
fromtypingimportAny,Callable,Dict,Optional
19+
fromtypingimportAny,Callable,Dict,Optional,Union
2020

2121
importrequests
2222

@@ -56,8 +56,13 @@ def copy_dict(dest: Dict[str, Any], src: Dict[str, Any]) -> None:
5656
dest[k]=v
5757

5858

59-
def_url_encode(id:str)->str:
60-
"""Encode/quote the characters in the string so that they can be used in a path.
59+
classEncodedId(str):
60+
"""A custom `str` class that will return the URL-encoded value of the string.
61+
62+
Features:
63+
* Using it recursively will only url-encode the value once.
64+
* Can accept either `str` or `int` as input value.
65+
* Can be used in an f-string and output the URL-encoded string.
6166
6267
Reference to documentation on why this is necessary.
6368
@@ -69,12 +74,52 @@ def _url_encode(id: str) -> str:
6974
https://docs.gitlab.com/ee/api/index.html#path-parameters
7075
7176
Path parameters that are required to be URL-encoded must be followed. If not, it
72-
doesn’t match an API endpoint and responds with a 404. If there’s something in front
73-
of the API (for example, Apache), ensure that it doesn’t decode the URL-encoded path
74-
parameters.
75-
76-
"""
77-
returnurllib.parse.quote(id,safe="")
77+
doesn’t match an API endpoint and responds with a 404. If there’s something in
78+
front of the API (for example, Apache), ensure that it doesn’t decode the
79+
URL-encoded path parameters."""
80+
81+
# `original_str` will contain the original string value that was used to create the
82+
# first instance of EncodedId. We will use this original value to generate the
83+
# URL-encoded value each time.
84+
original_str:str
85+
86+
def__init__(self,value:Union[int,str])->None:
87+
# At this point `super().__str__()` returns the URL-encoded value. Which means
88+
# when using this as a `str` it will return the URL-encoded value.
89+
#
90+
# But `value` contains the original value passed in `EncodedId(value)`. We use
91+
# this to always keep the original string that was received so that no matter
92+
# how many times we recurse we only URL-encode our original string once.
93+
ifisinstance(value,int):
94+
value=str(value)
95+
# Make sure isinstance() for `EncodedId` comes before check for `str` as
96+
# `EncodedId` is an instance of `str` and would pass that check.
97+
elifisinstance(value,EncodedId):
98+
# This is the key part as we are always keeping the original string even
99+
# through multiple recursions.
100+
value=value.original_str
101+
elifisinstance(value,str):
102+
pass
103+
else:
104+
raiseValueError(f"Unsupported type received:{type(value)}")
105+
self.original_str=value
106+
super().__init__()
107+
108+
def__new__(cls,value:Union[str,int,"EncodedId"])->"EncodedId":
109+
ifisinstance(value,int):
110+
value=str(value)
111+
# Make sure isinstance() for `EncodedId` comes before check for `str` as
112+
# `EncodedId` is an instance of `str` and would pass that check.
113+
elifisinstance(value,EncodedId):
114+
# We use the original string value to URL-encode
115+
value=value.original_str
116+
elifisinstance(value,str):
117+
pass
118+
else:
119+
raiseValueError(f"Unsupported type received:{type(value)}")
120+
# Set the value our string will return
121+
value=urllib.parse.quote(value,safe="")
122+
returnsuper().__new__(cls,value)
78123

79124

80125
defremove_none_from_dict(data:Dict[str,Any])->Dict[str,Any]:

‎gitlab/v4/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def _process_from_parent_attrs(self) -> None:
7575
ifkeynotinself.args:
7676
continue
7777

78-
self.parent_args[key]=gitlab.utils._url_encode(self.args[key])
78+
self.parent_args[key]=gitlab.utils.EncodedId(self.args[key])
7979
# If we don't delete it then it will be added to the URL as a query-string
8080
delself.args[key]
8181

‎gitlab/v4/objects/commits.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def diff(self, **kwargs: Any) -> Union[gitlab.GitlabList, List[Dict[str, Any]]]:
4242
Returns:
4343
The changes done in this commit
4444
"""
45-
path=f"{self.manager.path}/{self.get_id()}/diff"
45+
path=f"{self.manager.path}/{self.encoded_id}/diff"
4646
returnself.manager.gitlab.http_list(path,**kwargs)
4747

4848
@cli.register_custom_action("ProjectCommit", ("branch",))
@@ -58,7 +58,7 @@ def cherry_pick(self, branch: str, **kwargs: Any) -> None:
5858
GitlabAuthenticationError: If authentication is not correct
5959
GitlabCherryPickError: If the cherry-pick could not be performed
6060
"""
61-
path=f"{self.manager.path}/{self.get_id()}/cherry_pick"
61+
path=f"{self.manager.path}/{self.encoded_id}/cherry_pick"
6262
post_data= {"branch":branch}
6363
self.manager.gitlab.http_post(path,post_data=post_data,**kwargs)
6464

@@ -80,7 +80,7 @@ def refs(
8080
Returns:
8181
The references the commit is pushed to.
8282
"""
83-
path=f"{self.manager.path}/{self.get_id()}/refs"
83+
path=f"{self.manager.path}/{self.encoded_id}/refs"
8484
query_data= {"type":type}
8585
returnself.manager.gitlab.http_list(path,query_data=query_data,**kwargs)
8686

@@ -101,7 +101,7 @@ def merge_requests(
101101
Returns:
102102
The merge requests related to the commit.
103103
"""
104-
path=f"{self.manager.path}/{self.get_id()}/merge_requests"
104+
path=f"{self.manager.path}/{self.encoded_id}/merge_requests"
105105
returnself.manager.gitlab.http_list(path,**kwargs)
106106

107107
@cli.register_custom_action("ProjectCommit", ("branch",))
@@ -122,7 +122,7 @@ def revert(
122122
Returns:
123123
The new commit data (*not* a RESTObject)
124124
"""
125-
path=f"{self.manager.path}/{self.get_id()}/revert"
125+
path=f"{self.manager.path}/{self.encoded_id}/revert"
126126
post_data= {"branch":branch}
127127
returnself.manager.gitlab.http_post(path,post_data=post_data,**kwargs)
128128

@@ -141,7 +141,7 @@ def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
141141
Returns:
142142
The commit's signature data
143143
"""
144-
path=f"{self.manager.path}/{self.get_id()}/signature"
144+
path=f"{self.manager.path}/{self.encoded_id}/signature"
145145
returnself.manager.gitlab.http_get(path,**kwargs)
146146

147147

‎gitlab/v4/objects/environments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def stop(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
3636
Returns:
3737
A dict of the result.
3838
"""
39-
path=f"{self.manager.path}/{self.get_id()}/stop"
39+
path=f"{self.manager.path}/{self.encoded_id}/stop"
4040
returnself.manager.gitlab.http_post(path,**kwargs)
4141

4242

‎gitlab/v4/objects/epics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def save(self, **kwargs: Any) -> None:
7272
return
7373

7474
# call the manager
75-
obj_id=self.get_id()
75+
obj_id=self.encoded_id
7676
self.manager.update(obj_id,updated_data,**kwargs)
7777

7878

‎gitlab/v4/objects/features.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ def set(
5252
Returns:
5353
The created/updated attribute
5454
"""
55-
name=utils._url_encode(name)
56-
path=f"{self.path}/{name}"
55+
path=f"{self.path}/{utils.EncodedId(name)}"
5756
data= {
5857
"value":value,
5958
"feature_group":feature_group,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp