use std::iter::IntoIterator; use std::sync::Arc; use axum::Extension; use axum::extract::Path; use axum::http::{HeaderMap, Response, StatusCode}; use axum_extra::extract::Query; use mediatype::{MediaTypeBuf, MediaTypeList}; 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, } pub async fn webfinger_handler( Path(path): Path, 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:#?}"); let path = format!("/{path}"); log::debug!("Path is: {path:#?}"); log::debug!("Rel is: {rel:#?}"); let accept = headers.get("Accept"); log::debug!("Accept is: {accept:#?}"); let accept = accept .map(|h| h.to_str()) .unwrap_or(Ok("*/*")) .map(|h| MediaTypeList::new(h)) .map_err(|_| StatusCode::BAD_REQUEST)?; 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, &path, &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, &path, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let properties = MetaProperty::aquery_matching(&mut conn, &path, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let links = MetaLink::aquery_matching(&mut conn, &path, &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 media_type in accept.into_iter() { let media_type = match media_type { Err(e) => { log::debug!("Skipping error while parsing media type: {e:?}"); continue; }, Ok(media_type) => media_type }; { let headers = response.headers_mut(); headers.insert( "Content-Type", media_type.to_string() .parse() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? ); } match media_type.essence().to_string().to_ascii_lowercase().as_str() { "*/*" | "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("rd.html.j2") .expect("rd.html.j2 to exist") .render( minijinja::context!( path => path, 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) }