mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
* add support for inbound proxies * backend changes * fix file type * proxy -> tunnel, implement backend apis * wip start-tunneld * add domains and gateways, remove routers, fix docs links * dont show hidden actions * show and test dns * edit instead of chnage acme and change gateway * refactor: domains page * refactor: gateways page * domains and acme refactor * certificate authorities * refactor public/private gateways * fix fe types * domains mostly finished * refactor: add file control to form service * add ip util to sdk * domains api + migration * start service interface page, WIP * different options for clearnet domains * refactor: styles for interfaces page * minor * better placeholder for no addresses * start sorting addresses * best address logic * comments * fix unnecessary export * MVP of service interface page * domains preferred * fix: address comments * only translations left * wip: start-tunnel & fix build * forms for adding domain, rework things based on new ideas * fix: dns testing * public domain, max width, descriptions for dns * nix StartOS domains, implement public and private domains at interface scope * restart tor instead of reset * better icon for restart tor * dns * fix sort functions for public and private domains * with todos * update types * clean up tech debt, bump dependencies * revert to ts-rs v9 * fix all types * fix dns form * add missing translations * it builds * fix: comments (#3009) * fix: comments * undo default --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix: refactor legacy components (#3010) * fix: comments * fix: refactor legacy components * remove default again --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * more translations * wip * fix deadlock * coukd work * simple renaming * placeholder for empty service interfaces table * honor hidden form values * remove logs * reason instead of description * fix dns * misc fixes * implement toggling gateways for service interface * fix showing dns records * move status column in service list * remove unnecessary truthy check * refactor: refactor forms components and remove legacy Taiga UI package (#3012) * handle wh file uploads * wip: debugging tor * socks5 proxy working * refactor: fix multiple comments (#3013) * refactor: fix multiple comments * styling changes, add documentation to sidebar * translations for dns page * refactor: subtle colors * rearrange service page --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix file_stream and remove non-terminating test * clean up logs * support for sccache * fix gha sccache * more marketplace translations * install wizard clarity * stub hostnameInfo in migration * fix address info after setup, fix styling on SI page, new 040 release notes * remove tor logs from os * misc fixes * reset tor still not functioning... * update ts * minor styling and wording * chore: some fixes (#3015) * fix gateway renames * different handling for public domains * styling fixes * whole navbar should not be clickable on service show page * timeout getState request * remove links from changelog * misc fixes from pairing * use custom name for gateway in more places * fix dns parsing * closes #3003 * closes #2999 * chore: some fixes (#3017) * small copy change * revert hardcoded error for testing * dont require port forward if gateway is public * use old wan ip when not available * fix .const hanging on undefined * fix test * fix doc test * fix renames * update deps * allow specifying dependency metadata directly * temporarily make dependencies not cliackable in marketplace listings * fix socks bind * fix test --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: waterplea <alexander@inkin.ru>
210 lines
5.9 KiB
Rust
210 lines
5.9 KiB
Rust
use std::borrow::Cow;
|
|
use std::path::Path;
|
|
use std::str::FromStr;
|
|
|
|
use base64::Engine;
|
|
use color_eyre::eyre::eyre;
|
|
use reqwest::header::CONTENT_TYPE;
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::io::{AsyncRead, AsyncReadExt};
|
|
use ts_rs::TS;
|
|
use yasi::InternedString;
|
|
|
|
use crate::{mime, Error, ErrorKind, ResultExt};
|
|
|
|
#[derive(Clone, TS)]
|
|
#[ts(type = "string")]
|
|
pub struct DataUrl<'a> {
|
|
pub mime: InternedString,
|
|
pub data: Cow<'a, [u8]>,
|
|
}
|
|
impl<'a> DataUrl<'a> {
|
|
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
|
pub const MAX_SIZE: u64 = 100 * 1024;
|
|
|
|
fn to_string(&self) -> String {
|
|
use std::fmt::Write;
|
|
let mut res = String::with_capacity(self.len());
|
|
write!(&mut res, "{self}").unwrap();
|
|
res
|
|
}
|
|
|
|
fn len_without_mime(&self) -> usize {
|
|
5 + 8 + (4 * self.data.len() / 3) + 3
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.len_without_mime() + self.mime.len()
|
|
}
|
|
|
|
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
|
Self {
|
|
mime: InternedString::intern(mime),
|
|
data: Cow::Borrowed(data),
|
|
}
|
|
}
|
|
|
|
pub fn canonical_ext(&self) -> Option<&'static str> {
|
|
mime::unmime(&self.mime)
|
|
}
|
|
}
|
|
impl DataUrl<'static> {
|
|
pub async fn from_reader(
|
|
mime: &str,
|
|
rdr: impl AsyncRead + Unpin,
|
|
size_est: Option<u64>,
|
|
) -> Result<Self, Error> {
|
|
let check_size = |s| {
|
|
if s > Self::MAX_SIZE {
|
|
Err(Error::new(
|
|
eyre!("Data URLs must be smaller than 100KiB"),
|
|
ErrorKind::Filesystem,
|
|
))
|
|
} else {
|
|
Ok(s)
|
|
}
|
|
};
|
|
let mut buf = size_est
|
|
.map(check_size)
|
|
.transpose()?
|
|
.map(|s| Vec::with_capacity(s as usize))
|
|
.unwrap_or_default();
|
|
rdr.take(Self::MAX_SIZE + 1).read_to_end(&mut buf).await?;
|
|
check_size(buf.len() as u64)?;
|
|
|
|
Ok(Self {
|
|
mime: InternedString::intern(mime),
|
|
data: Cow::Owned(buf),
|
|
})
|
|
}
|
|
|
|
pub async fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
|
|
let path = path.as_ref();
|
|
let f = tokio::fs::File::open(path).await?;
|
|
let m = f.metadata().await?;
|
|
let mime = path
|
|
.extension()
|
|
.and_then(|s| s.to_str())
|
|
.and_then(mime)
|
|
.unwrap_or(Self::DEFAULT_MIME);
|
|
Self::from_reader(mime, f, Some(m.len())).await
|
|
}
|
|
|
|
pub async fn from_response(res: reqwest::Response) -> Result<Self, Error> {
|
|
let mime = InternedString::intern(
|
|
res.headers()
|
|
.get(CONTENT_TYPE)
|
|
.and_then(|h| h.to_str().ok())
|
|
.unwrap_or(Self::DEFAULT_MIME),
|
|
);
|
|
let data = res.bytes().await.with_kind(ErrorKind::Network)?.to_vec();
|
|
Ok(Self {
|
|
mime,
|
|
data: Cow::Owned(data),
|
|
})
|
|
}
|
|
|
|
pub fn from_vec(mime: &str, data: Vec<u8>) -> Self {
|
|
Self {
|
|
mime: InternedString::intern(mime),
|
|
data: Cow::Owned(data),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> std::fmt::Display for DataUrl<'a> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"data:{};base64,{}",
|
|
self.mime,
|
|
base64::display::Base64Display::new(
|
|
&*self.data,
|
|
&base64::engine::general_purpose::STANDARD
|
|
)
|
|
)
|
|
}
|
|
}
|
|
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DataUrlParseError;
|
|
impl std::fmt::Display for DataUrlParseError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "invalid base64 url")
|
|
}
|
|
}
|
|
impl std::error::Error for DataUrlParseError {}
|
|
impl From<DataUrlParseError> for Error {
|
|
fn from(e: DataUrlParseError) -> Self {
|
|
Error::new(e, ErrorKind::ParseUrl)
|
|
}
|
|
}
|
|
|
|
impl FromStr for DataUrl<'static> {
|
|
type Err = DataUrlParseError;
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
s.strip_prefix("data:")
|
|
.and_then(|v| v.split_once(";base64,"))
|
|
.and_then(|(mime, data)| {
|
|
Some(DataUrl {
|
|
mime: InternedString::intern(mime),
|
|
data: Cow::Owned(
|
|
base64::engine::general_purpose::STANDARD
|
|
.decode(data)
|
|
.ok()?,
|
|
),
|
|
})
|
|
})
|
|
.ok_or(DataUrlParseError)
|
|
}
|
|
}
|
|
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
struct Visitor;
|
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
|
type Value = DataUrl<'static>;
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(formatter, "a valid base64 data url")
|
|
}
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: serde::de::Error,
|
|
{
|
|
v.parse().map_err(|_| {
|
|
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
|
})
|
|
}
|
|
}
|
|
deserializer.deserialize_any(Visitor)
|
|
}
|
|
}
|
|
|
|
impl<'a> Serialize for DataUrl<'a> {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn doesnt_reallocate() {
|
|
let random: [u8; 10] = rand::random();
|
|
for i in 0..10 {
|
|
let icon = DataUrl {
|
|
mime: InternedString::intern("png"),
|
|
data: Cow::Borrowed(&random[..i]),
|
|
};
|
|
assert_eq!(icon.to_string().capacity(), icon.len());
|
|
}
|
|
}
|