101 lines
3.3 KiB
Rust
101 lines
3.3 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::mentions::Mention;
|
|
use crate::models::verification::*;
|
|
|
|
fn is_mention_link(endpoint: &Url, gemtext_link: &str) -> bool {
|
|
gemtext_link.starts_with(endpoint.as_str())
|
|
}
|
|
|
|
fn scan_for_gemention_endpoints(
|
|
endpoint: &Url,
|
|
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(endpoint, to) => Some(to),
|
|
_ => None,
|
|
})
|
|
.cloned()
|
|
.collect();
|
|
|
|
(VerificationSource::Page, endpoints)
|
|
}
|
|
|
|
fn verify_mentions(
|
|
mention: &mut Mention,
|
|
source_and_endpoints: (VerificationSource, Vec<String>),
|
|
) -> Result<VerificationStatus, GementionError> {
|
|
let (verification_source, mentions) = source_and_endpoints;
|
|
// TODO need to normalize url from page as well as ours, to make
|
|
// sure things like ports being in url or not are handled.
|
|
let expected_link = mention.receiving_endpoint().join(mention.target())?;
|
|
let expected_link = expected_link.as_ref();
|
|
|
|
let verification_status = 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 {
|
|
endpoint: link,
|
|
source: verification_source,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or(VerificationStatus::Invalid(
|
|
VerificationFailureReason::MentionLinkIncorrect(
|
|
mention.receiving_endpoint().to_string(),
|
|
),
|
|
))
|
|
} else {
|
|
VerificationStatus::Invalid(VerificationFailureReason::NoMentionLinkFound)
|
|
};
|
|
|
|
Ok(verification_status)
|
|
}
|
|
|
|
async fn verify_mention(mention: &mut Mention) -> Result<(), GementionError> {
|
|
let url = Url::parse(&format!("gemini://{}", mention.target()))?;
|
|
|
|
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 source_and_endpoints =
|
|
scan_for_gemention_endpoints(mention.receiving_endpoint(), meta, ast);
|
|
let status = verify_mentions(mention, source_and_endpoints)?;
|
|
|
|
mention.set_verify_status(status);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn verify(mention: &mut Mention) -> Result<(), GementionError> {
|
|
verify_mention(mention).await
|
|
}
|