Gemention/src/verification/mod.rs

95 lines
3.0 KiB
Rust

use germ::ast::{Ast as GeminiAst, Node as GemtextNode};
use germ::meta::Meta as GeminiMetadata;
use germ::request::request as germ_request;
use url::Url;
use crate::error::GementionError;
use crate::models::*;
const OUR_ENDPOINT: &'static str = "titan://localhost/receive/";
fn is_mention_link(gemtext_link: &str) -> bool {
gemtext_link.starts_with(OUR_ENDPOINT)
}
fn scan_for_mentions(meta: GeminiMetadata, ast: GeminiAst) -> (VerificationSource, Vec<String>) {
// Check metadata of the page for a gemention endpoint.
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) => Some(to),
_ => None,
})
.cloned()
.collect();
(VerificationSource::Page, endpoints)
}
fn verify_mentions<S: AsRef<str>>(
expected_link: S,
source_and_mentions: (VerificationSource, Vec<String>),
) -> 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 {
source: verification_source,
endpoint: link,
})
} else {
None
}
})
.unwrap_or(VerificationStatus::NotVerified(
VerificationFailureReason::MentionLinkIncorrect,
))
} else {
VerificationStatus::NotVerified(VerificationFailureReason::NoMentionLinkFound)
}
}
async fn verify_mention(target_page: &str) -> Result<VerificationStatus, GementionError> {
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(meta, ast);
Ok(verify_mentions(expected_link, mentions))
}
pub(crate) async fn verify(target_page: &str) -> VerificationStatus {
let result = verify_mention(target_page)
.await
.map_err(|e| VerificationStatus::NotVerified(VerificationFailureReason::Error(e)));
match result {
Ok(status) => status,
Err(status) => status,
}
}