main zip.c
  1#include <stdio.h>
  2#include <stdlib.h>
  3#include <stddef.h>
  4#include <string.h>
  5#include "util.h"
  6#include "zip.h"
  7#include <zlib.h>
  8
  9static const char cdfd_magic[4] = {'P', 'K', 1, 2};
 10static const char eocd_magic[4] = {'P', 'K', 5, 6};
 11#define MIN_EOCD_SIZE 22
 12#define MIN_CDFD_SIZE 46
 13#define ZIP_MAX_EOCD_OFFSET (64*1024+MIN_EOCD_SIZE)
 14
 15enum {
 16	ZIP_STORE = 0,
 17	ZIP_DEFLATE = 8
 18};
 19
 20zip_file *zip_open(const char *filename)
 21{
 22	FILE *f = fopen(filename, "rb");
 23	if (!f) {
 24		return NULL;
 25	}
 26	long fsize = file_size(f);
 27	if (fsize < MIN_EOCD_SIZE) {
 28		//too small to be a zip file
 29		goto fail;
 30	}
 31	
 32	long max_offset = fsize > ZIP_MAX_EOCD_OFFSET ? ZIP_MAX_EOCD_OFFSET : fsize;
 33	fseek(f, -max_offset, SEEK_END);
 34	uint8_t *buf = malloc(max_offset);
 35	if (max_offset != fread(buf, 1, max_offset, f)) {
 36		goto fail;
 37	}
 38	
 39	long current_offset;
 40	uint32_t cd_start, cd_size;
 41	uint16_t cd_count;
 42	for (current_offset = max_offset - MIN_EOCD_SIZE; current_offset >= 0; current_offset--)
 43	{
 44		if (memcmp(eocd_magic, buf + current_offset, sizeof(eocd_magic))) {
 45			continue;
 46		}
 47		uint16_t comment_size = buf[current_offset + 20] | buf[current_offset + 21] << 8;
 48		if (comment_size != (max_offset - current_offset - MIN_EOCD_SIZE)) {
 49			continue;
 50		}
 51		cd_start = buf[current_offset + 16] | buf[current_offset + 17] << 8
 52			| buf[current_offset + 18] << 16 | buf[current_offset + 19] << 24;
 53		if (cd_start > (fsize - (max_offset - current_offset))) {
 54			continue;
 55		}
 56		cd_size = buf[current_offset + 12] | buf[current_offset + 13] << 8
 57			| buf[current_offset + 14] << 16 | buf[current_offset + 15] << 24;
 58		if ((cd_start + cd_size) > (fsize - (max_offset - current_offset))) {
 59			continue;
 60		}
 61		cd_count = buf[current_offset + 10] | buf[current_offset + 11] << 8;
 62		break;
 63	}
 64	free(buf);
 65	if (current_offset < 0) {
 66		//failed to find EOCD
 67		goto fail;
 68	}
 69	buf = malloc(cd_size);
 70	fseek(f, cd_start, SEEK_SET);
 71	if (cd_size != fread(buf, 1, cd_size, f)) {
 72		goto fail_free;
 73	}
 74	zip_entry *entries = calloc(cd_count, sizeof(zip_entry));
 75	uint32_t cd_max_last = cd_size - MIN_CDFD_SIZE;
 76	zip_entry *cur_entry = entries;
 77	for (uint32_t off = 0; cd_count && off <= cd_max_last; cur_entry++, cd_count--)
 78	{
 79		if (memcmp(buf + off, cdfd_magic, sizeof(cdfd_magic))) {
 80			goto fail_entries;
 81		}
 82		uint32_t name_length = buf[off + 28] | buf[off + 29] << 8;
 83		uint32_t extra_length = buf[off + 30] | buf[off + 31] << 8;
 84		//TODO: verify name length doesn't go past end of CD
 85		
 86		cur_entry->name = malloc(name_length + 1);
 87		memcpy(cur_entry->name, buf + off + MIN_CDFD_SIZE, name_length);
 88		cur_entry->name[name_length] = 0;
 89		
 90		cur_entry->compressed_size = buf[off + 20] | buf[off + 21] << 8 
 91			| buf[off + 22] << 16 | buf[off + 23] << 24;
 92		cur_entry->size = buf[off + 24] | buf[off + 25] << 8 
 93			| buf[off + 26] << 16 | buf[off + 27] << 24;
 94			
 95		cur_entry->local_header_off = buf[off + 42] | buf[off + 43] << 8 
 96			| buf[off + 44] << 16 | buf[off + 45] << 24;
 97			
 98		cur_entry->compression_method = buf[off + 10] | buf[off + 11] << 8;
 99		
100		off += name_length + extra_length + MIN_CDFD_SIZE;
101	}
102	
103	zip_file *z = malloc(sizeof(zip_file));
104	z->entries = entries;
105	z->file = f;
106	z->num_entries = cur_entry - entries;
107	return z;
108	
109fail_entries:
110	for (cur_entry--; cur_entry >= entries; cur_entry--)
111	{
112		free(cur_entry->name);
113	}
114	free(entries);
115fail_free:
116	free(buf);
117fail:
118	fclose(f);
119	return NULL;
120}
121
122uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size)
123{
124	
125	fseek(f->file, f->entries[index].local_header_off + 26, SEEK_SET);
126	uint8_t tmp[4];
127	if (sizeof(tmp) != fread(tmp, 1, sizeof(tmp), f->file)) {
128		return NULL;
129	}
130	uint32_t local_variable = (tmp[0] | tmp[1] << 8) + (tmp[2] | tmp[3] << 8);
131	fseek(f->file, f->entries[index].local_header_off + local_variable + 30, SEEK_SET);
132	
133	size_t int_size;
134	if (!out_size) {
135		out_size = &int_size;
136		int_size = f->entries[index].size;
137	}
138	
139	uint8_t *buf = malloc(*out_size);
140	if (*out_size > f->entries[index].size) {
141		*out_size = f->entries[index].size;
142	}
143	switch(f->entries[index].compression_method)
144	{
145	case ZIP_STORE:
146		if (*out_size != fread(buf, 1, *out_size, f->file)) {
147			free(buf);
148			return NULL;
149		}
150		break;
151	case ZIP_DEFLATE: {
152		//note in unzip.c in zlib/contrib suggests a dummy byte is needed, so we allocate an extra byte here
153		uint8_t *src_buf = malloc(f->entries[index].compressed_size + 1);
154		if (f->entries[index].compressed_size != fread(src_buf, 1, f->entries[index].compressed_size, f->file)) {
155			free(src_buf);
156			return NULL;
157		}
158		uLongf destLen = *out_size;
159		z_stream stream;
160		memset(&stream, 0, sizeof(stream));
161		stream.avail_in = f->entries[index].compressed_size + 1;
162		stream.next_in = src_buf;
163		stream.next_out = buf;
164		stream.avail_out = *out_size;
165		if (Z_OK == inflateInit2(&stream, -15)) {
166			int result = inflate(&stream, Z_FINISH);
167			*out_size = stream.total_out;
168			free(src_buf);
169			inflateEnd(&stream);
170			if (result != Z_OK && result != Z_STREAM_END && result != Z_BUF_ERROR) {
171				free(buf);
172				return NULL;
173			}
174		}
175		break;
176	}
177	default:
178		free(buf);
179		return NULL;
180	}
181	
182	return buf;
183}
184
185void zip_close(zip_file *f)
186{
187	fclose(f->file);
188	for (uint32_t i = 0; i < f->num_entries; i++)
189	{
190		free(f->entries[i].name);
191	}
192	free(f->entries);
193	free(f);
194}
195
196