Kinx Library - Zip
Tags: ruby, javascript, kinx, zip
Published: 2020-07-14 13:02:55

Hello, everybody!

The script language [Kinx](https://github.com/Kray-G/kinx) is published with the concept of **Looks like JavaScript, Feels like Ruby, Stable like AC/DC(?)**.

This time it's a Zip library.

* Reference
    * First motivation ... [The post of introduction](https://dev.to/krayg/kinx-as-a-script-language-4iko)
        * Kinx, I wanted a scripting language with a syntax of C family.
    * Repository ... https://github.com/Kray-G/kinx
        * I am waiting for pull requests.

The Zip function is indispensable when making apps. By the way, everybody says that it is vulnerable, but it is also true that Zip with password is at least required in business applications. So, if it is not possible to make a password-protected Zip, in some cases it won't be accepted by a customer. Sometimes.

That is why Kinx is of course supporting a password-protected Zip even if it's vulnerable.

## Zip

### Zip archive creation

#### Create Zip Instance (`class Zip`)

Basically it looks like this.

```javascript
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);
```

Specify the file name and mode.

##### Mode

|     Mode      |   Meaning   |                       Operation overview                                |
| ------------- | ----------- | ----------------------------------------------------------------------- |
| `File.READ`   | Read mode   | When specified alone and the file does not exist, throw `ZipException`. |
| `File.WRITE`  | Write mode  | Mode to create a new file even if it exists.                            |
| `File.APPEND` | Append mode | Appends to the file if it exists.                                       |

These are same as `File`.

##### Methods

The methods of the Zip instance are as follows.

|              Method              |                      Content                        |              Options               |
| -------------------------------- | --------------------------------------------------- | ---------------------------------- |
| `extract(name, [opts])`          | Extract and get it as a string.                     | `{ password, overwrite, skip, }`   |
| `extractTo(name, file [, opts])` | Extract to file.                                    | (same as above)                    |
| `find(name)`                     | Finds an entry and returns the entry object.        |                                    |
| `addFile(filename [, opts])`     | Adds an entry to the Zip file.                      | `{ password, method, aes, level }` |
| `addString(text [, opts])`       | Adds an entry to the Zip file.                      | (same as above)                    |
| `setPassword(password)`          | Sets the password that is used for all Zip entries. |                                    |
| `setOverwrite(truefalse)`        | Used to specify overwrite settings for all.         |                                    |

#### Add entry (`addFile()`/`addString()`)

Use `addFile()` or `addString()`. The entry is added immediately.

```javascript
zip.addFile("README.md");
```

If a directory name is given to the file, the files will be added as the files inside the directory. You can also pass an option as the second argument. The options are as follows.

* `password`: Password for the Zip with password. No default.
* `method`: Compression method. The default is `deflate`. In addition, you can specify `"store"`, `"bzip2"`, `"lzma"`.
* `aes`: Whether to enable WinZIP compatible AES encryption (true/false). Default false. It is ignored in the case of `addString`.
* `level`: Compression level. 0-9.

An example of adding options is as below.

```javascript
zip.addFile("README.md", {
    method: "bzip2",
    password: "password",
    aes: true,
});
zip.addString("test/test1.txt", {
    content: "test/test\n",
    // aes: true, // ignored by addString.
});
```

The password can be set separately for each entry if it is specified individually during extracting.

#### encryption

It can be specified as an option at the time of `addFile()`, but if you want to set it all at the beginning, you can use `setPassword()`.

```javascript
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);
zip.setPassword("password");
```

### Display Zip file list

The `zip` instance already contains the entry objects as an array. A list can be displayed as follows. The number of entries is stored in `zip.totalFiles`.

```javascript
var zip = new Zip("zipfile.zip", File.READ);
System.println("totalFiles = ", zip.totalFiles);
zip.each(function(e) {
    System.println("%s:" % e.filename);
    e.keySet().each(&(key) => {
        if (e[key].isFunction || e[key].isObject || e[key].isUndefined) {
            return; // Skip a function, etc.
        }
        if (key == "crc32") { // CRC is in hex.
            System.println("    %-14s = %10X" % key % e[key]);
        } else if (key != "time" && key != "filename") { // Display in another.
            System.println("    %-14s = %10d" % key % e[key]);
        }
    });
    // time is a structure of an object.
    e.time.keySet().each(&(k) => {
        System.println("      time.%-7d = %10d" % k % e.time[k]);
    });
    // // Entry can be extracted for each.
    // if (e.filename == "README.md") {
    //     e.extractTo("READMEXX.md", { password: "text", overwrite: true });
    // }
});
```

It is displayed as below.

```
totalFiles = 4
README.md:
    compsize       =       4413
    size           =      11621
    isDirectory    =          0
    crc32          =   EFD9A09C
    isEncrypted    =          1
    method         =    deflate
      time.month   =          3
      time.minute  =          1
      time.day     =         19
      time.year    =       2020
      time.second  =          2
      time.hour    =         16
...
```

### Extract

There are two ways to extract the Zip file.

* Extract directly from a Zip instance.
* Extract from Zip entry object.

Extracting from a Zip entry is to do for each entry. In that case, there are two methods as follows.

* Iterating and selecting an entry.
* Using the `find` method to get directly your target entry.

The `find` method returns a Zip entry object if there is an entry with the specified filename.

The meanings of the options when extracting are as follows.

* `password`: Password to use for extraction. If not specified, the one set with `setPassword()` is used. If it is not set even with `setPassword()`, it tries to expand without a password.
* `overwrite`: Specify true and overwrite if a file with the same name already exists.
* `skip`: Specify true and skip if a file with the same name already exists.

If `overwrite` or `skip` is not specified and a file with the same name exists, a `ZipException` exception is thrown.

#### Extract all

To extract all, execute `extractTo` on the iterated entry. The necessary directories will be created automatically.

```javascript
zip.each(&(e) => e.extractTo("examples/zip/dst" / e.filename, {password: "text", skip: true }));
```

> Although I've not explained anything about it, when the `/` operator is applied to a string, it becomes a string concatenated with `/`.

#### Specify the file and extract

You can use the `extract` or `extractTo` methods directly to the Zip instance.

```javascript
zip.extractTo("README.md", "READMEXX.md", {password: "text", skip: true });
```

If you use `extract`, it returns the extracted contents as a string.

```javascript
var text = zip.extract("README.md", {password: "text" });
```

For Zip entry objects, there are no arguments to specify the entry name.

|           Method           |             Content             |             Options              |
| -------------------------- | ------------------------------- | -------------------------------- |
| `extract([opts])`          | Extract and acquire as a string | `{ password, overwrite, skip, }` |
| `extractTo(file [, opts])` | Extract to file                 | (same as above)                  |

Here is an example using `find`.

```javascript
zip.find("README.md")
   .extractTo("READMEXX.md", {password: "text", skip: true });
```

```javascript
var text = zip.find("README.md")
              .extract({ password: "text" });
```

## Other

### Library that Kinx used

It is this.

* [Minizip](https://github.com/nmoinvaz/minizip)

Here is the list of features. I don't feel like a Mini at all.

* Features
   * Creating and extracting zip archives.
   * Adding and removing entries from zip archives.
   * Read and write raw zip entry data.
   * Reading and writing zip archives from memory.
   * Zlib, BZIP2, and LZMA compression methods.
   * Password protection through Traditional PKWARE and WinZIP AES encryption.
   * Buffered streaming for improved I/O performance.
   * NTFS timestamp support for UTC last modified, last accessed, and creation dates.
   * Disk split support for splitting zip archives into multiple files.
   * Preservation of file attributes across file systems.
   * Follow and store symbolic links.
   * Unicode filename support through UTF-8 encoding.
   * Legacy character encoding support CP437, CP932, CP936, CP950.
   * Turn off compilation of compression, decompression, or encryption.
   * Windows (Win32 & WinRT), macOS and Linux platform support.
   * Streaming interface for easy implementation of additional platforms.
   * Support for Apple's compression library ZLIB implementation.
   * Zero out local file header information.
   * Zip/unzip of central directory to reduce size.
   * Ability to generate and verify CMS signature for each entry.
   * Recover the central directory if it is corrupt or missing.
   * Example minizip command line tool.

### Zip64 compatible

It seems that Zip64 is also supported. It is said that it is possible even if it exceeds 4G, but it has not been tested.

## Conclusion

Zip/Unzip would be one of a wanted feature for using scripting languages. I never want to handle it in C, and I guess everybody want to easily create a Zip file.

See you next time.