allow subpaths inside zipfiles
This commit is contained in:
parent
76a16e6ba6
commit
b7185bc313
|
@ -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())
|
||||||
|
|
|
@ -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()
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue