codetether_browser/browser/offline/
cookie_parse.rs1use 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}