use std::sync::Arc; use axum::Extension; use axum::http::{HeaderMap, Response, StatusCode}; use axum_extra::extract::Query; use serde::Deserialize; use acrate_database::diesel::GroupedBy; use acrate_database::diesel_async::{AsyncConnection, AsyncPgConnection}; use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject}; use acrate_rd::jrd::ResourceDescriptorLinkJRD; use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; use crate::config; #[derive(Debug, Clone, Deserialize)] pub struct WebfingerQuery { pub resource: Option, #[serde(default)] pub rel: Vec, } const WEBFINGER_DOC: &str = "/.well-known/webfinger"; #[axum::debug_handler] pub async fn webfinger_handler( Query(WebfingerQuery {resource, rel}): Query, headers: HeaderMap, Extension(mj): Extension>>, ) -> Result, StatusCode> { log::info!("Handling a WebFinger request!"); let resource = resource.unwrap_or_else(|| "".to_string()); log::debug!("Resource is: {resource:#?}"); log::debug!("Rel is: {rel:#?}"); let accept = headers.get("Accept") .map(|v| v.to_str()) .filter(Result::is_ok) .map(|v| v.unwrap()) .unwrap_or("application/json") .to_string(); log::debug!("Accept is: {accept:#?}"); let mut response = Response::new("".to_string()); let mut conn = AsyncPgConnection::establish(config::ACRATE_WEBFINGER_DATABASE_URL()) .await .map_err(|_| StatusCode::BAD_GATEWAY)?; let subjects = MetaSubject::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let subject = subjects.first() .ok_or(StatusCode::NOT_FOUND)?; if subject.redirect.is_some() { { let headers = response.headers_mut(); headers.insert( "Location", subject.redirect .as_ref() .expect("redirect not to have become suddenly None") .parse() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? ); } { let status = response.status_mut(); *status = StatusCode::FOUND; } return Ok(response); } let subject = subject.subject.clone(); let aliases = MetaAlias::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let properties = MetaProperty::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let links = MetaLink::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let link_properties = MetaLinkProperty::aquery_by_link(&mut conn, &links) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .grouped_by(&links); let link_titles = MetaLinkTitle::aquery_by_link(&mut conn, &links) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .grouped_by(&links); let links_full: Vec<(MetaLink, Vec, Vec)> = links .into_iter() .zip(link_properties) .zip(link_titles) .map(|((link, properties), titles)| (link, properties, titles)) .collect(); { let headers = response.headers_mut(); headers.insert( "Access-Control-Allow-Origin", "*".parse().unwrap() ); } for mime in accept.split(",") { { let headers = response.headers_mut(); headers.insert( "Content-Type", mime.parse().map_err(|_| StatusCode::BAD_REQUEST)? ); } let (mime, _params) = match mime.trim().split_once(";") { Some((mime, params)) => (mime, Some(params)), None => (mime, None), }; match mime { "*/*" | "application/json" | "application/jrd+json" => { let aliases = aliases .into_iter() .map(|alias| alias.alias) .collect(); let properties = properties .into_iter() .map(|prop| (prop.rel, prop.value)) .collect(); let links = links_full .into_iter() .map(|(link, properties, titles)| ResourceDescriptorLinkJRD { rel: link.rel, r#type: link.type_.map(|m| m.0), href: link.href, template: link.template, properties: properties .into_iter() .map(|property| (property.rel, property.value)) .collect(), titles: titles .into_iter() .map(|title| (title.language, title.value)) .collect(), }) .collect::>(); let rd = acrate_rd::jrd::ResourceDescriptorJRD { subject, aliases, properties, links, }; let json = serde_json::to_string_pretty(&rd) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; { let body = response.body_mut(); body.push_str(&json); } return Ok(response); }, "application/xml" | "application/xrd+xml" => { let aliases = aliases .into_iter() .map(|alias| alias.alias) .collect(); let properties: Vec = properties .into_iter() .map(|prop| ResourceDescriptorPropertyXRD { rel: prop.rel, value: prop.value, }) .collect(); let links = links_full .into_iter() .map(|(link, properties, titles)| ResourceDescriptorLinkXRD { rel: link.rel, r#type: link.type_.map(|m| m.0), href: link.href, template: link.template, properties: properties .into_iter() .map(|property| ResourceDescriptorPropertyXRD { rel: property.rel, value: property.value, }) .collect(), titles: titles .into_iter() .map(|title| ResourceDescriptorTitleXRD { language: title.language, value: title.value, }) .collect(), }) .collect::>(); let rd = acrate_rd::xrd::ResourceDescriptorXRD { subject, aliases, properties, links, }; let xml = quick_xml::se::to_string(&rd) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; { let body = response.body_mut(); body.push_str(r#""#); body.push_str(&xml); } return Ok(response); }, "text/html" => { let aliases: Vec = aliases.into_iter() .map(|alias| alias.alias) .collect(); let properties: Vec<(String, Option)> = properties.into_iter() .map(|prop| { (prop.rel, prop.value) }) .collect(); let links: Vec<(String, Option, Option, Option, Vec<(String, Option)>, Vec<(String, String)>)> = links_full .into_iter() .map(|(link, properties, titles)| { ( link.rel, link.type_.map(|m| m.0), link.href, link.template, properties.into_iter() .map(|prop| (prop.rel, prop.value)) .collect::)>>(), titles.into_iter() .map(|title| (title.language, title.value)) .collect::>() ) }) .collect(); let html = mj.get_template("webfinger.html.j2") .expect("webfinger.html.j2 to exist") .render( minijinja::context!( subject => subject, aliases => aliases, properties => properties, links => links, ) ) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; { let body = response.body_mut(); body.push_str(&html); } return Ok(response); }, _ => { continue; }, } } Err(StatusCode::NOT_ACCEPTABLE) }