Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 73 additions & 134 deletions src/cookie.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -53,151 +53,90 @@ pub fn cookie_to_string(cookie : Array[CookieItem]) -> String {

///|
pub fn parse_cookie(cookie : StringView) -> Map[String, CookieItem] {
let res = Map::new()
let mut view = cookie
let mut last_cookie_name = ""
while view.length() > 0 {
// Skip whitespace using lexmatch
while true {
if view lexmatch? (" ", rest) {
view = rest
continue
}
if view lexmatch? ("\t", rest) {
view = rest
continue
}
break
}
if view.length() == 0 {
break
}

// Parse key
let mut key_end = 0
while key_end < view.length() &&
view[key_end] != '=' &&
view[key_end] != ';' {
key_end = key_end + 1
fn dequote(value : StringView) -> StringView {
if value is ['"', .. rest, '"'] {
rest
} else {
value
}
let key = view[0:key_end].trim_space().to_string() catch { _ => "" }
view = view[key_end:] catch { _ => view }
}

// Parse value
let mut val = ""
if view lexmatch? ("=", rest) {
view = rest
// Skip whitespace
while true {
if view lexmatch? (" ", rest) {
view = rest
continue
let res = Map::new()
let mut last_cookie_item : (String, CookieItem)? = None
fn on_key_value(key : StringView, value : StringView) -> Unit {
let key = key.to_string()
let value = dequote(value).to_string()
if last_cookie_item is Some((name, item)) {
let new_item = lexmatch key with longest {
"(?i:path)" => { ..item, path: Some(value) }
"(?i:domain)" => { ..item, domain: Some(value) }
"(?i:max-age)" =>
{
..item,
max_age: try @strconv.parse_int(value) catch {
_ => None
} noraise {
x => Some(x)
},
}
"(?i:secure)" => {
{ ..item, secure: Some(true) }
}
if view lexmatch? ("\t", rest) {
view = rest
continue
"(?i:httponly)" => { ..item, http_only: Some(true) }
"(?i:samesite)" => {
let same_site = lexmatch value with longest {
"(?i:lax)" => Some(Lax)
"(?i:strict)" => Some(Strict)
"(?i:none)" => Some(SameSiteNone)
_ => None
}
{ ..item, same_site, }
}
break
_ => item
}
if view lexmatch? ("\"", rest) {
// Quoted value
view = rest
let mut q_end = 0
while q_end < view.length() && view[q_end] != '"' {
q_end = q_end + 1
}
val = view[0:q_end].to_string() catch { _ => "" }
// Skip closing quote if present
if q_end < view.length() {
view = view[q_end + 1:] catch {
_ => view[q_end:] catch { _ => view }
}
} else {
view = view[q_end:] catch { _ => view }
}
} else {
// Unquoted value
let mut v_end = 0
while v_end < view.length() && view[v_end] != ';' {
v_end = v_end + 1
}
val = view[0:v_end].trim_space().to_string() catch { _ => "" }
view = view[v_end:] catch { _ => view }
if !physical_equal(new_item, item) {
res.set(name, new_item)
last_cookie_item = Some((name, new_item))
return
}
}
if key != "" {
let lower_key = key.to_lower()
if last_cookie_name != "" &&
(
lower_key == "path" ||
lower_key == "domain" ||
lower_key == "max-age" ||
lower_key == "secure" ||
lower_key == "httponly" ||
lower_key == "samesite"
) {
// It's an attribute for the last cookie
match res.get(last_cookie_name) {
Some(item) => {
let mut new_item : CookieItem = item
match lower_key {
"path" => new_item = { ..item, path: Some(val) }
"domain" => new_item = { ..item, domain: Some(val) }
"max-age" =>
new_item = {
..item,
max_age: Some(@strconv.parse_int(val)) catch {
_ => None
},
}
"secure" => new_item = { ..item, secure: Some(true) }
"httponly" => new_item = { ..item, http_only: Some(true) }
"samesite" => {
let same_site = match val.to_lower() {
"lax" => Some(Lax)
"strict" => Some(Strict)
"none" => Some(SameSiteNone)
_ => None
}
new_item = { ..item, same_site, }
}
_ => ()
}
res.set(last_cookie_name, new_item)
}
None => ()
}
} else {
// New cookie
let item : CookieItem = {
name: key,
value: val,
max_age: None,
path: None,
domain: None,
secure: None,
http_only: None,
same_site: None,
}
res.set(key, item)
last_cookie_name = key
}
let item = CookieItem::{
name: key,
value,
max_age: None,
path: None,
domain: None,
secure: None,
http_only: None,
same_site: None,
}
res.set(key, item)
last_cookie_item = Some((key, item))
}

// Skip until next semicolon
if view lexmatch? (";", rest) {
view = rest
} else {
// Skip invalid chars until ;
let mut i = 0
while i < view.length() && view[i] != ';' {
i = i + 1
for curr = cookie {
// TODO: more spec compliance
lexmatch curr with longest {
("[ \t]+", rest) => continue rest
(
("[^ \t=;]([ \t]*[^ \t=;])*" as key)
"[ \t]*"
"="
"[ \t]*"
("[^ \t;]([ \t]*[^ \t;])*" as value)
"[ \t]*"
";?",
rest
) => {
on_key_value(key, value)
continue rest
}
if i < view.length() {
view = view[i + 1:] catch { _ => view }
} else {
view = view[i:] catch { _ => view }
(("[^ \t=;]([ \t]*[^ \t=;])*" as key) "[ \t]*" "(=[ \t]*)?" ";?", rest) => {
on_key_value(key, "")
continue rest
}
"" => break
_ => break
}
}
res
Expand Down
Loading