diff --git a/.idea/acrate.iml b/.idea/acrate.iml index b5a43c3..ac0db0c 100644 --- a/.idea/acrate.iml +++ b/.idea/acrate.iml @@ -4,12 +4,13 @@ - - - + + + + diff --git a/Cargo.toml b/Cargo.toml index 4a488a6..3776b1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["acrate_database", "acrate_rd", "acrate-nodeinfo", "acrate-webfinger"] +members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_mime"] diff --git a/acrate_mime/Cargo.toml b/acrate_mime/Cargo.toml new file mode 100644 index 0000000..f178e7a --- /dev/null +++ b/acrate_mime/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "acrate_mime" +version = "0.1.0" +authors = ["Stefano Pigozzi "] +edition = "2021" +description = "Rust typing and utilities for MIME / media types" +repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" +license = "EUPL-1.2" +keywords = ["mime", "mimetype", "media", "media-type", "mime-type"] +categories = ["web-programming"] + +[dependencies] +mime = "0.3.17" diff --git a/acrate_mime/src/lib.rs b/acrate_mime/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/acrate_mime/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/acrate-nodeinfo/Cargo.toml b/acrate_nodeinfo/Cargo.toml similarity index 52% rename from acrate-nodeinfo/Cargo.toml rename to acrate_nodeinfo/Cargo.toml index b641faa..60e55aa 100644 --- a/acrate-nodeinfo/Cargo.toml +++ b/acrate_nodeinfo/Cargo.toml @@ -1,11 +1,18 @@ [package] -name = "acrate-nodeinfo" -version = "0.1.0" +name = "acrate_nodeinfo" +version = "0.3.0" +authors = ["Stefano Pigozzi "] edition = "2021" +description = "Rust typing and utilities for the NodeInfo format" +repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" +license = "EUPL-1.2" +keywords = ["nodeinfo", "fediverse"] +categories = ["web-programming"] [dependencies] -acrate-hostmeta = { path = "../acrate-hostmeta" } +acrate_rd = { path = "../acrate_rd" } log = "0.4.22" +mime = "0.3.17" reqwest = { version = "0.12.9", features = ["json", "stream"] } serde = { version = "1.0.214", features = ["derive"] } serde_json = "1.0.132" diff --git a/acrate-nodeinfo/src/lib.rs b/acrate_nodeinfo/src/lib.rs similarity index 86% rename from acrate-nodeinfo/src/lib.rs rename to acrate_nodeinfo/src/lib.rs index 8500f91..5bc85f0 100644 --- a/acrate-nodeinfo/src/lib.rs +++ b/acrate_nodeinfo/src/lib.rs @@ -1,4 +1,4 @@ -//! Serde-based NodeInfo fetcher and loose parser. +//! Rust typing and utilities for the NodeInfo format. //! //! > NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. //! @@ -6,7 +6,9 @@ //! //! - //! - +//! +use std::str::FromStr; use serde::Deserialize; use thiserror::Error; @@ -15,6 +17,7 @@ use thiserror::Error; /// # Specification /// /// - +/// #[derive(Debug, Clone)] pub enum NodeInfo { V1(NodeInfo1), @@ -206,7 +209,7 @@ impl NodeInfo { base.set_path(Self::WELLKNOWN_NODEINFO_PATH); log::trace!("Discovering NodeInfo document locations..."); - let discovery = acrate_hostmeta::any::ResourceDescriptor::get(client, base) + let discovery = acrate_rd::any::ResourceDescriptor::get(client, base) .await .map_err(Get)? .jrd(); @@ -315,7 +318,7 @@ impl NodeInfo { pub enum NodeInfoGetWellknownError { /// The discovery of possible locations for NodeInfo documents failed. #[error("the discovery of possible locations for NodeInfo documents failed")] - Get(acrate_hostmeta::any::GetError), + Get(acrate_rd::any::GetError), /// No compatible NodeInfo documents were detected at the given URL. #[error("no compatible NodeInfo documents were detected at the given URL")] Unsupported, @@ -346,21 +349,27 @@ impl NodeInfo1 { let response = client.execute(request) .await .map_err(Request)?; - + log::trace!("Checking `Content-Type` of the response..."); let content_type = response .headers() .get(reqwest::header::CONTENT_TYPE) .ok_or(ContentTypeMissing)?; - - log::trace!("Extracting MIME type from the `Content-Type` header..."); - let mime_type = extract_mime_from_content_type(content_type) - .ok_or(ContentTypeInvalid)?; - - log::trace!("Ensuring MIME type is acceptable for NodeInfo documents..."); - if mime_type != "application/json" { - log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents."); - return Err(ContentTypeInvalid) + + log::trace!("Extracting media type from the `Content-Type` header..."); + let mime_type = content_type.to_str() + .map_err(ContentTypeUnprintable)?; + + log::trace!("Parsing media type: {mime_type:?}"); + let mime_type = mime::Mime::from_str(mime_type) + .map_err(ContentTypeInvalid)?; + + log::trace!("Checking if media type is supported: {mime_type:?}"); + let mime_is_json = mime_type == mime::APPLICATION_JSON; + log::trace!("Is media type application/json? {mime_is_json:?}"); + if !mime_is_json { + log::error!("MIME type `{mime_type}` is not acceptable for JSON parsing."); + return Err(ContentTypeUnsupported); } log::trace!("Attempting to parse response as JSON..."); @@ -418,21 +427,27 @@ impl NodeInfo2 { let response = client.execute(request) .await .map_err(Request)?; - + log::trace!("Checking `Content-Type` of the response..."); let content_type = response .headers() .get(reqwest::header::CONTENT_TYPE) .ok_or(ContentTypeMissing)?; - - log::trace!("Extracting MIME type from the `Content-Type` header..."); - let mime_type = extract_mime_from_content_type(content_type) - .ok_or(ContentTypeInvalid)?; - - log::trace!("Ensuring MIME type is acceptable for NodeInfo documents..."); - if mime_type != "application/json" { - log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents."); - return Err(ContentTypeInvalid) + + log::trace!("Extracting media type from the `Content-Type` header..."); + let mime_type = content_type.to_str() + .map_err(ContentTypeUnprintable)?; + + log::trace!("Parsing media type: {mime_type:?}"); + let mime_type = mime::Mime::from_str(mime_type) + .map_err(ContentTypeInvalid)?; + + log::trace!("Checking if media type is supported..."); + let mime_is_json = mime_type == mime::APPLICATION_JSON; + log::trace!("Is media type application/json? {mime_is_json:?}"); + if !mime_is_json { + log::error!("MIME type `{mime_type}` is not acceptable for JSON parsing."); + return Err(ContentTypeUnsupported); } log::trace!("Attempting to parse response as JSON..."); @@ -455,14 +470,22 @@ pub enum NodeInfoGetError { /// The HTTP request failed. #[error("the HTTP request failed")] Request(reqwest::Error), - + /// The `Content-Type` header of the response is missing. #[error("the Content-Type header of the response is missing")] ContentTypeMissing, - - /// The `Content-Type` header of the response is invalid. - #[error("the Content-Type header of the response is invalid")] - ContentTypeInvalid, + + /// The `Content-Type` header of the response can't be converted to a [`str`]. + #[error("the Content-Type header of the response cannot be converted to a &str")] + ContentTypeUnprintable(reqwest::header::ToStrError), + + /// The `Content-Type` header of the response is not a valid [`mime::Mime`] type. + #[error("the Content-Type header of the response is not a valid media type")] + ContentTypeInvalid(mime::FromStrError), + + /// The `Content-Type` header of the response is not a supported [`mime::Mime`] type. + #[error("the Content-Type header of the response is not a supported media type")] + ContentTypeUnsupported, /// The document failed to be parsed as JSON by [`reqwest`]. #[error("the document failed to be parsed as JSON")] @@ -472,12 +495,3 @@ pub enum NodeInfoGetError { #[error("the returned NodeInfo version would not match the version of the called method")] Version, } - -/// Extract the MIME type from the value of the `Content-Type` header. -fn extract_mime_from_content_type(value: &reqwest::header::HeaderValue) -> Option { - let value = value.to_str().ok()?; - match value.split_once("; ") { - None => Some(value.to_string()), - Some((mime, _)) => Some(mime.to_string()), - } -} diff --git a/acrate-nodeinfo/tests/nodeinfo_tests.rs b/acrate_nodeinfo/tests/integration_tests.rs similarity index 100% rename from acrate-nodeinfo/tests/nodeinfo_tests.rs rename to acrate_nodeinfo/tests/integration_tests.rs diff --git a/acrate_rd/Cargo.toml b/acrate_rd/Cargo.toml index 68f4fbb..8568dd0 100644 --- a/acrate_rd/Cargo.toml +++ b/acrate_rd/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" description = "Rust typing and utilities for the JSON and XML resource descriptior formats" repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" license = "EUPL-1.2" -keywords = ["jrd", "xrd", "hostmeta", "webfinger", "resource-descriptor"] +keywords = ["jrd", "xrd", "host-meta", "webfinger", "resource-descriptor"] categories = ["web-programming"] [dependencies] diff --git a/acrate_rd/src/jrd.rs b/acrate_rd/src/jrd.rs index 79dc599..1c08b40 100644 --- a/acrate_rd/src/jrd.rs +++ b/acrate_rd/src/jrd.rs @@ -181,7 +181,7 @@ impl ResourceDescriptorJRD { let mime_type = mime::Mime::from_str(mime_type) .map_err(ContentTypeInvalid)?; - log::trace!("Checking if media type is supported..."); + log::trace!("Checking if media type is supported: {mime_type:?}"); let mime_is_json = mime_type == mime::APPLICATION_JSON; log::trace!("Is media type application/json? {mime_is_json:?}"); let mime_is_jrd = diff --git a/acrate_rd/src/xrd.rs b/acrate_rd/src/xrd.rs index 93d18d5..3d6ae6b 100644 --- a/acrate_rd/src/xrd.rs +++ b/acrate_rd/src/xrd.rs @@ -217,9 +217,9 @@ impl ResourceDescriptorXRD { let mime_type = mime::Mime::from_str(mime_type) .map_err(ContentTypeInvalid)?; - log::trace!("Checking if media type is supported..."); + log::trace!("Checking if media type is supported: {mime_type:?}"); let mime_is_xrd = - mime_type.type_() == mime::APPLICATION + mime_type.type_().as_str() == mime::APPLICATION && mime_type.subtype() == "xrd" && mime_type.suffix() == Some(mime::XML); log::trace!("Is media type application/xrd+xml? {mime_is_xrd:?}"); diff --git a/acrate-webfinger/Cargo.toml b/acrate_rdserver/Cargo.toml similarity index 52% rename from acrate-webfinger/Cargo.toml rename to acrate_rdserver/Cargo.toml index c6983f1..b47b7af 100644 --- a/acrate-webfinger/Cargo.toml +++ b/acrate_rdserver/Cargo.toml @@ -1,11 +1,17 @@ [package] -name = "acrate-webfinger" -version = "0.1.0" +name = "acrate_rdserver" +version = "0.3.0" +authors = ["Stefano Pigozzi "] edition = "2021" +description = "Resource descriptor web server for the acrate project" +repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" +license = "EUPL-1.2" +keywords = ["jrd", "xrd", "hostmeta", "webfinger", "resource-descriptor"] +categories = ["web-programming"] [dependencies] acrate_database = { path = "../acrate_database" } -acrate-hostmeta = { path = "../acrate-hostmeta" } +acrate_rd = { path = "../acrate_rd" } anyhow = "1.0.93" axum = { version = "0.7.7", features = ["macros"] } axum-extra = { version = "0.9.4", features = ["query"] } @@ -17,3 +23,7 @@ quick-xml = { version = "0.37.0", features = ["serialize"] } serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.132" tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] } +mime = "0.3.17" + +[lints.clippy] +tabs-in-doc-comments = "allow" diff --git a/acrate-webfinger/src/config.rs b/acrate_rdserver/src/config.rs similarity index 100% rename from acrate-webfinger/src/config.rs rename to acrate_rdserver/src/config.rs diff --git a/acrate-webfinger/src/main.rs b/acrate_rdserver/src/main.rs similarity index 100% rename from acrate-webfinger/src/main.rs rename to acrate_rdserver/src/main.rs diff --git a/acrate-webfinger/src/route.rs b/acrate_rdserver/src/route.rs similarity index 86% rename from acrate-webfinger/src/route.rs rename to acrate_rdserver/src/route.rs index bda54ad..9ec95bc 100644 --- a/acrate-webfinger/src/route.rs +++ b/acrate_rdserver/src/route.rs @@ -6,8 +6,8 @@ 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_hostmeta::jrd::ResourceDescriptorLinkJRD; -use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; +use acrate_rd::jrd::ResourceDescriptorLinkJRD; +use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; use crate::config; #[derive(Debug, Clone, Deserialize)] @@ -47,7 +47,7 @@ pub async fn webfinger_handler( .await .map_err(|_| StatusCode::BAD_GATEWAY)?; - let subjects = MetaSubject::query_matching(&mut conn, WEBFINGER_DOC, &resource) + let subjects = MetaSubject::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; @@ -76,24 +76,24 @@ pub async fn webfinger_handler( let subject = subject.subject.clone(); - let aliases = MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource) + let aliases = MetaAlias::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - let properties = MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource) + let properties = MetaProperty::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - let links = MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource) + let links = MetaLink::aquery_matching(&mut conn, WEBFINGER_DOC, &resource) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - let link_properties = MetaLinkProperty::query_by_link(&mut conn, &links) + let link_properties = MetaLinkProperty::aquery_by_link(&mut conn, &links) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .grouped_by(&links); - let link_titles = MetaLinkTitle::query_by_link(&mut conn, &links) + let link_titles = MetaLinkTitle::aquery_by_link(&mut conn, &links) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .grouped_by(&links); @@ -143,7 +143,7 @@ pub async fn webfinger_handler( .into_iter() .map(|(link, properties, titles)| ResourceDescriptorLinkJRD { rel: link.rel, - r#type: link.type_, + r#type: link.type_.map(|m| m.0), href: link.href, template: link.template, properties: properties @@ -157,7 +157,7 @@ pub async fn webfinger_handler( }) .collect::>(); - let rd = acrate_hostmeta::jrd::ResourceDescriptorJRD { + let rd = acrate_rd::jrd::ResourceDescriptorJRD { subject, aliases, properties, @@ -192,7 +192,7 @@ pub async fn webfinger_handler( .into_iter() .map(|(link, properties, titles)| ResourceDescriptorLinkXRD { rel: link.rel, - r#type: link.type_, + r#type: link.type_.map(|m| m.0), href: link.href, template: link.template, properties: properties @@ -212,7 +212,7 @@ pub async fn webfinger_handler( }) .collect::>(); - let rd = acrate_hostmeta::xrd::ResourceDescriptorXRD { + let rd = acrate_rd::xrd::ResourceDescriptorXRD { subject, aliases, properties, @@ -241,12 +241,12 @@ pub async fn webfinger_handler( }) .collect(); - let links: Vec<(String, Option, Option, Option, Vec<(String, Option)>, Vec<(String, String)>)> = links_full + 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_, + link.type_.map(|m| m.0), link.href, link.template, properties.into_iter() diff --git a/acrate-webfinger/src/webfinger.html.j2 b/acrate_rdserver/src/webfinger.html.j2 similarity index 100% rename from acrate-webfinger/src/webfinger.html.j2 rename to acrate_rdserver/src/webfinger.html.j2