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