tail-fin-common 0.1.0

Shared infrastructure for tail-fin: error types, page_fetch, cookies, CDP helpers
Documentation
use std::path::Path;

use crate::TailFinError;

/// Parse a Netscape/curl cookie file into `(name, value)` pairs.
///
/// Ignores comment lines (`#`) and blank lines. Accepts the 7-field
/// tab-separated format: `domain flag path secure expires name value`.
pub fn parse_netscape_cookies(data: &str) -> Vec<(String, String)> {
    let mut cookies = Vec::new();
    for line in data.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let parts: Vec<&str> = line.splitn(7, '\t').collect();
        if parts.len() < 6 {
            continue;
        }
        let name = parts[5].to_string();
        let value = if parts.len() > 6 {
            parts[6].to_string()
        } else {
            String::new()
        };
        cookies.push((name, value));
    }
    cookies
}

/// Load a Netscape cookie file and return `(name, value)` pairs.
///
/// Returns `TailFinError::AuthRequired` if the file is empty or contains no cookies.
pub fn load_netscape_file(path: &Path) -> Result<Vec<(String, String)>, TailFinError> {
    let data = std::fs::read_to_string(path)
        .map_err(|e| TailFinError::Io(format!("Cannot read cookie file: {}", e)))?;
    let cookies = parse_netscape_cookies(&data);
    if cookies.is_empty() {
        return Err(TailFinError::AuthRequired);
    }
    Ok(cookies)
}

/// Write cookies as a Netscape cookie file.
///
/// `cookies` is a slice of JSON cookie objects as returned by CDP `Network.getAllCookies`.
pub fn write_netscape_file(path: &Path, cookies: &[serde_json::Value]) -> Result<(), TailFinError> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .map_err(|e| TailFinError::Io(format!("Cannot create directory: {}", e)))?;
    }

    let mut lines = vec![
        "# Netscape HTTP Cookie File".to_string(),
        "# Exported by tail-fin".to_string(),
        String::new(),
    ];

    for cookie in cookies {
        let domain = cookie.get("domain").and_then(|v| v.as_str()).unwrap_or("");
        let flag = if domain.starts_with('.') {
            "TRUE"
        } else {
            "FALSE"
        };
        let path_val = cookie.get("path").and_then(|v| v.as_str()).unwrap_or("/");
        let secure = if cookie
            .get("secure")
            .and_then(|v| v.as_bool())
            .unwrap_or(false)
        {
            "TRUE"
        } else {
            "FALSE"
        };
        let expires = cookie
            .get("expires")
            .and_then(|v| v.as_f64())
            .map(|v| v as u64)
            .unwrap_or(0);
        let name = cookie.get("name").and_then(|v| v.as_str()).unwrap_or("");
        let value = cookie.get("value").and_then(|v| v.as_str()).unwrap_or("");
        lines.push(format!(
            "{}\t{}\t{}\t{}\t{}\t{}\t{}",
            domain, flag, path_val, secure, expires, name, value
        ));
    }

    std::fs::write(path, lines.join("\n"))
        .map_err(|e| TailFinError::Io(format!("Cannot write cookie file: {}", e)))?;
    Ok(())
}