Zum Inhalt

ZIP-Modul (zip)

Das zip-Modul fasst mehrere Dokumente oder Byte-Quellen zu einem ZIP-Archiv zusammen — optional passwortgeschützt. Damit lassen sich z.B. ein PDF-Ausdruck und eine CSV der Formulardaten in einem Rutsch als verschlüsseltes Archiv per E-Mail verschicken.

Immer verfügbar

zip steht — wie mail, fs, csv — in jedem Formular-Skript, Job und REST-Service bereit. Kein Import, keine Konfiguration nötig.

Kürzer: zip.archive(…)

zip.archive(eintraege) macht dasselbe wie zip.create, liefert aber ein Dokument:

zip.archive(dateien).deliver("unterlagen.zip");

zip.create bleibt unverändert und gibt weiterhin rohe Bytes zurück.

Schnelleinstieg

var meinCsvPrinter = new csv.StandardPrinter();
meinCsvPrinter.println(["Nr", "Name"]).println([1, "Müller"]);

// Einträge sammeln — jede data-Quelle darf Text, byte[], ein CSV-Printer/
// ByteSource, eine fs-Datei oder ein Zahlen-Array sein.
var bytes = zip.create([
  { name: "Ausdruck.pdf", data: pdf.render().bytes },
  { name: "daten.csv",    data: meinCsvPrinter },
  { name: "hinweis.txt",  data: "Bitte prüfen." }
], { password: "geheim" });          // Passwort optional

// Das Ergebnis ist ein rohes byte[] — direkt an eine Mail hängen:
var mp = new mail.MessageMultipart();
mp.addText("Im Anhang die Unterlagen als ZIP.");
mp.addBytes("dokumente.zip", bytes, "application/zip", null);

Das byte[] von zip.create(...) passt überall dort, wo Bytes erwartet werden: mp.addBytes(...), delivery.deliver(...), fs["/ablage/x.zip"] = bytes und ctx.attachments.add("x.zip", bytes).

Methoden

Aufruf Ergebnis
zip.create(entries [, options]) ZIP als byte[]
zip.deliver(entries, filename [, options]) ZIP ausliefern → Download-URL (String)
zip.newArchive([options]) inkrementeller Builder
zip.list(source [, options]) Einträge (ohne Inhalt) — Array
zip.read(source [, options]) Einträge mit Inhalt — Array
zip.extract(source, name [, options]) Bytes eines Eintrags (byte[] oder null)

Der Builder aus zip.newArchive(...) hat:

Methode Ergebnis
add(name, data) fügt einen Eintrag hinzu, gibt den Builder zurück (Chaining)
bytes() / toByteArray() fertiges ZIP als byte[]
deliver(filename) ZIP ausliefern → Download-URL
size Anzahl bisher hinzugefügter Einträge

entries

Ein Array von { name, data }:

  • name — Dateiname im Archiv. Unterordner mit / erlaubt ("belege/2026/rechnung.pdf"); führende Slashes und ..-Segmente werden abgewiesen (kein „Zip-Slip").
  • data — die Bytes. Erlaubt: Text (UTF-8), byte[] (z.B. pdf.render().bytes), ein ByteSource/CSV-Printer (new csv.StandardPrinter()), eine fs-Datei (fs["/import.csv"]) oder ein Zahlen-Array.

options

Feld Werte Default
password beliebiger Text (keins → unverschlüsselt)
encryption "zip" (ZipCrypto) · "aes" (AES) "zip"
aesStrength 128 · 192 · 256 256

Ohne password entsteht ein normales, unverschlüsseltes ZIP; die anderen Optionen sind dann wirkungslos.

Verschlüsselung: ZipCrypto vs. AES

Kompromiss bewusst wählen

  • ZipCrypto (encryption:"zip", Default) — öffnet überall, auch mit den Windows-Bordmitteln (Explorer). Aber kryptografisch schwach: nicht für wirklich vertrauliche Daten geeignet.
  • AES-256 (encryption:"aes") — starke Verschlüsselung. Der Empfänger braucht dafür ein Werkzeug wie 7-Zip oder WinRAR; der Windows-Explorer kann AES-ZIPs nicht öffnen.
var entries = [{ name: "hinweis.txt", data: "Bitte prüfen." }];

// Kompatibel (Default):
zip.create(entries, { password: "geheim" });
// Stark:
zip.create(entries, { password: "geheim", encryption: "aes" });          // AES-256
zip.create(entries, { password: "geheim", encryption: "aes", aesStrength: 128 });

Das Passwort selbst kommt üblicherweise aus einem Formularfeld (ctx.form.passwort), einer Property (ctx.params.zipPassword) o.ä. — es wird nicht protokolliert.

Builder (inkrementell)

Wenn die Einträge nach und nach entstehen:

var z = zip.newArchive({ password: "geheim", encryption: "aes" });
z.add("Ausdruck.pdf", pdf.render().bytes);
z.add("ordner/notiz.txt", "Text");
var bytes = z.bytes();                 // oder .deliver("dokumente.zip")

add(name, data) gibt den Builder zurück (Chaining möglich), bytes() / toByteArray() liefert das fertige byte[], deliver(filename) einen Download-Link.

Als Download ausliefern

Analog zu pdf.render().deliver(...):

var entries = [{ name: "hinweis.txt", data: "Bitte prüfen." }];

var url = zip.deliver(entries, "dokumente.zip", { password: "geheim" });
view.add(new view.Link(url, "dokumente.zip herunterladen"));

Komplettes Beispiel: Ausdruck + CSV als geschütztes ZIP mailen

Ein vollständiger Schaltflächen-Hook — der Weg aus dem Demoformular. Er sammelt den PDF-Ausdruck plus eine CSV der Formulardaten, packt beides in ein passwortgeschütztes ZIP und verschickt es.

var to = (ctx.form.empfaenger || '').trim();
if (!to) {
  view.add(new view.Message('Bitte zuerst eine Empfaenger-E-Mail eintragen.', 'ZIP-Versand'));
} else {
  var pw  = ctx.form.passwort || '';
  var enc = ctx.form.verschluesselung || 'zip';   // 'zip' = ZipCrypto, 'aes' = AES-256

  // 1) Einträge sammeln
  var entries = [ { name: 'Ausdruck.pdf', data: pdf.render().bytes } ];
  var p = new csv.StandardPrinter();
  p.println('Feld', 'Wert');
  p.println('Betreff', ctx.form.betreff || '');
  entries.push({ name: 'daten.csv', data: p });    // csv.StandardPrinter ist ByteSource

  // 2) ZIP bauen (mit Passwort verschlüsselt, sonst offen)
  var zipBytes = zip.create(entries, pw ? { password: pw, encryption: enc } : {});

  // 3) Als Anhang verschicken
  var msg = new mail.Message();
  if (ctx.user && ctx.user.email) { msg.from = ctx.user.email; }
  msg.to = to;
  msg.subject = (ctx.form.betreff || 'Unterlagen') + ' (ZIP)';
  var mp = new mail.MessageMultipart();
  mp.addText('Im Anhang die Unterlagen als ZIP.');
  mp.addBytes('dokumente.zip', zipBytes, 'application/zip', null);
  msg.content = mp;
  mail.send(msg);

  view.add(new view.Message('ZIP an ' + to + ' gesendet.', 'ZIP-Versand'));
}

Lesen & Entpacken

Ein bestehendes ZIP — z. B. aus einer Dateianlage oder einer fs-Datei — lässt sich wieder auslesen. Die Quelle darf Bytes, ein CSV-Printer, eine fs-Datei oder ein Zahlen-Array sein. Ein ZIP aus dem Netz lädst du zuerst mit http.download in die Sandbox und liest es von dort (r.bytes() beim http-Client liefert Base64-Text, keine Bytes).

var zipBytes = zip.create([{ name: "hinweis.txt", data: "Bitte prüfen." }],
                         { password: "geheim" });
// im Betrieb meist: ctx.attachments.bytes(id) oder fs["/import.zip"]

// Nur die Einträge auflisten (ohne Inhalt). Bei einem verschlüsselten Archiv
// das Passwort mitgeben. zip.list/zip.read liefern eine Liste — for...of:
for (var e of zip.list(zipBytes, { password: "geheim" })) {
  log.info(e.name + " — " + e.size + " Bytes" + (e.encrypted ? " (verschlüsselt)" : ""));
}

// Alles entpacken (bei verschlüsselten Archiven Passwort mitgeben):
for (var e of zip.read(zipBytes, { password: "geheim" })) {
  // e = { name, size, directory, encrypted, bytes, text }
  fs["/entpackt/" + e.name] = e.bytes;         // z.B. in die Sandbox schreiben
}

// Eine einzelne Datei gezielt herausholen (byte[] oder null):
var pdf = zip.extract(zipBytes, "Ausdruck.pdf", { password: "geheim" });
Feld eines gelesenen Eintrags Bedeutung
name Pfad im Archiv
size Größe der entpackten Bytes (bei zip.list von KCForms-erzeugten Streaming-ZIPs ggf. 0 — die echte Größe liefert zip.read)
directory true bei Ordner-Einträgen
bytes roher Inhalt (byte[])
text Inhalt als UTF-8-Text (bequem für Text-Dateien)

Zip-Bomb-Schutz

zip.read/zip.extract deckeln die entpackte Gesamtgröße über dasselbe maxTotalBytes-Limit — ein Archiv, das sich beim Entpacken „aufbläht", bricht mit einer Fehlermeldung ab, statt den Speicher zu erschöpfen.

Verschlüsselte Archive brauchen das Passwort — auch beim Auflisten

KCForms schreibt Streaming-ZIPs (mit Data-Descriptor); der Reader muss auch zum bloßen Weiterspringen die Einträge entschlüsseln. Deshalb braucht auch zip.list bei verschlüsselten Archiven options.password — nicht nur zip.read/zip.extract.

Grenzen

Zum Schutz vor Speicher-Erschöpfung gelten zwei Obergrenzen (konfigurierbar unter kcforms.zip): Höchstzahl Einträge (maxEntries, Default 1000) und Gesamtgröße der unkomprimierten Eingabebytes (maxTotalBytes, Default 200 MB). Wird eine Grenze überschritten, bricht der Aufruf mit einer sprechenden Fehlermeldung ab.

Beispielformular

Das Beispiel „ZIP-Versand (Demo)" (zip-demo; installieren unter System-Administration → Formular-Bausteine → Beispiele, danach im Katalog Demo → Integration) zeigt den kompletten Weg: PDF-Ausdruck (+ optional CSV) zu einem — auf Wunsch passwortgeschützten — ZIP zusammenfassen und per E-Mail versenden (zipMail), als Download bereitstellen (zipDownload) oder gleich wieder auslesen (zipRoundtrip — demonstriert zip.list/zip.read/zip.extract).

Fehlermeldungen

Meldung / Verhalten Ursache
zip: keine Einträge — das Archiv wäre leer. entries/Builder ohne einen einzigen Eintrag
zip: Eintrag #N braucht die Felder { name, data }. Ein Array-Element hat kein name oder data
zip: data muss Text, byte[], … sein. data ist ein nicht unterstützter Typ
zip: Eintragsname darf kein '..'-Segment enthalten „Zip-Slip"-Schutz — kein Ausbrechen aus dem Zielordner
zip: Höchstzahl Einträge (…) überschritten. mehr als maxEntries Einträge
zip: Gesamtgröße überschreitet das Limit … Eingabebytes > maxTotalBytes (beim Packen)
zip.read: entpackte Gesamtgröße überschreitet das Limit … Zip-Bomb-Schutz beim Entpacken
zip.read/…: Entpacken fehlgeschlagen — Wrong Password … falsches/fehlendes Passwort für ein verschlüsseltes Archiv
zip.extract(…) liefert null Der gesuchte Eintragsname ist nicht im Archiv
zip.deliver: DeliveryService nicht verfügbar … Auslieferung in diesem Kontext nicht möglich — stattdessen zip.create(...) nutzen

Alle Fehler kommen als sprechende Ausnahme zurück; ein Schaltflächen-Hook zeigt sie als Fehlermeldung an, statt „erledigt" zu melden.

Siehe auch