Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions imagekitio/constants/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ class Default(enum.Enum):
TRANSFORM_KEY_VALUE_DELIMITER = "-"
SIGNATURE_PARAMETER = "ik-s"
TIMESTAMP_PARAMETER = "ik-t"
IGNORE_CHARACTERS = '~@#$&()*!+=:;,?/\''
22 changes: 20 additions & 2 deletions imagekitio/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from datetime import datetime as dt
from typing import Any, Dict, List
from urllib.parse import ParseResult, urlparse, urlunparse, parse_qsl, urlencode
from urllib.parse import ParseResult, urlparse, urlunparse, parse_qsl, urlencode, quote, unquote

from .constants.defaults import Default
from .constants.supported_transform import SUPPORTED_TRANS
Expand Down Expand Up @@ -142,7 +142,7 @@ def get_signature(private_key, url, url_endpoint, expiry_timestamp: int) -> str:
expiry_timestamp = Default.DEFAULT_TIMESTAMP.value

replaced_url = url.replace(url_endpoint, "") + str(expiry_timestamp)

replaced_url = Url.encode_string_if_required(replaced_url)
signature = hmac.new(
key=private_key.encode(), msg=replaced_url.encode(), digestmod=hashlib.sha1
)
Expand Down Expand Up @@ -211,3 +211,21 @@ def transformation_to_str(transformation):
)

return Default.CHAIN_TRANSFORM_DELIMITER.value.join(parsed_transforms)

@staticmethod
def encodeURI(url_str):
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
if "?" in url_str:
# here we are not encoding query parameters as it is allready encoded
encoded_url = quote(url_str.split('?')[0], safe=Default.IGNORE_CHARACTERS.value)+"?"+url_str.split('?')[1]
else:
encoded_url = quote(url_str, safe=Default.IGNORE_CHARACTERS.value)
return encoded_url

@staticmethod
def has_more_than_ascii(s):
return any(ord(char) > 127 for char in s)

@staticmethod
def encode_string_if_required(s):
return Url.encodeURI(s) if Url.has_more_than_ascii(s) else s
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setuptools.setup(
name="imagekitio",
version="4.0.0",
version="4.0.1",
description="Python wrapper for the ImageKit API",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
86 changes: 85 additions & 1 deletion tests/test_generate_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from imagekitio.client import ImageKit
from imagekitio.constants.defaults import Default

from imagekitio.url import Url

class TestGenerateURL(unittest.TestCase):
def setUp(self) -> None:
Expand Down Expand Up @@ -326,6 +326,90 @@ def test_url_signed_with_expire_in_seconds(self):
url = self.client.url(options)
self.assertIn("ik-t", url)

def test_url_signed_with_diacritic_in_filename(self):
url = "https://test-domain.com/test-endpoint/test_é_path_alt.jpg"
encodedUrl = Url.encode_string_if_required(url)
self.assertEqual(
encodedUrl,
"https://test-domain.com/test-endpoint/test_%C3%A9_path_alt.jpg",
)
signature = Url.get_signature("private_key_test", url, "https://test-domain.com/test-endpoint", 9999999999)
options = {
"path": "/test_é_path_alt.jpg",
"signed": True,
}
url = self.client.url(options)
self.assertEqual(
url,
"https://test-domain.com/test-endpoint/test_é_path_alt.jpg?ik-s="+signature,
)

def test_url_signed_with_diacritic_in_filename_and_path(self):
url = "https://test-domain.com/test-endpoint/aéb/test_é_path_alt.jpg"
encodedUrl = Url.encode_string_if_required(url)
self.assertEqual(
encodedUrl,
"https://test-domain.com/test-endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg",
)
signature = Url.get_signature("private_key_test", url, "https://test-domain.com/test-endpoint", 9999999999)
options = {
"path": "/aéb/test_é_path_alt.jpg",
"signed": True,
}
url = self.client.url(options)
self.assertEqual(
url,
"https://test-domain.com/test-endpoint/aéb/test_é_path_alt.jpg?ik-s="+signature,
)

def test_url_signed_with_diacritic_in_filename_path_transforamtion_in_path(self):
url = "https://test-domain.com/test-endpoint/tr:l-text,i-Imagekité,fs-50,l-end/aéb/test_é_path_alt.jpg"
encodedUrl = Url.encode_string_if_required(url)
self.assertEqual(
encodedUrl,
"https://test-domain.com/test-endpoint/tr:l-text,i-Imagekit%C3%A9,fs-50,l-end/a%C3%A9b/test_%C3%A9_path_alt.jpg",
)
signature = Url.get_signature("private_key_test", url, "https://test-domain.com/test-endpoint", 9999999999)
options = {
"path": "/aéb/test_é_path_alt.jpg",
"transformation": [
{
"raw": "l-text,i-Imagekité,fs-50,l-end"
},
],
"signed": True,
"transformation_position": "path"
}
url = self.client.url(options)
self.assertEqual(
url,
"https://test-domain.com/test-endpoint/tr:l-text,i-Imagekité,fs-50,l-end/aéb/test_é_path_alt.jpg?ik-s="+signature,
)

def test_url_signed_with_diacritic_in_filename_path_transforamtion_in_query(self):
url = "https://test-domain.com/test-endpoint/aéb/test_é_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end"
encodedUrl = Url.encode_string_if_required(url)
self.assertEqual(
encodedUrl,
"https://test-domain.com/test-endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end",
)
signature = Url.get_signature("private_key_test", url, "https://test-domain.com/test-endpoint", 9999999999)
options = {
"path": "/aéb/test_é_path_alt.jpg",
"transformation": [
{
"raw": "l-text,i-Imagekité,fs-50,l-end"
},
],
"signed": True,
"transformation_position": "query"
}
url = self.client.url(options)
self.assertEqual(
url,
"https://test-domain.com/test-endpoint/aéb/test_é_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end&ik-s="+signature,
)

def test_generate_url_with_path_and_src_uses_path(self):
"""
In case when both path and src fields are provided, the `path` should be preferred
Expand Down