no-more-proxy #3
3 changed files with 67 additions and 42 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
|
|
Loading…
Add table
Reference in a new issue