From 968fe675cd83c825dbebff9e5c1c999a92f4f2dc Mon Sep 17 00:00:00 2001 From: projectmoon Date: Fri, 29 Mar 2024 20:00:02 +0100 Subject: [PATCH] Also attempt to verify from meta --- src/main.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 933f0b7..d31e7c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ +use std::fmt::Display; + use fluffer::{App, AppErr, Client, Fluff}; use germ::ast::{Ast as GeminiAst, Node as GemtextNode}; +use germ::meta::Meta as GeminiMetadata; use germ::request::request as germ_request; use thiserror::Error; use url::Url; @@ -17,19 +20,39 @@ enum GementionError { } enum VerificationStatus { - Verified(String), + Verified { + endpoint: String, + source: VerificationSource, + }, + NotVerified(VerificationFailureReason), } impl ToString for VerificationStatus { fn to_string(&self) -> String { match self { - Self::Verified(url) => format!("verified: {}", url), + Self::Verified { endpoint, source } => format!("verified: {} [{}]", endpoint, source), Self::NotVerified(failure) => failure.to_string(), } } } +#[derive(Debug, Clone, Copy)] +enum VerificationSource { + Meta, + Page, +} + +impl Display for VerificationSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Meta => write!(f, "source=meta"), + Self::Page => write!(f, "source=page"), + } + + } +} + enum VerificationFailureReason { /// No titan link to our endpoint exists on this page. NoMentionLinkFound, @@ -58,25 +81,48 @@ fn is_mention_link(gemtext_link: &str) -> Result { Ok(gemtext_link.starts_with(OUR_ENDPOINT)) } -fn scan_for_mentions(ast: GeminiAst) -> Vec { - ast.inner() +fn scan_for_mentions(meta: GeminiMetadata, ast: GeminiAst) -> (VerificationSource, Vec) { + // Check metadata of the page for a gemention endpoint. + println!("meta is {:?}", meta); + if let Some(endpoint) = meta.parameters().get("gemention") { + let endpoint = endpoint.trim_start_matches("="); + return (VerificationSource::Meta, vec![endpoint.to_owned()]); + } + + // If that fails, check the page itself for the first available + // link that matches. + let endpoints = ast + .inner() .into_iter() .filter_map(|node| match node { GemtextNode::Link { ref to, .. } if is_mention_link(to).unwrap_or(false) => Some(to), _ => None, }) .cloned() - .collect() + .collect(); + + (VerificationSource::Page, endpoints) } -fn verify_mentions>(expected_link: S, mentions: Vec) -> VerificationStatus { +fn verify_mentions>( + expected_link: S, + source_and_mentions: (VerificationSource, Vec), +) -> VerificationStatus { + let (verification_source, mentions) = source_and_mentions; let expected_link = expected_link.as_ref(); + if mentions.len() > 0 { + // We have links that go to our endpoint. Scan links for the + // one we expect (i.e. for the target), otherwise we say + // incorrect link. mentions .into_iter() .find_map(|link| { if link == expected_link { - Some(VerificationStatus::Verified(link)) + Some(VerificationStatus::Verified { + source: verification_source, + endpoint: link, + }) } else { None } @@ -89,18 +135,20 @@ fn verify_mentions>(expected_link: S, mentions: Vec) -> Ve } } -async fn verify_mention(target: &str) -> Result { - let url = Url::parse(&format!("gemini://{}", target))?; - let expected_link = Url::parse(OUR_ENDPOINT)?.join(target)?; +async fn verify_mention(target_page: &str) -> Result { + let url = Url::parse(&format!("gemini://{}", target_page))?; + let expected_link = Url::parse(OUR_ENDPOINT)?.join(target_page)?; let resp = germ_request(&url).await?; + let meta = GeminiMetadata::from_string(resp.meta()); + let content = resp .content() .as_deref() .ok_or(GementionError::NoContentFoundForTarget)?; let ast = GeminiAst::from_string(content); - let mentions = scan_for_mentions(ast); + let mentions = scan_for_mentions(meta, ast); Ok(verify_mentions(expected_link, mentions)) }