// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Create a new cookie value
#alias(new, deprecated)
pub fn Cookie::Cookie(
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,
}
}
///|
/// Parse the value of a `Set-Cookie` header.
/// Following RFC 6265 section 5.2, a line without `=` in its name-value pair
/// is ignored (`None`), and so is a malformed `Max-Age` attribute, instead of
/// failing the whole response.
#cfg(any(target="native", target="wasm"))
fn Cookie::parse(line : StringView) -> Cookie? {
let (name_value, attrs) = match line.split_once(";") {
Some(pair) => pair
None => (line, "")
}
guard name_value.split_once("=") is Some((name, value)) else { return None }
let name = name.to_owned()
let value = value.to_owned()
if attrs is "" {
return Some(Cookie(name, value))
}
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 = []
for attr in attrs.split(";") {
let attr = attr.trim()
if attr.split_once("=") is Some((name, value)) {
match name.to_lower() {
"path" => path = Some(value.to_owned())
"expires" => expires_raw = Some(value.to_owned())
"max-age" =>
if parse_max_age(value) is Some(value) {
max_age = Some(value)
}
"domain" => domain = Some(value.to_owned())
_ => extensions.push(attr.to_owned())
}
} else {
match attr.to_lower() {
"secure" => secure = true
"httponly" => http_only = true
_ => extensions.push(attr.to_owned())
}
}
}
Some({
name,
value,
path,
expires_raw,
max_age,
domain,
secure,
http_only,
extensions,
})
}
///|
/// RFC 6265 section 5.2.2 ignores a value that is not an optional `-`
/// followed by digits. A value that has this form but does not fit in `Int64`
/// is ignored as well, since it cannot be represented.
#cfg(any(target="native", target="wasm"))
fn parse_max_age(value : StringView) -> Int64? {
let digits = if value.has_prefix("-") { value[1:] } else { value }
guard digits.length() > 0 && digits.iter().all(c => c is ('0'..<='9')) else {
return None
}
try @string.parse_int64(value) catch {
_ => None
} noraise {
value => Some(value)
}
}
///|
#cfg(any(target="native", target="wasm"))
async fn[W : @io.BinaryWriter] Cookie::write_to(cookie : Cookie, w : W) -> Unit {
w
..write(@utf8.encode(cookie.name))
..write(b"=")
.write(@utf8.encode(cookie.value))
if cookie.path is Some(path) {
w..write(b"; Path=").write(@utf8.encode(path))
}
if cookie.expires_raw is Some(expires_raw) {
w..write(b"; Expires=").write(@utf8.encode(expires_raw))
}
if cookie.max_age is Some(max_age) {
w..write(b"; Max-Age=").write(@utf8.encode(max_age.to_string()))
}
if cookie.domain is Some(domain) {
w..write(b"; Domain=").write(@utf8.encode(domain))
}
if cookie.secure {
w.write(b"; Secure")
}
if cookie.http_only {
w.write(b"; HttpOnly")
}
for ext in cookie.extensions {
w..write(b"; ").write(@utf8.encode(ext))
}
}