Skip to content

Commit 78adef0

Browse files
Use parse.quote for URL formatting (#309)
## 📝 Description This change enforces path escaping for all string formatted URL endpoints. ## ✔️ How to Test ``` pytest test ```
1 parent 5292b79 commit 78adef0

8 files changed

Lines changed: 51 additions & 17 deletions

File tree

linode_api4/groups/object_storage.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from urllib import parse
2+
13
from linode_api4.errors import UnexpectedResponseError
24
from linode_api4.groups import Group
35
from linode_api4.objects import (
@@ -327,7 +329,7 @@ def object_url_create(
327329

328330
result = self.client.post(
329331
"/object-storage/buckets/{}/{}/object-url".format(
330-
cluster_id, bucket
332+
parse.quote(str(cluster_id)), parse.quote(str(bucket))
331333
),
332334
data=drop_null_keys(params),
333335
)

linode_api4/linode_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import logging
55
from typing import BinaryIO, Tuple
6+
from urllib import parse
67

78
import pkg_resources
89
import requests
@@ -227,7 +228,10 @@ def _api_call(
227228
raise ValueError("Method is required for API calls!")
228229

229230
if model:
230-
endpoint = endpoint.format(**vars(model))
231+
endpoint = endpoint.format(
232+
**{k: parse.quote(str(v)) for k, v in vars(model).items()}
233+
)
234+
231235
url = "{}{}".format(self.base_url, endpoint)
232236
headers = {
233237
"Authorization": "Bearer {}".format(self.token),

linode_api4/objects/linode.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from enum import Enum
55
from os import urandom
66
from random import randint
7+
from urllib import parse
78

89
from linode_api4 import util
910
from linode_api4.common import load_and_validate_keys
@@ -590,7 +591,11 @@ def transfer_year_month(self, year, month):
590591
"""
591592

592593
result = self._client.get(
593-
"{}/transfer/{}/{}".format(Instance.api_endpoint, year, month),
594+
"{}/transfer/{}/{}".format(
595+
Instance.api_endpoint,
596+
parse.quote(str(year)),
597+
parse.quote(str(month)),
598+
),
594599
model=self,
595600
)
596601

@@ -1415,7 +1420,9 @@ def stats_for(self, dt):
14151420
if not isinstance(dt, datetime):
14161421
raise TypeError("stats_for requires a datetime object!")
14171422
return self._client.get(
1418-
"{}/stats/{}".format(Instance.api_endpoint, dt.strftime("%Y/%m")),
1423+
"{}/stats/{}".format(
1424+
Instance.api_endpoint, parse.quote(dt.strftime("%Y/%m"))
1425+
),
14191426
model=self,
14201427
)
14211428

linode_api4/objects/lke.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from urllib import parse
2+
13
from linode_api4.errors import UnexpectedResponseError
24
from linode_api4.objects import (
35
Base,
@@ -254,7 +256,10 @@ def node_view(self, nodeId):
254256
"""
255257

256258
node = self._client.get(
257-
"{}/nodes/{}".format(LKECluster.api_endpoint, nodeId), model=self
259+
"{}/nodes/{}".format(
260+
LKECluster.api_endpoint, parse.quote(str(nodeId))
261+
),
262+
model=self,
258263
)
259264

260265
return LKENodePoolNode(self._client, node)
@@ -270,7 +275,10 @@ def node_delete(self, nodeId):
270275
"""
271276

272277
self._client.delete(
273-
"{}/nodes/{}".format(LKECluster.api_endpoint, nodeId), model=self
278+
"{}/nodes/{}".format(
279+
LKECluster.api_endpoint, parse.quote(str(nodeId))
280+
),
281+
model=self,
274282
)
275283

276284
def node_recycle(self, nodeId):
@@ -284,7 +292,9 @@ def node_recycle(self, nodeId):
284292
"""
285293

286294
self._client.post(
287-
"{}/nodes/{}/recycle".format(LKECluster.api_endpoint, nodeId),
295+
"{}/nodes/{}/recycle".format(
296+
LKECluster.api_endpoint, parse.quote(str(nodeId))
297+
),
288298
model=self,
289299
)
290300

linode_api4/objects/networking.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class IPv6Pool(Base):
77
DEPRECATED
88
"""
99

10-
api_endpoint = "/networking/ipv6/pools/{}"
10+
api_endpoint = "/networking/ipv6/pools/{range}"
1111
id_attribute = "range"
1212

1313
properties = {
@@ -101,7 +101,7 @@ class VLAN(Base):
101101
API Documentation: https://www.linode.com/docs/api/networking/#vlans-list
102102
"""
103103

104-
api_endpoint = "/networking/vlans/{}"
104+
api_endpoint = "/networking/vlans/{label}"
105105
id_attribute = "label"
106106

107107
properties = {

linode_api4/objects/nodebalancer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from urllib import parse
23

34
from linode_api4.errors import UnexpectedResponseError
45
from linode_api4.objects import (
@@ -271,7 +272,7 @@ def config_rebuild(self, config_id, nodes, **kwargs):
271272

272273
result = self._client.post(
273274
"{}/configs/{}/rebuild".format(
274-
NodeBalancer.api_endpoint, config_id
275+
NodeBalancer.api_endpoint, parse.quote(str(config_id))
275276
),
276277
model=self,
277278
data=params,

linode_api4/objects/object_storage.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from urllib import parse
2+
13
from linode_api4.errors import UnexpectedResponseError
24
from linode_api4.objects import (
35
Base,
@@ -99,7 +101,7 @@ def access_modify(
99101

100102
resp = self._client.post(
101103
"/object-storage/buckets/{}/{}/access".format(
102-
self.cluster, self.id
104+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
103105
),
104106
data=drop_null_keys(params),
105107
)
@@ -147,7 +149,7 @@ def access_update(
147149

148150
resp = self._client.put(
149151
"/object-storage/buckets/{}/{}/access".format(
150-
self.cluster, self.id
152+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
151153
),
152154
data=drop_null_keys(params),
153155
)
@@ -177,7 +179,9 @@ def ssl_cert_delete(self):
177179
"""
178180

179181
resp = self._client.delete(
180-
"/object-storage/buckets/{}/{}/ssl".format(self.cluster, self.id)
182+
"/object-storage/buckets/{}/{}/ssl".format(
183+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
184+
)
181185
)
182186

183187
if "error" in resp:
@@ -206,7 +210,9 @@ def ssl_cert(self):
206210
:rtype: MappedObject
207211
"""
208212
result = self._client.get(
209-
"/object-storage/buckets/{}/{}/ssl".format(self.cluster, self.id)
213+
"/object-storage/buckets/{}/{}/ssl".format(
214+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
215+
)
210216
)
211217

212218
if not "ssl" in result:
@@ -253,7 +259,9 @@ def ssl_cert_upload(self, certificate, private_key):
253259
"private_key": private_key,
254260
}
255261
result = self._client.post(
256-
"/object-storage/buckets/{}/{}/ssl".format(self.cluster, self.id),
262+
"/object-storage/buckets/{}/{}/ssl".format(
263+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
264+
),
257265
data=params,
258266
)
259267

@@ -325,7 +333,7 @@ def contents(
325333
}
326334
result = self._client.get(
327335
"/object-storage/buckets/{}/{}/object-list".format(
328-
self.cluster, self.id
336+
parse.quote(str(self.cluster)), parse.quote(str(self.id))
329337
),
330338
data=drop_null_keys(params),
331339
)
@@ -492,7 +500,9 @@ def buckets_in_cluster(self, *filters):
492500
return self._client._get_and_filter(
493501
ObjectStorageBucket,
494502
*filters,
495-
endpoint="/object-storage/buckets/{}".format(self.id),
503+
endpoint="/object-storage/buckets/{}".format(
504+
parse.quote(str(self.id))
505+
),
496506
)
497507

498508

test/fixtures/networking_ipv6_ranges_2600:3c01::.json renamed to test/fixtures/networking_ipv6_ranges_2600%3A3c01%3A%3A.json

File renamed without changes.

0 commit comments

Comments
 (0)