asfml-core 0.1.1

Core library for reading Apache Pony Mail archives
Documentation
use crate::error::{Error, Result};

const COOKIE_NAME: &str = "ponymail";

pub fn parse_ponymail_cookie(input: &str) -> Result<String> {
    if let Some(value) = parse_netscape_cookie(input) {
        return Ok(value);
    }

    let normalized = input.trim();
    let normalized = normalized
        .strip_prefix("Cookie:")
        .or_else(|| normalized.strip_prefix("cookie:"))
        .unwrap_or(normalized);

    if looks_like_cookie_value(normalized) {
        return validate_cookie_value(normalized);
    }

    for part in normalized.split(';') {
        let part = part.trim();
        if let Some((name, value)) = part.split_once('=') {
            if name.trim() == COOKIE_NAME {
                return validate_cookie_value(value.trim());
            }
        }
    }

    Err(Error::InvalidCookie(
        "could not find a ponymail cookie".to_string(),
    ))
}

fn parse_netscape_cookie(input: &str) -> Option<String> {
    for line in input.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }

        let fields: Vec<&str> = line.split('\t').collect();
        if fields.len() < 7 {
            continue;
        }

        let domain = fields[0].trim_start_matches('.');
        let name = fields[5];
        let value = fields[6];
        if domain == "lists.apache.org" && name == COOKIE_NAME {
            return validate_cookie_value(value).ok();
        }
    }

    None
}

fn looks_like_cookie_value(input: &str) -> bool {
    !input.is_empty()
        && !input.contains('=')
        && !input.contains(';')
        && !input.contains(char::is_whitespace)
}

fn validate_cookie_value(value: &str) -> Result<String> {
    let value = value.trim();
    if value.is_empty() {
        return Err(Error::InvalidCookie(
            "ponymail cookie value is empty".to_string(),
        ));
    }
    if value.contains(';') || value.contains('\n') || value.contains('\r') {
        return Err(Error::InvalidCookie(
            "ponymail cookie value contains invalid characters".to_string(),
        ));
    }
    Ok(value.to_string())
}

#[cfg(test)]
mod tests {
    use super::parse_ponymail_cookie;

    #[test]
    fn parse_plain_cookie() {
        let cookie = parse_ponymail_cookie("ponymail=abc-123").unwrap();
        assert_eq!(cookie, "abc-123");
    }

    #[test]
    fn parse_bare_cookie_value() {
        let cookie = parse_ponymail_cookie("abc-123").unwrap();
        assert_eq!(cookie, "abc-123");
    }

    #[test]
    fn parse_cookie_header() {
        let cookie = parse_ponymail_cookie("Cookie: other=x; ponymail=abc; foo=bar").unwrap();
        assert_eq!(cookie, "abc");
    }

    #[test]
    fn parse_netscape_cookie() {
        let input = ".lists.apache.org\tTRUE\t/\tTRUE\t0\tponymail\tabc";
        let cookie = parse_ponymail_cookie(input).unwrap();
        assert_eq!(cookie, "abc");
    }
}