Blob


1 /*
2 * This is free and unencumbered software released into the public domain.
3 */
5 #include <sys/mman.h>
7 #include <endian.h>
8 #include <err.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <zlib.h>
17 #define COMPRESSION_NONE 0x00
18 #define COMPRESSION_DEFLATE 0x08
20 #define MIN(a, b) ((a) < (b) ? (a) : (b))
22 void *
23 find_central_directory(uint8_t *addr, size_t len)
24 {
25 uint32_t offset;
26 uint16_t clen;
27 uint8_t *p, *end;
29 /*
30 * At -22 bytes from the end there is the end of the central
31 * directory assuming an empty comment. It's a sensible place
32 * from which start.
33 */
34 if (len < 22)
35 return NULL;
36 end = addr + len;
37 p = end - 22;
39 again:
40 for (; p > addr; --p)
41 if (memcmp(p, "\x50\x4b\x05\x06", 4) == 0)
42 break;
44 if (p == addr)
45 return NULL;
47 /* read comment length */
48 memcpy(&clen, p + 20, sizeof(clen));
49 clen = le16toh(clen);
51 /* false signature inside a comment? */
52 if (clen + 22 != end - p) {
53 p--;
54 goto again;
55 }
57 /* read the offset for the central directory */
58 memcpy(&offset, p + 16, sizeof(offset));
59 offset = le32toh(offset);
61 if (addr + offset > p)
62 return NULL;
64 return addr + offset;
65 }
67 void
68 unzip_none(uint8_t *data, size_t size, unsigned long ocrc)
69 {
70 unsigned long crc = 0;
72 fwrite(data, 1, size, stdout);
74 crc = crc32(0, data, size);
75 if (crc != ocrc)
76 errx(1, "CRC mismatch");
77 }
79 void
80 unzip_deflate(uint8_t *data, size_t size, unsigned long ocrc)
81 {
82 z_stream stream;
83 size_t have;
84 unsigned long crc = 0;
85 char buf[BUFSIZ];
87 stream.zalloc = Z_NULL;
88 stream.zfree = Z_NULL;
89 stream.opaque = Z_NULL;
90 stream.next_in = data;
91 stream.avail_in = size;
92 stream.next_out = Z_NULL;
93 stream.avail_out = 0;
94 if (inflateInit2(&stream, -15) != Z_OK)
95 err(1, "inflateInit failed");
97 do {
98 stream.next_out = buf;
99 stream.avail_out = sizeof(buf);
101 switch (inflate(&stream, Z_BLOCK)) {
102 case Z_STREAM_ERROR:
103 errx(1, "stream error");
104 case Z_NEED_DICT:
105 errx(1, "need dict");
106 case Z_DATA_ERROR:
107 errx(1, "data error: %s", stream.msg);
108 case Z_MEM_ERROR:
109 errx(1, "memory error");
112 have = sizeof(buf) - stream.avail_out;
113 fwrite(buf, 1, have, stdout);
114 crc = crc32(crc, buf, have);
115 } while (stream.avail_out == 0);
117 inflateEnd(&stream);
119 if (crc != ocrc)
120 errx(1, "CRC mismatch");
123 void
124 unzip(uint8_t *zip, size_t len, uint8_t *entry)
126 uint32_t size, osize, crc, off;
127 uint16_t flags, compression;
128 uint16_t flen, xlen;
129 uint8_t *data, *offset;
131 /* read the offset of the file record */
132 memcpy(&off, entry + 42, sizeof(off));
133 offset = zip + le32toh(off);
135 if (offset > zip + len - 4 ||
136 memcmp(offset, "\x50\x4b\x03\x04", 4) != 0)
137 errx(1, "invalid offset or file header signature");
139 memcpy(&flags, offset + 6, sizeof(flags));
140 memcpy(&compression, offset + 8, sizeof(compression));
142 flags = le16toh(flags);
143 compression = le16toh(compression);
145 memcpy(&crc, entry + 16, sizeof(crc));
146 memcpy(&size, entry + 20, sizeof(size));
147 memcpy(&osize, entry + 24, sizeof(osize));
149 crc = le32toh(crc);
150 size = le32toh(size);
151 osize = le32toh(osize);
153 memcpy(&flen, offset + 26, sizeof(flen));
154 memcpy(&xlen, offset + 28, sizeof(xlen));
156 flen = le16toh(flen);
157 xlen = le16toh(xlen);
159 data = offset + 30 + flen + xlen;
160 if (data + size > zip + len)
161 errx(1, "corrupted zip, offset out of file");
163 switch (compression) {
164 case COMPRESSION_NONE:
165 unzip_none(data, size, crc);
166 break;
167 case COMPRESSION_DEFLATE:
168 unzip_deflate(data, size, crc);
169 break;
170 default:
171 errx(1, "unknown compression method 0x%02x",
172 compression);
176 void *
177 next(uint8_t *zip, size_t len, uint8_t *entry)
179 uint16_t flen, xlen, clen;
180 uint8_t *next, *end;
182 memcpy(&flen, entry + 28, sizeof(flen));
183 memcpy(&xlen, entry + 28 + 2, sizeof(xlen));
184 memcpy(&clen, entry + 28 + 2 + 2, sizeof(xlen));
186 flen = le16toh(flen);
187 xlen = le16toh(xlen);
188 clen = le16toh(clen);
190 next = entry + 46 + flen + xlen + clen;
191 end = zip + len;
192 if (next >= end - 46 ||
193 memcmp(next, "\x50\x4b\x01\x02", 4) != 0)
194 return NULL;
195 return next;
198 void
199 filename(uint8_t *zip, size_t len, uint8_t *entry, char *buf,
200 size_t size)
202 uint16_t flen;
203 size_t s;
205 memcpy(&flen, entry + 28, sizeof(flen));
206 flen = le16toh(flen);
208 s = MIN(size-1, flen);
209 memcpy(buf, entry + 46, s);
210 buf[s] = '\0';
213 void
214 ls(uint8_t *zip, size_t len, uint8_t *cd)
216 char name[PATH_MAX];
218 do {
219 filename(zip, len, cd, name, sizeof(name));
220 printf("%s\n", name);
221 } while ((cd = next(zip, len, cd)) != NULL);
224 void *
225 find_file(uint8_t *zip, size_t len, uint8_t *cd, const char *target)
227 char name[PATH_MAX];
229 do {
230 filename(zip, len, cd, name, sizeof(name));
231 if (!strcmp(name, target))
232 return cd;
233 } while ((cd = next(zip, len, cd)) != NULL);
235 return NULL;
238 int
239 extract_file(uint8_t *zip, size_t len, uint8_t *cd, const char *target)
241 if ((cd = find_file(zip, len, cd, target)) == NULL)
242 return -1;
244 unzip(zip, len, cd);
245 return 0;
248 void *
249 map_file(int fd, size_t *len)
251 off_t jump;
252 void *addr;
254 if ((jump = lseek(fd, 0, SEEK_END)) == -1)
255 err(1, "lseek");
257 if (lseek(fd, 0, SEEK_SET) == -1)
258 err(1, "lseek");
260 if ((addr = mmap(NULL, jump, PROT_READ, MAP_PRIVATE, fd, 0))
261 == MAP_FAILED)
262 err(1, "mmap");
264 *len = jump;
265 return addr;
268 int
269 main(int argc, char **argv)
271 int i, fd;
272 void *zip, *cd;
273 size_t len;
275 if (argc < 2) {
276 fprintf(stderr, "Usage: %s archive.zip [files...]",
277 *argv);
278 return 1;
281 if ((fd = open(argv[1], O_RDONLY)) == -1)
282 err(1, "can't open %s", argv[1]);
284 zip = map_file(fd, &len);
286 #ifdef __OpenBSD__
287 if (pledge("stdio", NULL) == -1)
288 err(1, "pledge");
289 #endif
291 if ((cd = find_central_directory(zip, len)) == NULL)
292 errx(1, "can't find the central directory");
294 if (argc == 2)
295 ls(zip, len, cd);
296 else {
297 for (i = 2; i < argc; ++i)
298 extract_file(zip, len, cd, argv[i]);
301 munmap(zip, len);
302 close(fd);
304 return 0;