Skip to content

Commit f04f63f

Browse files
committed
rewrite parse_cookie using lexmatch
1 parent e061b87 commit f04f63f

File tree

1 file changed

+73
-133
lines changed

1 file changed

+73
-133
lines changed

src/cookie.mbt

Lines changed: 73 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -54,150 +54,90 @@ pub fn cookie_to_string(cookie : Array[CookieItem]) -> String {
5454
///|
5555
pub fn parse_cookie(cookie : StringView) -> Map[String, CookieItem] {
5656
let res = Map::new()
57-
let mut view = cookie
58-
let mut last_cookie_name = ""
59-
while view.length() > 0 {
60-
// Skip whitespace using lexmatch
61-
while true {
62-
if view lexmatch? (" ", rest) {
63-
view = rest
64-
continue
65-
}
66-
if view lexmatch? ("\t", rest) {
67-
view = rest
68-
continue
69-
}
70-
break
71-
}
72-
if view.length() == 0 {
73-
break
74-
}
75-
76-
// Parse key
77-
let mut key_end = 0
78-
while key_end < view.length() &&
79-
view[key_end] != '=' &&
80-
view[key_end] != ';' {
81-
key_end = key_end + 1
57+
let mut last_cookie_item : (String, CookieItem)? = None
58+
fn dequote(value : StringView) -> StringView {
59+
if value is ['"', .. rest, '"'] {
60+
rest
61+
} else {
62+
value
8263
}
83-
let key = view[0:key_end].trim_space().to_string() catch { _ => "" }
84-
view = view[key_end:] catch { _ => view }
64+
}
8565

86-
// Parse value
87-
let mut val = ""
88-
if view lexmatch? ("=", rest) {
89-
view = rest
90-
// Skip whitespace
91-
while true {
92-
if view lexmatch? (" ", rest) {
93-
view = rest
94-
continue
66+
fn on_key_value(key : StringView, value : StringView) -> Unit {
67+
let key = key.to_string()
68+
let value = dequote(value).to_string()
69+
if last_cookie_item is Some((name, item)) {
70+
let new_item = lexmatch key with longest {
71+
"(?i:path)" => { ..item, path: Some(value) }
72+
"(?i:domain)" => { ..item, domain: Some(value) }
73+
"(?i:max-age)" =>
74+
{
75+
..item,
76+
max_age: try @strconv.parse_int(value) catch {
77+
_ => None
78+
} noraise {
79+
x => Some(x)
80+
},
81+
}
82+
"(?i:secure)" => {
83+
println("Found secure attribute")
84+
{ ..item, secure: Some(true) }
9585
}
96-
if view lexmatch? ("\t", rest) {
97-
view = rest
98-
continue
86+
"(?i:httponly)" => { ..item, http_only: Some(true) }
87+
"(?i:samesite)" => {
88+
let same_site = lexmatch value with longest {
89+
"(?i:lax)" => Some(Lax)
90+
"(?i:strict)" => Some(Strict)
91+
"(?i:none)" => Some(SameSiteNone)
92+
_ => None
93+
}
94+
{ ..item, same_site, }
9995
}
100-
break
96+
_ => item
10197
}
102-
if view lexmatch? ("\"", rest) {
103-
// Quoted value
104-
view = rest
105-
let mut q_end = 0
106-
while q_end < view.length() && view[q_end] != '"' {
107-
q_end = q_end + 1
108-
}
109-
val = view[0:q_end].to_string() catch { _ => "" }
110-
// Skip closing quote if present
111-
if q_end < view.length() {
112-
view = view[q_end + 1:] catch {
113-
_ => view[q_end:] catch { _ => view }
114-
}
115-
} else {
116-
view = view[q_end:] catch { _ => view }
117-
}
118-
} else {
119-
// Unquoted value
120-
let mut v_end = 0
121-
while v_end < view.length() && view[v_end] != ';' {
122-
v_end = v_end + 1
123-
}
124-
val = view[0:v_end].trim_space().to_string() catch { _ => "" }
125-
view = view[v_end:] catch { _ => view }
98+
if !physical_equal(new_item, item) {
99+
res.set(name, new_item)
100+
last_cookie_item = Some((name, new_item))
101+
return
126102
}
127103
}
128-
if key != "" {
129-
let lower_key = key.to_lower()
130-
if last_cookie_name != "" &&
131-
(
132-
lower_key == "path" ||
133-
lower_key == "domain" ||
134-
lower_key == "max-age" ||
135-
lower_key == "secure" ||
136-
lower_key == "httponly" ||
137-
lower_key == "samesite"
138-
) {
139-
// It's an attribute for the last cookie
140-
match res.get(last_cookie_name) {
141-
Some(item) => {
142-
let mut new_item : CookieItem = item
143-
match lower_key {
144-
"path" => new_item = { ..item, path: Some(val) }
145-
"domain" => new_item = { ..item, domain: Some(val) }
146-
"max-age" =>
147-
new_item = {
148-
..item,
149-
max_age: Some(@strconv.parse_int(val)) catch {
150-
_ => None
151-
},
152-
}
153-
"secure" => new_item = { ..item, secure: Some(true) }
154-
"httponly" => new_item = { ..item, http_only: Some(true) }
155-
"samesite" => {
156-
let same_site = match val.to_lower() {
157-
"lax" => Some(Lax)
158-
"strict" => Some(Strict)
159-
"none" => Some(SameSiteNone)
160-
_ => None
161-
}
162-
new_item = { ..item, same_site, }
163-
}
164-
_ => ()
165-
}
166-
res.set(last_cookie_name, new_item)
167-
}
168-
None => ()
169-
}
170-
} else {
171-
// New cookie
172-
let item : CookieItem = {
173-
name: key,
174-
value: val,
175-
max_age: None,
176-
path: None,
177-
domain: None,
178-
secure: None,
179-
http_only: None,
180-
same_site: None,
181-
}
182-
res.set(key, item)
183-
last_cookie_name = key
184-
}
104+
let item = CookieItem::{
105+
name: key,
106+
value,
107+
max_age: None,
108+
path: None,
109+
domain: None,
110+
secure: None,
111+
http_only: None,
112+
same_site: None,
185113
}
114+
res.set(key, item)
115+
last_cookie_item = Some((key, item))
116+
}
186117

187-
// Skip until next semicolon
188-
if view lexmatch? (";", rest) {
189-
view = rest
190-
} else {
191-
// Skip invalid chars until ;
192-
let mut i = 0
193-
while i < view.length() && view[i] != ';' {
194-
i = i + 1
118+
for curr = cookie {
119+
// TODO: more spec compliance
120+
lexmatch curr with longest {
121+
("[ \t]+", rest) => continue rest
122+
(
123+
("[^ \t=;]([ \t]*[^ \t=;])*" as key)
124+
"[ \t]*"
125+
"="
126+
"[ \t]*"
127+
("[^ \t;]([ \t]*[^ \t;])*" as value)
128+
"[ \t]*"
129+
";?",
130+
rest
131+
) => {
132+
on_key_value(key, value)
133+
continue rest
195134
}
196-
if i < view.length() {
197-
view = view[i + 1:] catch { _ => view }
198-
} else {
199-
view = view[i:] catch { _ => view }
135+
(("[^ \t=;]([ \t]*[^ \t=;])*" as key) "[ \t]*" "(=[ \t]*)?" ";?", rest) => {
136+
on_key_value(key, "")
137+
continue rest
200138
}
139+
"" => break
140+
_ => break
201141
}
202142
}
203143
res

0 commit comments

Comments
 (0)