diff --git a/src/activitypub/fetcher.rs b/src/activitypub/fetcher.rs index 49d4f94..ca0f78f 100644 --- a/src/activitypub/fetcher.rs +++ b/src/activitypub/fetcher.rs @@ -56,13 +56,13 @@ pub async fn fetch_avatar_and_banner( pub async fn fetch_profile( username: &str, - instance_uri: &str, + instance_host: &str, media_dir: &PathBuf, ) -> Result { - let actor_address = format!("{}@{}", &username, &instance_uri); + let actor_address = format!("{}@{}", &username, &instance_host); let webfinger_account_uri = format!("acct:{}", actor_address); // TOOD: support http - let webfinger_url = format!("https://{}/.well-known/webfinger", instance_uri); + let webfinger_url = format!("https://{}/.well-known/webfinger", instance_host); let client = reqwest::Client::new(); let webfinger_data = client.get(&webfinger_url) .query(&[("resource", webfinger_account_uri)]) diff --git a/src/config.rs b/src/config.rs index d55ec16..cfeebfc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use std::str::FromStr; use serde::{de, Deserialize, Deserializer}; -use url::{Url, ParseError as UrlParseError}; +use url::Url; use crate::errors::ConversionError; @@ -82,7 +82,7 @@ pub struct Config { pub http_port: u32, // Instance info - pub instance_uri: String, + instance_uri: String, pub instance_title: String, pub instance_short_description: String, pub instance_description: String, @@ -102,18 +102,24 @@ pub struct Config { } impl Config { - fn try_instance_url(&self) -> Result { + fn try_instance_url(&self) -> Result { // TODO: allow http in production let scheme = match self.environment { Environment::Development => "http", Environment::Production => "https", }; let url_str = format!("{}://{}", scheme, self.instance_uri); - Url::parse(&url_str) + let url = Url::parse(&url_str).map_err(|_| ConversionError)?; + url.host().ok_or(ConversionError)?; // validates URL + Ok(url) + } + + pub fn instance(&self) -> Instance { + Instance { _url: self.try_instance_url().unwrap() } } pub fn instance_url(&self) -> String { - self.try_instance_url().unwrap().origin().ascii_serialization() + self.instance().url() } pub fn media_dir(&self) -> PathBuf { @@ -121,6 +127,21 @@ impl Config { } } +pub struct Instance { + _url: Url, +} + +impl Instance { + + pub fn url(&self) -> String { + self._url.origin().ascii_serialization() + } + + pub fn host(&self) -> String { + self._url.host_str().unwrap().to_string() + } +} + pub fn parse_config() -> Config { let env = parse_env(); let config_yaml = std::fs::read_to_string(env.config_path) @@ -144,3 +165,26 @@ pub fn parse_config() -> Config { config } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_instance_url_https_dns() { + let instance_url = Url::parse("https://example.com/").unwrap(); + let instance = Instance { _url: instance_url }; + + assert_eq!(instance.url(), "https://example.com"); + assert_eq!(instance.host(), "example.com"); + } + + #[test] + fn test_instance_url_http_ipv4() { + let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap(); + let instance = Instance { _url: instance_url }; + + assert_eq!(instance.url(), "http://1.2.3.4:3777"); + assert_eq!(instance.host(), "1.2.3.4"); + } +} diff --git a/src/mastodon_api/instance/types.rs b/src/mastodon_api/instance/types.rs index becdcb7..75e19a5 100644 --- a/src/mastodon_api/instance/types.rs +++ b/src/mastodon_api/instance/types.rs @@ -4,7 +4,7 @@ use crate::config::Config; use crate::ethereum::contracts::MANAGER; #[derive(Serialize)] -pub struct Instance { +pub struct InstanceInfo { uri: String, title: String, short_description: String, @@ -19,10 +19,10 @@ pub struct Instance { ipfs_gateway_url: Option, } -impl From<&Config> for Instance { +impl From<&Config> for InstanceInfo { fn from(config: &Config) -> Self { Self { - uri: config.instance_uri.clone(), + uri: config.instance().host(), title: config.instance_title.clone(), short_description: config.instance_short_description.clone(), description: config.instance_description.clone(), diff --git a/src/mastodon_api/instance/views.rs b/src/mastodon_api/instance/views.rs index 0064854..ecfd215 100644 --- a/src/mastodon_api/instance/views.rs +++ b/src/mastodon_api/instance/views.rs @@ -2,13 +2,13 @@ use actix_web::{get, web, HttpResponse, Scope}; use crate::config::Config; use crate::errors::HttpError; -use super::types::Instance; +use super::types::InstanceInfo; #[get("")] async fn instance_view( config: web::Data, ) -> Result { - let instance = Instance::from(config.as_ref()); + let instance = InstanceInfo::from(config.as_ref()); Ok(HttpResponse::Ok().json(instance)) } diff --git a/src/mastodon_api/search/helpers.rs b/src/mastodon_api/search/helpers.rs index f8f54d4..8a61452 100644 --- a/src/mastodon_api/search/helpers.rs +++ b/src/mastodon_api/search/helpers.rs @@ -21,9 +21,9 @@ fn parse_profile_query(query: &str) -> let username = acct_caps.name("user") .ok_or(ValidationError("invalid search query"))? .as_str().to_string(); - let instance = acct_caps.name("instance") + let maybe_instance = acct_caps.name("instance") .and_then(|val| Some(val.as_str().to_string())); - Ok((username, instance)) + Ok((username, maybe_instance)) } async fn search_profiles( @@ -40,9 +40,9 @@ async fn search_profiles( }; let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?; if profiles.len() == 0 && instance.is_some() { - let instance_uri = instance.unwrap(); + let instance_host = instance.unwrap(); let media_dir = config.media_dir(); - match fetch_profile(&username, &instance_uri, &media_dir).await { + match fetch_profile(&username, &instance_host, &media_dir).await { Ok(profile_data) => { let profile = create_profile(db_client, &profile_data).await?; log::info!( diff --git a/src/webfinger/views.rs b/src/webfinger/views.rs index a35f8c2..1a23e60 100644 --- a/src/webfinger/views.rs +++ b/src/webfinger/views.rs @@ -1,9 +1,10 @@ use actix_web::{get, web, HttpResponse}; use regex::Regex; +use tokio_postgres::GenericClient; use crate::activitypub::views::get_actor_url; use crate::activitypub::constants::ACTIVITY_CONTENT_TYPE; -use crate::config::Config; +use crate::config::{Config, Instance}; use crate::database::{Pool, get_database_client}; use crate::errors::HttpError; use crate::models::users::queries::is_registered_user; @@ -15,8 +16,8 @@ use super::types::{ }; async fn get_user_info( - db_pool: &Pool, - config: &Config, + db_client: &impl GenericClient, + instance: Instance, query_params: WebfingerQueryParams, ) -> Result { // Parse 'acct' URI @@ -27,19 +28,18 @@ async fn get_user_info( let username = uri_caps.name("user") .ok_or(HttpError::ValidationError("invalid query target".into()))? .as_str(); - let instance_uri = uri_caps.name("instance") + let instance_host = uri_caps.name("instance") .ok_or(HttpError::ValidationError("invalid query target".into()))? .as_str(); - if instance_uri != config.instance_uri { - // Wrong instance URI + if instance_host != instance.host() { + // Wrong instance return Err(HttpError::NotFoundError("user")); } - let db_client = &**get_database_client(db_pool).await?; if !is_registered_user(db_client, &username).await? { return Err(HttpError::NotFoundError("user")); } - let actor_url = get_actor_url(&config.instance_url(), &username); + let actor_url = get_actor_url(&instance.url(), &username); let link = Link { rel: "self".to_string(), link_type: Some(ACTIVITY_CONTENT_TYPE.to_string()), @@ -55,10 +55,15 @@ async fn get_user_info( #[get("/.well-known/webfinger")] pub async fn get_descriptor( config: web::Data, - db_bool: web::Data, + db_pool: web::Data, query_params: web::Query, ) -> Result { - let jrd = get_user_info(&db_bool, &config, query_params.into_inner()).await?; + let db_client = &**get_database_client(&db_pool).await?; + let jrd = get_user_info( + db_client, + config.instance(), + query_params.into_inner(), + ).await?; let response = HttpResponse::Ok() .content_type(JRD_CONTENT_TYPE) .json(jrd);