#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; }