Skip to main content

asfml_core/
cookie.rs

1use crate::error::{Error, Result};
2
3const COOKIE_NAME: &str = "ponymail";
4
5pub fn parse_ponymail_cookie(input: &str) -> Result<String> {
6    if let Some(value) = parse_netscape_cookie(input) {
7        return Ok(value);
8    }
9
10    let normalized = input.trim();
11    let normalized = normalized
12        .strip_prefix("Cookie:")
13        .or_else(|| normalized.strip_prefix("cookie:"))
14        .unwrap_or(normalized);
15
16    if looks_like_cookie_value(normalized) {
17        return validate_cookie_value(normalized);
18    }
19
20    for part in normalized.split(';') {
21        let part = part.trim();
22        if let Some((name, value)) = part.split_once('=') {
23            if name.trim() == COOKIE_NAME {
24                return validate_cookie_value(value.trim());
25            }
26        }
27    }
28
29    Err(Error::InvalidCookie(
30        "could not find a ponymail cookie".to_string(),
31    ))
32}
33
34fn parse_netscape_cookie(input: &str) -> Option<String> {
35    for line in input.lines() {
36        let line = line.trim();
37        if line.is_empty() || line.starts_with('#') {
38            continue;
39        }
40
41        let fields: Vec<&str> = line.split('\t').collect();
42        if fields.len() < 7 {
43            continue;
44        }
45
46        let domain = fields[0].trim_start_matches('.');
47        let name = fields[5];
48        let value = fields[6];
49        if domain == "lists.apache.org" && name == COOKIE_NAME {
50            return validate_cookie_value(value).ok();
51        }
52    }
53
54    None
55}
56
57fn looks_like_cookie_value(input: &str) -> bool {
58    !input.is_empty()
59        && !input.contains('=')
60        && !input.contains(';')
61        && !input.contains(char::is_whitespace)
62}
63
64fn validate_cookie_value(value: &str) -> Result<String> {
65    let value = value.trim();
66    if value.is_empty() {
67        return Err(Error::InvalidCookie(
68            "ponymail cookie value is empty".to_string(),
69        ));
70    }
71    if value.contains(';') || value.contains('\n') || value.contains('\r') {
72        return Err(Error::InvalidCookie(
73            "ponymail cookie value contains invalid characters".to_string(),
74        ));
75    }
76    Ok(value.to_string())
77}
78
79#[cfg(test)]
80mod tests {
81    use super::parse_ponymail_cookie;
82
83    #[test]
84    fn parse_plain_cookie() {
85        let cookie = parse_ponymail_cookie("ponymail=abc-123").unwrap();
86        assert_eq!(cookie, "abc-123");
87    }
88
89    #[test]
90    fn parse_bare_cookie_value() {
91        let cookie = parse_ponymail_cookie("abc-123").unwrap();
92        assert_eq!(cookie, "abc-123");
93    }
94
95    #[test]
96    fn parse_cookie_header() {
97        let cookie = parse_ponymail_cookie("Cookie: other=x; ponymail=abc; foo=bar").unwrap();
98        assert_eq!(cookie, "abc");
99    }
100
101    #[test]
102    fn parse_netscape_cookie() {
103        let input = ".lists.apache.org\tTRUE\t/\tTRUE\t0\tponymail\tabc";
104        let cookie = parse_ponymail_cookie(input).unwrap();
105        assert_eq!(cookie, "abc");
106    }
107}