allow subpaths inside zipfiles

This commit is contained in:
xenofem 2025-01-14 12:35:40 -05:00
parent 76a16e6ba6
commit b7185bc313
2 changed files with 32 additions and 9 deletions

View file

@ -65,11 +65,11 @@ async def file_loader(files):
file_progress.update(len(data)) file_progress.update(len(data))
yield data yield data
async def send(paths, host, password, lifetime, collection_name=None): async def send(paths, host, password, lifetime, collection_name=None, relpaths=False):
paths = [path for path in paths if path.is_file()] paths = [path for path in paths if path.is_file()]
fileMetadata = [ fileMetadata = [
{ {
"name": path.name, "name": str(path) if relpaths else path.name,
"size": path.stat().st_size, "size": path.stat().st_size,
"modtime": math.floor(path.stat().st_mtime * 1000), "modtime": math.floor(path.stat().st_mtime * 1000),
} for path in paths } for path in paths
@ -97,6 +97,7 @@ parser = argparse.ArgumentParser(description="Upload files to transbeam")
parser.add_argument("-l", "--lifetime", type=int, default=7, help="Lifetime in days for files (default 7)") parser.add_argument("-l", "--lifetime", type=int, default=7, help="Lifetime in days for files (default 7)")
parser.add_argument("-H", "--host", type=str, default="transbeam.link", help="transbeam host (default transbeam.link)") parser.add_argument("-H", "--host", type=str, default="transbeam.link", help="transbeam host (default transbeam.link)")
parser.add_argument("-n", "--collection-name", type=str, help="Name for a collection of multiple files") parser.add_argument("-n", "--collection-name", type=str, help="Name for a collection of multiple files")
parser.add_argument("-R", "--relative-paths", action="store_true", help="Preserve file paths relative to working directory")
parser.add_argument("files", type=pathlib.Path, nargs="+", help="Files to upload") parser.add_argument("files", type=pathlib.Path, nargs="+", help="Files to upload")
async def main(): async def main():
@ -105,6 +106,6 @@ async def main():
print("--collection-name is only applicable when multiple files are being uploaded") print("--collection-name is only applicable when multiple files are being uploaded")
exit(1) exit(1)
password = getpass.getpass() password = getpass.getpass()
await send(args.files, args.host, password, args.lifetime, args.collection_name) await send(args.files, args.host, password, args.lifetime, args.collection_name, args.relative_paths)
asyncio.run(main()) asyncio.run(main())

View file

@ -1,4 +1,4 @@
use std::{collections::HashSet, fs::File, io::Write}; use std::{collections::HashSet, fs::File, io::Write, path::Path, path::Component};
use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, StreamHandler}; use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, StreamHandler};
use actix_http::ws::{CloseReason, Item}; use actix_http::ws::{CloseReason, Item};
@ -22,20 +22,42 @@ const MAX_FILES: usize = 256;
const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] = const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] =
time::macros::format_description!("[year]-[month]-[day]-[hour][minute][second]"); time::macros::format_description!("[year]-[month]-[day]-[hour][minute][second]");
/// Sanitises a filename after performing unicode normalization, /// Sanitises a file or directory name after performing unicode normalization,
/// optionally reducing the length limit to leave space for an /// optionally reducing the length limit to leave space for an
/// extension yet to be added. /// extension yet to be added.
fn sanitise(name: &str, extension_length: usize) -> String { fn sanitise_path_component(name: &str, extension_length: usize) -> String {
let name = name.nfd().collect::<String>(); let name = name.nfd().collect::<String>();
sanitise_with_options( sanitise_with_options(
&name, &name,
&SanOptions { &SanOptions {
length_limit: SanOptions::DEFAULT.length_limit - extension_length, length_limit: SanOptions::DEFAULT.length_limit.saturating_sub(extension_length),
..SanOptions::DEFAULT ..SanOptions::DEFAULT
}, },
) )
} }
fn sanitise_path(path: &str) -> String {
let mut san_path = Path::new(path).components().rfold(String::new(), |subpath, c| {
if subpath.len() >= SanOptions::DEFAULT.length_limit*8 {
return subpath;
}
if let Component::Normal(s) = c {
let mut component = sanitise_path_component(&s.to_string_lossy(), 0);
if !subpath.is_empty() {
component.push('/');
component.push_str(&subpath);
}
component
} else {
subpath
}
});
if san_path.is_empty() {
san_path.push('_');
}
san_path
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
enum Error { enum Error {
#[error("Failed to parse file metadata")] #[error("Failed to parse file metadata")]
@ -114,7 +136,7 @@ pub use crate::state::v1::UploadedFile;
impl UploadedFile { impl UploadedFile {
fn new(name: &str, size: u64, modtime: OffsetDateTime) -> Self { fn new(name: &str, size: u64, modtime: OffsetDateTime) -> Self {
Self { Self {
name: sanitise(name, 0), name: sanitise_path(name),
size, size,
modtime, modtime,
} }
@ -285,7 +307,7 @@ impl Uploader {
info!("Wrapping in zipfile generator"); info!("Wrapping in zipfile generator");
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
let collection_name = let collection_name =
collection_name.map(|f| sanitise(&f, 4)).unwrap_or_else(|| { collection_name.map(|f| sanitise_path_component(&f, 4)).unwrap_or_else(|| {
super::APP_NAME.to_owned() super::APP_NAME.to_owned()
+ &now.format(FILENAME_DATE_FORMAT).unwrap() + &now.format(FILENAME_DATE_FORMAT).unwrap()
}); });