From b23a3ab831f91553d34a48f51370ed9525de07ac Mon Sep 17 00:00:00 2001 From: zlago Date: Tue, 24 Sep 2024 20:54:48 +0200 Subject: initial commit --- src/zip.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/zip.c (limited to 'src/zip.c') 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 // malloc +#include // 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 ™ + 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, ¢ral->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; +} -- cgit 1.4.1-2-gfad0