Skip to main content

codetether_browser/browser/offline/
cookie_parse.rs

1//! Minimal Set-Cookie parser. Captures name, value, and lowercase attribute map.
2
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6pub struct CookieRecord {
7    pub name: String,
8    pub value: String,
9    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
10    pub attrs: BTreeMap<String, String>,
11}
12
13pub fn parse(header: &str) -> Option<CookieRecord> {
14    let mut parts = header.split(';');
15    let (name, value) = split_pair(parts.next()?)?;
16    let mut attrs = BTreeMap::new();
17    for raw in parts {
18        if let Some((k, v)) = split_pair(raw) {
19            attrs.insert(k.to_ascii_lowercase(), v);
20        } else {
21            attrs.insert(raw.trim().to_ascii_lowercase(), String::new());
22        }
23    }
24    Some(CookieRecord { name, value, attrs })
25}
26
27fn split_pair(raw: &str) -> Option<(String, String)> {
28    let raw = raw.trim();
29    if raw.is_empty() {
30        return None;
31    }
32    match raw.split_once('=') {
33        Some((k, v)) => Some((k.trim().to_string(), v.trim().to_string())),
34        None => None,
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    #[test]
42    fn parses_session_cookie() {
43        let c = parse("session=abc; Path=/; HttpOnly; Secure").unwrap();
44        assert_eq!(c.name, "session");
45        assert_eq!(c.value, "abc");
46        assert_eq!(c.attrs.get("path").unwrap(), "/");
47        assert!(c.attrs.contains_key("httponly"));
48    }
49}