ZIM file format

Beginning 2021, we change the way we handle namespaces in ZIM file format.

This is major change in the way we handle entries in libzim and the semantics around but it is not a break in the binary zim format. Old library/readers are compatible with new zim files.

This page describe the new format. The old format can be found here : ZIM file format old namespace.

Header
A ZIM archive starts with a header :

Major version is updated when an incompatible change is integrated in the format (a lib made for a version N will probably not be able to read a version N+1)

Minor version is updated when an compatible change is integrated (a lib made for a minor version n will be able to read a version n+1)

The current major version is 6. You may found old zim archives with major version 5. They are the same than 6 less extended cluster, so you can read a 5 major version as if it was a 6.

The minor version can be : A zim archive may be embedded in another file at a specific offset. In the context of zim format, the start of the zim header is the offset 0. Readers allowing to read an embedded archive must adapt offset accordingly.
 * 0 : We use the old namespace usage (see ZIM file format old namespace)
 * 1 : We use the new namespace usage (describe here).

MIME Type List (mimeListPos)
The MIME type list always follows directly after the header, so the mimeListPos also defines the end and size of the ZIM file header.

The MIME types in this list are zero terminated strings. An empty string marks the end of the MIME type list.

URL Pointer List (urlPtrPos)
The URL pointer list is a list of 8 byte offsets to the directory entries.

The directory entries are always ordered by "full" URL. Ordering is simply done by comparing the URL strings.

Since directory entries have variable sizes this is needed for random access.

Libzim caches directory entries and references the cached entries via the URL pointers.

Title Pointer List (titlePtrPos)
The title pointer list is a list of entry indices ordered by title. The title pointer list actually points to entries in the URL pointer list.

Note that the title pointers are only 4 bytes. They are not offsets in the file but entry numbers.

To get the offset of an entry from the title pointer list, you have to look it up in the URL pointer list.

The indirection from titles via URLs to directory entries has two reasons:
 * the pointer list is only half in size as 4 bytes are enough for each entry
 * accessing directory entries by title also makes use of cached directory entries which are referenced by the URL pointers, as implemented in libzim.

Directory Entries
Directory entries hold the meta information about all entries, images and other objects in a ZIM archive.

There are different types of directory entries:

Linktarget or deleted Entry (DEPRECATED)
There is two kinds of deprecated entry that could be found in pretty old zim files (I, main develloper of libzim, never saw it).

They have mimetype equal to 0xfffe or 0xfffd. Reader implementation may check for those value and ignore the whole dirent.

Cluster Pointer List (clusterPtrPos)
The cluster pointer list is a list of 8 byte offsets which point to all data clusters in a ZIM archive.

Clusters
The clusters contain the actual data of the directory entries. Clusters can be compressed or uncompressed. The purpose of the clusters are that data of more than one directory entry can be compressed inside one cluster, making the compression much more efficient. Typically clusters have a size of about 1 MB.

The first byte of the cluster identifies some information about the cluster.

The first fourth low bits identifies if the cluster compression type:
 * No compression is indicated by a value of 1
 * Compressed clusters are indicated by a value of 4 (LZMA2 compression (or more precisely XZ, since there is a XZ header)) or 5 (Zstandard compression).
 * There have been other compression algorithms used before which have been removed: 2 for zlib and 3 for bzip2.
 * 0 is an obselete code for no compression (inhereted from the Zeno)

The fifth bit identifies the cluster is extended or not : A cluster can be extended only if the zim major version is 6. Else (major version == 5) cluster will always be not extended.
 * By default (5th bit == 0) the cluster is not extended. It means that the offsets are stored in a 4 bytes length integer. Thus contents stored in the cluster cannot exceed 4Go.
 * If the cluster is extended (5th bit == 1), the offsets are stored in 8 bytes length integer. Thus contents stored in the cluster can exceed 4Go.

The libzim uses xz-utils as a C++ implementation of lzma2, for Java see XZ-Java.

To find the data of a specific directory entry within a cluster the uncompressed cluster has a list of pointers to blobs within the uncompressed cluster after the first byte.

The offset addresses uncompressed data. The last pointer points to the end of the data area. So there is always one more offset than blobs. Since the first offset points to the start of the first data, the number of offsets can be determined by dividing this offset by OFFSET_SIZE. The size of one blob is calculated by the difference of two consecutive offsets.

Namespaces
Namespaces separate different types of directory entries - which might have the same title or url - stored in the ZIM archive Format.

The new namespace usage put a strong semantics on the namespaces. The libzim uses this semantics and provide different kind of API to access the different kind of entries.

The libzim hide the namespace to the user, so a entry `foo.html` in namespace `C` will be accessible as `foo.html`. libzim provides specific API to access metadata.

Other implementation are free to explicit the namespace or not (but need to provide some fallback for all namespace usage)

URL Encoding
The URLs in the UrlPointerlist are utf-8 and are not url encoded (https://www.ietf.org/rfc/rfc1738.txt)

Some readers process the requests that already do the url decoding internally whereas most readers will handle the URLs directly. In this case you have to do the decoding before you pass the parameter to libzim.

Local Anchors
Many articles - especially when a table of contents is used - use local anchors to jump within an article.

jump to article foo, headline 1

The browser handles these local anchors by itself. It will determine if another article has to be loaded (local anchor inside another article than the currently shown) and will send a request only with the article URL without the local anchor - in our example "foo". After the article has been loaded the browser will then search for the local anchor tag and jump to the right location.

If you use a common rendering engine or HTML widget you don't have to care for this cases, you can just use the requests as they are submitted by the engine / widget.

Should you render the article contents by yourself you have to consider this and take care of it before you hand requests to libzim.

Character Encoding
The standard encoding for ZIM archive content is UTF-8. So both article data and URLs should be handled accordingly.

Integer Encoding
All types are little-endian.

All integers are unsigned integers (uint_16, uint_32, uint_64).

All lengths are bytes.

Split ZIM files
ZIM archives can be split in multiple files. This is necessary to be able to store big (over 4GB for example) ZIM archives to limited file systems (like FAT32). That said, the files can be of any size, but the naming is really important. The ZIM files should be named like following (the file name extensions matter): foobar.zimaa, foobar.zimab, foobar.zimac...