Skip to content

Commit b56dd6c

Browse files
namsnathgaurpulkit
andcommitted
update: makes url path normalization case sensitive
- Updates `normalise_url_path_or_throw_error` to be case sensitive - URL paths will not be converted to lower-case, and will be kept as-is. Co-authored-by: Pulkit Gaur <gaurpulkit99@gmail.com>
1 parent 5b55b07 commit b56dd6c

File tree

3 files changed

+82
-112
lines changed

3 files changed

+82
-112
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
## [unreleased]
1010

1111
## [0.29.0] - 2025-02-17
12+
- **[Breaking] Makes URL path normalization case sensitive**
13+
- Updates `normalise_url_path_or_throw_error` to be case sensitive
14+
- URL paths will not be converted to lower-case, and will be kept as-is.
1215
- Fixes an issue where `removeDevice` API allowed removing TOTP devices without the user completing MFA.
1316
- Brings SDK in-line with Node SDK v21.1.0
1417

supertokens_python/normalised_url_path.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,35 @@ def is_a_recipe_path(self) -> bool:
4545

4646

4747
def normalise_url_path_or_throw_error(input_str: str) -> str:
48-
input_str = input_str.strip().lower()
48+
input_str = input_str.strip()
49+
input_str_lower = input_str.lower()
50+
4951
try:
50-
if not input_str.startswith("http://") and not input_str.startswith("https://"):
52+
if not input_str_lower.startswith(("http://", "https://")):
5153
raise Exception("converting to proper URL")
54+
5255
url_obj = urlparse(input_str)
53-
input_str = url_obj.path
54-
if input_str.endswith("/"):
55-
return input_str[:-1]
56-
return input_str
56+
url_path = url_obj.path
57+
58+
if url_path.endswith("/"):
59+
return url_path[:-1]
60+
61+
return url_path
5762
except Exception:
5863
pass
5964

6065
if (
61-
(domain_given(input_str) or input_str.startswith("localhost"))
62-
and not input_str.startswith("http://")
63-
and not input_str.startswith("https://")
64-
):
66+
domain_given(input_str_lower) or input_str_lower.startswith("localhost")
67+
) and not input_str_lower.startswith(("http://", "https://")):
6568
input_str = "http://" + input_str
6669
return normalise_url_path_or_throw_error(input_str)
6770

6871
if not input_str.startswith("/"):
6972
input_str = "/" + input_str
7073

7174
try:
72-
urlparse("http://example.com" + input_str)
73-
return normalise_url_path_or_throw_error("http://example.com" + input_str)
75+
urlparse(f"http://example.com{input_str}")
76+
return normalise_url_path_or_throw_error(f"http://example.com{input_str}")
7477
except Exception:
7578
raise_general_exception("Please provide a valid URL path")
7679

tests/test_config.py

Lines changed: 64 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import Any, Dict, Optional
1514
from unittest.mock import MagicMock
1615

1716
import pytest
@@ -24,8 +23,9 @@
2423
from supertokens_python.recipe.session import SessionRecipe
2524
from supertokens_python.recipe.session.asyncio import create_new_session
2625
from supertokens_python.types import RecipeUserId
26+
from typing_extensions import Any, Dict, Optional
2727

28-
from tests.utils import get_new_core_app_url, reset
28+
from tests.utils import get_new_core_app_url, outputs, reset
2929

3030

3131
# Tests do not rely on the core.
@@ -35,109 +35,73 @@ def st_config() -> SupertokensConfig:
3535
return SupertokensConfig(get_new_core_app_url())
3636

3737

38-
def testing_URL_path_normalisation():
38+
@mark.parametrize(
39+
("input", "expectation"),
40+
[
41+
("exists?email=john.doe%40gmail.com", outputs("/exists")),
42+
(
43+
"/auth/email/exists?email=john.doe%40gmail.com",
44+
outputs("/auth/email/exists"),
45+
),
46+
("http://api.example.com", outputs("")),
47+
("https://api.example.com", outputs("")),
48+
("http://api.example.com?hello=1", outputs("")),
49+
("http://api.example.com/hello", outputs("/hello")),
50+
("http://api.example.com/HellO", outputs("/HellO")),
51+
("http://api.example.com/", outputs("")),
52+
("http://api.example.com:8080", outputs("")),
53+
("api.example.com/", outputs("")),
54+
("api.example.com#random", outputs("")),
55+
(".example.com", outputs("")),
56+
("api.example.com/?hello=1&bye=2", outputs("")),
57+
("exists", outputs("/exists")),
58+
("eXiStS", outputs("/eXiStS")),
59+
("/exists", outputs("/exists")),
60+
("/eXiStS", outputs("/eXiStS")),
61+
("/exists?email=john.doe%40gmail.com", outputs("/exists")),
62+
("http://api.example.com/one/two", outputs("/one/two")),
63+
("http://1.2.3.4/one/two", outputs("/one/two")),
64+
("1.2.3.4/one/two", outputs("/one/two")),
65+
("https://api.example.com/one/two/", outputs("/one/two")),
66+
("http://api.example.com/one/two?hello=1", outputs("/one/two")),
67+
("http://api.example.com/hello/", outputs("/hello")),
68+
("http://api.example.com/one/two/", outputs("/one/two")),
69+
("http://api.example.com/one/two#random2", outputs("/one/two")),
70+
("api.example.com/one/two", outputs("/one/two")),
71+
(".example.com/one/two", outputs("/one/two")),
72+
("api.example.com/one/two?hello=1&bye=2", outputs("/one/two")),
73+
("/one/two", outputs("/one/two")),
74+
("one/two", outputs("/one/two")),
75+
("one/two/", outputs("/one/two")),
76+
("/one", outputs("/one")),
77+
("one", outputs("/one")),
78+
("one/", outputs("/one")),
79+
("/one/two/", outputs("/one/two")),
80+
("/one/two?hello=1", outputs("/one/two")),
81+
("one/two?hello=1", outputs("/one/two")),
82+
("/one/two/#randm,", outputs("/one/two")),
83+
("one/two#random", outputs("/one/two")),
84+
("localhost:4000/one/two", outputs("/one/two")),
85+
("127.0.0.1:4000/one/two", outputs("/one/two")),
86+
("127.0.0.1/one/two", outputs("/one/two")),
87+
("https://127.0.0.1:80/one/two", outputs("/one/two")),
88+
("/", outputs("")),
89+
("", outputs("")),
90+
("/.netlify/functions/api", outputs("/.netlify/functions/api")),
91+
("/netlify/.functions/api", outputs("/netlify/.functions/api")),
92+
("app.example.com/.netlify/functions/api", outputs("/.netlify/functions/api")),
93+
("app.example.com/netlify/.functions/api", outputs("/netlify/.functions/api")),
94+
("/app.example.com", outputs("/app.example.com")),
95+
],
96+
)
97+
def testing_URL_path_normalisation(input: str, expectation: Any) -> None:
3998
def normalise_url_path_or_throw_error(
4099
input: str,
41100
): # pylint: disable=redefined-builtin
42101
return NormalisedURLPath(input).get_as_string_dangerous()
43102

44-
assert (
45-
normalise_url_path_or_throw_error("exists?email=john.doe%40gmail.com")
46-
== "/exists"
47-
)
48-
assert (
49-
normalise_url_path_or_throw_error(
50-
"/auth/email/exists?email=john.doe%40gmail.com"
51-
)
52-
== "/auth/email/exists"
53-
)
54-
assert normalise_url_path_or_throw_error("exists") == "/exists"
55-
assert normalise_url_path_or_throw_error("/exists") == "/exists"
56-
assert (
57-
normalise_url_path_or_throw_error("/exists?email=john.doe%40gmail.com")
58-
== "/exists"
59-
)
60-
assert normalise_url_path_or_throw_error("http://api.example.com") == ""
61-
assert normalise_url_path_or_throw_error("https://api.example.com") == ""
62-
assert normalise_url_path_or_throw_error("http://api.example.com?hello=1") == ""
63-
assert normalise_url_path_or_throw_error("http://api.example.com/hello") == "/hello"
64-
assert normalise_url_path_or_throw_error("http://api.example.com/") == ""
65-
assert normalise_url_path_or_throw_error("http://api.example.com:8080") == ""
66-
assert normalise_url_path_or_throw_error("api.example.com/") == ""
67-
assert normalise_url_path_or_throw_error("api.example.com#random") == ""
68-
assert normalise_url_path_or_throw_error(".example.com") == ""
69-
assert normalise_url_path_or_throw_error("api.example.com/?hello=1&bye=2") == ""
70-
71-
assert (
72-
normalise_url_path_or_throw_error("http://api.example.com/one/two")
73-
== "/one/two"
74-
)
75-
assert normalise_url_path_or_throw_error("http://1.2.3.4/one/two") == "/one/two"
76-
assert normalise_url_path_or_throw_error("1.2.3.4/one/two") == "/one/two"
77-
assert (
78-
normalise_url_path_or_throw_error("https://api.example.com/one/two/")
79-
== "/one/two"
80-
)
81-
assert (
82-
normalise_url_path_or_throw_error("http://api.example.com/one/two?hello=1")
83-
== "/one/two"
84-
)
85-
assert (
86-
normalise_url_path_or_throw_error("http://api.example.com/hello/") == "/hello"
87-
)
88-
assert (
89-
normalise_url_path_or_throw_error("http://api.example.com/one/two/")
90-
== "/one/two"
91-
)
92-
assert (
93-
normalise_url_path_or_throw_error("http://api.example.com/one/two#random2")
94-
== "/one/two"
95-
)
96-
assert normalise_url_path_or_throw_error("api.example.com/one/two") == "/one/two"
97-
assert normalise_url_path_or_throw_error(".example.com/one/two") == "/one/two"
98-
assert (
99-
normalise_url_path_or_throw_error("api.example.com/one/two?hello=1&bye=2")
100-
== "/one/two"
101-
)
102-
103-
assert normalise_url_path_or_throw_error("/one/two") == "/one/two"
104-
assert normalise_url_path_or_throw_error("one/two") == "/one/two"
105-
assert normalise_url_path_or_throw_error("one/two/") == "/one/two"
106-
assert normalise_url_path_or_throw_error("/one") == "/one"
107-
assert normalise_url_path_or_throw_error("one") == "/one"
108-
assert normalise_url_path_or_throw_error("one/") == "/one"
109-
assert normalise_url_path_or_throw_error("/one/two/") == "/one/two"
110-
assert normalise_url_path_or_throw_error("/one/two?hello=1") == "/one/two"
111-
assert normalise_url_path_or_throw_error("one/two?hello=1") == "/one/two"
112-
assert normalise_url_path_or_throw_error("/one/two/#randm,") == "/one/two"
113-
assert normalise_url_path_or_throw_error("one/two#random") == "/one/two"
114-
115-
assert normalise_url_path_or_throw_error("localhost:4000/one/two") == "/one/two"
116-
assert normalise_url_path_or_throw_error("127.0.0.1:4000/one/two") == "/one/two"
117-
assert normalise_url_path_or_throw_error("127.0.0.1/one/two") == "/one/two"
118-
assert (
119-
normalise_url_path_or_throw_error("https://127.0.0.1:80/one/two") == "/one/two"
120-
)
121-
assert normalise_url_path_or_throw_error("/") == ""
122-
assert normalise_url_path_or_throw_error("") == ""
123-
124-
assert (
125-
normalise_url_path_or_throw_error("/.netlify/functions/api")
126-
== "/.netlify/functions/api"
127-
)
128-
assert (
129-
normalise_url_path_or_throw_error("/netlify/.functions/api")
130-
== "/netlify/.functions/api"
131-
)
132-
assert (
133-
normalise_url_path_or_throw_error("app.example.com/.netlify/functions/api")
134-
== "/.netlify/functions/api"
135-
)
136-
assert (
137-
normalise_url_path_or_throw_error("app.example.com/netlify/.functions/api")
138-
== "/netlify/.functions/api"
139-
)
140-
assert normalise_url_path_or_throw_error("/app.example.com") == "/app.example.com"
103+
with expectation as output:
104+
assert normalise_url_path_or_throw_error(input) == output
141105

142106

143107
def testing_URL_domain_normalisation():

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy