Skip to content

Commit 632a507

Browse files
committed
sync
2 parents 5a816ac + 350d31b commit 632a507

File tree

3 files changed

+93
-11
lines changed

3 files changed

+93
-11
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: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ from memory import Span
33
from collections import Optional, Dict
44
from lightbug_http.io.bytes import Bytes, bytes, ByteReader, Constant
55
from lightbug_http.strings import (
6+
find_all,
67
strSlash,
78
strHttp11,
89
strHttp10,
@@ -12,19 +13,81 @@ from lightbug_http.strings import (
1213
https,
1314
)
1415

16+
17+
fn unquote[expand_plus: Bool = False](input_str: String, disallowed_escapes: List[String] = List[String]()) -> String:
18+
var encoded_str = input_str.replace(QueryDelimiters.PLUS_ESCAPED_SPACE, " ") if expand_plus else input_str
19+
20+
var percent_idxs: List[Int] = find_all(encoded_str, URIDelimiters.CHAR_ESCAPE)
21+
22+
if len(percent_idxs) < 1:
23+
return encoded_str
24+
25+
var sub_strings = List[String]()
26+
27+
var current_idx = 0
28+
var slice_start = 0
29+
var slice_end = 0
30+
31+
var str_bytes = List[UInt8]()
32+
while current_idx < len(percent_idxs):
33+
slice_end = percent_idxs[current_idx]
34+
sub_strings.append(encoded_str[slice_start:slice_end])
35+
36+
var current_offset = slice_end
37+
while current_idx < len(percent_idxs):
38+
var char_byte = -1
39+
if (current_offset + 3) <= len(encoded_str):
40+
try:
41+
char_byte = atol(
42+
encoded_str[current_offset + 1 : current_offset + 3],
43+
base=16,
44+
)
45+
except:
46+
pass
47+
48+
if char_byte < 0:
49+
break
50+
51+
str_bytes.append(char_byte)
52+
53+
if percent_idxs[current_idx + 1] != (current_offset + 3):
54+
current_offset += 3
55+
break
56+
57+
current_idx += 1
58+
current_offset = percent_idxs[current_idx]
59+
60+
if len(str_bytes) > 0:
61+
str_bytes.append(0x00)
62+
var sub_str_from_bytes = String(str_bytes)
63+
for disallowed in disallowed_escapes:
64+
sub_str_from_bytes = sub_str_from_bytes.replace(disallowed[], "")
65+
sub_strings.append(sub_str_from_bytes)
66+
str_bytes.clear()
67+
68+
slice_start = current_offset
69+
current_idx += 1
70+
71+
sub_strings.append(encoded_str[slice_start:])
72+
73+
return str("").join(sub_strings)
74+
75+
1576
alias QueryMap = Dict[String, String]
1677

1778

1879
struct QueryDelimiters:
1980
alias STRING_START = "?"
2081
alias ITEM = "&"
2182
alias ITEM_ASSIGN = "="
83+
alias PLUS_ESCAPED_SPACE = "+"
2284

2385

2486
struct URIDelimiters:
2587
alias SCHEMA = "://"
2688
alias PATH = strSlash
2789
alias ROOT_PATH = strSlash
90+
alias CHAR_ESCAPE = "%"
2891

2992

3093
@value
@@ -152,13 +215,12 @@ struct URI(Writable, Stringable, Representable):
152215

153216
for item in query_items:
154217
var key_val = item[].split(QueryDelimiters.ITEM_ASSIGN, 1)
218+
var key = unquote[expand_plus=True](key_val[0])
155219

156-
if key_val[0]:
157-
queries[key_val[0]] = ""
220+
if key:
221+
queries[key] = ""
158222
if len(key_val) == 2:
159-
# TODO: Query values are going to be URI encoded strings and should be decoded as part of the
160-
# query processing
161-
queries[key_val[0]] = key_val[1]
223+
queries[key] = unquote[expand_plus=True](key_val[1])
162224

163225
return URI(
164226
_original_path=path,

tests/lightbug_http/test_uri.mojo

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ def test_uri_parse_https_with_path():
6262
testing.assert_equal(uri.query_string, empty_string)
6363

6464

65+
def test_uri_parse_path_with_encoding():
66+
var uri = URI.parse("https://example.com/test%20test/index.html")
67+
testing.assert_equal(uri.path, "/test test/index.html")
68+
69+
70+
def test_uri_parse_path_with_encoding_ignore_slashes():
71+
var uri = URI.parse("https://example.com/trying_to%2F_be_clever/42.html")
72+
testing.assert_equal(uri.path, "/trying_to_be_clever/42.html")
73+
74+
6575
def test_uri_parse_http_basic():
6676
var uri = URI.parse("http://example.com")
6777
testing.assert_equal(uri.scheme, "http")
@@ -106,13 +116,14 @@ def test_uri_parse_multiple_query_parameters():
106116

107117

108118
def test_uri_parse_query_with_special_characters():
109-
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com")
119+
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com&escaped%40%20name=42")
110120
testing.assert_equal(uri.scheme, "https")
111121
testing.assert_equal(uri.host, "example.com")
112122
testing.assert_equal(uri.path, "/path")
113-
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com")
114-
# testing.assert_equal(uri.queries["name"], "John Doe") - fails, contains John+Doe
115-
# testing.assert_equal(uri.queries["email"], "john@example.com") - fails, contains john%40example.com
123+
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com&escaped%40%20name=42")
124+
testing.assert_equal(uri.queries["name"], "John Doe")
125+
testing.assert_equal(uri.queries["email"], "john@example.com")
126+
testing.assert_equal(uri.queries["escaped@ name"], "42")
116127

117128

118129
def test_uri_parse_empty_query_values():
@@ -139,8 +150,8 @@ def test_uri_parse_complex_query():
139150
def test_uri_parse_query_with_unicode():
140151
var uri = URI.parse("http://example.com/search?q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
141152
testing.assert_equal(uri.query_string, "q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
142-
# testing.assert_equal(uri.queries["q"], "€") - fails, contains %E2%82%AC
143-
# testing.assert_equal(uri.queries["lang"], "🇩🇪") - fails, contains %F0%9F%87%A9%F0%9F%87%AA
153+
testing.assert_equal(uri.queries["q"], "")
154+
testing.assert_equal(uri.queries["lang"], "🇩🇪")
144155

145156

146157
# def test_uri_parse_query_with_fragments():

0 commit comments

Comments
 (0)