7-Zip
7-Zip archives listed, one file read, opened safely into a folder, and made from a folder.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use sevenz.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use sevenz_list, sevenz_read_file from sevenz (Quillo 0.6).
sevenz_list(path: str) → listor an errorThe archive's entries as { name, size, kind, modified }: kind is file or dir, size in bytes, modified in seconds since 1970 (null when not kept).
sevenz_read_file(path: str, name: str) → stror an errorThe text of one file in the archive, by its name there; an error when it is missing. Up to 64 MB.
sevenz_extract(path: str, to: str) → listor an errorOpens the archive into the folder to (made if needed): the paths written. Safe for archives from anywhere: names that would land outside to (absolute, with "..", or a drive) are skipped, and it stops past 100,000 entries or 4 GB. Archives with a password are refused.
sevenz_create(to: str, from: str) → intor an errorMakes the .7z archive to from the folder from, its files named relative to it (LZMA2): the archive's size in bytes.
Get it
$ quillo install sevenz # sevenz.qwer into this folder$ quillo install sevenz --example # and its example, sevenz.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/sevenz.qwer$ curl -O https://quillolang.org/plugins/files/sevenz.qwe$ curl -O https://quillolang.org/plugins/files/sevenz_sample.7zThen, in your program, all of it or only what you call:
use sevenz.qweruse sevenz_list, sevenz_read_file from sevenz // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
sevenz-rust2 = { version = "0.20", default-features = false, features = ["compress", "util", "bzip2", "ppmd"] }Example
// A .7z of menus sent by a head office: listed, one file read, unpacked, and a reply packed.//// quillo run sevenz.qwe then open http://localhost:8080/menususe "sevenz.qwer"let ARCHIVE = "sevenz_sample.7z"route GET /menus = { files: sevenz_list(ARCHIVE)? | filter((e) => e.kind == "file") | map((e) => { name: e.name, size: e.size }), readme: sevenz_read_file(ARCHIVE, "readme.txt")?,}route GET /unpack = sevenz_extract(ARCHIVE, "data/menus")?route GET /reply { write_file("data/reply/answer.txt", "Thank you: the menus are up on the website.\n") { bytes: sevenz_create("data/reply.7z", "data/reply")?, inside: sevenz_list("data/reply.7z")? | map((e) => e.name) }}$ quillo run sevenz.qwe$ curl 'localhost:8080/menus'$ curl 'localhost:8080/unpack'$ curl 'localhost:8080/reply'Source
//! 7-Zip archives (.7z): listed, one file read, opened safely into a folder, and made from a folder.//! version: 1.0.0//!//! [dependencies]//! sevenz-rust2 = { version = "0.20", default-features = false, features = ["compress", "util", "bzip2", "ppmd"] }use serde_json::{json, Value};use sevenz_rust2::{ArchiveReader, Password};use std::io::Read;use std::path::{Component, Path, PathBuf};const MAX_ENTRIES: usize = 100_000;const MAX_TOTAL: u64 = 4 * 1024 * 1024 * 1024;const MAX_TEXT: u64 = 64 * 1024 * 1024;fn open(path: &str) -> Result<ArchiveReader<std::fs::File>, String> { ArchiveReader::open(path, Password::empty()).map_err(|e| format!("{}: {}", path, e))}// A name inside the archive that stays inside the folder: no absolute paths, no "..", no drive letters.fn safe(name: &str) -> Option<PathBuf> { let mut out = PathBuf::new(); for c in Path::new(&name.replace('\\', "/")).components() { match c { Component::Normal(p) => out.push(p), Component::CurDir => {} _ => return None, } } if out.as_os_str().is_empty() || name.contains(':') { None } else { Some(out) }}fn modified(e: &sevenz_rust2::ArchiveEntry) -> Value { if !e.has_last_modified_date { return Value::Null; } let t: std::time::SystemTime = e.last_modified_date.into(); t.duration_since(std::time::UNIX_EPOCH).map(|d| Value::from(d.as_secs())).unwrap_or(Value::Null)}/// The archive's entries as `{ name, size, kind, modified }`: `kind` is file or dir, `size` in bytes, `modified` in seconds since 1970 (null when not kept).pub fn sevenz_list(path: &str) -> Result<Vec<Value>, String> { let r = open(path)?; // A folder packed whole has an entry with no name, for the folder itself: it is left out. Ok(r.archive().files.iter().filter(|e| !e.name.is_empty()).take(MAX_ENTRIES).map(|e| json!({ "name": e.name, "size": e.size, "kind": if e.is_directory { "dir" } else { "file" }, "modified": modified(e), })).collect())}/// The text of one file in the archive, by its name there; an error when it is missing. Up to 64 MB.pub fn sevenz_read_file(path: &str, name: &str) -> Result<String, String> { let mut r = open(path)?; let want = name.replace('\\', "/"); let entry = r.archive().files.iter().find(|e| e.name.replace('\\', "/") == want && !e.is_directory).ok_or_else(|| format!("{}: no file `{}` in it", path, name))?; if entry.size > MAX_TEXT { return Err(format!("{}: larger than 64 MB", name)); } let bytes = r.read_file(&entry.name.clone()).map_err(|e| format!("{}: {}", name, e))?; String::from_utf8(bytes).map_err(|_| format!("{}: not UTF-8 text", name))}/// Opens the archive into the folder `to` (made if needed): the paths written. Safe for archives from anywhere:/// names that would land outside `to` (absolute, with "..", or a drive) are skipped, and it stops past 100,000 entries or 4 GB./// Archives with a password are refused.pub fn sevenz_extract(path: &str, to: &str) -> Result<Vec<String>, String> { let mut r = open(path)?; let files = &r.archive().files; if files.len() > MAX_ENTRIES { return Err(format!("{}: more than {} entries", path, MAX_ENTRIES)); } if files.iter().map(|e| e.size).sum::<u64>() > MAX_TOTAL { return Err(format!("{}: more than 4 GB unpacked", path)); } std::fs::create_dir_all(to).map_err(|e| format!("{}: {}", to, e))?; let mut written = vec![]; let mut failed: Option<String> = None; let mut total = 0u64; r.for_each_entries(|entry, data| { let target = safe(&entry.name).map(|rel| Path::new(to).join(rel)); let result: Result<(), String> = (|| { let Some(dest) = target else { std::io::copy(data, &mut std::io::sink()).map_err(|e| e.to_string())?; return Ok(()); }; if entry.is_directory { return std::fs::create_dir_all(&dest).map_err(|e| format!("{}: {}", dest.display(), e)); } if let Some(parent) = dest.parent() { std::fs::create_dir_all(parent).map_err(|e| format!("{}: {}", parent.display(), e))?; } let mut out = std::fs::File::create(&dest).map_err(|e| format!("{}: {}", dest.display(), e))?; total += std::io::copy(&mut data.take(MAX_TOTAL + 1 - total.min(MAX_TOTAL)), &mut out).map_err(|e| format!("{}: {}", dest.display(), e))?; if total > MAX_TOTAL { return Err("more than 4 GB unpacked".into()); } written.push(dest.to_string_lossy().into_owned()); Ok(()) })(); match result { Ok(()) => Ok(true), Err(e) => { failed = Some(e); Ok(false) } } }).map_err(|e| format!("{}: {}", path, e))?; match failed { Some(e) => Err(format!("{}: {}", path, e)), None => Ok(written), }}/// Makes the .7z archive `to` from the folder `from`, its files named relative to it (LZMA2): the archive's size in bytes.pub fn sevenz_create(to: &str, from: &str) -> Result<i64, String> { if !Path::new(from).is_dir() { return Err(format!("{}: not a folder", from)); } if let Some(dir) = Path::new(to).parent().filter(|d| !d.as_os_str().is_empty()) { std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?; } sevenz_rust2::compress_to_path(from, to).map_err(|e| format!("{}: {}", to, e))?; Ok(std::fs::metadata(to).map_err(|e| e.to_string())?.len() as i64)}Related
tar.qwer · 1.0.0Tar archives, gzipped or not: made from files and folders, listed, one file read, and opened safely into a folder.7 views · 8 downloadsBrotli brotli.qwer · 1.0.0Brotli compression of files and text: smaller than gzip for HTML, CSS and JavaScript served pre-compressed.7 views · 9 downloadsbzip2 bzip2.qwer · 1.0.0bzip2 compression of files and text in pure Rust, .bz2 dumps unpacked, and their first lines read without unpacking them all.6 views · 7 downloadsLZ4 lz4.qwer · 1.0.0LZ4 compression of files and text in the standard .lz4 frame format: the fastest to pack and unpack, for caches, logs and data passed between services.6 views · 7 downloadsText encodings text_encoding.qwer · 1.0.0Text files in old encodings (Windows-1252, Latin-1, Shift_JIS, GBK, Big5...) detected and read as UTF-8, and written back in them.8 views · 8 downloadsxz xz.qwer · 1.0.0xz (LZMA2) compression of files and text, and old .lzma files read: the smallest archives, for backups and downloads.6 views · 8 downloadsThread 0 posts
On 7-Zip: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.