Skip to content

Commit e8d1a3e

Browse files
committed
Additional fixes to url escapes decoding
- Do not allow escaped slashes `/` in uri path - Decode/ unqoute query param names/ keys - Move find_all utility func. to `strings.mojo` module
1 parent eacb5ce commit e8d1a3e

File tree

3 files changed

+44
-20
lines changed

3 files changed

+44
-20
lines changed

lightbug_http/strings.mojo

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,12 @@ fn to_string(owned bytes: Bytes) -> String:
150150
if bytes[-1] != 0:
151151
bytes.append(0)
152152
return String(bytes^)
153+
154+
155+
fn find_all(s: String, sub_str: String) -> List[Int]:
156+
match_idxs = List[Int]()
157+
var current_idx: Int = s.find(sub_str)
158+
while current_idx > -1:
159+
match_idxs.append(current_idx)
160+
current_idx = s.find(sub_str, start=current_idx + 1)
161+
return match_idxs^

lightbug_http/uri.mojo

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ from collections import Dict
22
from utils import Variant
33
from lightbug_http.io.bytes import Bytes, bytes
44
from lightbug_http.strings import (
5+
find_all,
56
strSlash,
67
strHttp11,
78
strHttp10,
@@ -12,16 +13,11 @@ from lightbug_http.strings import (
1213
)
1314

1415

15-
fn find_all(s: String, sub_str: String) -> List[Int]:
16-
match_idxs = List[Int]()
17-
var current_idx: Int = s.find(sub_str)
18-
while current_idx > -1:
19-
match_idxs.append(current_idx)
20-
current_idx = s.find(sub_str, start=current_idx + 1)
21-
return match_idxs^
22-
23-
24-
fn unquote[expand_plus: Bool = False](input_str: String) -> String:
16+
fn unquote[
17+
expand_plus: Bool = False
18+
](
19+
input_str: String, disallowed_escapes: List[String] = List[String]()
20+
) -> String:
2521
var encoded_str = input_str.replace(
2622
QueryDelimiters.PLUS_ESCAPED_SPACE, " "
2723
) if expand_plus else input_str
@@ -70,7 +66,12 @@ fn unquote[expand_plus: Bool = False](input_str: String) -> String:
7066

7167
if len(str_bytes) > 0:
7268
str_bytes.append(0x00)
73-
sub_strings.append(String(str_bytes))
69+
var sub_str_from_bytes = String(str_bytes)
70+
for disallowed in disallowed_escapes:
71+
sub_str_from_bytes = sub_str_from_bytes.replace(
72+
disallowed[], ""
73+
)
74+
sub_strings.append(sub_str_from_bytes)
7475
str_bytes.clear()
7576

7677
slice_start = current_offset
@@ -152,10 +153,14 @@ struct URI(Writable, Stringable, Representable):
152153
var original_path: String
153154
var query_string: String
154155
if n >= 0:
155-
original_path = unquote(request_uri[:n])
156+
original_path = unquote(
157+
request_uri[:n], disallowed_escapes=List(str("/"))
158+
)
156159
query_string = request_uri[n + 1 :]
157160
else:
158-
original_path = unquote(request_uri)
161+
original_path = unquote(
162+
request_uri, disallowed_escapes=List(str("/"))
163+
)
159164
query_string = ""
160165

161166
var queries = QueryMap()
@@ -164,13 +169,12 @@ struct URI(Writable, Stringable, Representable):
164169

165170
for item in query_items:
166171
var key_val = item[].split(QueryDelimiters.ITEM_ASSIGN, 1)
172+
var key = unquote[expand_plus=True](key_val[0])
167173

168-
if key_val[0]:
169-
queries[key_val[0]] = ""
174+
if key:
175+
queries[key] = ""
170176
if len(key_val) == 2:
171-
queries[key_val[0]] = unquote[expand_plus=True](
172-
key_val[1]
173-
)
177+
queries[key] = unquote[expand_plus=True](key_val[1])
174178

175179
return URI(
176180
_original_path=original_path,

tests/lightbug_http/test_uri.mojo

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ def test_uri_parse_https_with_path():
6060
testing.assert_equal(uri.query_string, empty_string)
6161

6262

63+
def test_uri_parse_path_with_encoding():
64+
var uri = URI.parse("https://example.com/test%20test/index.html")
65+
testing.assert_equal(uri.path, "/test test/index.html")
66+
67+
68+
def test_uri_parse_path_with_encoding_ignore_slashes():
69+
var uri = URI.parse("https://example.com/trying_to%2F_be_clever/42.html")
70+
testing.assert_equal(uri.path, "/trying_to_be_clever/42.html")
71+
72+
6373
def test_uri_parse_http_basic():
6474
var uri = URI.parse("http://example.com")
6575
testing.assert_equal(uri.scheme, "http")
@@ -102,13 +112,14 @@ def test_uri_parse_multiple_query_parameters():
102112
testing.assert_equal(uri.request_uri, "/search?q=python&page=1&limit=20")
103113

104114
def test_uri_parse_query_with_special_characters():
105-
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com")
115+
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com&escaped%40%20name=42")
106116
testing.assert_equal(uri.scheme, "https")
107117
testing.assert_equal(uri.host, "example.com")
108118
testing.assert_equal(uri.path, "/path")
109-
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com")
119+
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com&escaped%40%20name=42")
110120
testing.assert_equal(uri.queries["name"], "John Doe")
111121
testing.assert_equal(uri.queries["email"], "john@example.com")
122+
testing.assert_equal(uri.queries["escaped@ name"], "42")
112123

113124
def test_uri_parse_empty_query_values():
114125
var uri = URI.parse("http://example.com/api?key=&token=&empty")

0 commit comments

Comments
 (0)