123 / Input, output and data

Build and read a .zip file

An ERP packs several reports into one .zip to attach to an email, and receives zips from outside — a bank, an accountant — that have to be opened. std/zip is pure Nyx with no zlib, so the same program runs on the server and compiled to wasm32-wasi. Everything here happens in memory; the recipe writes no files.

123-zip-crear-y-leer.nxSource →
// Nyx by Example: armar y leer un archivo .zip.
//
// El caso real: un ERP empaqueta varios reportes en un solo .zip para
// adjuntarlo a un correo, y recibe zips de afuera (de un banco, de un
// contador) que hay que abrir. std/zip es Nyx puro, sin zlib: el mismo
// programa corre en el servidor y compilado a wasm32-wasi.
//
// Lo que muestra:
//   - zip_writer() + add(nombre, datos) + finish(): los bytes del .zip. Cada
//     entrada va con DEFLATE salvo que comprimir la agrande;
//   - zip_read() lee el índice (directorio central); entry(nombre) descomprime
//     UNA entrada y verifica su CRC-32;
//   - los topes contra bombas zip (ZipReadOpts): un archivo que llega de
//     afuera no se lee sin límites;
//   - un zip que no es zip da Err, no datos a medias.
//
// Correr: nyx run (todo en memoria; no escribe archivos)

import "std/zip"
import "std/error"

fn armar() -> String {
    var w: ZipWriter = zip_writer()
    var ventas: String = "fecha,monto\n"
    var i: int = 1
    while i <= 200 {
        ventas = ventas + "2026-09-" + int_to_string(1 + i % 28) + "," + int_to_string(i * 15) + "\n"
        i = i + 1
    }
    w.add("ventas.csv", ventas)              // se repite mucho: DEFLATE lo achica
    w.add("notas/lea-me.txt", "Cierre de septiembre.\n")
    return w.finish()
}

fn main() -> int {
    let bytes: String = armar()
    print("zip armado: " + int_to_string(bytes.length()) + " bytes")

    // Lo que llega de afuera se lee con topes. Los de por omisión (64 MB de
    // zip, 256 MB por entrada, 512 MB en total) se pueden ajustar.
    var opts: ZipReadOpts = zip_read_opts()
    opts.max_entry = 1024 * 1024
    match zip_read(bytes, opts) {
        Result.Ok(z) => {
            var arch: ZipArchive = z
            for nombre: String in arch.names() {
                print("entrada: " + nombre)
            }
            if arch.has("ventas.csv") { print("tiene ventas.csv") }
            match arch.entry("notas/lea-me.txt") {
                Result.Ok(txt) => { print("contenido: " + txt.trim()) }
                Result.Err(e) => {
                    print("no se pudo leer la entrada: " + error_to_string(e))
                    return 1
                }
            }
            match arch.entry("ventas.csv") {
                Result.Ok(csv) => { print("ventas.csv: " + int_to_string(csv.length()) + " bytes descomprimidos") }
                Result.Err(e) => {
                    print("no se pudo leer la entrada: " + error_to_string(e))
                    return 1
                }
            }
        }
        Result.Err(e) => {
            print("zip inválido: " + error_to_string(e))
            return 1
        }
    }

    // Un archivo que no es zip: Err con el motivo.
    match zip_read("esto no es un zip", zip_read_opts()) {
        Result.Ok(_) => { print("inesperado: lo aceptó") return 1 }
        Result.Err(e) => { print("rechazado: " + e.msg) }
    }
    return 0
}
Outputstdout
zip armado: 1361 bytes
entrada: ventas.csv
entrada: notas/lea-me.txt
tiene ventas.csv
contenido: Cierre de septiembre.
ventas.csv: 3073 bytes descomprimidos
rechazado: zip: no es un zip (falta el fin del directorio central)

How it works

zip_writer(), add(name, data) and finish() produce the bytes of the archive; each entry goes in with DEFLATE unless compressing would make it bigger. Reading is two steps: zip_read() loads the index (the central directory) and entry(name) decompresses ONE entry and verifies its CRC-32, so a corrupted entry is an error and not wrong data.

A file that arrives from outside is not read without limits: ZipReadOpts caps what a zip bomb can make the program do. And bytes that are not a zip at all come back as an Err — the last line of the output — never as half-read data.