Compare commits
3 commits
aec5a01f2c
...
79e604eb96
Author | SHA1 | Date | |
---|---|---|---|
79e604eb96 | |||
f223ce64fe | |||
8b01cc14c0 |
20 changed files with 203 additions and 264 deletions
|
@ -4,12 +4,13 @@
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate-inbox/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/acrate-inbox/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate-nodeinfo/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate-nodeinfo/tests" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate-webfinger/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate_database/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/acrate_database/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/acrate_nodeinfo/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/acrate_nodeinfo/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/tests" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/tests" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/acrate_mime/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/acrate_rdserver/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["acrate_database", "acrate_rd", "acrate-nodeinfo", "acrate-webfinger"]
|
members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver"]
|
||||||
|
|
|
@ -12,13 +12,18 @@ Federation database
|
||||||
>
|
>
|
||||||
> This software suite is in active development!
|
> This software suite is in active development!
|
||||||
|
|
||||||
|
> [!Warning]
|
||||||
|
>
|
||||||
|
> **Monorepo!** Make sure to open the root directory in your IDE, or weird things might happen until the packages are published!
|
||||||
|
|
||||||
## Crates
|
## Crates
|
||||||
|
|
||||||
### Libraries
|
### Libraries
|
||||||
|
|
||||||
- `acrate_database`: Database schema, migrations, and high level database-reliant structures for the [`acrate`] project
|
- `acrate_database`: Database schema, migrations, and high level database-reliant structures
|
||||||
|
- `acrate_nodeinfo`: Rust typing and utilities for the NodeInfo format
|
||||||
- `acrate_rd`: Rust typing and utilities for the JSON and XML resource descriptior formats
|
- `acrate_rd`: Rust typing and utilities for the JSON and XML resource descriptior formats
|
||||||
|
|
||||||
### Binaries
|
### Binaries
|
||||||
|
|
||||||
- `acrate-webfinger`: WebFinger server
|
- `acrate_rdserver`: Resource descriptor web server
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "acrate-nodeinfo"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
acrate-hostmeta = { path = "../acrate-hostmeta" }
|
|
||||||
log = "0.4.22"
|
|
||||||
reqwest = { version = "0.12.9", features = ["json", "stream"] }
|
|
||||||
serde = { version = "1.0.214", features = ["derive"] }
|
|
||||||
serde_json = "1.0.132"
|
|
||||||
thiserror = "2.0.3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_env_logger = "0.5.0"
|
|
||||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
|
|
||||||
tokio-test = "0.4.4"
|
|
||||||
|
|
||||||
[lints.clippy]
|
|
||||||
tabs-in-doc-comments = "allow"
|
|
|
@ -14,6 +14,7 @@ diesel = { version = "2.2.4", features = ["postgres", "uuid"] }
|
||||||
diesel-async = { version = "0.5.1", features = ["postgres"] }
|
diesel-async = { version = "0.5.1", features = ["postgres"] }
|
||||||
diesel_migrations = { version = "2.2.0", optional = true }
|
diesel_migrations = { version = "2.2.0", optional = true }
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
|
mediatype = "0.19.18"
|
||||||
micronfig = { version = "0.3.0", optional = true }
|
micronfig = { version = "0.3.0", optional = true }
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
pretty_env_logger = { version = "0.5.0", optional = true }
|
pretty_env_logger = { version = "0.5.0", optional = true }
|
||||||
|
|
|
@ -26,12 +26,12 @@
|
||||||
//! - [`acrate_rdserver`]
|
//! - [`acrate_rdserver`]
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use diesel::deserialize::FromSql;
|
use diesel::deserialize::FromSql;
|
||||||
use diesel::{AsExpression, Associations, FromSqlRow, Identifiable, Insertable, IntoSql, PgTextExpressionMethods, QueryResult, Queryable, QueryableByName, Selectable, SelectableHelper, ExpressionMethods, BelongingToDsl};
|
use diesel::{AsExpression, Associations, FromSqlRow, Identifiable, Insertable, IntoSql, PgTextExpressionMethods, QueryResult, Queryable, QueryableByName, Selectable, SelectableHelper, ExpressionMethods, BelongingToDsl};
|
||||||
use diesel::pg::{Pg, PgConnection};
|
use diesel::pg::{Pg, PgConnection};
|
||||||
use diesel::serialize::{Output, ToSql};
|
use diesel::serialize::{Output, ToSql};
|
||||||
use diesel_async::AsyncPgConnection;
|
use diesel_async::AsyncPgConnection;
|
||||||
|
use mediatype::MediaTypeBuf;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::schema;
|
use super::schema;
|
||||||
|
@ -40,7 +40,7 @@ use super::schema;
|
||||||
/// Wrapper to use [`mime::Mime`] with [`diesel`].
|
/// Wrapper to use [`mime::Mime`] with [`diesel`].
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression)]
|
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression)]
|
||||||
#[diesel(sql_type = diesel::sql_types::Text)]
|
#[diesel(sql_type = diesel::sql_types::Text)]
|
||||||
pub struct Mime(pub mime::Mime);
|
pub struct MediaTypeDatabase(pub MediaTypeBuf);
|
||||||
|
|
||||||
|
|
||||||
/// A matchable record denoting the existence of a resource descriptor.
|
/// A matchable record denoting the existence of a resource descriptor.
|
||||||
|
@ -173,7 +173,7 @@ pub struct MetaLink {
|
||||||
/// The media type of the value of the link.
|
/// The media type of the value of the link.
|
||||||
///
|
///
|
||||||
/// Can be [`None`] if it shouldn't be specified.
|
/// Can be [`None`] if it shouldn't be specified.
|
||||||
pub type_: Option<Mime>,
|
pub type_: Option<MediaTypeDatabase>,
|
||||||
|
|
||||||
/// The URI to the document this property is linking the subject to.
|
/// The URI to the document this property is linking the subject to.
|
||||||
///
|
///
|
||||||
|
@ -206,7 +206,7 @@ pub struct MetaLinkInsert {
|
||||||
/// The media type of the value of the link.
|
/// The media type of the value of the link.
|
||||||
///
|
///
|
||||||
/// Can be [`None`] if it shouldn't be specified.
|
/// Can be [`None`] if it shouldn't be specified.
|
||||||
pub type_: Option<Mime>,
|
pub type_: Option<MediaTypeDatabase>,
|
||||||
|
|
||||||
/// The URI to the document this property is linking the subject to.
|
/// The URI to the document this property is linking the subject to.
|
||||||
///
|
///
|
||||||
|
@ -348,8 +348,8 @@ pub struct MetaPropertyInsert {
|
||||||
pub value: Option<String>,
|
pub value: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow [`diesel::sql_types::Text`] values to be parsed as [`Mime`].
|
/// Allow [`diesel::sql_types::Text`] values to be parsed as [`MediaTypeDatabase`].
|
||||||
impl<DB> FromSql<diesel::sql_types::Text, DB> for Mime
|
impl<DB> FromSql<diesel::sql_types::Text, DB> for MediaTypeDatabase
|
||||||
where
|
where
|
||||||
DB: diesel::backend::Backend,
|
DB: diesel::backend::Backend,
|
||||||
String: FromSql<diesel::sql_types::Text, DB>,
|
String: FromSql<diesel::sql_types::Text, DB>,
|
||||||
|
@ -359,25 +359,25 @@ impl<DB> FromSql<diesel::sql_types::Text, DB> for Mime
|
||||||
let s = <String as FromSql<diesel::sql_types::Text, DB>>::from_sql(bytes)?;
|
let s = <String as FromSql<diesel::sql_types::Text, DB>>::from_sql(bytes)?;
|
||||||
|
|
||||||
log::trace!("Attempting to parse as a media type: {s:?}");
|
log::trace!("Attempting to parse as a media type: {s:?}");
|
||||||
let mime = mime::Mime::from_str(&s)?;
|
let mt: MediaTypeBuf = s.parse()?;
|
||||||
|
|
||||||
log::trace!("Successfully parsed media type: {mime:?}");
|
log::trace!("Successfully parsed media type: {mt:?}");
|
||||||
Ok(Self(mime))
|
Ok(Self(mt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow [`diesel::sql_types::Text`] values to be written to with [`Mime`].
|
/// Allow [`diesel::sql_types::Text`] values to be written to with [`MediaTypeDatabase`].
|
||||||
impl<DB> ToSql<diesel::sql_types::Text, DB> for Mime
|
impl<DB> ToSql<diesel::sql_types::Text, DB> for MediaTypeDatabase
|
||||||
where
|
where
|
||||||
DB: diesel::backend::Backend,
|
DB: diesel::backend::Backend,
|
||||||
str: ToSql<diesel::sql_types::Text, DB>,
|
str: ToSql<diesel::sql_types::Text, DB>,
|
||||||
{
|
{
|
||||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
||||||
log::trace!("Getting the essence of a media type to prepare for serialization...");
|
log::trace!("Getting the essence of a media type to prepare for serialization...");
|
||||||
let mime = self.0.essence_str();
|
let mt = self.0.as_str();
|
||||||
|
|
||||||
log::trace!("Serializing media type as TEXT: {mime:?}");
|
log::trace!("Serializing media type as TEXT: {mt:?}");
|
||||||
<str as ToSql<diesel::sql_types::Text, DB>>::to_sql(mime, out)
|
<str as ToSql<diesel::sql_types::Text, DB>>::to_sql(mt, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
acrate_nodeinfo/Cargo.toml
Normal file
27
acrate_nodeinfo/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "acrate_nodeinfo"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||||
|
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_rd = { path = "../acrate_rd" }
|
||||||
|
log = "0.4.22"
|
||||||
|
mediatype = { version = "0.19.18", features = ["serde"] }
|
||||||
|
reqwest = { version = "0.12.9", features = ["json", "stream"] }
|
||||||
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
|
serde_json = "1.0.132"
|
||||||
|
thiserror = "2.0.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_env_logger = "0.5.0"
|
||||||
|
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tokio-test = "0.4.4"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
tabs-in-doc-comments = "allow"
|
|
@ -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.
|
//! > 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 @@
|
||||||
//!
|
//!
|
||||||
//! - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
|
//! - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
|
||||||
//! - <https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md>
|
//! - <https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md>
|
||||||
|
//!
|
||||||
|
|
||||||
|
use mediatype::MediaTypeBuf;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -15,6 +17,7 @@ use thiserror::Error;
|
||||||
/// # Specification
|
/// # Specification
|
||||||
///
|
///
|
||||||
/// - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
|
/// - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
|
||||||
|
///
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum NodeInfo {
|
pub enum NodeInfo {
|
||||||
V1(NodeInfo1),
|
V1(NodeInfo1),
|
||||||
|
@ -206,7 +209,7 @@ impl NodeInfo {
|
||||||
base.set_path(Self::WELLKNOWN_NODEINFO_PATH);
|
base.set_path(Self::WELLKNOWN_NODEINFO_PATH);
|
||||||
|
|
||||||
log::trace!("Discovering NodeInfo document locations...");
|
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
|
.await
|
||||||
.map_err(Get)?
|
.map_err(Get)?
|
||||||
.jrd();
|
.jrd();
|
||||||
|
@ -315,7 +318,7 @@ impl NodeInfo {
|
||||||
pub enum NodeInfoGetWellknownError {
|
pub enum NodeInfoGetWellknownError {
|
||||||
/// The discovery of possible locations for NodeInfo documents failed.
|
/// The discovery of possible locations for NodeInfo documents failed.
|
||||||
#[error("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.
|
/// No compatible NodeInfo documents were detected at the given URL.
|
||||||
#[error("no compatible NodeInfo documents were detected at the given URL")]
|
#[error("no compatible NodeInfo documents were detected at the given URL")]
|
||||||
Unsupported,
|
Unsupported,
|
||||||
|
@ -346,21 +349,28 @@ impl NodeInfo1 {
|
||||||
let response = client.execute(request)
|
let response = client.execute(request)
|
||||||
.await
|
.await
|
||||||
.map_err(Request)?;
|
.map_err(Request)?;
|
||||||
|
|
||||||
log::trace!("Checking `Content-Type` of the response...");
|
log::trace!("Checking `Content-Type` of the response...");
|
||||||
let content_type = response
|
let content_type = response
|
||||||
.headers()
|
.headers()
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
.ok_or(ContentTypeMissing)?;
|
.ok_or(ContentTypeMissing)?;
|
||||||
|
|
||||||
log::trace!("Extracting MIME type from the `Content-Type` header...");
|
log::trace!("Extracting media type from the `Content-Type` header: {content_type:?}");
|
||||||
let mime_type = extract_mime_from_content_type(content_type)
|
let media_type: MediaTypeBuf = content_type
|
||||||
.ok_or(ContentTypeInvalid)?;
|
.to_str()
|
||||||
|
.map_err(ContentTypeUnprintable)?
|
||||||
log::trace!("Ensuring MIME type is acceptable for NodeInfo documents...");
|
.parse()
|
||||||
if mime_type != "application/json" {
|
.map_err(ContentTypeInvalid)?;
|
||||||
log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents.");
|
|
||||||
return Err(ContentTypeInvalid)
|
log::trace!("Checking if media type is supported: {media_type:?}");
|
||||||
|
|
||||||
|
let mime_is_json = media_type.essence().eq(&"application/json".parse::<MediaTypeBuf>().unwrap());
|
||||||
|
log::trace!("Is media type `application/json`? {mime_is_json:?}");
|
||||||
|
|
||||||
|
if !mime_is_json {
|
||||||
|
log::error!("Media type `{media_type}` is not acceptable for NodeInfo parsing.");
|
||||||
|
return Err(ContentTypeUnsupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Attempting to parse response as JSON...");
|
log::trace!("Attempting to parse response as JSON...");
|
||||||
|
@ -418,21 +428,28 @@ impl NodeInfo2 {
|
||||||
let response = client.execute(request)
|
let response = client.execute(request)
|
||||||
.await
|
.await
|
||||||
.map_err(Request)?;
|
.map_err(Request)?;
|
||||||
|
|
||||||
log::trace!("Checking `Content-Type` of the response...");
|
log::trace!("Checking `Content-Type` of the response...");
|
||||||
let content_type = response
|
let content_type = response
|
||||||
.headers()
|
.headers()
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
.ok_or(ContentTypeMissing)?;
|
.ok_or(ContentTypeMissing)?;
|
||||||
|
|
||||||
log::trace!("Extracting MIME type from the `Content-Type` header...");
|
log::trace!("Extracting media type from the `Content-Type` header: {content_type:?}");
|
||||||
let mime_type = extract_mime_from_content_type(content_type)
|
let media_type: MediaTypeBuf = content_type
|
||||||
.ok_or(ContentTypeInvalid)?;
|
.to_str()
|
||||||
|
.map_err(ContentTypeUnprintable)?
|
||||||
log::trace!("Ensuring MIME type is acceptable for NodeInfo documents...");
|
.parse()
|
||||||
if mime_type != "application/json" {
|
.map_err(ContentTypeInvalid)?;
|
||||||
log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents.");
|
|
||||||
return Err(ContentTypeInvalid)
|
log::trace!("Checking if media type is supported: {media_type:?}");
|
||||||
|
|
||||||
|
let mime_is_json = media_type.essence().eq(&"application/json".parse::<MediaTypeBuf>().unwrap());
|
||||||
|
log::trace!("Is media type `application/json`? {mime_is_json:?}");
|
||||||
|
|
||||||
|
if !mime_is_json {
|
||||||
|
log::error!("Media type `{media_type}` is not acceptable for NodeInfo parsing.");
|
||||||
|
return Err(ContentTypeUnsupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Attempting to parse response as JSON...");
|
log::trace!("Attempting to parse response as JSON...");
|
||||||
|
@ -455,14 +472,22 @@ pub enum NodeInfoGetError {
|
||||||
/// The HTTP request failed.
|
/// The HTTP request failed.
|
||||||
#[error("the HTTP request failed")]
|
#[error("the HTTP request failed")]
|
||||||
Request(reqwest::Error),
|
Request(reqwest::Error),
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is missing.
|
/// The `Content-Type` header of the response is missing.
|
||||||
#[error("the Content-Type header of the response is missing")]
|
#[error("the Content-Type header of the response is missing")]
|
||||||
ContentTypeMissing,
|
ContentTypeMissing,
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is invalid.
|
/// The `Content-Type` header of the response can't be converted to a [`str`].
|
||||||
#[error("the Content-Type header of the response is invalid")]
|
#[error("the Content-Type header of the response cannot be converted to a &str")]
|
||||||
ContentTypeInvalid,
|
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(mediatype::MediaTypeError),
|
||||||
|
|
||||||
|
/// 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`].
|
/// The document failed to be parsed as JSON by [`reqwest`].
|
||||||
#[error("the document failed to be parsed as JSON")]
|
#[error("the document failed to be parsed as JSON")]
|
||||||
|
@ -472,12 +497,3 @@ pub enum NodeInfoGetError {
|
||||||
#[error("the returned NodeInfo version would not match the version of the called method")]
|
#[error("the returned NodeInfo version would not match the version of the called method")]
|
||||||
Version,
|
Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the MIME type from the value of the `Content-Type` header.
|
|
||||||
fn extract_mime_from_content_type(value: &reqwest::header::HeaderValue) -> Option<String> {
|
|
||||||
let value = value.to_str().ok()?;
|
|
||||||
match value.split_once("; ") {
|
|
||||||
None => Some(value.to_string()),
|
|
||||||
Some((mime, _)) => Some(mime.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,12 +6,12 @@ edition = "2021"
|
||||||
description = "Rust typing and utilities for the JSON and XML resource descriptior formats"
|
description = "Rust typing and utilities for the JSON and XML resource descriptior formats"
|
||||||
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
|
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
|
||||||
license = "EUPL-1.2"
|
license = "EUPL-1.2"
|
||||||
keywords = ["jrd", "xrd", "hostmeta", "webfinger", "resource-descriptor"]
|
keywords = ["jrd", "xrd", "host-meta", "webfinger", "resource-descriptor"]
|
||||||
categories = ["web-programming"]
|
categories = ["web-programming"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
mime = "0.3.17"
|
mediatype = { version = "0.19.18", features = ["serde"] }
|
||||||
quick-xml = { version = "0.37.0", features = ["overlapped-lists", "serialize"] }
|
quick-xml = { version = "0.37.0", features = ["overlapped-lists", "serialize"] }
|
||||||
reqwest = { version = "0.12.9", features = ["json", "stream"] }
|
reqwest = { version = "0.12.9", features = ["json", "stream"] }
|
||||||
serde = { version = "1.0.214", features = ["derive"] }
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
|
|
|
@ -30,7 +30,7 @@ impl ResourceDescriptor {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # tokio_test::block_on(async {
|
/// # tokio_test::block_on(async {
|
||||||
/// use acrate_hostmeta::any::ResourceDescriptor;
|
/// use acrate_rd::any::ResourceDescriptor;
|
||||||
///
|
///
|
||||||
/// let client = reqwest::Client::new();
|
/// let client = reqwest::Client::new();
|
||||||
/// let url: reqwest::Url = "https://junimo.party/.well-known/host-meta".parse()
|
/// let url: reqwest::Url = "https://junimo.party/.well-known/host-meta".parse()
|
||||||
|
@ -185,7 +185,7 @@ impl ResourceDescriptor {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # tokio_test::block_on(async {
|
/// # tokio_test::block_on(async {
|
||||||
/// use acrate_hostmeta::any::ResourceDescriptor;
|
/// use acrate_rd::any::ResourceDescriptor;
|
||||||
///
|
///
|
||||||
/// let client = reqwest::Client::new();
|
/// let client = reqwest::Client::new();
|
||||||
/// let base: reqwest::Url = "https://junimo.party".parse()
|
/// let base: reqwest::Url = "https://junimo.party".parse()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Definition and implementation of [`ResourceDescriptorJRD`].
|
//! Definition and implementation of [`ResourceDescriptorJRD`].
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::str::FromStr;
|
use mediatype::MediaTypeBuf;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD, ResourceDescriptorXRD};
|
use crate::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD, ResourceDescriptorXRD};
|
||||||
|
@ -76,8 +76,7 @@ pub struct ResourceDescriptorLinkJRD {
|
||||||
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2>
|
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2>
|
||||||
///
|
///
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(with = "crate::utils::serde_mime_opt")]
|
pub r#type: Option<MediaTypeBuf>,
|
||||||
pub r#type: Option<mime::Mime>,
|
|
||||||
|
|
||||||
/// URI to the resource put in relation.
|
/// URI to the resource put in relation.
|
||||||
///
|
///
|
||||||
|
@ -173,24 +172,23 @@ impl ResourceDescriptorJRD {
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
.ok_or(ContentTypeMissing)?;
|
.ok_or(ContentTypeMissing)?;
|
||||||
|
|
||||||
log::trace!("Extracting media type from the `Content-Type` header...");
|
log::trace!("Extracting media type from the `Content-Type` header: {content_type:?}");
|
||||||
let mime_type = content_type.to_str()
|
let media_type: MediaTypeBuf = content_type
|
||||||
.map_err(ContentTypeUnprintable)?;
|
.to_str()
|
||||||
|
.map_err(ContentTypeUnprintable)?
|
||||||
log::trace!("Parsing media type: {mime_type:?}");
|
.parse()
|
||||||
let mime_type = mime::Mime::from_str(mime_type)
|
|
||||||
.map_err(ContentTypeInvalid)?;
|
.map_err(ContentTypeInvalid)?;
|
||||||
|
|
||||||
log::trace!("Checking if media type is supported...");
|
log::trace!("Checking if media type is supported: {media_type:?}");
|
||||||
let mime_is_json = mime_type == mime::APPLICATION_JSON;
|
|
||||||
log::trace!("Is media type application/json? {mime_is_json:?}");
|
let mime_is_json = media_type.essence().eq(&"application/json".parse::<MediaTypeBuf>().unwrap());
|
||||||
let mime_is_jrd =
|
log::trace!("Is media type `application/json`? {mime_is_json:?}");
|
||||||
mime_type.type_() == mime::APPLICATION
|
|
||||||
&& mime_type.subtype() == "jrd"
|
let mime_is_jrd = media_type.essence().eq(&"application/jrd+json".parse::<MediaTypeBuf>().unwrap());
|
||||||
&& mime_type.suffix() == Some(mime::JSON);
|
log::trace!("Is media type `application/jrd+json`? {mime_is_jrd:?}");
|
||||||
log::trace!("Is media type application/jrd+json? {mime_is_jrd:?}");
|
|
||||||
if !(mime_is_json || mime_is_jrd) {
|
if !(mime_is_json || mime_is_jrd) {
|
||||||
log::error!("MIME type `{mime_type}` is not acceptable for JRD parsing.");
|
log::error!("Media type `{media_type}` is not acceptable for JRD parsing.");
|
||||||
return Err(ContentTypeUnsupported);
|
return Err(ContentTypeUnsupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +265,7 @@ pub enum GetJRDError {
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is not a valid [`mime::Mime`] type.
|
/// 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")]
|
#[error("the Content-Type header of the response is not a valid media type")]
|
||||||
ContentTypeInvalid(mime::FromStrError),
|
ContentTypeInvalid(mediatype::MediaTypeError),
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is not a supported [`mime::Mime`] type.
|
/// 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")]
|
#[error("the Content-Type header of the response is not a supported media type")]
|
||||||
|
|
|
@ -8,4 +8,3 @@
|
||||||
pub mod jrd;
|
pub mod jrd;
|
||||||
pub mod xrd;
|
pub mod xrd;
|
||||||
pub mod any;
|
pub mod any;
|
||||||
mod utils;
|
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
//! Various utilities reused in the whole crate.
|
|
||||||
|
|
||||||
/// Module to use in `serde(with = ...)` to [`serde`] a [`mime::Mime`].
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub mod serde_mime {
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use serde::de::{Error, Visitor};
|
|
||||||
use serde::{Deserializer, Serializer};
|
|
||||||
|
|
||||||
pub struct MimeVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for MimeVisitor {
|
|
||||||
type Value = mime::Mime;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a media type (MIME type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
mime::Mime::from_str(v)
|
|
||||||
.map_err(|_| E::custom("failed to parse media type"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, De>(deserializer: De) -> Result<<MimeVisitor as Visitor<'de>>::Value, De::Error>
|
|
||||||
where
|
|
||||||
De: Deserializer<'de>
|
|
||||||
{
|
|
||||||
let s = deserializer.deserialize_str(MimeVisitor)?;
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serialize<Ser>(data: mime::Mime, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
|
||||||
where
|
|
||||||
Ser: Serializer
|
|
||||||
{
|
|
||||||
let s = data.essence_str();
|
|
||||||
serializer.serialize_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Module to use in `serde(with = ...)` to [`serde`] an [`Option`] of [`mime::Mime`].
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub mod serde_mime_opt {
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use serde::de::{Error, Visitor};
|
|
||||||
use serde::{Deserializer, Serializer};
|
|
||||||
|
|
||||||
pub struct MimeVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for MimeVisitor {
|
|
||||||
type Value = Option<mime::Mime>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("optionally, a media type (MIME type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
Ok(
|
|
||||||
Some(
|
|
||||||
mime::Mime::from_str(v)
|
|
||||||
.map_err(|_| E::custom("failed to parse media type"))?
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, De>(deserializer: De) -> Result<<MimeVisitor as Visitor<'de>>::Value, De::Error>
|
|
||||||
where
|
|
||||||
De: Deserializer<'de>
|
|
||||||
{
|
|
||||||
let s = deserializer.deserialize_str(MimeVisitor)?;
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serialize<Ser>(data: &Option<mime::Mime>, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
|
||||||
where
|
|
||||||
Ser: Serializer
|
|
||||||
{
|
|
||||||
match data {
|
|
||||||
None => {
|
|
||||||
serializer.serialize_none()
|
|
||||||
}
|
|
||||||
Some(data) => {
|
|
||||||
let s = data.essence_str();
|
|
||||||
serializer.serialize_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Definition and implementation of [`ResourceDescriptorXRD`].
|
//! Definition and implementation of [`ResourceDescriptorXRD`].
|
||||||
|
|
||||||
use std::str::FromStr;
|
use mediatype::MediaTypeBuf;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::jrd::{ResourceDescriptorJRD, ResourceDescriptorLinkJRD};
|
use crate::jrd::{ResourceDescriptorJRD, ResourceDescriptorLinkJRD};
|
||||||
|
@ -82,8 +82,7 @@ pub struct ResourceDescriptorLinkXRD {
|
||||||
///
|
///
|
||||||
#[serde(rename = "@type")]
|
#[serde(rename = "@type")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(with = "crate::utils::serde_mime_opt")]
|
pub r#type: Option<MediaTypeBuf>,
|
||||||
pub r#type: Option<mime::Mime>,
|
|
||||||
|
|
||||||
/// URI to the resource put in relation.
|
/// URI to the resource put in relation.
|
||||||
///
|
///
|
||||||
|
@ -209,22 +208,20 @@ impl ResourceDescriptorXRD {
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
.ok_or(ContentTypeMissing)?;
|
.ok_or(ContentTypeMissing)?;
|
||||||
|
|
||||||
log::trace!("Extracting media type from the `Content-Type` header...");
|
log::trace!("Extracting media type from the `Content-Type` header: {content_type:?}");
|
||||||
let mime_type = content_type.to_str()
|
let media_type: MediaTypeBuf = content_type
|
||||||
.map_err(ContentTypeUnprintable)?;
|
.to_str()
|
||||||
|
.map_err(ContentTypeUnprintable)?
|
||||||
log::trace!("Parsing media type: {mime_type:?}");
|
.parse()
|
||||||
let mime_type = mime::Mime::from_str(mime_type)
|
|
||||||
.map_err(ContentTypeInvalid)?;
|
.map_err(ContentTypeInvalid)?;
|
||||||
|
|
||||||
log::trace!("Checking if media type is supported...");
|
log::trace!("Checking if media type is supported: {media_type:?}");
|
||||||
let mime_is_xrd =
|
|
||||||
mime_type.type_() == mime::APPLICATION
|
let mime_is_xrd = media_type.essence().eq(&"application/xrd+xml".parse::<MediaTypeBuf>().unwrap());
|
||||||
&& mime_type.subtype() == "xrd"
|
log::trace!("Is media type `application/xrd+xml`? {mime_is_xrd:?}");
|
||||||
&& mime_type.suffix() == Some(mime::XML);
|
|
||||||
log::trace!("Is media type application/xrd+xml? {mime_is_xrd:?}");
|
|
||||||
if !mime_is_xrd {
|
if !mime_is_xrd {
|
||||||
log::error!("MIME type `{mime_type}` is not acceptable for XRD parsing.");
|
log::error!("MIME type `{media_type}` is not acceptable for XRD parsing.");
|
||||||
return Err(ContentTypeUnsupported);
|
return Err(ContentTypeUnsupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +306,7 @@ pub enum GetXRDError {
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is not a valid [`mime::Mime`] type.
|
/// 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")]
|
#[error("the Content-Type header of the response is not a valid media type")]
|
||||||
ContentTypeInvalid(mime::FromStrError),
|
ContentTypeInvalid(mediatype::MediaTypeError),
|
||||||
|
|
||||||
/// The `Content-Type` header of the response is not a supported [`mime::Mime`] type.
|
/// 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")]
|
#[error("the Content-Type header of the response is not a supported media type")]
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
[package]
|
[package]
|
||||||
name = "acrate-webfinger"
|
name = "acrate_rdserver"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
|
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||||
edition = "2021"
|
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]
|
[dependencies]
|
||||||
acrate_database = { path = "../acrate_database" }
|
acrate_database = { path = "../acrate_database" }
|
||||||
acrate-hostmeta = { path = "../acrate-hostmeta" }
|
acrate_rd = { path = "../acrate_rd" }
|
||||||
anyhow = "1.0.93"
|
anyhow = "1.0.93"
|
||||||
axum = { version = "0.7.7", features = ["macros"] }
|
axum = { version = "0.7.7", features = ["macros"] }
|
||||||
axum-extra = { version = "0.9.4", features = ["query"] }
|
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 = { version = "1.0.215", features = ["derive"] }
|
||||||
serde_json = "1.0.132"
|
serde_json = "1.0.132"
|
||||||
tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] }
|
tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] }
|
||||||
|
mediatype = { version = "0.19.18", features = ["serde"] }
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
tabs-in-doc-comments = "allow"
|
|
@ -15,12 +15,12 @@ async fn main() -> anyhow::Result<std::convert::Infallible> {
|
||||||
let mut mj = minijinja::Environment::<'static>::new();
|
let mut mj = minijinja::Environment::<'static>::new();
|
||||||
|
|
||||||
log::trace!("Adding webfinger page to the Minijinja environment...");
|
log::trace!("Adding webfinger page to the Minijinja environment...");
|
||||||
mj.add_template("webfinger.html.j2", include_str!("webfinger.html.j2"))
|
mj.add_template("rd.html.j2", include_str!("rd.html.j2"))
|
||||||
.expect("webfinger.html.j2 to be a valid Minijinja template");
|
.expect("rd.html.j2 to be a valid Minijinja template");
|
||||||
|
|
||||||
log::trace!("Creating Axum router...");
|
log::trace!("Creating Axum router...");
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
.route("/.well-known/webfinger", axum::routing::get(route::webfinger_handler))
|
.route("/*path", axum::routing::get(route::webfinger_handler))
|
||||||
.layer(Extension(Arc::new(mj)));
|
.layer(Extension(Arc::new(mj)));
|
||||||
log::trace!("Axum router created successfully!");
|
log::trace!("Axum router created successfully!");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ subject }} · Acrate Webfinger</title>
|
<title>{{ subject }} · Acrate RDServer</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--wf-yellow: #e2bb03;
|
--wf-yellow: #e2bb03;
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
Acrate Webfinger
|
<span id="path">{{ path }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
|
@ -1,13 +1,16 @@
|
||||||
|
use std::iter::IntoIterator;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use axum::Extension;
|
use axum::Extension;
|
||||||
|
use axum::extract::Path;
|
||||||
use axum::http::{HeaderMap, Response, StatusCode};
|
use axum::http::{HeaderMap, Response, StatusCode};
|
||||||
use axum_extra::extract::Query;
|
use axum_extra::extract::Query;
|
||||||
|
use mediatype::{MediaTypeBuf, MediaTypeList};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use acrate_database::diesel::GroupedBy;
|
use acrate_database::diesel::GroupedBy;
|
||||||
use acrate_database::diesel_async::{AsyncConnection, AsyncPgConnection};
|
use acrate_database::diesel_async::{AsyncConnection, AsyncPgConnection};
|
||||||
use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
|
use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
|
||||||
use acrate_hostmeta::jrd::ResourceDescriptorLinkJRD;
|
use acrate_rd::jrd::ResourceDescriptorLinkJRD;
|
||||||
use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
|
use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -18,10 +21,8 @@ pub struct WebfingerQuery {
|
||||||
pub rel: Vec<String>,
|
pub rel: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const WEBFINGER_DOC: &str = "/.well-known/webfinger";
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn webfinger_handler(
|
pub async fn webfinger_handler(
|
||||||
|
Path(path): Path<String>,
|
||||||
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
|
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(mj): Extension<Arc<minijinja::Environment<'static>>>,
|
Extension(mj): Extension<Arc<minijinja::Environment<'static>>>,
|
||||||
|
@ -31,15 +32,18 @@ pub async fn webfinger_handler(
|
||||||
let resource = resource.unwrap_or_else(|| "".to_string());
|
let resource = resource.unwrap_or_else(|| "".to_string());
|
||||||
log::debug!("Resource is: {resource:#?}");
|
log::debug!("Resource is: {resource:#?}");
|
||||||
|
|
||||||
|
let path = format!("/{path}");
|
||||||
|
log::debug!("Path is: {path:#?}");
|
||||||
|
|
||||||
log::debug!("Rel is: {rel:#?}");
|
log::debug!("Rel is: {rel:#?}");
|
||||||
|
|
||||||
let accept = headers.get("Accept")
|
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:#?}");
|
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 response = Response::new("".to_string());
|
||||||
|
|
||||||
|
@ -47,7 +51,7 @@ pub async fn webfinger_handler(
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::BAD_GATEWAY)?;
|
.map_err(|_| StatusCode::BAD_GATEWAY)?;
|
||||||
|
|
||||||
let subjects = MetaSubject::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
let subjects = MetaSubject::aquery_matching(&mut conn, &path, &resource)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
@ -76,24 +80,24 @@ pub async fn webfinger_handler(
|
||||||
|
|
||||||
let subject = subject.subject.clone();
|
let subject = subject.subject.clone();
|
||||||
|
|
||||||
let aliases = MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
let aliases = MetaAlias::aquery_matching(&mut conn, &path, &resource)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
let properties = MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
let properties = MetaProperty::aquery_matching(&mut conn, &path, &resource)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
let links = MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
let links = MetaLink::aquery_matching(&mut conn, &path, &resource)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.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
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
.grouped_by(&links);
|
.grouped_by(&links);
|
||||||
|
|
||||||
let link_titles = MetaLinkTitle::query_by_link(&mut conn, &links)
|
let link_titles = MetaLinkTitle::aquery_by_link(&mut conn, &links)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
.grouped_by(&links);
|
.grouped_by(&links);
|
||||||
|
@ -113,21 +117,26 @@ pub async fn webfinger_handler(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for mime in accept.split(",") {
|
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();
|
let headers = response.headers_mut();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
mime.parse().map_err(|_| StatusCode::BAD_REQUEST)?
|
media_type.to_string()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mime, _params) = match mime.trim().split_once(";") {
|
match media_type.essence().to_string().to_ascii_lowercase().as_str() {
|
||||||
Some((mime, params)) => (mime, Some(params)),
|
|
||||||
None => (mime, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
match mime {
|
|
||||||
"*/*" | "application/json" | "application/jrd+json" => {
|
"*/*" | "application/json" | "application/jrd+json" => {
|
||||||
let aliases = aliases
|
let aliases = aliases
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -143,7 +152,7 @@ pub async fn webfinger_handler(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(link, properties, titles)| ResourceDescriptorLinkJRD {
|
.map(|(link, properties, titles)| ResourceDescriptorLinkJRD {
|
||||||
rel: link.rel,
|
rel: link.rel,
|
||||||
r#type: link.type_,
|
r#type: link.type_.map(|m| m.0),
|
||||||
href: link.href,
|
href: link.href,
|
||||||
template: link.template,
|
template: link.template,
|
||||||
properties: properties
|
properties: properties
|
||||||
|
@ -157,7 +166,7 @@ pub async fn webfinger_handler(
|
||||||
})
|
})
|
||||||
.collect::<Vec<ResourceDescriptorLinkJRD>>();
|
.collect::<Vec<ResourceDescriptorLinkJRD>>();
|
||||||
|
|
||||||
let rd = acrate_hostmeta::jrd::ResourceDescriptorJRD {
|
let rd = acrate_rd::jrd::ResourceDescriptorJRD {
|
||||||
subject,
|
subject,
|
||||||
aliases,
|
aliases,
|
||||||
properties,
|
properties,
|
||||||
|
@ -192,7 +201,7 @@ pub async fn webfinger_handler(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(link, properties, titles)| ResourceDescriptorLinkXRD {
|
.map(|(link, properties, titles)| ResourceDescriptorLinkXRD {
|
||||||
rel: link.rel,
|
rel: link.rel,
|
||||||
r#type: link.type_,
|
r#type: link.type_.map(|m| m.0),
|
||||||
href: link.href,
|
href: link.href,
|
||||||
template: link.template,
|
template: link.template,
|
||||||
properties: properties
|
properties: properties
|
||||||
|
@ -212,7 +221,7 @@ pub async fn webfinger_handler(
|
||||||
})
|
})
|
||||||
.collect::<Vec<ResourceDescriptorLinkXRD>>();
|
.collect::<Vec<ResourceDescriptorLinkXRD>>();
|
||||||
|
|
||||||
let rd = acrate_hostmeta::xrd::ResourceDescriptorXRD {
|
let rd = acrate_rd::xrd::ResourceDescriptorXRD {
|
||||||
subject,
|
subject,
|
||||||
aliases,
|
aliases,
|
||||||
properties,
|
properties,
|
||||||
|
@ -241,12 +250,12 @@ pub async fn webfinger_handler(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let links: Vec<(String, Option<String>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full
|
let links: Vec<(String, Option<MediaTypeBuf>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(link, properties, titles)| {
|
.map(|(link, properties, titles)| {
|
||||||
(
|
(
|
||||||
link.rel,
|
link.rel,
|
||||||
link.type_,
|
link.type_.map(|m| m.0),
|
||||||
link.href,
|
link.href,
|
||||||
link.template,
|
link.template,
|
||||||
properties.into_iter()
|
properties.into_iter()
|
||||||
|
@ -259,10 +268,11 @@ pub async fn webfinger_handler(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let html = mj.get_template("webfinger.html.j2")
|
let html = mj.get_template("rd.html.j2")
|
||||||
.expect("webfinger.html.j2 to exist")
|
.expect("rd.html.j2 to exist")
|
||||||
.render(
|
.render(
|
||||||
minijinja::context!(
|
minijinja::context!(
|
||||||
|
path => path,
|
||||||
subject => subject,
|
subject => subject,
|
||||||
aliases => aliases,
|
aliases => aliases,
|
||||||
properties => properties,
|
properties => properties,
|
Loading…
Reference in a new issue