no-more-proxy #3

Merged
CenTdemeern1 merged 12 commits from no-more-proxy into main 2025-02-03 00:03:51 +00:00
3 changed files with 67 additions and 42 deletions
Showing only changes of commit 1449e2f5df - Show all commits

3
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -285,6 +285,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"tokio",
"url",
]

View file

@ -10,4 +10,5 @@ rocket = { version = "0.5.1", features = ["json"] }
semver = "1.0.24"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"
tokio = { version = "1.43.0", features = ["process"] }
url = "2.5.4"

View file

@ -2,10 +2,20 @@ use std::net::ToSocketAddrs;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use tokio::process::Command;
use url::Url;
use crate::known_software::KNOWN_SOFTWARE_NODEINFO_NAMES;
const MINIMUM_ICON_SIZE: usize = 16;
fn parse_size(size: &str) -> Option<usize> {
let (x, y) = size.split_once("x")?;
let x: usize = x.parse().ok()?;
let y: usize = y.parse().ok()?;
Some(x.max(y))
}
#[derive(Serialize)]
pub struct InstanceInfo {
name: String,
@ -15,24 +25,38 @@ pub struct InstanceInfo {
}
#[derive(Deserialize)]
struct InstanceManifest {
name: Option<String>,
short_name: Option<String>,
icons: Option<Vec<InstanceIcon>>,
struct InstanceIcon {
href: String,
size: usize,
}
#[derive(Deserialize)]
struct InstanceIcon {
src: String,
sizes: String,
struct ScrapedIcon {
url: String,
size: Option<String>,
sizes: Option<Vec<String>>,
}
impl InstanceIcon {
fn get_size(&self) -> Option<usize> {
let (x, y) = self.sizes.split_once("x")?;
let x: usize = x.parse().ok()?;
let y: usize = y.parse().ok()?;
Some(x.max(y))
impl TryInto<InstanceIcon> for ScrapedIcon {
type Error = ();
fn try_into(self) -> Result<InstanceIcon, Self::Error> {
let size = if let Some(size) = self.size {
parse_size(&size)
} else if let Some(sizes) = self.sizes {
sizes
.into_iter()
.filter_map(|s| parse_size(&s))
.filter(|&s| s >= MINIMUM_ICON_SIZE)
.min()
} else {
return Err(());
}
.ok_or(())?;
Ok(InstanceIcon {
href: self.url,
size,
})
}
}
@ -64,31 +88,34 @@ struct NodeInfoMetadata {
name: Option<String>,
}
async fn get_info_from_manifest(url: Url) -> Option<[Option<String>; 3]> {
// FIXME: Iceshrimp.NET doesn't have a manifest...
let response = reqwest::get(url.clone()).await.ok()?.text().await.ok()?;
let manifest: InstanceManifest = serde_json::from_str(&response).ok()?;
Some([
manifest.name,
manifest.short_name,
manifest
.icons
.as_ref()
.and_then(|icons| icons.iter().min_by_key(|icon| icon.get_size()))
.map(|icon| icon.src.to_owned()),
])
async fn find_icon(host: &str) -> Option<String> {
let icons: Vec<ScrapedIcon> = serde_json::from_slice(
&Command::new("site-icons")
.args(["--json", host])
.output()
.await
.ok()?
.stdout,
)
.ok()?;
icons
.into_iter()
.filter_map(|i| -> Option<InstanceIcon> { i.try_into().ok() })
.filter(|i| i.size > MINIMUM_ICON_SIZE)
.min_by_key(|i| i.size)
.map(|i| i.href)
}
#[get("/instance_info/<secure>/<host>")]
pub async fn instance_info(secure: bool, host: &str) -> Option<Json<InstanceInfo>> {
let mut url = Url::parse(if secure {
"https://temp.host/manifest.json"
"https://temp.host/"
} else {
"http://temp.host/manifest.json"
"http://temp.host/"
})
.ok()?;
.unwrap();
url.set_host(Some(host)).ok()?; // Using this to catch malformed hosts
let host = url.host_str()?; // Shadow the original host in case things were filtered out
let host = url.host_str()?.to_owned(); // Shadow the original host in case things were filtered out
// Check if the host is globally routable.
// This should help filter out a bunch of invalid or potentially malicious requests
@ -103,13 +130,8 @@ pub async fn instance_info(secure: bool, host: &str) -> Option<Json<InstanceInfo
return None;
}
let [name, short_name, icon_url] = get_info_from_manifest(url.clone())
.await
.unwrap_or_default();
let icon_url = icon_url
.and_then(|i| url.join(&i).ok())
.map(|u| u.to_string());
// FIXME: Iceshrimp.NET doesn't have a nodeinfo discovery file either.............
let icon_url = find_icon(url.as_str()).await;
url.set_path("/.well-known/nodeinfo");
let response = reqwest::get(url.clone()).await.ok()?.text().await.ok()?;
let nodeinfo_discovery: NodeInfoDiscovery = serde_json::from_str(&response).ok()?;
@ -126,11 +148,12 @@ pub async fn instance_info(secure: bool, host: &str) -> Option<Json<InstanceInfo
.and_then(|v| fork_map.get(v.build.as_str()))
.unwrap_or(software_name)
.to_owned();
Some(Json(InstanceInfo {
name: name
.or(short_name)
.or(nodeinfo.metadata.and_then(|m| m.name))
.unwrap_or(url.host_str().unwrap().to_owned()),
name: nodeinfo
.metadata
.and_then(|m| m.name)
.unwrap_or(host.to_owned()),
software,
icon_url,
}))