Trazando la creación de un ZIP

Figura
Figura. Trazado de la creación de un ZIP

Para entender la estructura de un ZIP usaremos la herramienta ZIP. Su uso se explica en el tema Gestor ZIP. Nos permite crear y trazar un ZIP a partir de archivos del ordenador. Y también con una muestra de texto a modo de ejemplo para observar el trazado, como la captura de la Figura. La creación de un ZIP se genera en el módulo zip.js, cuyo uso para crear una aplicación vimos en el tema anterior.

Tal y como comentamos en el tema anterior, la especificación técnica APPNOTE ZIP, versión 6.3.9 de julio 2020, expone lo necesario para implementar una aplicación para crear y/o leer un formato ZIP. Expone esa especificación que se permite el uso de la información contenida en ese documento para el propósito de crear programas y procesos que lean o escriban archivos en el formato ZIP. Algunas opciones no pueden usarse sin obtener una licencia, como el cifrado fuerte (7.0 Strong Encryption Specification) y el parcheado (4.5.8 -PATCH Descriptor Extra Field). Además algunos métodos de compresión y otras tecnologías pueden tener restricciones de uso. En estos temas y en las aplicaciones que desarrollo a partir de esa especificación se relacionan sólo con un propósito informativo.

En nuestro caso no usaremos esas opciones con licencia en ningún caso. Ni siquiera otras que si se pueden usar sin licencia. De hecho y por ahora está implementada para crear ZIP sin compresión ni cifrado. Las opciones se limitan al método de compresión almacenamiento (stored), uso de comentarios en archivos y en el ZIP, el uso del descriptor de datos (data descriptor), especificar que los nombres y comentarios de los archivos pudieran ir codificados en UTF-8, y uso de atributos internos y externos.

Estructura básica de un ZIP

Figura
Figura. Trazando un ZIP básico

Para explicar la estructura básica de un ZIP usaremos la herramienta que señalamos antes usando un ejemplo muy sencillo, como se observa en la Figura. Al abrir el panel de trazado disponemos de un área de texto, donde es posible escribir un objeto que representa la colección de archivos (files) que alimentará la función createZip({files}). El objeto debe ser representado en notación JSON, con los nombres de propiedades entrecomillados.

Cada archivo se identifica de forma única con su ruta. Ha de disponer obligatoriamente de la propiedad content. Se trata del contenido del archivo que en esta área de texto puede pasarse como texto, pero antes de incluirlo en el argumento de createZip({files}) se realizará una conversión a bytes. La otra propiedad posible es la fecha date, aunque es opcional. Si no se pasa se genera la fecha del momento en el que se ejecuta la creación del ZIP. Otras propiedades que se acompañen serán ignoradas.

Usaremos el siguiente objeto de archivos como entrada, con un único archivo:

{
    "x/texto1.txt": {
        "date": "08/06/2020 20:39:10",
        "content": "z"
    }
}

Observe que la ruta del archivo aparece con las barras derecha. Así "x/texto1.txt" sería el archivo texto1.txt en la carpeta x. Tras esto pulsamos el botón Crear ZIP y obtenemos una ristra de bytes en el buffer. Estos son los bytes del ZIP que podemos descargar con el botón

50 4b 03 04 14 00 00 00 00 00 e5 a4 c8 50 af 77 d2 62 01 00 00 00 01 00 00 00 0c 00 00 00 78 2f 74 65 78 74 6f 31 2e 74 78 74 7a 50 4b 01 02 14 00
14 00 00 00 00 00 e5 a4 c8 50 af 77 d2 62 01 00 00 00 01 00 00 00 0c 00 00 00 00 00 00 00 01 00 20 00 00 00 00 00 00 00 78 2f 74 65 78 74 6f 31 2e 74 78 74 50 4b 05 06 00 00 00 00 
01 00 01 00 3a 00 00 00 2b 00 00 00 00 00
Figura
Figura. Descargando un ZIP básico

Los bytes se presentan en esta traza como números hexadecimales entre 00 y FF, separados por espacio. Son en total 123 bytes para un contenido "z" que sólo ocupa un byte. Esta relación irá disminuyendo a medida que se incrementa el tamaño de los contenidos.

Abrimos el ZIP descargado en Windows para observar que la ejecución fue correcta. Vemos el archivo en la ruta de la carpeta de descargas "download.zip/x/texto1.txt". El ZIP descargado siempre es nombrado con "download.zip". Observe que la fecha de modificación fue la que pusimos en el objeto de archivos. Y que el contenido "z" es el esperado.

Y ahora vamos a lo que nos interesa. La ejecución también nos produce la siguiente traza. En ella observamos tres partes que definiremos y luego explicaremos más a fondo cada parte:

  • Local file headers (Encabezados locales de archivos). Por cada archivo del conjunto se crea un encabezado que contiene todo lo necesario para extraer ese archivo en el destino. En esta sección va el contenido del archivo.
  • Central directory (Directorio central). Tras todos los encabezadas de todos los archivos se inicia un directorio centralizado con una entrada para cada archivo. Contiene campos que ya se incluyeron en el encabezado, a excepción del contenido. Luego explicaremos el motivo de duplicar algunos campos en los encabezados y en las entradas del directorio.
  • Ending (Finalización). Se finaliza el ZIP con un único bloque de campos.

Encabezados locales de archivos (Local file headers)

Empezamos con Local file headers, que para nuestro ejemplo sólo contiene un archivo. Junto al buffer obtenido también se nos presenta en la herramienta una traza separada por bloques. El primero es el de las cabeceras de archivos, una única cabecera en este caso al haber un sólo archivo:

==================================================
LOCAL FILE HEADERS
--------------------------------------------------
Signature: 50 4b 03 04
Version: 14 00 (Number: 20)
Flags: 00 00 (Bits: 0000000000000000)
Compression Method: 00 00 (Number: 0)
Mod Time: e5 a4 (Time: 20:39:10)
Mod Date: c8 50 (Date: 8/6/2020)
Crc32: af 77 d2 62 (Bytes: 62d277af)
Compressed Size: 01 00 00 00 (Number: 1)
Uncompressed Size: 01 00 00 00 (Number: 1)
File Name Length: 0c 00 (Number: 12)
Extra Field Length: 00 00 (Number: 0)
File Name: 78 2f 74 65 78 74 6f 31 2e 74 78 74 (Text: "x/texto1.txt")
Extra Field:  (Bytes: )
File Data: 7a (Text: "z")

Todas las secciones se inician con una Signatura (Signature). Para estos encabezados es 50 4b 03 04. Observe que en cada línea se traza el nombre del campo y su valor en bytes hexadecimales que se insertan exactamente en ese orden en el ZIP. Puede comprobarlo siguiendo el buffer que expusimos más arriba. El valor real es el hexadecimal 0x04034b50, pero los bytes de la mayoría de los valores se insertan en formato little endian invirtiendo los bytes 0x504b0304.

Cuando sea necesario incluimos entre paréntesis el valor númerico, texto o cualquier otro que nos aclare el contenido. Por ejemplo, la Versión (Version) tiene el valor númerico 20, que en hexadecimal es 0x14. A excepción de algunos campos, la mayor parte tiene una longitud fija. Así la versión tiene una longitud de 2 bytes, por lo que el valor a invertir sería 0x0014 produciendo 0x1400.

Estos son lo campos con longitud variable en la sección de encabezados:

  • File Name (Nombre archivo), cuya longitud se define en File Name Length. Vease que se disponen de 2 bytes, por lo que el nombre del archivo no puede superar 216-1 = 65535 bytes. Para nuestro ejemplo el nombre del archivo "x/texto1.txt" tiene una longitud de 12 bytes. No se invierten los bytes y se insertan en el orden en que aparecen. En nuestro ejemplo los bytes son 78 2f 74 65 78 74 6f 31 2e 74 78 74 que están en el mismo orden de los caracteres de "x/texto1.txt". La ruta completa ha de pasar el filtro de la expresión regular regPath:
    const regNoChar = `\\\/:*?"<>|`;
    const regName = `[^${regNoChar}\\s]+(?:[^${regNoChar}]| )*`;
    const regPath = new RegExp(`^${regName}(?:\\/${regName})*$`);
    Como se observa, no debe contener barras izquierda y sólo la derecha separará carpetas, no permiténdose los caracteres :*?"<>|. Hay una aclaración más que hacer sobre la codificación de los nombres y comentarios de archivos que explicaremos más adelante cuando veamos los Flags.
  • Extra Field (Campo extra), cuya longitud se define en Extra Field Length. Es un campo para datos extras que por ahora nuestra aplicación no usa.
  • File Data (Datos de archivo) que es el contenido del archivo, una "z" en nuestro ejemplo. Su longitud se define en los campos Compressed Size y Uncompressed Size, tamaños comprimidos y descomprimidos, ambos con valor 1. En nuestra aplicación no existe la posibilidad de comprimir (por ahora), por lo que ambos campos tendrán el mismo valor. Observe que en este caso tenemos 4 bytes, por lo que el tamaño del contenido tendrá un máximo de 232-1 = 4294967295 bytes, 4.29 GB. En este campo no se invierten los valores y se reflejan en el orden en el que están en el archivo.

Los Flags son 2 bytes, que anotamos como 16 bits, pues cada bit es una opción que condiciona la ejecución. Esto lo veremos con más detalle en un apartado más adelante que explica con detalle los Flags.

El método de compresión (Compression method) siempre será un cero en nuestra aplicación. Por ahora no podemos comprimir archivos y los datos se envían con la opción cero Stored, como simple almacenamiento. La lista de métodos que aparecen en la especificación de referencia son los siguientes:

  • 0: Stored
  • 1: Shrunk
  • 2: Reduced factor 1
  • 3: Reduced factor 2
  • 4: Reduced factor 3
  • 5: Reduced factor 4
  • 6: Imploded
  • 7: Tokenizing
  • 8: Deflated
  • 9: Deflate64(tm)
  • 10: PKWARE Imploding
  • 11: Reserved
  • 12: BZIP2
  • 13: Reserved
  • 14: LZMA
  • 15: Reserved
  • 16: IBM z/OS CMPSC
  • 17: Reserved
  • 18: IBM TERSE
  • 19: IBM LZ77
  • 20: Deprecated
  • 93: Zstandard
  • 94: MP3
  • 95: XZ

Los campos Mod Time y Mod Date contienen la hora y la fecha del archivo. Son 2 bytes para cada parte, por lo que sólo podemos precisar 30 segundos en cada minuto. Es decir, en un día hay 24×60×60 = 86400 segundos, superando 216-1 = 65535 posiciones de los 2 bytes. Así que se reducen cada dos segundos a uno por lo que el cálculo sería 24×60×30 = 43200 segundos, que si caben en 65535. Por otro lado el año se inicia el 01/01/1980 por lo que la fecha máxima que podemos representar sería sumar 65535 días a esa fecha resultando 06/06/2159.

La fecha y hora que pasamos en nuestro archivo fue 08/06/2020 20:39:10. A continuación se expone un esquema de la obtención de estos dos campos:

modDate: yyyy/m/d = 2020/6/8 = 2020-1980 / 6 / 8 = 40 / 6 / 8 = 
         0101000 / 0110 / 01000 = 0101 0000 1100 1000 = 
         50 c8 reverse c8 50

modTime: h:n:s = 20:39:10 = 20 : 39 : 10÷2 = 20 : 39 : 5 = 
         10100  : 100111 : 00101 = 1010 0100 1110 0101 = 
         a4 e5 reverse e5 a4

Esta representación de fecha y hora viene de un antiguo formato de MSDOS, momento en el que el formato ZIP fue ideado. En cualquier caso existe la posibilidad de incorporar un formato de fecha moderno como UTC usando el campo Extra Field, aunque aún no lo he incorporado en la herramienta.

El campo Crc32 lleva el código de verificación por redundancia cíclica. Sobre esto ya publiqué una serie de temas explicando qué es el CRC y como generarlo. En el tema final se expone una implementación práctica de CRC-32/ISO-HDLC a usar en los ZIP, algoritmo que se incorpora para generar el CRC del contenido de cada archivo. Para el contenido "z" el algoritmo obtiene 0x62d277af, que se incorpora invertido 0xaf77d262. El software de destino que vaya a extraer el contenido deberá comprobar que ese CRC es correcto con el número mágico o residuo (magic number or residue) 0xdebb20e3, verificándose que el contenido no fue modificado debido a errores en la transmisión.

Directorio central (Central directory)

El Directorio central (Central directory) es exactamente eso, un directorio de todos los archivos que encontramos en el Local file headers. Por cada archivo hay una entrada en el directorio que repite muchos de los campos que ya vimos en los encabezados: Version, Flags, Compression Method, Mod Time, Mod Date, Crc32, Compressed Size, Uncompressed Size, File Name Length, File Name y Extra Field. En este apartado explicaremos los campos que no aparecen en el bloque anterior.

==================================================
CENTRAL DIRECTORY
--------------------------------------------------
Signature: 50 4b 01 02
Version: 14 00 (Number: 20)
Version Needed: 14 00 (Number: 20)
Flags: 00 00 (Bits: 0000000000000000)
Compression Method: 00 00 (Number: 0)
Mod Time: e5 a4 (Time: 20:39:10)
Mod Date: c8 50 (Date: 8/6/2020)
Crc32: af 77 d2 62 (Bytes: 62d277af)
Compressed Size: 01 00 00 00 (Number: 1)
Uncompressed Size: 01 00 00 00 (Number: 1)
File Name Length: 0c 00 (Number: 12)
Extra Field Length: 00 00 (Number: 0)
File Comment Length: 00 00 (Number: 0)
Disk Start: 00 00 (Number: 0)
Internal Attributes: 01 00 (Bits: 0000000000000001)
External Attributes: 20 00 00 00 (Bytes: 00000020)
Offset Local Header: 00 00 00 00 (Number: 0)
File Name: 78 2f 74 65 78 74 6f 31 2e 74 78 74 (Text: "x/texto1.txt")
Extra Field:  (Bytes: )
File Comment:  (Text: "")

La signatura de cada entrada es 50 4b 01 02. Ahora encontramos un nuevo campo denominado Versión necesaria para extraer (Version needed). Para el caso de ZIP donde se use compresión Stored o Deflate es suficiente con poner la versión 2.0, que se codifica como el número decimal 20, el mismo que el campo de la versión, que en este caso se supone que era la que se usó cuando se creó el ZIP, apareciendo también el mismo número.

El campo Disk Start indica el Disco de inicio, cuando se usa la posibilidad de almacenar un ZIP en varios discos. Esto viene de hace años cuando se usaban sistemas de almacenamiento en disquetes de poca capacidad. Ocupa 2 bytes y, al no usarlo, su valor es 00 00.

El campo Internal Attributes, que indica los Atributos internos del archivo, también ocupa 2 bytes. Pero funciona como flags de 16 bits. De ellos sólo se usa el bit cero. Si está activado, indica que el archivo es aparentemente un archivo de texto o codificado ASCII. En otro caso el achivo contiene datos binarios. En la herramienta sólo usaremos ese bit, activando una casilla para indicar que el archivo es uno de texto ASCII (ANSI en Windows).

El campo External Attributes, que indica los Atributos externos del archivo ocupa 4 bytes. Depende del sistema donde vaya a realizarse la extracción. Parece que debe establecerse al valor 0x00000020, con el que he comprobado que pueden extraerse los archivos en Windows. Tengo aún muchas dudas acera de este campo. Por ejemplo, si se pasa un valor distinto como 0x00000010 entonces el archivo se considera una carpeta.

El campo Offset Local Header indica la Distancia de la cabecera local del archivo. Es decir, a cuántos bytes desde el inicio se encuentra el Local File Header para el archivo de la entrada del directorio. Si pensamos en el directorio como el índice de capítulos en un libro, este offset equivale al número de la página donde se inicia ese capítulo. Y aquí es donde cobra sentido el directorio. El formato ZIP permite extraer cualquier archivo sin necesidad de tener que recorrer todo el ZIP. Solo basta ir al directorio, buscar el archivo por el File Name y con su offset sabremos donde encontrarlo para empezar a leerlo.

Cuando leamos un archivo podríamos comprobar que los campos duplicados son iguales en el Local File Header y en la entrada del Central Directory. Si se hubiese producido un error en la transmisión podríamos detectarlo con esta información redundante, aparte de la que ya aporta el campo Crc32 sobre los datos del archivo.

Los campos File Comment Length y File Comment nos permiten incluir un Comentario de Archivo indicándose su longitud, que está limitada a 65535 bytes, el máximo número que puede representarse en los 2 bytes del campo de longitud.

Finalización (Ending)

El bloque de Finalización (Ending) reúne varios campos relacionados con el uso de discos a la hora de almacenar el ZIP, utilidadad que tal como hemos comentado no usaremos. En cualquier caso pondremos en Disk Entries el valor de 1 y en Total Entries el total de archivos que contiene el ZIP, que para nuestro ejemplo trazado es también 1.

==================================================
ENDING
--------------------------------------------------
Signature: 50 4b 05 06
Disk Number: 00 00 (Number: 0)
Disk Start Central Directory: 00 00 (Number: 0)
Disk Entries: 01 00 (Number: 1)
Total Entries: 01 00 (Number: 1)
Central Directory Size: 3a 00 00 00 (Number: 58)
Offset Start Central Directory: 2b 00 00 00 (Number: 43)
Zip Comment Length: 00 00 (Number: 0)
Zip Comment:  (Text: "")

El campo Tamaño del Directorio Central (Central Directory Size) así como la Posición donde se inicial el Directorio Central (Offset Start Central Directory) son importantes a la hora de cargar el directorio. Tal como dijimos en el apartado anterior, en la extracción es posible cargar el directorio para listar todo el contenido sin necesidad de tener que leer todos los archivos. Aunque la herramienta no contempla (por ahora) la posibilidad de extracción, yo supongo que la aplicación que haga la extracción podría ir al bloque de finalización y leer estos dos campos para localizar y leer el directorio. En nuestro ejemplo empieza en el byte 43 y ocupa 58 bytes.

Los campos Longitud del comentario del ZIP (ZIP Comment Length) y Comentario del ZIP (ZIP Comment) permiten incluir un comentario para el ZIP. Tiene una limitación de 65535 bytes tal como los comentarios de archivo.

Una duda a la hora de buscar la posición del directorio central a partir del bloque de finalización es que primero tendremos que localizar este bloque, cuya signatura es 50 4b 05 06. Uno podría buscar la última aparición de esos bytes, lo que daría resultado siempre que en el comentario no se incluyan también esos cuatro bytes. En Windows si aparecen en el comentario cursará un error, pues no podrá localizar los campos de posición y longitud correctos del directorio central. Sin embargo en otras aplicaciones como Win-Rar o 7-Zip si es capaz de diferenciar apariciones de falsas signaturas de finalización en los comentarios.

En nuestro caso hemos agregado el campo Permitir signaturas en comentarios (Allow signatures in comments), de tal forma que si se activa no permitirá la presencia de los bytes 0x504b0102, 0x504b0304, 0x504b0506, 0x504b0708 en los comentarios, cursando un aviso y vaciando el contenido del comentario. Este campo no forma parte de la especificación oficial del ZIP.

Flags

Figura
Figura. Flags ZIP

Los Flags son 16 bits (2 bytes) donde cada bit es una opción. Se enumeran desde cero para el bit menos significativo, el de la derecha. Incluyo el caracter (★) para indicar que la opción es soportada en la aplicación. En las opciones no soportadas sólo voy a presentar la información que se deduce de la especificación, pues poco más conocimiento tengo sobre ellas. Se reservan o no se usan los bits 7, 8, 9, 10, 12, 14, 15.

El Bit 0 establece el Cifrado (Encrypted).

Los Bits 1 y 2 son adicionales según métodos de compresión, ignorándose en otro caso. Si el método de compresión es el número 6 Implode, entonces si el bit 1 está activado significa que se usará una ventana deslizante (sliding window) de 8 KB y en otro caso será de 4 KB. Si el bit 2 está activado indica que se usarán 3 árboles Shannon-Fano, en otro caso se usarán 2.

Si el método de compresión es el número 8 o 9 Deflate, entonces los valores posibles son: 00 compresión normal; 01 compresión máxima; 10 compresión rápida; 11 compresión super rápida.

Si el método de compresión es el número 14 LZMA, entonces sólo se usará el bit 1. Si está activado indica la existen de un marcador EOS end-of-stream que marcará el final del stream de datos comprimidos. En otro caso el tamaño de los datos a comprimir debe ser conocido antes de extraerlos.

El Bit 3 (★) indica el Descriptor de datos (Data descriptor). Si está activado indica que los campos Crc32, Compressed size y Uncompressed size se establecen a cero en el Local file header, ubicándose a continuación de los datos. En la siguiente traza se observa en rojo que se establecen a cero y que se colocan al final (en color azul). Se usará la signatura 50 4b 07 08:

==================================================
LOCAL FILE HEADERS
--------------------------------------------------
Signature: 50 4b 03 04
Version: 14 00 (Number: 20)
Flags: 08 00 (Bits: 0000000000001000)
Compression Method: 00 00 (Number: 0)
Mod Time: e5 a4 (Time: 20:39:10)
Mod Date: c8 50 (Date: 8/6/2020)
Crc32: 00 00 00 00 (Bytes: 00000000)
Compressed Size: 00 00 00 00 (Number: 0)
Uncompressed Size: 00 00 00 00 (Number: 0)
File Name Length: 0c 00 (Number: 12)
Extra Field Length: 00 00 (Number: 0)
File Name: 78 2f 74 65 78 74 6f 31 2e 74 78 74 (Text: "x/texto1.txt")
Extra Field:  (Bytes: )
File Data: 7a (Text: "z")
Signature  Data  Descriptor: 50 4b 07 08
Crc32: af 77 d2 62 (Bytes: 62d277af)
Compressed Size: 01 00 00 00 (Number: 1)
Uncompressed Size: 01 00 00 00 (Number: 1)
==================================================

Aunque la aplicación permite esta opción, no debería usarse si conocemos previamente los campos Crc32, Compressed size y Uncompressed size. Como es el caso, dado que no estamos comprimiendo los archivos. La especificación habla del caso cuando no puedan conocerse esos datos hasta finalizar la compresión.

El Bit 4 se reserva para usar con el método de compresión 8 Deflate (Enhanced Deflating).

Si el Bit 5 está activado indica que los datos comprimidos son parcheados (patched).

El Bit 6 indica cifrado fuerte (Strong Encryption).

Figura
Figura. UTF-8 en nombre y comentario archivo ZIP

Si el Bit 11 (★) está activado indica que el nombre y comentario de archivo se codifican en UTF-8. En la Figura puede ver una extracción de un ZIP creado con un archivo con nombre ñáéíóúü.txt y con el comentario ñáéíóúü, usando letras codificadas en UTF-8. En la parte superior se ejecutó sin activar este bit, con lo que la codificación de caracteres no es la correcta. En la parte inferior con una ejecución con el bit activado si aparecen correctamente. Este bit no actuará en el comentario del ZIP, bloque donde sólo pueden usarse caracteres ASCII.

El Bit 13 indica directorio cifrado (Encrypting directory).

Pasando opciones a la creación del ZIP

En el módulo zip.js creamos un ZIP usando la función createZip({files=null, options=null, tracing=false}) que devuelve el objeto {error: "", warning: [], arrayBytes: null, trace: []}. En arrayBytes tendremos el buffer del ZIP. Devuelve una traza trace siempre que activemos el argumento tracing. Si hay un error la ejecución se detiene y devuelve el mensaje de error. En cambio siempre devolverá la lista de avisos en warning sin detenerse la ejecución como explicaremos más abajo.

Las opciones se agrupan en un objeto con claves para cada archivo. Se usa la clave "*" para indicar que se aplicarán a todos los archivos. Las opciones zipComment, allowSignaturesInComments, zipLang y zipFactor sólo se aplica a nivel de ZIP, no de archivo. Este es un ejemplo activando todas las opciones posibles:

{
    "*": {
        "flags": 10345,
        "internalAttributeAscii": true,
        "externalAttributes": "00000020",
        "compressionMethod": 6,
        "fileComment": "abc",
        "zipComment": "def",
        "allowSignaturesInComments": true,
        "zipLang": "en",
        "zipFactor": 1.05
    }
}

En lo anterior vemos el campo zipLang que no forman parte de la especificación. Con ello se establece el Idioma del ZIP (ZIP Lang), permitiéndose los idiomas español "es" e inglés "en". Nos servirá para ofrecer los mensajes de error y avisos en ese idioma.

Tampoco forma parte de la especificación el campo Factor ZIP, cuyo objetivo es iniciar el tamaño del buffer con suficiente tamaño para construir el ZIP. Al final antes de devolver el ZIP recortaremos los bytes finales no utilizados.

Los Flas son 16 bits que se representan con un número decimal entre 0 y 65535. Su extracción en opciones se lleva a cabo usando una máscara de flags:

const flagMasks = {
    encrypted:            0b0000000000000001, // 0
    implodeDeflateLzma:   0b0000000000000110, // 1,2
    dataDescriptor:       0b0000000000001000, // 3
    enhancedDeflating:    0b0000000000010000, // 4
    patched:              0b0000000000100000, // 5
    strongEncryption:     0b0000000001000000, // 6
    utf8:                 0b0000100000000000, // 11
    encryptingDirectory:  0b0010000000000000  // 13
};

La función createZip() no acusa errores sino avisos cuando una opción no es soportada, cambiando el valor al que por defecto le corresponde para que sea soportada. Con el ejemplo anterior con todas las opciones activadas se obtiene la siguiente traza, donde los mensajes se obtienen en inglés pues le pasamos el campo zipLang con valor "en":

WARNING WITH OPTIONS NOT ALLOWED
==================================================

One or more files have a compression method not allowed. It is converted
to method 0 "Stored".
The option flag "encrypted" is not allowed and will be ignored.
The option flag "patched" is not allowed and will be ignored.
The option flag "strongEncryption" is not allowed and will be ignored.
The option flag "encryptingDirectory" is not allowed and will be ignored.

==================================================
OPTIONS
--------------------------------------------------
*
    allowSignaturesInComments: true
    compressionMethod: 0
    externalAttributes: 0x00000020
    fileComment: 61 62 63 (Text: "abc")
    flags: 10345
    flagsBits: 0x2869  0b0010100001101001
    flagsRevised: 2056
    flagsRevisedBits: 0x0808  0b0000100000001000
    flagsRevisedValues: 
        encrypted: false
        implodeDeflateLzma: 0
        dataDescriptor: true
        enhancedDeflating: false
        patched: false
        strongEncryption: false
        utf8: true
        encryptingDirectory: false
    internalAttributeAscii: true
    zipComment: 64 65 66 (Text: "def")
    zipFactor: 1.05
    zipLang: en

Se observa que le pasamos un método de compresión 6 (Imploded) no soportado y se cambió a 0 (Stored), modificando el flag implodeDeflateLzma al valor por defecto 00 si fuese diferente. Los flags encrypted, patched, strongEncryption y encryptingDirectory no son soportados y se cambian el valor false. De esta forma se construye flagsRevisedValues que serán los que finalmente se usarán para crear el ZIP.