Skip to content

Commit 3c95321

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 d00c0a6 commit 3c95321

File tree

4 files changed

+111
-113
lines changed

4 files changed

+111
-113
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88

99
## [unreleased]
10+
11+
## [0.29.0] - 2025-02-17
12+
### Breaking changes
13+
- Makes URL path normalization case sensitive
14+
- Updates `normalise_url_path_or_throw_error` to be case sensitive
15+
- URL paths will not be converted to lower-case, and will be kept as-is.
16+
17+
### Changes
1018
- Upgrades `pip` and `setuptools` in CI publish job
11-
- Also upgrades `poetry` and it's dependency - `clikit`
19+
- Also upgrades `poetry` and it's dependency - `clikit`
1220

1321
## [0.29.0] - 2025-03-03
1422
- Migrates unit tests to use a containerized core

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():

tests/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
# Import AsyncMock
1818
import sys
19+
from contextlib import contextmanager
1920
from datetime import datetime
2021
from functools import lru_cache
2122
from http.cookies import SimpleCookie
@@ -487,3 +488,25 @@ async def create_users(
487488
await manually_create_or_update_user(
488489
"public", user["provider"], user["userId"], user["email"], True, None
489490
)
491+
492+
493+
@contextmanager
494+
def outputs(value: Any):
495+
"""
496+
Context manager that can be used for a common interface in test parameters.
497+
498+
Example:
499+
```python
500+
@mark.parametrize(
501+
("input", "expectation"),
502+
[
503+
(1, outputs(1)),
504+
(0, raises(Exception)),
505+
]
506+
)
507+
def test(input, expectation):
508+
with expectation as output:
509+
assert 1 / input == output
510+
```
511+
"""
512+
yield value

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