loaders: ZIP: Propagate errno from GP_ReadImage()
[gfxprim.git] / libs / loaders / GP_ZIP.c
blobec80e963916c296c7dd364a5715532f233ebb95c
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
23 #include <stdio.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stdint.h>
27 #include <inttypes.h>
29 #include "../../config.h"
31 #ifdef HAVE_ZLIB
33 #include <zlib.h>
35 #endif /* HAVE_ZLIB */
37 #include "core/GP_Common.h"
38 #include "core/GP_Debug.h"
40 #include "loaders/GP_ByteUtils.h"
41 #include "loaders/GP_Loader.h"
43 #include "loaders/GP_ZIP.h"
45 #ifdef HAVE_ZLIB
47 #define ZIP_CHUNKS_IN_TABLE 128
50 * Table used for seeks, populated on the go
52 struct zip_chunks_table {
53 long offsets[ZIP_CHUNKS_IN_TABLE];
54 struct zip_chunks_table *next;
55 struct zip_chunks_table *prev;
58 struct zip_priv {
59 GP_IO *io;
61 /* Current position in zip continer counted in images we found */
62 unsigned int cur_pos;
64 /* Current table */
65 unsigned int cur_table_pos;
66 struct zip_chunks_table *cur_table;
68 /* Last table and index into it, this is used for addition */
69 unsigned int tables_used;
70 unsigned int table_used;
71 struct zip_chunks_table *last_table;
73 struct zip_chunks_table table;
76 #define GEN_FLAG_ENCRYPTED 0x01
78 struct zip_local_header {
79 uint16_t ver;
80 uint16_t bit_flags;
81 uint16_t comp_type;
83 uint32_t crc;
84 uint32_t comp_size;
85 uint32_t uncomp_size;
87 uint16_t fname_len;
88 uint16_t extf_len;
90 char *file_name;
93 enum compress_method {
94 COMPRESS_STORED = 0x00, /* no compression at all */
95 COMPRESS_SHRUNK = 0x01,
96 COMPRESS_REDUCE1 = 0x02,
97 COMPRESS_REDUCE2 = 0x03,
98 COMPRESS_REDUCE3 = 0x04,
99 COMPRESS_REDUCE4 = 0x05,
100 COMPRESS_IMPLODE = 0x06,
101 COMPRESS_RESERVED1 = 0x07,
102 COMPRESS_DEFLATE = 0x08,
103 COMPRESS_DEFLATE64 = 0x09,
104 COMPRESS_PKWARE_IMPLODE = 0x0a,
105 COMPRESS_RESERVED2 = 0x0b,
106 COMPRESS_BZIP2 = 0x0c,
109 static const char *compress_method_names[] = {
110 "Stored (no compression)",
111 "Shrunk",
112 "Reduced with factor 1",
113 "Reduced with factor 2",
114 "Reduced with factor 3",
115 "Reduced with factor 4",
116 "Imploded",
117 "Reserved 1",
118 "Deflate",
119 "Deflate64",
120 "PKWARE Implode",
121 "Reserved 2",
122 "BZIP2",
125 static const char *compress_method_name(enum compress_method comp)
127 if (comp > COMPRESS_BZIP2)
128 return "Unknown";
130 return compress_method_names[comp];
133 static int seek_bytes(GP_IO *io, uint32_t bytes)
135 if (bytes == 0)
136 return 0;
138 GP_DEBUG(4, "Moving forward by %"PRIu32" bytes", bytes);
140 if (GP_IOSeek(io, bytes, GP_IO_SEEK_CUR) == (off_t)-1) {
141 int err = errno;
142 GP_DEBUG(1, "Failed to seek: %s", strerror(errno));
143 return err;
146 return 0;
149 #define CHUNK 512
151 struct deflate_inbuf {
152 struct zip_local_header *zip_header;
153 uint32_t to_read;
154 unsigned char buf[CHUNK];
155 GP_IO *io;
158 struct deflate_outbuf {
159 uint32_t crc;
160 uint32_t size;
161 uint8_t *buf;
164 static unsigned deflate_in(void *in_desc, unsigned char **buf)
166 struct deflate_inbuf *in = in_desc;
167 int chunk = in->to_read >= CHUNK ? CHUNK : in->to_read;
169 if (GP_IORead(in->io, in->buf, chunk) != chunk)
170 return 0;
172 *buf = in->buf;
173 in->to_read -= chunk;
175 return chunk;
178 static int deflate_out(void *out_desc, unsigned char *buf, unsigned len)
180 struct deflate_outbuf *out = out_desc;
182 out->crc = crc32(out->crc, buf, len);
183 memcpy(out->buf + out->size, buf, len);
184 out->size += len;
186 return 0;
189 static int read_deflate(GP_IO *io, struct zip_local_header *header, GP_IO **rio)
191 uint8_t *window;
192 int err = 0, ret;
193 uint8_t *buf;
195 window = malloc(32 * 1024);
196 //TODO: Unsafe
197 buf = malloc(header->uncomp_size);
199 if (!window || !buf) {
200 err = ENOMEM;
201 goto err1;
204 z_stream strm = {
205 .zalloc = Z_NULL,
206 .zfree = Z_NULL,
207 .opaque = Z_NULL,
208 .next_in = Z_NULL,
211 if (inflateBackInit(&strm, 15, window) != Z_OK) {
212 GP_DEBUG(1, "Failed to initialize inflate stream");
213 //TODO: Better errno?
214 err = EIO;
215 goto err1;
218 struct deflate_outbuf outbuf = {
219 .crc = crc32(0, NULL, 0),
220 .size = 0,
221 .buf = buf,
224 struct deflate_inbuf inbuf = {
225 .zip_header = header,
226 .io = io,
227 .to_read = header->comp_size,
230 ret = inflateBack(&strm, deflate_in, &inbuf, deflate_out, &outbuf);
232 if (ret != Z_STREAM_END) {
233 GP_DEBUG(1, "Failed to inflate stream %i", ret);
234 err = EINVAL;
235 goto err2;
238 if (outbuf.crc != header->crc) {
239 GP_DEBUG(1, "CRC does not match");
240 err = EIO;
241 goto err2;
244 if (outbuf.size != header->uncomp_size) {
245 GP_DEBUG(1, "Decompressed size does not match");
246 err = EIO;
247 goto err2;
250 inflateBackEnd(&strm);
251 free(window);
253 //TODO: Failure
254 *rio = GP_IOMem(outbuf.buf, outbuf.size, free);
255 return 0;
256 err2:
257 inflateBackEnd(&strm);
258 err1:
259 free(window);
260 free(buf);
261 return err;
264 static int zip_load_header(GP_IO *io, struct zip_local_header *header)
266 int ret;
267 uint8_t byte;
269 uint16_t zip_header[] = {
270 'P',
271 'K',
272 GP_IO_BYTE,
273 GP_IO_END
276 if (GP_IOReadF(io, zip_header, &byte) != 3) {
277 GP_DEBUG(1, "Failed to read header");
278 return EIO;
281 switch (byte) {
282 /* Central directory -> end of archive */
283 case 0x01:
284 GP_DEBUG(1, "Reached end of the archive");
285 return EINVAL;
286 break;
287 /* File header */
288 case 0x03:
289 break;
290 default:
291 GP_DEBUG(1, "Unexpected header PK%x", byte);
292 return EIO;
295 uint16_t zip_local_header[] = {
296 0x04,
297 GP_IO_L2, /* version */
298 GP_IO_L2, /* bit flags */
299 GP_IO_L2, /* compression type */
300 GP_IO_IGN | 4,
301 GP_IO_L4, /* CRC */
302 GP_IO_L4, /* compressed size */
303 GP_IO_L4, /* uncompressed size */
304 GP_IO_L2, /* filename length */
305 GP_IO_L2, /* extra fields lenght */
306 GP_IO_END
309 ret = GP_IOReadF(io, zip_local_header,
310 &header->ver, &header->bit_flags, &header->comp_type,
311 &header->crc, &header->comp_size, &header->uncomp_size,
312 &header->fname_len, &header->extf_len);
314 if (ret != 10) {
315 GP_DEBUG(1, "Failed to read header");
316 return EIO;
319 return 0;
322 static GP_Context *zip_next_file(struct zip_priv *priv,
323 GP_ProgressCallback *callback)
325 struct zip_local_header header = {.file_name = NULL};
326 int err = 0;
327 GP_Context *ret = NULL;
328 GP_IO *io;
330 if ((err = zip_load_header(priv->io, &header)))
331 goto out;
333 GP_DEBUG(1, "Have ZIP local header version %u.%u compression %s",
334 header.ver/10, header.ver%10,
335 compress_method_name(header.comp_type));
337 if (header.bit_flags & GEN_FLAG_ENCRYPTED) {
338 GP_DEBUG(1, "Can't handle encrypted files");
339 err = ENOSYS;
340 goto out;
344 * If input was taken from stdin the fname_len is either set to zero or
345 * to one and filename is set to '-'.
347 if (header.fname_len) {
348 header.file_name = malloc(header.fname_len + 1);
350 if (!header.file_name) {
351 err = ENOMEM;
352 goto out;
355 header.file_name[header.fname_len] = '\0';
357 if (GP_IORead(priv->io, header.file_name, header.fname_len) != header.fname_len) {
358 GP_DEBUG(1, "Failed to read filename");
359 err = EIO;
360 goto out;
363 GP_DEBUG(1, "Filename '%s' compressed size=%"PRIu32
364 " uncompressed size=%"PRIu32,
365 header.file_name, header.comp_size,
366 header.uncomp_size);
369 seek_bytes(priv->io, header.extf_len);
371 switch (header.comp_type) {
372 case COMPRESS_STORED:
373 /* skip directories */
374 if (header.uncomp_size == 0) {
375 GP_DEBUG(2, "Skipping directory");
376 goto out;
379 GP_IOMark(priv->io, GP_IO_MARK);
381 ret = GP_ReadImage(priv->io, callback);
382 if (errno == ECANCELED)
383 err = errno;
385 GP_IOSeek(priv->io, priv->io->mark + header.comp_size, GP_IO_SEEK_SET);
387 goto out;
388 break;
389 case COMPRESS_DEFLATE:
390 if ((err = read_deflate(priv->io, &header, &io))) {
391 err = errno;
392 goto out;
394 GP_DEBUG(1, "Reading image");
395 ret = GP_ReadImage(io, callback);
396 if (errno == ECANCELED)
397 err = errno;
399 GP_IOClose(io);
400 goto out;
401 break;
402 default:
403 GP_DEBUG(1, "Unimplemented compression %s",
404 compress_method_name(header.comp_type));
405 err = ENOSYS;
406 goto out;
409 out:
410 free(header.file_name);
411 errno = err;
412 return ret;
415 static unsigned int last_offset_idx(struct zip_priv *priv)
417 return priv->table_used + priv->tables_used * ZIP_CHUNKS_IN_TABLE;
420 static long last_recorded_offset(struct zip_priv *priv)
422 const unsigned int last_idx = ZIP_CHUNKS_IN_TABLE - 1;
424 if (priv->table_used == 0) {
425 if (priv->last_table->prev)
426 return priv->last_table->prev->offsets[last_idx];
428 return -1;
431 return priv->last_table->offsets[priv->table_used - 1];
434 static void record_offset(struct zip_priv *priv, long offset)
436 if (offset <= last_recorded_offset(priv))
437 return;
439 GP_DEBUG(2, "Recording offset to %i image (%li)",
440 last_offset_idx(priv), offset);
442 if (priv->table_used >= ZIP_CHUNKS_IN_TABLE) {
443 struct zip_chunks_table *new_table;
445 GP_DEBUG(1, "Allocating chunks table (table nr. %u) (size %i)",
446 priv->tables_used+1, ZIP_CHUNKS_IN_TABLE);
448 new_table = malloc(sizeof(struct zip_chunks_table));
450 if (!new_table) {
451 GP_WARN("Malloc failed :(");
452 return;
455 priv->tables_used++;
456 priv->table_used = 0;
457 new_table->prev = priv->last_table;
458 new_table->next = NULL;
459 priv->last_table->next = new_table;
460 priv->last_table = new_table;
463 priv->last_table->offsets[priv->table_used++] = offset;
465 printf("OFFSET table\n");
466 unsigned int i;
467 for (i = 0; i < priv->table_used; i++)
468 printf("** %u -> %li\n", i, priv->last_table->offsets[i]);
472 static GP_Context *zip_load_next(GP_Container *self,
473 GP_ProgressCallback *callback)
475 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
476 GP_Context *ret;
477 long offset;
479 GP_DEBUG(1, "Trying to load next image from ZIP container");
481 do {
482 offset = GP_IOTell(priv->io);
483 ret = zip_next_file(priv, callback);
484 } while (ret == NULL && errno == 0);
486 if (!ret)
487 return NULL;
489 if (ret)
490 record_offset(priv, offset);
492 priv->cur_pos++;
493 //self->cur_img++;
494 self->cur_img = priv->cur_pos;
496 return ret;
499 /* Seek to the current position */
500 static void seek_cur_pos(struct zip_priv *priv)
502 unsigned int cur_table = priv->cur_pos / ZIP_CHUNKS_IN_TABLE;
503 unsigned int cur_pos;
505 if (priv->cur_table_pos != cur_table) {
506 unsigned int i;
508 GP_DEBUG(3, "cur_pos %u out of cur table %u",
509 priv->cur_pos, priv->cur_table_pos);
511 priv->cur_table = &priv->table;
513 for (i = 0; i < cur_table; i++) {
514 if (priv->cur_table->next)
515 priv->cur_table = priv->cur_table->next;
516 else
517 GP_WARN("The cur_pos points after last table");
520 priv->cur_table_pos = cur_table;
523 //TODO: Asert that we are not in last table and cur_pos < table_used
525 cur_pos = priv->cur_pos % ZIP_CHUNKS_IN_TABLE;
527 GP_DEBUG(2, "Setting current position to %u (%li)",
528 priv->cur_pos, priv->cur_table->offsets[cur_pos]);
530 GP_IOSeek(priv->io, priv->cur_table->offsets[cur_pos], GP_IO_SEEK_SET);
533 static int load_next_offset(struct zip_priv *priv)
535 struct zip_local_header header = {.file_name = NULL};
536 int ret;
537 long offset = GP_IOTell(priv->io);
539 if ((ret = zip_load_header(priv->io, &header)))
540 return ret;
542 //TODO: Match image extension and signature
543 record_offset(priv, offset);
545 /* Seek to the next local header */
546 seek_bytes(priv->io, (uint32_t)header.fname_len +
547 (uint32_t)header.extf_len);
548 seek_bytes(priv->io, header.comp_size);
550 return 0;
554 * Sets current position.
556 static int set_cur_pos(struct zip_priv *priv, unsigned int where)
558 unsigned int max = last_offset_idx(priv);
559 int err;
561 GP_DEBUG(2, "where %u max %u", where, max);
563 /* Move to the max and beyond */
564 if (where >= max) {
565 if (max == 0) {
566 if ((err = load_next_offset(priv)))
567 return err;
568 priv->cur_pos = 0;
569 } else {
570 priv->cur_pos = max - 1;
571 seek_cur_pos(priv);
574 while (priv->cur_pos < where) {
575 if ((err = load_next_offset(priv)))
576 return err;
577 priv->cur_pos++;
580 return 0;
583 priv->cur_pos = where;
584 seek_cur_pos(priv);
586 return 0;
589 static int zip_seek(GP_Container *self, int offset,
590 enum GP_ContainerWhence whence)
592 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
593 unsigned int where;
594 int ret;
596 GP_DEBUG(1, "Seek offset=%i whence=%i", offset, whence);
598 switch (whence) {
599 case GP_CONT_CUR:
600 if (offset < 0 && priv->cur_pos < (unsigned int)-offset) {
601 GP_WARN("Current position %u offset %i",
602 priv->cur_pos, offset);
603 where = 0;
604 } else {
605 where = priv->cur_pos + offset;
607 break;
608 case GP_CONT_FIRST:
609 where = offset;
610 break;
611 default:
612 return ENOSYS;
615 ret = set_cur_pos(priv, where);
617 self->cur_img = priv->cur_pos;
619 return ret;
622 static GP_Context *zip_load(GP_Container *self,
623 GP_ProgressCallback *callback)
625 GP_Context *img;
627 img = zip_load_next(self, callback);
629 if (!img)
630 return NULL;
632 zip_seek(self, -1, GP_CONT_CUR);
634 return img;
637 static void zip_close(GP_Container *self)
639 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
640 struct zip_chunks_table *i, *j;
642 GP_DEBUG(1, "Closing ZIP container");
644 /* Free allocated offset tables */
645 for (i = priv->table.next; i != NULL; j = i, i = i->next, free(j));
647 GP_IOClose(priv->io);
648 free(self);
651 static GP_IO *open_zip(const char *path)
653 GP_IO *io;
654 int err = 0;
656 io = GP_IOFile(path, GP_IO_RDONLY);
658 if (!io) {
659 err = errno;
660 GP_DEBUG(1, "Failed to open '%s': %s", path, strerror(errno));
661 goto err0;
664 /* Check zip local file header and seek back */
665 if (GP_IOMark(io, GP_IO_MARK)) {
666 err = errno;
667 goto err1;
670 static uint16_t zip_header[] = {
671 'P',
672 'K',
673 0x03,
674 0x04,
675 GP_IO_END
678 if (GP_IOReadF(io, zip_header) != 4) {
679 GP_DEBUG(1, "Invalid zip header");
680 err = EINVAL;
681 goto err1;
684 if (GP_IOMark(io, GP_IO_REWIND)) {
685 err = errno;
686 goto err1;
689 return io;
690 err1:
691 GP_IOClose(io);
692 err0:
693 errno = err;
694 return NULL;
697 static const struct GP_ContainerOps zip_ops = {
698 .LoadNext = zip_load_next,
699 .Load = zip_load,
700 .Close = zip_close,
701 .Seek = zip_seek,
702 .type = "ZIP",
705 GP_Container *GP_OpenZip(const char *path)
707 struct zip_priv *priv;
708 GP_Container *ret;
709 GP_IO *io;
710 int err;
712 io = open_zip(path);
714 if (!io)
715 return NULL;
717 ret = malloc(sizeof(GP_Container) + sizeof(struct zip_priv));
719 if (!ret) {
720 err = ENOMEM;
721 goto err0;
724 GP_DEBUG(1, "ZIP Container initialized");
726 ret->img_count = -1;
727 ret->cur_img = 0;
728 ret->ops = &zip_ops;
730 priv = GP_CONTAINER_PRIV(ret);
732 priv->io = io;
734 priv->table.next = NULL;
735 priv->table.prev = NULL;
737 /* Cache for current table for seeks */
738 priv->cur_table = &priv->table;
739 priv->cur_table_pos = 0;
741 /* Current position */
742 priv->cur_pos = 0;
744 /* Last table, used for insertion */
745 priv->tables_used = 0;
746 priv->table_used = 0;
747 priv->last_table = &priv->table;
749 return ret;
750 err0:
751 GP_IOClose(io);
752 errno = err;
753 return NULL;
756 #else
758 GP_Container *GP_OpenZip(const char GP_UNUSED(*path))
760 GP_FATAL("zlib support not compiled in");
761 errno = ENOSYS;
762 return NULL;
765 #endif /* HAVE_ZLIB */
767 int GP_MatchZip(const char *buf)
769 return !memcmp("PK\03\04", buf, 4);