Skip to content

Commit 0016c2e

Browse files
author
Marius Kleidl
committed
Allow duplicate URL query parameters
1 parent f87b0b6 commit 0016c2e

File tree

2 files changed

+21
-13
lines changed

2 files changed

+21
-13
lines changed

tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ def test_get_signed_smart_cdn_url(self):
107107
input="foo/input",
108108
url_params={
109109
"foo": "bar",
110-
"aaa": 42 # Should be sorted as first param
110+
"aaa": [42, 21] # Should be sorted before `foo`
111111
}
112112
)
113113

114114
expected_url = (
115-
"https://foo_workspace.tlcdn.com/foo_template/foo%2Finput?aaa=42&auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256:995dd1aae135fb77fa98b0e6946bd9768e0443a6028eba0361c03807e8fb68a5"
115+
"https://foo_workspace.tlcdn.com/foo_template/foo%2Finput?aaa=42&aaa=21&auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256%3A9a8df3bb28eea621b46ec808a250b7903b2546be7e66c048956d4f30b8da7519"
116116
)
117117

118118
self.assertEqual(url, expected_url)

transloadit/client.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,35 +189,43 @@ def get_signed_smart_cdn_url(
189189
- workspace (str): Workspace slug
190190
- template (str): Template slug or template ID
191191
- input (str): Input value that is provided as ${fields.input} in the template
192-
- url_params (Optional[dict]): Additional parameters for the URL query string
192+
- url_params (Optional[dict]): Additional parameters for the URL query string. Values can be strings, numbers, booleans or arrays thereof.
193193
- expires_in (Optional[int]): Expiration time of signature in milliseconds. Defaults to 1 hour.
194194
195195
:Returns:
196196
str: The signed Smart CDN URL
197+
198+
:Raises:
199+
ValueError: If url_params contains values that are not strings, numbers, booleans or arrays
197200
"""
198201
workspace_slug = quote_plus(workspace)
199202
template_slug = quote_plus(template)
200203
input_field = quote_plus(input)
201204

202-
# Convert url_params values to strings
203-
params = {}
205+
params = []
204206
if url_params:
205-
params.update({k: str(v) for k, v in url_params.items()})
207+
for k, v in url_params.items():
208+
if isinstance(v, (str, int, float, bool)):
209+
params.append((k, str(v)))
210+
elif isinstance(v, (list, tuple)):
211+
params.append((k, [str(vv) for vv in v]))
212+
else:
213+
raise ValueError(f"URL parameter values must be strings, numbers, booleans or arrays. Got {type(v)} for {k}")
206214

207-
params["auth_key"] = self.auth_key
208-
params["exp"] = str(int(time.time() * 1000) + expires_in)
215+
params.append(("auth_key", self.auth_key))
216+
params.append(("exp", str(int(time.time() * 1000) + expires_in)))
209217

210-
# Sort params alphabetically
211-
sorted_params = dict(sorted(params.items()))
212-
query_string = urlencode(sorted_params)
218+
# Sort params alphabetically by key
219+
sorted_params = sorted(params, key=lambda x: x[0])
220+
query_string = urlencode(sorted_params, doseq=True)
213221

214222
string_to_sign = f"{workspace_slug}/{template_slug}/{input_field}?{query_string}"
215223
algorithm = "sha256"
216224

217-
signature = hmac.new(
225+
signature = algorithm + ":" + hmac.new(
218226
self.auth_secret.encode("utf-8"),
219227
string_to_sign.encode("utf-8"),
220228
hashlib.sha256
221229
).hexdigest()
222230

223-
return f"https://{workspace_slug}.tlcdn.com/{template_slug}/{input_field}?{query_string}&sig={algorithm}:{signature}"
231+
return f"https://{workspace_slug}.tlcdn.com/{template_slug}/{input_field}?{query_string}&sig={quote_plus(signature)}"

0 commit comments

Comments
 (0)