Files

Zip files

Zip files made from several files and opened safely: nothing written outside the folder, no zip bombs.

Version 1.0.0 · 3 functions · 1 crate

#zip#exports#uploads#files

Functions

What use archive.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use zip_files, zip_list from archive (Quillo 0.6).

zip_files(to: str, files: list, base: str) → intor an error

A zip at to of files, paths relative to base (kept so inside the zip); its size in bytes.

zip_list(path: str) → listor an error

What a zip holds: [{ name, size, packed, folder }], sizes in bytes.

unzip(from: str, to: str) → listor an error

Unpacks the zip at from into the folder to (made if needed); the names written. Paths that leave the folder are refused, and so is a zip over 1 GiB unpacked.

Get it

$ quillo install archive               # archive.qwer into this folder$ quillo install archive --example     # and its example, archive.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/archive.qwer$ curl -O https://quillolang.org/plugins/files/archive.qwe

Then, in your program, all of it or only what you call:

main.qwe
use archive.qweruse zip_files, zip_list from archive      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
zip = { version = "2", default-features = false, features = ["deflate"] }

Example

archive.qwe
// An export of several files as one download, and the zip read back.////   quillo run archive.qwe      then open http://localhost:8080/exportuse "archive.qwer"route GET /export {    write_file("export/orders.csv", "id,total\n1,120.50\n")    write_file("export/customers.csv", "id,name\n7,Ada\n")    zip_files("export.zip", ["orders.csv", "customers.csv"], "export")?    { files: zip_list("export.zip")?, unpacked: unzip("export.zip", "unpacked")? }}route GET /export.zip = file("export.zip")
$ quillo run archive.qwe$ curl 'localhost:8080/export'

Source

archive.qwer
//! Zip files made and opened: exports of several files, uploads of a folder, backups. Unpacking never writes outside its folder.//! version: 1.0.0//!//! [dependencies]//! zip = { version = "2", default-features = false, features = ["deflate"] }use serde_json::{json, Value};use std::fs::File;use std::path::Path;/// Larger than this unpacked, a zip is refused: a small file can unpack to gigabytes.const MAX_UNPACKED: u64 = 1 << 30;/// A zip at `to` of `files`, paths relative to `base` (kept so inside the zip); its size in bytes.pub fn zip_files(to: &str, files: Vec<String>, base: &str) -> Result<i64, String> {    let mut z = zip::ZipWriter::new(File::create(to).map_err(|e| format!("{}: {}", to, e))?);    let opts = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);    for f in &files {        let full = Path::new(base).join(f);        z.start_file(f.replace('\\', "/"), opts).map_err(|e| e.to_string())?;        let mut src = File::open(&full).map_err(|e| format!("{}: {}", full.display(), e))?;        std::io::copy(&mut src, &mut z).map_err(|e| e.to_string())?;    }    z.finish().map_err(|e| e.to_string())?;    Ok(std::fs::metadata(to).map_err(|e| e.to_string())?.len() as i64)}/// What a zip holds: `[{ name, size, packed, folder }]`, sizes in bytes.pub fn zip_list(path: &str) -> Result<Vec<Value>, String> {    let mut a = zip::ZipArchive::new(File::open(path).map_err(|e| format!("{}: {}", path, e))?).map_err(|e| e.to_string())?;    (0..a.len()).map(|i| {        let e = a.by_index(i).map_err(|e| e.to_string())?;        Ok(json!({ "name": e.name(), "size": e.size(), "packed": e.compressed_size(), "folder": e.is_dir() }))    }).collect()}/// Unpacks the zip at `from` into the folder `to` (made if needed); the names written. Paths that leave the folder are refused, and so is a zip over 1 GiB unpacked.pub fn unzip(from: &str, to: &str) -> Result<Vec<String>, String> {    let mut a = zip::ZipArchive::new(File::open(from).map_err(|e| format!("{}: {}", from, e))?).map_err(|e| e.to_string())?;    let mut total = 0u64;    for i in 0..a.len() {        let e = a.by_index(i).map_err(|e| e.to_string())?;        if e.enclosed_name().is_none() {            return Err(format!("`{}` would be written outside the folder", e.name()));        }        total += e.size();    }    if total > MAX_UNPACKED {        return Err(format!("the zip unpacks to {} bytes, over the 1 GiB allowed", total));    }    std::fs::create_dir_all(to).map_err(|e| e.to_string())?;    a.extract(to).map_err(|e| e.to_string())?;    Ok(a.file_names().map(str::to_string).collect())}
Thread
65Useful? Like it, or add to its thread below.

Thread 0 posts

On Zip files: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.