summary refs log tree commit diff
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;
+}