///|
pub fn Cookie::new(
name : String,
value : String,
path? : String,
expires_raw? : String,
max_age? : Int64,
domain? : String,
secure? : Bool = false,
http_only? : Bool = false,
extensions? : Array[String] = [],
) -> Cookie {
{
name,
value,
path,
expires_raw,
max_age,
domain,
secure,
http_only,
extensions,
}
}
///|
#cfg(target="native")
fn Cookie::parse(line : StringView) -> Cookie raise {
// Tokenize `name=value; attr=val; flag` with a lexbuf-based scanner.
// Each lexscan call consumes the prefix of one token and commits the
// cursor, so repeated calls walk the cookie the same way a lexer consumes
// a stream (see the streaming scanner pattern in the MoonBit docs).
let lexbuf = @lexbuf.Lexbuf::from_string(line.to_owned())
let name = lex_cookie_name(lexbuf)
guard lex_cookie_equals(lexbuf) else { raise BadRequest }
let value = lex_cookie_value(lexbuf)
let mut path = None
let mut expires_raw = None
let mut max_age = None
let mut domain = None
let mut secure = false
let mut http_only = false
let extensions = []
while lex_cookie_separator(lexbuf) {
lex_cookie_skip_spaces(lexbuf)
let attr_name = lex_cookie_name(lexbuf)
let (attr_value, has_value) = if lex_cookie_equals(lexbuf) {
(Some(lex_cookie_attr_value(lexbuf)), true)
} else {
(None, false)
}
match (attr_name.to_lower(), has_value) {
("path", true) => path = Some(attr_value.unwrap().to_owned())
("expires", true) => expires_raw = Some(attr_value.unwrap().to_owned())
("max-age", true) => {
let value = @string.parse_int64(attr_value.unwrap()) catch {
_ => raise BadRequest
}
max_age = Some(value)
}
("domain", true) => domain = Some(attr_value.unwrap().to_owned())
("secure", false) => secure = true
("httponly", false) => http_only = true
_ =>
extensions.push(
if has_value {
attr_name.to_owned() + "=" + attr_value.unwrap().to_owned()
} else {
attr_name.to_owned()
},
)
}
}
{
name: name.to_owned(),
value: value.to_owned(),
path,
expires_raw,
max_age,
domain,
secure,
http_only,
extensions,
}
}
///|
#cfg(target="native")
/// Scans a cookie name: every character up to the first `=` (or `;` for
/// attribute names, which never contain `;` because separators are consumed
/// as their own tokens). An empty name is allowed for compatibility with the
/// previous hand-written parser.
fn lex_cookie_name(lexbuf : @lexbuf.Lexbuf) -> StringView {
lexscan lexbuf with longest {
re"^[^=;]*" as name => name
}
}
///|
#cfg(target="native")
/// Consumes a single `=` token, returning whether one was present.
fn lex_cookie_equals(lexbuf : @lexbuf.Lexbuf) -> Bool {
lexscan lexbuf with longest {
re"^=" => true
_ => false
}
}
///|
#cfg(target="native")
/// Scans a cookie value: everything up to the next `;` (possibly empty).
fn lex_cookie_value(lexbuf : @lexbuf.Lexbuf) -> StringView {
lexscan lexbuf with longest {
re"^[^;]*" as value => value
}
}
///|
#cfg(target="native")
/// Like `lex_cookie_value`, but strips trailing whitespace to mirror the
/// whole-attribute trim performed by the previous parser.
fn lex_cookie_attr_value(lexbuf : @lexbuf.Lexbuf) -> StringView {
lexscan lexbuf with longest {
re"^[^;]*" as value => value.trim_end()
}
}
///|
#cfg(target="native")
/// Consumes a single `;` separator, returning whether one was present.
fn lex_cookie_separator(lexbuf : @lexbuf.Lexbuf) -> Bool {
lexscan lexbuf with longest {
re"^;" => true
_ => false
}
}
///|
#cfg(target="native")
/// Skips leading whitespace before an attribute name; the previous parser
/// trimmed each attribute segment.
fn lex_cookie_skip_spaces(lexbuf : @lexbuf.Lexbuf) -> Unit {
lexscan lexbuf with longest {
re"^[[:space:]]+" as spaces => ignore(spaces)
_ => ()
}
}