@@ -2,6 +2,7 @@ from collections import Dict
22from utils import Variant
33from lightbug_http.io.bytes import Bytes, bytes
44from lightbug_http.strings import (
5+ find_all,
56 strSlash,
67 strHttp11,
78 strHttp10,
@@ -11,19 +12,91 @@ from lightbug_http.strings import (
1112 https,
1213)
1314
15+
16+ fn unquote [
17+ expand_plus : Bool = False
18+ ](
19+ input_str : String, disallowed_escapes : List[String] = List[String]()
20+ ) -> String:
21+ var encoded_str = input_str.replace(
22+ QueryDelimiters.PLUS_ESCAPED_SPACE , " "
23+ ) if expand_plus else input_str
24+
25+ var percent_idxs : List[Int] = find_all(
26+ encoded_str, URIDelimiters.CHAR_ESCAPE
27+ )
28+
29+ if len (percent_idxs) < 1 :
30+ return encoded_str
31+
32+ var sub_strings = List[String]()
33+
34+ var current_idx = 0
35+ var slice_start = 0
36+ var slice_end = 0
37+
38+ var str_bytes = List[UInt8]()
39+ while current_idx < len (percent_idxs):
40+ slice_end = percent_idxs[current_idx]
41+ sub_strings.append(encoded_str[slice_start:slice_end])
42+
43+ var current_offset = slice_end
44+ while current_idx < len (percent_idxs):
45+ var char_byte = - 1
46+ if (current_offset + 3 ) <= len (encoded_str):
47+ try :
48+ char_byte = atol(
49+ encoded_str[current_offset + 1 : current_offset + 3 ],
50+ base = 16 ,
51+ )
52+ except :
53+ pass
54+
55+ if char_byte < 0 :
56+ break
57+
58+ str_bytes.append(char_byte)
59+
60+ if percent_idxs[current_idx + 1 ] != (current_offset + 3 ):
61+ current_offset += 3
62+ break
63+
64+ current_idx += 1
65+ current_offset = percent_idxs[current_idx]
66+
67+ if len (str_bytes) > 0 :
68+ str_bytes.append(0x 00 )
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)
75+ str_bytes.clear()
76+
77+ slice_start = current_offset
78+ current_idx += 1
79+
80+ sub_strings.append(encoded_str[slice_start:])
81+
82+ return str (" " ).join(sub_strings)
83+
84+
1485alias QueryMap = Dict[String, String]
1586
1687
1788struct QueryDelimiters :
1889 alias STRING_START = " ?"
1990 alias ITEM = " &"
2091 alias ITEM_ASSIGN = " ="
92+ alias PLUS_ESCAPED_SPACE = " +"
2193
2294
2395struct URIDelimiters :
2496 alias SCHEMA = " ://"
2597 alias PATH = strSlash
2698 alias ROOT_PATH = strSlash
99+ alias CHAR_ESCAPE = " %"
27100
28101
29102@value
@@ -80,10 +153,14 @@ struct URI(Writable, Stringable, Representable):
80153 var original_path : String
81154 var query_string : String
82155 if n >= 0 :
83- original_path = request_uri[:n]
156+ original_path = unquote(
157+ request_uri[:n], disallowed_escapes = List(str (" /" ))
158+ )
84159 query_string = request_uri[n + 1 :]
85160 else :
86- original_path = request_uri
161+ original_path = unquote(
162+ request_uri, disallowed_escapes = List(str (" /" ))
163+ )
87164 query_string = " "
88165
89166 var queries = QueryMap()
@@ -92,13 +169,12 @@ struct URI(Writable, Stringable, Representable):
92169
93170 for item in query_items:
94171 var key_val = item[].split(QueryDelimiters.ITEM_ASSIGN , 1 )
172+ var key = unquote[expand_plus=True ](key_val[0 ])
95173
96- if key_val[ 0 ] :
97- queries[key_val[ 0 ] ] = " "
174+ if key :
175+ queries[key ] = " "
98176 if len (key_val) == 2 :
99- # TODO : Query values are going to be URI encoded strings and should be decoded as part of the
100- # query processing
101- queries[key_val[0 ]] = key_val[1 ]
177+ queries[key] = unquote[expand_plus=True ](key_val[1 ])
102178
103179 return URI(
104180 _original_path = original_path,
@@ -115,7 +191,9 @@ struct URI(Writable, Stringable, Representable):
115191 )
116192
117193 fn __str__ (self ) -> String:
118- var result = String.write(self .scheme, URIDelimiters.SCHEMA , self .host, self .path)
194+ var result = String.write(
195+ self .scheme, URIDelimiters.SCHEMA , self .host, self .path
196+ )
119197 if len (self .query_string) > 0 :
120198 result.write(QueryDelimiters.STRING_START , self .query_string)
121199 return result^
0 commit comments