/* * Basic PKZIP parser * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT * https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html * * Copyright (c) 2023 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "logger.h" #include "puff.h" #include "util.h" #include "zip.h" static unsigned char zip_file_magic[] = { 0x50, 0x4b, 0x03, 0x04 }; static unsigned char zip_dir_magic[] = { 0x50, 0x4b, 0x01, 0x02 }; size_t zip_read(short frefnum, void *buf, size_t len); size_t zip_read(short frefnum, void *buf, size_t len) { short error; long rlen = len; error = FSRead(frefnum, &rlen, buf); if (error) { logger_printf("[zip] error reading zip: %d", error); if (error != eofErr) warn("error reading zip: %d", error); return 0; } if (rlen != len) { logger_printf("[zip] short read: got %ld wanted %ld", rlen, len); return 0; } return rlen; } bool zip_is_zip_file(Str255 path) { char buf[32]; short error, frefnum; bool ret = false; error = FSOpen(path, 0, &frefnum); if (error) { warn("failed opening %s: %d", PtoCstr(path), error); CtoPstr(path); return false; } if (zip_read(frefnum, &buf, 4) == 4 && memcmp(buf, &zip_file_magic, 4) == 0) ret = true; read_done: FSClose(frefnum); return ret; } short zip_read_file(Str255 path, zip_extract_decider *decider, zip_extract_processor *processor) { char buf[32]; char filename[256]; unsigned char *comp, *uncomp; unsigned long comp_len, uncomp_len; u_int16_t t, fn_len, ex_len; short ret, fret, error, frefnum; error = FSOpen(path, 0, &frefnum); if (error) { warn("failed opening %s: %d", PtoCstr(path), error); CtoPstr(path); return ZIP_FAILED_OPEN; } for (;;) { fret = 0; if (zip_read(frefnum, &buf, 4) != 4) { fret = ZIP_SHORT_READ; goto read_fail; } if (memcmp(buf, &zip_dir_magic, 4) == 0) break; if (memcmp(buf, &zip_file_magic, 4) != 0) { fret = ZIP_BAD_FILE; goto read_fail; } /* version */ if (zip_read(frefnum, &buf, 2) != 2) { fret = ZIP_UNSUPPORTED; goto read_fail; } /* flags */ if (zip_read(frefnum, &buf, 2) != 2) { fret = ZIP_SHORT_READ; goto read_fail; } t = GET_U16(&buf); if (t & (1 << 0)) { logger_printf("[zip] Encryption not supported"); fret = ZIP_UNSUPPORTED; goto read_fail; } /* compression */ if (zip_read(frefnum, &buf, 2) != 2) { fret = ZIP_SHORT_READ; goto read_fail; } t = GET_U16(&buf); if (t != 8) { logger_printf("[zip] Compression method %d not supported", t); fret = ZIP_UNSUPPORTED; goto read_fail; } /* mod time/date */ if (zip_read(frefnum, &buf, 4) != 4) { fret = ZIP_SHORT_READ; goto read_fail; } /* crc32 */ if (zip_read(frefnum, &buf, 4) != 4) { fret = ZIP_SHORT_READ; goto read_fail; } /* compressed size */ if (zip_read(frefnum, &buf, 4) != 4) { fret = ZIP_SHORT_READ; goto read_fail; } comp_len = GET_U32(&buf); if (comp_len == 0xffffffff) { logger_printf("[zip] ZIP64 not supported"); fret = ZIP_UNSUPPORTED; goto read_fail; } if ((signed long)comp_len < 0) { logger_printf("[zip] Bogus compressed length (%lu)", comp_len); fret = ZIP_BAD_FILE; goto read_fail; } if (comp_len == 0) { logger_printf("[zip] Data descriptor not supported"); fret = ZIP_UNSUPPORTED; goto read_fail; } /* uncompressed size */ if (zip_read(frefnum, &buf, 4) != 4) { fret = ZIP_SHORT_READ; goto read_fail; } uncomp_len = GET_U32(&buf); if (uncomp_len == 0xffffffff) { logger_printf("[zip] ZIP64 not supported"); fret = ZIP_UNSUPPORTED; goto read_fail; } if ((signed long)uncomp_len < 0) { logger_printf("[zip] Bogus uncompressed length (%lu)", uncomp_len); fret = ZIP_BAD_FILE; goto read_fail; } if (uncomp_len == 0) { logger_printf("[zip] Data descriptor not supported"); fret = ZIP_UNSUPPORTED; goto read_fail; } /* file name len */ if (zip_read(frefnum, &buf, 2) != 2) { fret = ZIP_SHORT_READ; goto read_fail; } fn_len = GET_U16(&buf); if (fn_len >= sizeof(buf)) { logger_printf("[zip] Filename len %d too big", fn_len); fret = ZIP_UNSUPPORTED; goto read_fail; } /* extra field len */ if (zip_read(frefnum, &buf, 2) != 2) { fret = ZIP_SHORT_READ; goto read_fail; } ex_len = GET_U16(&buf); /* filename */ if (fn_len > sizeof(filename)) { logger_printf("[zip] Filename too long (%ld)", fn_len); fret = ZIP_UNSUPPORTED; goto read_fail; } if (zip_read(frefnum, &filename, fn_len) != fn_len) { fret = ZIP_SHORT_READ; goto read_fail; } filename[fn_len] = '\0'; /* extra field */ if (zip_read(frefnum, &buf, ex_len) != ex_len) { fret = ZIP_SHORT_READ; goto read_fail; } if (decider(filename, uncomp_len)) { comp = xmalloc(comp_len); if (comp == NULL) { logger_printf("[zip] Failed to malloc(%ld) for " "compressed data", comp_len); fret = ZIP_NO_MEMORY; goto read_fail; } /* read compressed data */ if (zip_read(frefnum, comp, comp_len) != comp_len) { xfree(&comp); fret = ZIP_SHORT_READ; goto read_fail; } uncomp = xmalloc(uncomp_len); if (uncomp == NULL) { logger_printf("[zip] Failed to malloc(%ld) for " "uncompressed data", uncomp_len); fret = ZIP_NO_MEMORY; xfree(&comp); goto read_fail; } ret = puff(uncomp, &uncomp_len, comp, &comp_len); if (ret != 0) { logger_printf("[zip] Unzip failed: %d", ret); xfree(&comp); xfree(&uncomp); fret = ZIP_BAD_FILE; goto read_fail; } xfree(&comp); processor(filename, uncomp, uncomp_len); xfree(&uncomp); } else { /* skip over it */ SetFPos(frefnum, fsFromMark, comp_len); } } FSClose(frefnum); return ZIP_OK; read_fail: FSClose(frefnum); if (fret == 0) fret = ZIP_BAD_FILE; return fret; }