summary refs log tree commit diff
path: root/src/zip.c
blob: aca980da8a7736a55f7affc5ba9d3965b134b8b4 (plain)
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
#include <stdlib.h> // malloc
#include <string.h> // 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 &trade;
	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, &central->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;
}