Rename GP_Context -> GP_Pixmap
[gfxprim.git] / libs / loaders / GP_ZIP.c
blobfd1d7ab77a28760366e4adf557ee70e94aa71f96
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_Loader.h"
41 #include "loaders/GP_IOZlib.h"
42 #include "loaders/GP_ZIP.h"
44 #ifdef HAVE_ZLIB
46 #define ZIP_CHUNKS_IN_TABLE 128
49 * Table used for seeks, populated on the go
51 struct zip_chunks_table {
52 long offsets[ZIP_CHUNKS_IN_TABLE];
53 struct zip_chunks_table *next;
54 struct zip_chunks_table *prev;
57 struct zip_priv {
58 GP_IO *io;
60 /* Current position in zip continer counted in images we found */
61 unsigned int cur_pos;
63 /* Current table */
64 unsigned int cur_table_pos;
65 struct zip_chunks_table *cur_table;
67 /* Last table and index into it, this is used for addition */
68 unsigned int tables_used;
69 unsigned int table_used;
70 struct zip_chunks_table *last_table;
72 struct zip_chunks_table table;
75 struct zip_local_header {
76 uint16_t ver;
77 uint16_t flags;
78 uint16_t comp_type;
80 uint32_t crc;
81 uint32_t comp_size;
82 uint32_t uncomp_size;
84 uint16_t fname_len;
85 uint16_t extf_len;
87 char *file_name;
90 enum compress_method {
91 COMPRESS_STORED = 0x00, /* no compression at all */
92 COMPRESS_SHRUNK = 0x01,
93 COMPRESS_REDUCE1 = 0x02,
94 COMPRESS_REDUCE2 = 0x03,
95 COMPRESS_REDUCE3 = 0x04,
96 COMPRESS_REDUCE4 = 0x05,
97 COMPRESS_IMPLODE = 0x06,
98 COMPRESS_RESERVED1 = 0x07,
99 COMPRESS_DEFLATE = 0x08,
100 COMPRESS_DEFLATE64 = 0x09,
101 COMPRESS_PKWARE_IMPLODE = 0x0a,
102 COMPRESS_RESERVED2 = 0x0b,
103 COMPRESS_BZIP2 = 0x0c,
106 static const char *compress_method_names[] = {
107 "Stored (no compression)",
108 "Shrunk",
109 "Reduced with factor 1",
110 "Reduced with factor 2",
111 "Reduced with factor 3",
112 "Reduced with factor 4",
113 "Imploded",
114 "Reserved 1",
115 "Deflate",
116 "Deflate64",
117 "PKWARE Implode",
118 "Reserved 2",
119 "BZIP2",
122 static const char *compress_method_name(enum compress_method comp)
124 if (comp > COMPRESS_BZIP2)
125 return "Unknown";
127 return compress_method_names[comp];
130 enum zip_flags {
131 /* File is encrypted */
132 FLAG_ENCRYPTED = 0x0001,
133 /* Size and CRC are in data descriptor after the compressed data */
134 FLAG_DATA_DESC_HEADER = 0x0008,
135 /* Filename and comment fileds are in UTF-8 */
136 FLAG_UTF8 = 0x0800,
139 static void print_flags(struct zip_local_header *header)
141 if (header->flags & FLAG_ENCRYPTED)
142 GP_DEBUG(2, "File is encrypted");
144 if (header->flags & FLAG_DATA_DESC_HEADER)
145 GP_DEBUG(2, "File size and CRC are after compressed data");
147 if (header->flags & FLAG_UTF8)
148 GP_DEBUG(2, "Filename and comment are encoded in UTF-8");
151 static int seek_bytes(GP_IO *io, uint32_t bytes)
153 if (bytes == 0)
154 return 0;
156 GP_DEBUG(4, "Moving forward by %"PRIu32" bytes", bytes);
158 if (GP_IOSeek(io, bytes, GP_IO_SEEK_CUR) == (off_t)-1) {
159 int err = errno;
160 GP_DEBUG(1, "Failed to seek: %s", strerror(errno));
161 return err;
164 return 0;
167 static int zip_load_header(GP_IO *io, struct zip_local_header *header)
169 int ret;
170 uint8_t byte;
172 uint16_t zip_header[] = {
173 'P',
174 'K',
175 GP_IO_BYTE,
176 GP_IO_END
179 if (GP_IOReadF(io, zip_header, &byte) != 3) {
180 GP_DEBUG(1, "Failed to read header");
181 return EIO;
184 switch (byte) {
185 /* Central directory -> end of archive */
186 case 0x01:
187 GP_DEBUG(1, "Reached end of the archive");
188 return EINVAL;
189 break;
190 /* File header */
191 case 0x03:
192 break;
193 default:
194 GP_DEBUG(1, "Unexpected header PK%x", byte);
195 return EIO;
198 uint16_t zip_local_header[] = {
199 0x04,
200 GP_IO_L2, /* version */
201 GP_IO_L2, /* bit flags */
202 GP_IO_L2, /* compression type */
203 GP_IO_IGN | 4,
204 GP_IO_L4, /* CRC */
205 GP_IO_L4, /* compressed size */
206 GP_IO_L4, /* uncompressed size */
207 GP_IO_L2, /* filename length */
208 GP_IO_L2, /* extra fields lenght */
209 GP_IO_END
212 ret = GP_IOReadF(io, zip_local_header,
213 &header->ver, &header->flags, &header->comp_type,
214 &header->crc, &header->comp_size, &header->uncomp_size,
215 &header->fname_len, &header->extf_len);
217 if (ret != 10) {
218 GP_DEBUG(1, "Failed to read header");
219 return EIO;
222 return 0;
225 static int zip_read_data_desc(GP_IO *io, struct zip_local_header *header)
227 uint16_t data_desc_header[] = {
228 'P', 'K', 0x07, 0x08, /* Data desc header */
229 GP_IO_L4, /* CRC */
230 GP_IO_L4, /* Compressed size */
231 GP_IO_L4, /* Uncompressed size */
232 GP_IO_END
235 if (GP_IOReadF(io, data_desc_header, &header->crc,
236 &header->comp_size, &header->uncomp_size) != 7) {
237 GP_DEBUG(1, "Failed to read Data Description Header");
238 return 1;
241 return 0;
244 static int zip_next_file(struct zip_priv *priv, GP_Pixmap **img,
245 GP_DataStorage *storage,
246 GP_ProgressCallback *callback)
248 struct zip_local_header header = {.file_name = NULL};
249 int err = 0, res;
250 GP_Pixmap *ret = NULL;
251 GP_IO *io;
253 if ((err = zip_load_header(priv->io, &header)))
254 goto out;
256 GP_DEBUG(1, "Have ZIP local header version %u.%u compression %s",
257 header.ver/10, header.ver%10,
258 compress_method_name(header.comp_type));
260 print_flags(&header);
262 if (header.flags & FLAG_ENCRYPTED) {
263 GP_DEBUG(1, "Can't handle encrypted files");
264 err = ENOSYS;
265 goto out;
269 * If input was taken from stdin the fname_len is either set to zero or
270 * to one and filename is set to '-'.
272 if (header.fname_len) {
273 header.file_name = malloc(header.fname_len + 1);
275 if (!header.file_name) {
276 err = ENOMEM;
277 goto out;
280 header.file_name[header.fname_len] = '\0';
281 //FILL
282 if (GP_IORead(priv->io, header.file_name, header.fname_len) != header.fname_len) {
283 GP_DEBUG(1, "Failed to read filename");
284 err = EIO;
285 goto out;
288 GP_DEBUG(1, "Filename '%s' compressed size=%"PRIu32
289 " uncompressed size=%"PRIu32,
290 header.file_name, header.comp_size,
291 header.uncomp_size);
294 seek_bytes(priv->io, header.extf_len);
296 switch (header.comp_type) {
297 case COMPRESS_STORED:
298 /* skip directories */
299 if (header.uncomp_size == 0) {
300 GP_DEBUG(2, "Skipping directory");
301 goto out;
304 GP_IOMark(priv->io, GP_IO_MARK);
306 res = GP_ReadImageEx(priv->io, &ret, storage, callback);
307 if (res && errno == ECANCELED)
308 err = errno;
310 GP_IOSeek(priv->io, priv->io->mark + header.comp_size, GP_IO_SEEK_SET);
312 goto out;
313 break;
314 case COMPRESS_DEFLATE:
315 io = GP_IOZlib(priv->io, header.comp_size);
316 if (!io) {
317 err = errno;
318 goto out;
321 GP_DEBUG(1, "Reading image");
322 res = GP_ReadImageEx(io, &ret, storage, callback);
323 if (res && errno == ECANCELED)
324 err = errno;
327 * We need to finish the Zlib IO for any of:
329 * - File is not image -> need to get to the end of the record
330 * - All image data were not consumed by loader (may happen)
332 if (GP_IOSeek(io, 0, GP_IO_SEEK_END) == (off_t)-1)
333 GP_DEBUG(1, "Failed to seek Zlib IO");
335 GP_IOClose(io);
337 if (header.flags & FLAG_DATA_DESC_HEADER) {
338 if (zip_read_data_desc(priv->io, &header))
339 goto out;
342 goto out;
343 break;
344 default:
345 GP_DEBUG(1, "Unimplemented compression %s",
346 compress_method_name(header.comp_type));
347 err = ENOSYS;
348 goto out;
351 out:
352 free(header.file_name);
353 errno = err;
354 *img = ret;
355 return err;
358 static unsigned int last_offset_idx(struct zip_priv *priv)
360 return priv->table_used + priv->tables_used * ZIP_CHUNKS_IN_TABLE;
363 static long last_recorded_offset(struct zip_priv *priv)
365 const unsigned int last_idx = ZIP_CHUNKS_IN_TABLE - 1;
367 if (priv->table_used == 0) {
368 if (priv->last_table->prev)
369 return priv->last_table->prev->offsets[last_idx];
371 return -1;
374 return priv->last_table->offsets[priv->table_used - 1];
377 static void record_offset(struct zip_priv *priv, long offset)
379 if (offset <= last_recorded_offset(priv))
380 return;
382 GP_DEBUG(2, "Recording offset to %i image (%li)",
383 last_offset_idx(priv), offset);
385 if (priv->table_used >= ZIP_CHUNKS_IN_TABLE) {
386 struct zip_chunks_table *new_table;
388 GP_DEBUG(1, "Allocating chunks table (table nr. %u) (size %i)",
389 priv->tables_used+1, ZIP_CHUNKS_IN_TABLE);
391 new_table = malloc(sizeof(struct zip_chunks_table));
393 if (!new_table) {
394 GP_WARN("Malloc failed :(");
395 return;
398 priv->tables_used++;
399 priv->table_used = 0;
400 new_table->prev = priv->last_table;
401 new_table->next = NULL;
402 priv->last_table->next = new_table;
403 priv->last_table = new_table;
406 priv->last_table->offsets[priv->table_used++] = offset;
408 printf("OFFSET table\n");
409 unsigned int i;
410 for (i = 0; i < priv->table_used; i++)
411 printf("** %u -> %li\n", i, priv->last_table->offsets[i]);
415 static int zip_load_next(GP_Container *self, GP_Pixmap **img,
416 GP_DataStorage *storage,
417 GP_ProgressCallback *callback)
419 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
420 int err;
422 GP_DEBUG(1, "Trying to load next image from ZIP container");
424 *img = NULL;
426 do {
427 err = zip_next_file(priv, img, storage, callback);
428 } while (!*img && errno == 0);
430 if (err)
431 return 1;
433 record_offset(priv, GP_IOTell(priv->io));
435 priv->cur_pos++;
436 self->cur_img = priv->cur_pos;
438 return 0;
441 static GP_Pixmap *load_next(GP_Container *self, GP_ProgressCallback *callback)
443 GP_Pixmap *img = NULL;
445 zip_load_next(self, &img, NULL, callback);
447 return img;
450 /* Seek to the current position */
451 static void seek_cur_pos(struct zip_priv *priv)
453 unsigned int cur_table = priv->cur_pos / ZIP_CHUNKS_IN_TABLE;
454 unsigned int cur_pos;
456 if (priv->cur_table_pos != cur_table) {
457 unsigned int i;
459 GP_DEBUG(3, "cur_pos %u out of cur table %u",
460 priv->cur_pos, priv->cur_table_pos);
462 priv->cur_table = &priv->table;
464 for (i = 0; i < cur_table; i++) {
465 if (priv->cur_table->next)
466 priv->cur_table = priv->cur_table->next;
467 else
468 GP_WARN("The cur_pos points after last table");
471 priv->cur_table_pos = cur_table;
474 //TODO: Asert that we are not in last table and cur_pos < table_used
476 cur_pos = priv->cur_pos % ZIP_CHUNKS_IN_TABLE;
478 GP_DEBUG(2, "Setting current position to %u (%li)",
479 priv->cur_pos, priv->cur_table->offsets[cur_pos]);
481 GP_IOSeek(priv->io, priv->cur_table->offsets[cur_pos], GP_IO_SEEK_SET);
484 static int load_next_offset(struct zip_priv *priv)
486 struct zip_local_header header = {.file_name = NULL};
487 int ret;
488 long offset = GP_IOTell(priv->io);
490 if ((ret = zip_load_header(priv->io, &header)))
491 return ret;
493 //TODO: Match image extension and signature
494 record_offset(priv, offset);
496 /* Seek to the next local header */
497 seek_bytes(priv->io, (uint32_t)header.fname_len +
498 (uint32_t)header.extf_len);
499 seek_bytes(priv->io, header.comp_size);
501 return 0;
505 * Sets current position.
507 static int set_cur_pos(struct zip_priv *priv, unsigned int where)
509 unsigned int max = last_offset_idx(priv);
510 int err;
512 GP_DEBUG(2, "where %u max %u", where, max);
514 /* Move to the max and beyond */
515 if (where >= max) {
516 if (max == 0) {
517 if ((err = load_next_offset(priv)))
518 return err;
519 priv->cur_pos = 0;
520 } else {
521 priv->cur_pos = max - 1;
522 seek_cur_pos(priv);
525 while (priv->cur_pos < where) {
526 if ((err = load_next_offset(priv)))
527 return err;
528 priv->cur_pos++;
531 return 0;
534 priv->cur_pos = where;
535 seek_cur_pos(priv);
537 return 0;
540 static int zip_seek(GP_Container *self, int offset,
541 enum GP_ContainerWhence whence)
543 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
544 unsigned int where;
545 int ret;
547 GP_DEBUG(1, "Seek offset=%i whence=%i", offset, whence);
549 switch (whence) {
550 case GP_CONT_CUR:
551 if (offset < 0 && priv->cur_pos < (unsigned int)-offset) {
552 GP_WARN("Current position %u offset %i",
553 priv->cur_pos, offset);
554 where = 0;
555 } else {
556 where = priv->cur_pos + offset;
558 break;
559 case GP_CONT_FIRST:
560 where = offset;
561 break;
562 default:
563 return ENOSYS;
566 ret = set_cur_pos(priv, where);
568 self->cur_img = priv->cur_pos;
570 return ret;
573 static int zip_load(GP_Container *self, GP_Pixmap **img,
574 GP_DataStorage *storage, GP_ProgressCallback *callback)
576 if (zip_load_next(self, img, storage, callback))
577 return 1;
579 zip_seek(self, -1, GP_CONT_CUR);
581 return 0;
584 static void zip_close(GP_Container *self)
586 struct zip_priv *priv = GP_CONTAINER_PRIV(self);
587 struct zip_chunks_table *i, *j;
589 GP_DEBUG(1, "Closing ZIP container");
591 /* Free allocated offset tables */
592 for (i = priv->table.next; i != NULL; j = i, i = i->next, free(j));
594 GP_IOClose(priv->io);
595 free(self);
598 static GP_IO *open_zip(const char *path)
600 GP_IO *io;
601 int err = 0;
603 io = GP_IOFile(path, GP_IO_RDONLY);
605 if (!io) {
606 err = errno;
607 GP_DEBUG(1, "Failed to open '%s': %s", path, strerror(errno));
608 goto err0;
611 /* Check zip local file header and seek back */
612 if (GP_IOMark(io, GP_IO_MARK)) {
613 err = errno;
614 goto err1;
617 static uint16_t zip_header[] = {
618 'P',
619 'K',
620 0x03,
621 0x04,
622 GP_IO_END
625 if (GP_IOReadF(io, zip_header) != 4) {
626 GP_DEBUG(1, "Invalid zip header");
627 err = EINVAL;
628 goto err1;
631 if (GP_IOMark(io, GP_IO_REWIND)) {
632 err = errno;
633 goto err1;
636 return io;
637 err1:
638 GP_IOClose(io);
639 err0:
640 errno = err;
641 return NULL;
644 static const struct GP_ContainerOps zip_ops = {
645 .LoadNext = load_next,
646 .LoadEx = zip_load,
647 .Close = zip_close,
648 .Seek = zip_seek,
649 .type = "ZIP",
652 GP_Container *GP_OpenZip(const char *path)
654 struct zip_priv *priv;
655 GP_Container *ret;
656 GP_IO *io;
657 int err;
659 io = open_zip(path);
661 if (!io)
662 return NULL;
664 ret = malloc(sizeof(GP_Container) + sizeof(struct zip_priv));
666 if (!ret) {
667 err = ENOMEM;
668 goto err0;
671 GP_DEBUG(1, "ZIP Container initialized");
673 ret->img_count = -1;
674 ret->cur_img = 0;
675 ret->ops = &zip_ops;
677 priv = GP_CONTAINER_PRIV(ret);
679 priv->io = io;
681 priv->table.next = NULL;
682 priv->table.prev = NULL;
684 /* Cache for current table for seeks */
685 priv->cur_table = &priv->table;
686 priv->cur_table_pos = 0;
688 /* Current position */
689 priv->cur_pos = 0;
691 /* Last table, used for insertion */
692 priv->tables_used = 0;
693 priv->table_used = 0;
694 priv->last_table = &priv->table;
696 return ret;
697 err0:
698 GP_IOClose(io);
699 errno = err;
700 return NULL;
703 #else
705 GP_Container *GP_OpenZip(const char GP_UNUSED(*path))
707 GP_FATAL("zlib support not compiled in");
708 errno = ENOSYS;
709 return NULL;
712 #endif /* HAVE_ZLIB */
714 int GP_MatchZip(const char *buf)
716 return !memcmp("PK\03\04", buf, 4);