1#define FUSE_USE_VERSION 3123#include <assert.h>4#include <fcntl.h>5#include <stdint.h>6#include <stdio.h>7#include <stdlib.h>8#include <string.h>9#include <sys/mman.h>10#include <sys/stat.h>11#include <unistd.h>12#include <stdbool.h>1314#include <zlib.h>15#include <fuse3/fuse.h>1617struct EOCD {18 int16_t disk_number;19 int16_t dir_disk;20 int16_t dir_records_this_disk;21 int16_t dir_records;22 int32_t dir_size;23 int32_t dir_offset;24 int32_t comment_len;2526 char* comment;27};2829struct ExtraField{30 int16_t id;31 int16_t length;32 int8_t* data;33};3435struct CD {36 int16_t made_version;37 int16_t needed_version;38 int16_t flags;39 int16_t compress_method;40 int16_t mod_time;41 int16_t mod_date;42 int32_t uncompress_checksum;43 int32_t compress_size;44 int32_t uncompress_size;45 int16_t name_len;46 int16_t extra_field_len;47 int16_t comment_len;48 int16_t file_disk;49 int16_t internal_attrs;50 int32_t external_attrs;51 int32_t file_offset;52 int32_t num_extra_fields;5354 char* filename;55 char* comment;5657 struct ExtraField* extra_fields;58};5960struct File {61 int16_t needed_version;62 int16_t flags;63 int16_t compress_method;64 int16_t mod_time;65 int16_t mod_date;66 int32_t uncompress_checksum;67 int32_t compress_size;68 int32_t uncompress_size;69 int16_t name_len;70 int16_t extra_field_len;71 int32_t num_extra_fields;7273 char* filename;74 uint8_t* data;7576 struct ExtraField* extra_fields;77};7879uint16_t parse_le16(uint8_t* data) {80 return (data[0] << 0) | (data[1] << 8);81}8283uint32_t parse_le32(uint8_t* data) {84 return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);85}8687void eocd_free(struct EOCD* eocd) {88 free(eocd->comment);89}9091void extra_field_free(struct ExtraField ef) {92 free(ef.data);93}9495void cd_free(struct CD cd) {96 free(cd.filename);97 free(cd.comment);9899 for (int i = 0; i < cd.num_extra_fields; i++) {100 extra_field_free(cd.extra_fields[i]);101 }102}103104void free_cds(struct CD* cds, size_t num_cds) {105 for (int i = 0; i < num_cds; i++) {106 cd_free(cds[i]);107 }108109 free(cds);110}111112void file_free(struct File f) {113 free(f.filename);114 free(f.data);115116 for (int i = 0; i < f.num_extra_fields; i++) {117 extra_field_free(f.extra_fields[i]);118 }119}120121bool parse_eocd(uint8_t* file, size_t filelen, struct EOCD* eocd) {122 uint8_t* eocd_start = NULL;123 for (int i = filelen-4; i > 0; i--) {124 if (parse_le32(&file[i]) == 0x6054B50) {125 eocd_start = &file[i];126 break;127 }128 }129130 if (eocd_start == NULL) {131 return false;132 }133134 eocd->disk_number = parse_le16(&eocd_start[4]);135 eocd->dir_disk = parse_le16(&eocd_start[6]);136 eocd->dir_records_this_disk = parse_le16(&eocd_start[8]);137 eocd->dir_records = parse_le16(&eocd_start[10]);138 eocd->dir_size = parse_le32(&eocd_start[12]);139 eocd->dir_offset = parse_le32(&eocd_start[16]);140 eocd->comment_len = parse_le16(&eocd_start[20]);141142 eocd->comment = malloc(eocd->comment_len+1);143 memcpy(eocd->comment, &eocd_start[22], eocd->comment_len);144 eocd->comment[eocd->comment_len] = '\0';145146 return true;147}148149off_t parse_extra_field(uint8_t* data, struct ExtraField* ef) {150 ef->id = parse_le16(&data[0]);151 ef->length = parse_le16(&data[2]);152 ef->data = malloc(ef->length);153154 memcpy(ef->data, &data[4], ef->length);155156 return ef->length + 4;157}158159// Populates `cd` if possible and returns the size of the header, so the caller160// can skip to the next one.161//162// If unable to populate, returns 01163off_t parse_cd(uint8_t* data, struct CD* cd) {164 if (parse_le32(&data[0]) != 0x02014b50) {165 return -1;166 }167168 cd->made_version = parse_le16(&data[4]);169 cd->needed_version = parse_le16(&data[6]);170 cd->flags = parse_le16(&data[8]);171 cd->compress_method = parse_le16(&data[10]);172 cd->mod_time = parse_le16(&data[12]);173 cd->mod_date = parse_le16(&data[14]);174 cd->uncompress_checksum = parse_le32(&data[16]);175 cd->compress_size = parse_le32(&data[20]);176 cd->uncompress_size = parse_le32(&data[24]);177 cd->name_len = parse_le16(&data[28]);178 cd->extra_field_len = parse_le16(&data[30]);179 cd->comment_len = parse_le16(&data[32]);180 cd->file_disk = parse_le16(&data[34]);181 cd->internal_attrs = parse_le16(&data[36]);182 cd->external_attrs = parse_le32(&data[38]);183 cd->file_offset = parse_le32(&data[42]);184 cd->num_extra_fields = 0;185186 cd->filename = malloc(cd->name_len+1);187 memcpy(cd->filename, &data[46], cd->name_len);188 cd->filename[cd->name_len] = '\0';189190 if (cd->extra_field_len > 0) {191 cd->extra_fields = malloc(sizeof(struct ExtraField));192 uint8_t* end = &data[46+cd->name_len+cd->extra_field_len];193 uint8_t* curr = &data[46+cd->name_len];194 int num_fields = 0;195 int fields_cap = 1;196 while (curr < end) {197 if (num_fields >= fields_cap) {198 fields_cap *= 2;199 cd->extra_fields = realloc(cd->extra_fields, sizeof(struct ExtraField) * fields_cap);200 }201202 off_t length = parse_extra_field(curr, &cd->extra_fields[num_fields++]);203 if (length == -1) {204 return -1;205 }206207 cd->num_extra_fields++;208 curr += length;209 }210 }211212 cd->comment = malloc(cd->comment_len+1);213 memcpy(cd->comment, &data[46+cd->name_len+cd->extra_field_len], cd->comment_len);214 cd->comment[cd->name_len] = '\0';215216217 return 46+cd->name_len+cd->extra_field_len+cd->comment_len;218}219220bool parse_cds(uint8_t *file, off_t cds_offset, size_t num_files,221 struct CD **out_cds) {222 struct CD *cds = calloc(num_files, sizeof(struct CD));223224 off_t offset = cds_offset;225 for (int i = 0; i < num_files; i++) {226 off_t new_off = parse_cd(&file[offset], &cds[i]);227 if (new_off == -1) {228 return false;229 }230 offset += new_off;231 }232233 *out_cds = cds;234 return true;235}236237bool parse_file(uint8_t* data, struct File* f) {238 if (parse_le32(&data[0]) != 0x04034b50) {239 return false;240 }241242 f->needed_version = parse_le16(&data[4]);243 f->flags = parse_le16(&data[6]);244 f->compress_method = parse_le16(&data[8]);245 f->mod_time = parse_le16(&data[10]);246 f->mod_date = parse_le16(&data[12]);247 f->uncompress_checksum = parse_le32(&data[14]);248 f->compress_size = parse_le32(&data[18]);249 f->uncompress_size = parse_le32(&data[22]);250 f->name_len = parse_le16(&data[26]);251 f->extra_field_len = parse_le16(&data[28]);252 f->num_extra_fields = 0;253254 f->filename = malloc(f->name_len+1);255 memcpy(f->filename, &data[30], f->name_len);256 f->filename[f->name_len] = '\0';257258 if (f->extra_field_len > 0) {259 f->extra_fields = malloc(sizeof(struct ExtraField));260 uint8_t* end = &data[30+f->name_len+f->extra_field_len];261 uint8_t* curr = &data[30+f->name_len];262 int num_fields = 0;263 int fields_cap = 1;264 while (curr < end) {265 if (num_fields >= fields_cap) {266 fields_cap *= 2;267 f->extra_fields = realloc(f->extra_fields, sizeof(struct ExtraField) * fields_cap);268 }269270 off_t length = parse_extra_field(curr, &f->extra_fields[num_fields++]);271 if (length == -1) {272 return -1;273 }274275 f->num_extra_fields++;276 curr += length;277 }278 }279280 f->data = malloc(f->compress_size);281 memcpy(f->data, &data[30+f->name_len+f->extra_field_len], f->compress_size);282283 return true;284}285286int parse_zip(char* filename, struct File** out_files, size_t* num_files) {287 int retval = false;288 int fd = open(filename, O_RDONLY);289 if (fd == -1) {290 perror("Opening file:");291 goto error;292 }293294 struct stat buf;295 if (fstat(fd, &buf) == -1) {296 perror("Statting file");297 goto close;298 }299300 uint8_t *file =301 mmap(NULL, buf.st_size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);302 if (file == MAP_FAILED) {303 perror("Mapping file");304 goto close;305 }306307 struct EOCD eocd;308 if (!parse_eocd(file, buf.st_size, &eocd)) {309 goto munmap;310 }311312 struct CD *cds;313 if (!parse_cds(file, eocd.dir_offset, eocd.dir_records, &cds)) {314 goto eocd_free;315 }316317 struct File* files = calloc(sizeof(struct File), eocd.dir_records);318 for (int i = 0; i < eocd.dir_records; i++) {319 if (!parse_file(&file[cds[i].file_offset], &files[i])) {320 fprintf(stderr, "failed to parse file");321 goto free_cds;322 }323 }324325 retval = true;326 *out_files = files;327 *num_files = eocd.dir_records;328329free_cds:330 free_cds(cds, eocd.dir_records);331eocd_free:332 eocd_free(&eocd);333munmap:334 munmap(file, buf.st_size);335close:336 close(fd);337error:338339 return true;340}341342uint8_t* inflate_file(struct File f, size_t* out_length) {343 uint8_t* buf = NULL;344345 if (f.compress_method == 0) {346 buf = malloc(f.uncompress_size);347 *out_length = f.compress_size;348 memcpy(buf, f.data, f.compress_size);349 } else if (f.compress_method == 8) {350 buf = malloc(f.uncompress_size);351 *out_length = f.uncompress_size;352 z_stream strm;353 strm.next_in = f.data;354 strm.avail_in = f.compress_size;355 strm.next_out = buf;356 strm.avail_out = f.uncompress_size;357358 strm.zalloc = NULL;359 strm.zfree = NULL;360 strm.opaque = NULL;361362 int ret = inflateInit2(&strm, -MAX_WBITS);363 if (ret < 0) {364 fputs("Error encountered\n", stderr);365 return NULL;366 }367368 ret = inflate(&strm, Z_SYNC_FLUSH);369 if (ret != Z_STREAM_END) {370 fprintf(stderr, "Stream not finished: %d\n", ret);371 return NULL;372 }373 } else {374 fprintf(stderr, "Unknown compression: %d\n", f.compress_method);375 return NULL;376 }377378 return buf;379}380381int main(int argc, char **argv) {382 bool bad = false;383384 if (argc != 2) {385 puts("usage: zip <FILE>");386 return 1;387 }388389 struct File *files;390 size_t num_files;391 if (!parse_zip(argv[1], &files, &num_files)) {392 fprintf(stderr, "failed to parse zip\n");393 return 1;394 }395396 for (int i = 0; i < num_files; i++) {397 struct File f = files[i];398 size_t length;399 uint8_t* buf = inflate_file(f, &length);400401 fwrite(buf, sizeof(uint8_t), length, stdout);402 }403404 return 0;405}