initial commit

This commit is contained in:
yggverse 2025-06-24 03:59:55 +03:00
parent d3661f8865
commit ab625aa96a
14 changed files with 533 additions and 0 deletions

108
src/session/storage.rs Normal file
View file

@ -0,0 +1,108 @@
use crate::response::Response;
use anyhow::{Result, bail};
use std::{fs, io::Read, path::PathBuf, str::FromStr};
pub struct Storage {
public_dir: PathBuf,
read_chunk: usize,
}
impl Storage {
pub fn init(path: &str, read_chunk: usize) -> Result<Self> {
let public_dir = PathBuf::from_str(path)?.canonicalize()?;
let t = fs::metadata(&public_dir)?;
if !t.is_dir() {
bail!("Storage destination is not directory!");
}
if t.is_symlink() {
bail!("Symlinks yet not supported!");
}
Ok(Self {
public_dir,
read_chunk,
})
}
pub fn request(&self, query: &str, mut callback: impl FnMut(Response)) {
let p = {
// access restriction zone, change carefully!
let mut p = PathBuf::from(&self.public_dir);
p.push(query.trim_matches('/'));
match p.canonicalize() {
Ok(c) => {
if !c.starts_with(&self.public_dir) {
return callback(Response::AccessDenied(query));
}
c
}
Err(_) => return callback(Response::NotFound(query)),
}
};
match fs::metadata(&p) {
Ok(t) => match (t.is_dir(), t.is_file()) {
(true, _) => callback(match self.list(&p) {
Ok(ref l) => Response::Success(l.as_bytes()),
Err(e) => Response::InternalServerError(e.to_string()),
}),
(_, true) => match fs::File::open(p) {
Ok(mut f) => loop {
let mut b = vec![0; self.read_chunk];
match f.read(&mut b) {
Ok(0) => break,
Ok(n) => callback(Response::Success(&b[..n])),
Err(e) => {
return callback(Response::InternalServerError(format!(
"failed to read response chunk for `{query}`: `{e}`"
)));
}
}
},
Err(e) => callback(Response::InternalServerError(format!(
"failed to read response for query`{query}`: `{e}`"
))),
},
_ => panic!(), // unexpected
},
Err(e) => callback(Response::InternalServerError(format!(
"failed to read storage for `{query}`: `{e}`"
))),
}
}
/// Build entries list for given `path`,
/// sort ASC, by directories first.
///
/// * make sure the `path` is allowed before call this method!
fn list(&self, path: &PathBuf) -> Result<String> {
use urlencoding::encode;
const C: usize = 25; // @TODO optional
let mut d = Vec::with_capacity(C);
let mut f = Vec::with_capacity(C);
for entry in fs::read_dir(path)? {
let e = entry?;
let t = fs::metadata(e.path())?;
match (t.is_dir(), t.is_file()) {
(true, _) => d.push(e.file_name()),
(_, true) => f.push(e.file_name()),
_ => {} // @TODO symlinks support?
}
}
let mut l = Vec::with_capacity(d.len() + f.len());
if &self.public_dir != path {
l.push("=> ../".to_string())
}
d.sort();
for dir in d {
if let Some(s) = dir.to_str() {
l.push(format!("=> {}/", encode(s)))
}
}
f.sort();
for file in f {
if let Some(s) = file.to_str() {
l.push(format!("=> {}", encode(s)))
}
}
Ok(l.join("\n"))
}
}