diff --git a/src/cookie.mbt b/src/cookie.mbt index 886c333..6282ea2 100644 --- a/src/cookie.mbt +++ b/src/cookie.mbt @@ -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