Add back-end part of ephemeral shares

This commit is contained in:
NGnius (Graham) 2025-01-22 23:13:50 -05:00
parent 523e2b9df9
commit 606e0a4c69
11 changed files with 404 additions and 30 deletions

160
Cargo.lock generated
View file

@ -383,11 +383,13 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.79"
version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
@ -709,9 +711,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.8"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
@ -936,6 +938,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "html-escape"
version = "0.2.13"
@ -1067,7 +1075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1084,9 +1092,9 @@ checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "jobserver"
version = "0.1.25"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
@ -1114,9 +1122,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.138"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linux-raw-sys"
@ -1201,7 +1209,7 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1309,7 +1317,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1541,6 +1549,21 @@ dependencies = [
"winreg",
]
[[package]]
name = "ring"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@ -1561,7 +1584,7 @@ dependencies = [
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1582,7 +1605,7 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
dependencies = [
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1686,6 +1709,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@ -1720,6 +1749,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "strsim"
version = "0.10.0"
@ -1747,7 +1782,7 @@ dependencies = [
"fastrand",
"redox_syscall",
"rustix",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1844,7 +1879,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1974,6 +2009,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.3.1"
@ -2161,13 +2202,38 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.42.0",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm 0.42.0",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@ -2176,42 +2242,90 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winreg"
version = "0.10.1"
@ -2232,9 +2346,11 @@ dependencies = [
"clap",
"futures",
"gloo-utils 0.2.0",
"hex",
"log",
"rand",
"reqwest",
"ring",
"serde",
"tokio",
"url-escape",

View file

@ -2,12 +2,12 @@
name = "yarrr"
version = "0.1.0"
edition = "2021"
description = "Template for starting a Yew project using Trunk"
description = "An edgy web server for files"
readme = "README.md"
repository = "https://github.com/yewstack/yew-trunk-minimal-template"
repository = "https://git.ngni.us/NGnius/yarrr"
license = "MIT OR Apache-2.0"
keywords = ["yew", "trunk"]
categories = ["gui", "wasm", "web-programming"]
keywords = ["ngnius", "yarrr"]
categories = ["gui", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
@ -31,6 +31,7 @@ yew_icons = {version = "0.7", features = [
"FeatherRefreshCcw"
] }
log = "0.4"
hex = "0.4"
[target.'cfg(target_arch = "wasm32")'.dependencies]
yew = { version = "0.20", features = [ "csr", "hydration" ] }
@ -58,3 +59,4 @@ actix-web-httpauth = { version = "0.8" }
clap = { version = "3.1.7", features = ["derive"] }
url-escape = { version = "0.1.1" }
rand = "0.8"
ring = "0.17"

1
aes256_test.key Normal file
View file

@ -0,0 +1 @@
7a56056a0276ab447bcb6b5a5162c3300b60325cc8eb25b1f39f3ab260539e2a

2
run.sh
View file

@ -1,4 +1,4 @@
#!/bin/bash
#trunk build ./index.html && cargo run --bin backend -- localhost:7534
trunk build ./index.html && cargo run --bin backend -- localhost:7534 --dir /mnt/nas/media -u user -p password --port 7534
trunk build ./index.html && cargo run --bin backend -- localhost:7534 --dir /mnt/nas/media -u user -p password --port 7534 --key ./aes256_test.key

View file

@ -26,6 +26,12 @@ pub struct CliArgs {
/// Local network port
#[clap(long)]
pub port: u16,
/// Share links expiry time (seconds)
#[clap(long)]
pub expiry: Option<u64>,
/// Share encyption key location
#[clap(long)]
pub key: Option<std::path::PathBuf>,
}
impl CliArgs {
@ -39,6 +45,11 @@ impl CliArgs {
assert!(self.username.is_some(), "Credentials must be used to secure the hosted dir");
assert!(self.password.is_some(), "Credentials must be used to secure the hosted dir");
}
if let Some(key) = &self.key {
assert!(key.exists(), "Key file does not exist");
assert!(key.is_file(), "Key file is not a file");
assert!(std::fs::read_to_string(key).is_ok_and(|s| s.trim().chars().count() == 64), "Key file is not the expected size");
}
self
}

View file

@ -7,7 +7,7 @@ use crate::data::FileEntry;
#[derive(Debug, Deserialize)]
pub struct FileRequest {
mode: Option<DeliveryMode>,
pub mode: Option<DeliveryMode>,
}
#[derive(Debug, Deserialize, Clone, Copy)]
@ -18,14 +18,14 @@ pub enum DeliveryMode {
}
impl DeliveryMode {
fn disposition(self) -> actix_web::http::header::DispositionType {
pub fn disposition(self) -> actix_web::http::header::DispositionType {
match self {
Self::Download => actix_web::http::header::DispositionType::Attachment,
Self::Browser => actix_web::http::header::DispositionType::Inline,
}
}
fn default_from_ext(ext: Option<&std::ffi::OsStr>) -> Self {
pub fn default_from_ext(ext: Option<&std::ffi::OsStr>) -> Self {
let ext_str = ext.map(|x| x.to_str()).flatten();
if let Some(ext) = ext_str {
match ext {

229
src/api/get_share.rs Normal file
View file

@ -0,0 +1,229 @@
use std::io::{Read, Write};
use std::sync::RwLock;
use std::collections::VecDeque;
use actix_web::{get, web, Responder};
use actix_web_httpauth::extractors::basic::BasicAuth;
use ring::aead::{UnboundKey, SealingKey, OpeningKey, BoundKey, AES_256_GCM, NonceSequence, Nonce, NONCE_LEN, Aad};
use bytes::Buf;
use super::get_files::{FileRequest, DeliveryMode};
use crate::data::ShareInfo;
const DEFAULT_SHARE_TIME_S: u64 = 24 * 60 * 60;
static AES256_KEY: RwLock<Option<[u8; 32]>> = RwLock::new(None);
fn get_key(key_file: impl AsRef<std::path::Path>) -> std::io::Result<UnboundKey> {
if let Ok(Some(key)) = AES256_KEY.read().map(|k| k.clone()) {
Ok(UnboundKey::new(&AES_256_GCM, &key).unwrap())
} else {
let mut lock = AES256_KEY.write().map_err(|e| std::io::Error::other(format!("write lock error: {}", e)))?;
let hex_str = std::fs::read_to_string(key_file)?;
let decoded_hex = hex::decode(hex_str.trim()).map_err(|e| std::io::Error::other(e))?;
let mut buf_key = [0u8; 32];
if buf_key.len() != decoded_hex.len() {
return Err(std::io::Error::other("Length of bytes read for share encryption key did not match expected"));
}
for i in 0..buf_key.len() {
buf_key[i] = decoded_hex[i];
}
*lock = Some(buf_key.clone());
Ok(UnboundKey::new(&AES_256_GCM, &buf_key).unwrap())
}
}
fn unix_time_s_now() -> u64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()
}
struct NonceGenerator {
offset: u64,
seed: String,
}
impl NonceSequence for NonceGenerator {
fn advance(&mut self) -> Result<Nonce, ring::error::Unspecified> {
if self.offset == u64::MAX {
Err(ring::error::Unspecified)
} else {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA256);
let seed_bytes = self.seed.as_bytes();
let real_offset = self.offset as usize % seed_bytes.len();
ctx.update(&seed_bytes[real_offset..]);
ctx.update(&seed_bytes[..real_offset]);
ctx.update(&(seed_bytes.len() as u64).to_le_bytes());
ctx.update(&(real_offset as u64).to_le_bytes());
let digest = ctx.finish();
self.offset += 1;
Ok(Nonce::try_assume_unique_for_key(&digest.as_ref()[0..NONCE_LEN]).unwrap())
}
}
}
pub struct SharePayload {
path: String,
expiry: u64,
}
impl SharePayload {
pub fn from_hex(hex_str: &str, key_file: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let args = super::CliArgs::get();
let username = args.username.unwrap_or_else(|| "nouser".to_string());
// nonce-prefixed encrypted data
let mut bytes = VecDeque::from(hex::decode(hex_str).map_err(|e| std::io::Error::other(e))?);
// pull out nonce from data payload
let mut buf_u64 = [0u8; 8];
for i in 0..buf_u64.len() {
buf_u64[i] = bytes.pop_front().unwrap();
}
// decrypt
let mut bytes = Vec::from(bytes);
let nonce_offset = u64::from_le_bytes(buf_u64);
let nonce_gen = NonceGenerator {
offset: nonce_offset,
seed: args.password.unwrap_or_else(|| "you fool".to_string()),
};
let mut key = OpeningKey::new(get_key(key_file)?, nonce_gen);
key.open_in_place(Aad::from(username), &mut bytes).map_err(|e| std::io::Error::other(format!("Failed to decrypt: {}", e)))?;
Self::from_bytes(&mut bytes.reader())
}
fn from_bytes(bytes: &mut impl Read) -> std::io::Result<Self> {
let mut buf_u64 = [0u8; 8];
let size = bytes.read(&mut buf_u64)?;
if size != buf_u64.len() {
return Err(std::io::Error::other("Length of bytes read for path length did not match expected"));
}
let path_len = u64::from_le_bytes(buf_u64);
let mut buf_path = vec![0u8; path_len as _];
let size = bytes.read(&mut buf_path)?;
if size != buf_path.len() {
return Err(std::io::Error::other("Length of bytes read for path did not match expected"));
}
let path = String::from_utf8(buf_path).map_err(|e| std::io::Error::other(e))?;
// reuse buf_u64
let size = bytes.read(&mut buf_u64)?;
if size != buf_u64.len() {
return Err(std::io::Error::other("Length of bytes read for expiry timestamp did not match expected"));
}
let expiry = u64::from_le_bytes(buf_u64);
Ok(Self {
path,
expiry,
})
}
fn to_hex(&self, key_file: impl AsRef<std::path::Path>) -> std::io::Result<String> {
let args = super::CliArgs::get();
let username = args.username.unwrap_or_else(|| "nouser".to_string());
let mut bytes = Vec::new();
let nonce_offset: u64 = rand::random();
self.to_bytes(&mut bytes)?;
// encrypt
let nonce_gen = NonceGenerator {
offset: nonce_offset,
seed: args.password.unwrap_or_else(|| "you fool".to_string()),
};
let mut key = SealingKey::new(get_key(key_file)?, nonce_gen);
key.seal_in_place_append_tag(Aad::from(username), &mut bytes).map_err(|e| std::io::Error::other(format!("Failed to encrypt: {}", e)))?;
// add nonce hint
let mut bytes = VecDeque::from(bytes);
let buf_u64 = nonce_offset.to_le_bytes();
for i in (0..buf_u64.len()).rev() {
bytes.push_front(buf_u64[i]);
}
let bytes = Vec::from(bytes);
Ok(hex::encode(bytes))
}
fn to_bytes(&self, bytes: &mut impl Write) -> std::io::Result<usize> {
let path_bytes = self.path.as_bytes();
let path_len: u64 = path_bytes.len() as _;
let mut size = bytes.write(&path_len.to_le_bytes())?;
size += bytes.write(path_bytes)?;
size += bytes.write(&self.expiry.to_le_bytes())?;
Ok(size)
}
}
#[get("/share/{encrypted:.*}")]
pub async fn shared_file(encrypted: web::Path<String>, query: web::Query<FileRequest>) -> std::io::Result<impl Responder> {
let args = super::CliArgs::get();
if args.key.is_none() {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Share Authentication failed"));
}
let enc_key_file = args.key.unwrap();
let payload = SharePayload::from_hex(&encrypted, enc_key_file)?;
let time_now = unix_time_s_now();
if payload.expiry < time_now {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Share expired"));
}
let root = args.dir.unwrap();
let filepath = root.join(&*payload.path);
let req_disposition = query.mode.unwrap_or_else(
|| DeliveryMode::default_from_ext(filepath.extension())
).disposition();
log::debug!("file PATH: {}", filepath.display());
Ok(
actix_files::NamedFile::open_async(&filepath).await?
.prefer_utf8(false)
.set_content_disposition(
actix_web::http::header::ContentDisposition {
disposition: req_disposition,
parameters: vec![
/*actix_web::http::header::DispositionParam::FilenameExt(
actix_web::http::header::ExtendedValue {
charset: actix_web::http::header::Charset::Iso_8859_1,
language_tag: None,
value: filepath.file_name()
.map(
|name| name
.to_string_lossy()
.as_bytes()
.to_vec()
).unwrap_or_else(|| Vec::with_capacity(0)),
}
),*/
actix_web::http::header::DispositionParam::Filename(
filepath.file_name()
.map(
|name| name
.to_string_lossy()
.to_string()
).unwrap_or_else(|| "unknown".into()),
)
],
})
)
}
#[get("/api/share/{path:.*}")]
pub async fn share(path: web::Path<String>, auth: BasicAuth) -> std::io::Result<impl Responder> {
let args = super::CliArgs::get();
if !args.authenticate(auth.user_id(), auth.password().unwrap_or("")).await {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Basic Authentication failed"))
}
if args.key.is_none() {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Share Authentication failed"));
}
let enc_key_file = args.key.unwrap();
let root = args.dir.unwrap();
let filepath = root.join(&*path);
if !(filepath.exists() && filepath.is_file()) {
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Invalid path"))
}
// build payload
let expiry_time = unix_time_s_now() + args.expiry.unwrap_or(DEFAULT_SHARE_TIME_S);
let hex_str = SharePayload {
path: path.to_owned(),
expiry: expiry_time,
}.to_hex(enc_key_file)?;
let share_info = ShareInfo {
path: std::path::PathBuf::from(&*path),
url_path: format!("/share/{}", &hex_str),
full_url: format!("{}/share/{}", args.domain, &hex_str),
};
Ok(web::Json(share_info))
}

View file

@ -4,6 +4,7 @@ mod get_banner;
mod get_files;
mod get_index;
mod get_resources;
mod get_share;
#[cfg(not(target_arch = "wasm32"))]
pub use get_args::CliArgs;
@ -11,3 +12,4 @@ pub use get_banner::bruce;
pub use get_files::{dir, file};
pub use get_index::{index_auth, index_no_auth, IndexPage};
pub use get_resources::resource;
pub use get_share::{share, shared_file};

View file

@ -6,6 +6,7 @@ use yarrr::api::{
resource,
dir, file,
bruce,
share, shared_file,
};
@ -25,6 +26,8 @@ async fn main() -> std::io::Result<()> {
.service(resource)
.service(dir)
.service(file)
.service(share)
.service(shared_file)
})
.bind(("127.0.0.1", args.port))?
.run()

View file

@ -1,3 +1,5 @@
mod file_entry;
mod share_info;
pub use file_entry::FileEntry;
pub use share_info::ShareInfo;

8
src/data/share_info.rs Normal file
View file

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ShareInfo {
pub path: std::path::PathBuf,
pub url_path: String,
pub full_url: String,
}