tail-fin-gemini 0.7.5

Gemini (Google) adapter for tail-fin: cookie-authenticated HTTP client — text + file attach + multi-turn; no browser
Documentation
//! SAPISIDHASH — Google's auth scheme for cookie-authenticated web
//! endpoints. Format:
//!
//! ```text
//! Authorization: SAPISIDHASH <timestamp>_<sha1(timestamp " " SAPISID " " origin)>
//! ```
//!
//! Gemini accepts the hash via the `Authorization` header on the
//! StreamGenerate endpoint when `Origin: https://gemini.google.com` is
//! also set.

use std::time::{SystemTime, UNIX_EPOCH};

use sha1::{Digest, Sha1};

pub const ORIGIN: &str = "https://gemini.google.com";

/// Build the `Authorization: SAPISIDHASH ...` header value.
///
/// `now_secs` is the current unix timestamp — taking it as a parameter
/// makes this deterministic for tests.
pub fn build_sapisidhash(sapisid: &str, origin: &str, now_secs: u64) -> String {
    let payload = format!("{} {} {}", now_secs, sapisid, origin);
    let hash = Sha1::digest(payload.as_bytes());
    format!("SAPISIDHASH {}_{}", now_secs, hex::encode(hash))
}

/// Same as `build_sapisidhash` but takes `now` from the system clock.
pub fn current_sapisidhash(sapisid: &str) -> String {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    build_sapisidhash(sapisid, ORIGIN, now)
}

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

    // Reference value:
    //   echo -n "1700000000 test_sapisid https://gemini.google.com" | shasum -a 1
    #[test]
    fn sapisidhash_matches_reference_sha1() {
        let h = build_sapisidhash("test_sapisid", ORIGIN, 1_700_000_000);
        assert_eq!(
            h,
            "SAPISIDHASH 1700000000_c32887cd7ccc34c5e44af1847e82649133736f44"
        );
    }

    #[test]
    fn sapisidhash_prefixed_with_scheme() {
        let h = build_sapisidhash("x", ORIGIN, 0);
        assert!(h.starts_with("SAPISIDHASH "));
    }

    #[test]
    fn sapisidhash_changes_with_timestamp() {
        let a = build_sapisidhash("x", ORIGIN, 1);
        let b = build_sapisidhash("x", ORIGIN, 2);
        assert_ne!(a, b);
    }
}