summaryrefslogtreecommitdiff
path: root/src/zip.c
diff options
context:
space:
mode:
authorzlago2024-09-24 20:54:48 +0200
committerzlago2024-09-24 20:54:48 +0200
commitb23a3ab831f91553d34a48f51370ed9525de07ac (patch)
treebd4adf20ba92d17433f386c0b5347a8d8cc9045f /src/zip.c
initial commit
Diffstat (limited to 'src/zip.c')
-rw-r--r--src/zip.c168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/zip.c b/src/zip.c
new file mode 100644
index 0000000..aca980d
--- /dev/null
+++ b/src/zip.c
@@ -0,0 +1,168 @@
+#include <stdlib.h> // malloc
+#include <string.h> // memcmp
+
+#include "zip.h"
+
+static struct zip_footer *zip_find_footer(char *const file, size_t const size, int *const error);
+static int index_zip(struct zip_index *const restrict headers, char *const restrict file, struct zip_footer const *const restrict footer);
+
+enum {
+ ZIP_OK,
+ ZIP_BIG,
+ ZIP_SMALL,
+ ZIP_SPLIT,
+ ZIP_NO_FOOTER,
+ ZIP_BAD_SIGNATURE,
+ ZIP_UNSUPPORTED,
+ ZIP_SIZE_MISMATCH,
+ ZIP_VALUE_MISMATCH,
+ ZIP_64,
+};
+
+char const *const zip_error(int const code) {
+ return (char const *const []) {
+ "ok",
+ "probably not a zip file (file too big)",
+ "not a zip file (file too smol)",
+ "split archives arent supported",
+ "found no zip footer",
+ "bad signature",
+ "unsupported configuration",
+ "size mismatch",
+ "central and local headers dont match",
+ "zip 64 is not supported",
+ } [code];
+}
+
+struct zip_file *zip_index(char *const restrict file, size_t const size, int *const restrict error) {
+ int err;
+ // find and verify the footer
+ struct zip_footer *footer = zip_find_footer(file, size, &err);
+ if (footer == NULL) {
+ error && (*error = err);
+ return NULL;
+ }
+
+ // The Rest &trade;
+ struct zip_file *headers = malloc(sizeof (*headers) + footer->headers_no * sizeof (*headers->files));
+ err = index_zip(headers->files, file, footer);
+ if (err) {
+ error && (*error = err);
+ free(headers);
+ return NULL;
+ }
+ headers->file_count = footer->headers_no;
+ return headers;
+}
+
+static struct zip_footer *zip_find_footer(char *const file, size_t const size, int *const error) {
+ // check if zip file is too big, and if size_t can store values bigger than a max* size zip file
+ if ((size_t) 1 << 32 && size >= (size_t) 1 << 32) {
+ *error = ZIP_BIG;
+ return NULL;
+ }
+
+ // the other way around, smallest zip file is 22 bytes
+ if (size < sizeof (struct zip_footer)) {
+ *error = ZIP_SMALL;
+ return NULL;
+ }
+
+ // scan for the footer
+ for (size_t i = 0, bounds = size - sizeof (struct zip_footer) + 1; i < 65536 /* hacky limit */ && /* obligatory check */ bounds; i++, bounds--) {
+ struct zip_footer *footer = (struct zip_footer *) (file + (size - sizeof (struct zip_footer) - i));
+ if (
+ footer->magic != ZIP_FOOTER_MAGIC || // incorrect signature
+ footer->comment_length != i || // incorrect comment length
+ footer->headers_no_total < footer->headers_no || // logical error
+ footer->headers_addr > size || // metadata allegedly located after EOF
+ footer->headers_addr + footer->headers_size > size // metadata ends after EOF
+ ) {
+ continue; // not the footer / unusable, try again
+ }
+ if (footer->disk_no != 0 || footer->central_disk_no != 0) { // split archive
+ *error = ZIP_SPLIT;
+ continue;
+ }
+ *error = ZIP_OK;
+ return footer; // 100% valid footer, safe to return
+ }
+ if (*error != ZIP_SPLIT) {
+ *error = ZIP_NO_FOOTER;
+ }
+ return NULL;
+}
+
+static int index_zip(struct zip_index *const restrict headers, char *const restrict file, struct zip_footer const *const footer) {
+ // needs more error checking
+ char *current_header = file + footer->headers_addr;
+ for (uint16_t i = 0; i < footer->headers_no; i++) {
+ struct zip_central *const central = (void *) current_header;
+ struct zip_header *const local = (void *) (file + central->file_addr);
+
+ // magic number check
+ if (central->magic != ZIP_CENTRAL_MAGIC) {
+ return ZIP_BAD_SIGNATURE;
+ }
+
+ // zip64 detection
+ if (
+ central->compressed_size == 0xffffffff &&
+ central->original_size == 0xffffffff &&
+ central->file_disk_no == 0xffff &&
+ central->file_addr == 0xffffffff
+ ) {
+ return ZIP_64;
+ }
+
+ // magic check 2
+ if (local->magic != ZIP_HEADER_MAGIC) {
+ return ZIP_BAD_SIGNATURE;
+ }
+
+ // more zip64 detection
+ if (
+ local->compressed_size == 0xffffffff &&
+ local->original_size == 0xffffffff
+ ) {
+ return ZIP_64;
+ }
+
+ // various checks
+ if (
+ local->ver_needed != central->ver_needed ||
+ local->flags != central->flags ||
+ local->method != central->method ||
+ memcmp(&local->mtime, &central->mtime, sizeof (struct dos_date))||
+ local->checksum != central->checksum ||
+ local->compressed_size != central->compressed_size ||
+ local->original_size != central->original_size ||
+ local->filename_length != central->filename_length ||
+ memcmp(local->filename, central->filename, local->filename_length)
+ ) {
+ return ZIP_VALUE_MISMATCH;
+ }
+
+ // feature check
+ if (central->flags & 0x0079) { // 0b0000'0000'0111'1001, that is encryption, data trailer, "enhanced deflating", or patches
+ return ZIP_UNSUPPORTED;
+ }
+
+ headers[i].central = central;
+ headers[i].header = local;
+
+ headers[i].data = headers[i].header->filename + local->filename_length + local->extra_length;
+ headers[i].filename = local->filename;
+ headers[i].filename_length = local->filename_length;
+ headers[i].crc32 = local->checksum;
+ headers[i].compressed_size = local->compressed_size;
+ headers[i].original_size = local->original_size;
+ headers[i].method = local->method;
+ headers[i].flags = local->flags;
+ current_header += sizeof (struct zip_central) + central->filename_length + central->extra_length + central->comment_length;
+ }
+ if (current_header - footer->headers_size != file + footer->headers_addr) {
+ return ZIP_SIZE_MISMATCH;
+ }
+ return 0;
+}