1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
29 #include "../../config.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"
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
;
61 /* Current position in zip continer counted in images we found */
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
{
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)",
112 "Reduced with factor 1",
113 "Reduced with factor 2",
114 "Reduced with factor 3",
115 "Reduced with factor 4",
125 static const char *compress_method_name(enum compress_method comp
)
127 if (comp
> COMPRESS_BZIP2
)
130 return compress_method_names
[comp
];
133 static int seek_bytes(GP_IO
*io
, uint32_t bytes
)
138 GP_DEBUG(4, "Moving forward by %"PRIu32
" bytes", bytes
);
140 if (GP_IOSeek(io
, bytes
, GP_IO_SEEK_CUR
) == (off_t
)-1) {
142 GP_DEBUG(1, "Failed to seek: %s", strerror(errno
));
151 struct deflate_inbuf
{
152 struct zip_local_header
*zip_header
;
154 unsigned char buf
[CHUNK
];
158 struct deflate_outbuf
{
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
)
173 in
->to_read
-= 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
);
189 static int read_deflate(GP_IO
*io
, struct zip_local_header
*header
, GP_IO
**rio
)
195 window
= malloc(32 * 1024);
197 buf
= malloc(header
->uncomp_size
);
199 if (!window
|| !buf
) {
211 if (inflateBackInit(&strm
, 15, window
) != Z_OK
) {
212 GP_DEBUG(1, "Failed to initialize inflate stream");
213 //TODO: Better errno?
218 struct deflate_outbuf outbuf
= {
219 .crc
= crc32(0, NULL
, 0),
224 struct deflate_inbuf inbuf
= {
225 .zip_header
= header
,
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
);
238 if (outbuf
.crc
!= header
->crc
) {
239 GP_DEBUG(1, "CRC does not match");
244 if (outbuf
.size
!= header
->uncomp_size
) {
245 GP_DEBUG(1, "Decompressed size does not match");
250 inflateBackEnd(&strm
);
254 *rio
= GP_IOMem(outbuf
.buf
, outbuf
.size
, free
);
257 inflateBackEnd(&strm
);
264 static int zip_load_header(GP_IO
*io
, struct zip_local_header
*header
)
269 uint16_t zip_header
[] = {
276 if (GP_IOReadF(io
, zip_header
, &byte
) != 3) {
277 GP_DEBUG(1, "Failed to read header");
282 /* Central directory -> end of archive */
284 GP_DEBUG(1, "Reached end of the archive");
291 GP_DEBUG(1, "Unexpected header PK%x", byte
);
295 uint16_t zip_local_header
[] = {
297 GP_IO_L2
, /* version */
298 GP_IO_L2
, /* bit flags */
299 GP_IO_L2
, /* compression type */
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 */
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
);
315 GP_DEBUG(1, "Failed to read header");
322 static GP_Context
*zip_next_file(struct zip_priv
*priv
,
323 GP_ProgressCallback
*callback
)
325 struct zip_local_header header
= {.file_name
= NULL
};
327 GP_Context
*ret
= NULL
;
330 if ((err
= zip_load_header(priv
->io
, &header
)))
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");
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
) {
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");
363 GP_DEBUG(1, "Filename '%s' compressed size=%"PRIu32
364 " uncompressed size=%"PRIu32
,
365 header
.file_name
, header
.comp_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");
379 GP_IOMark(priv
->io
, GP_IO_MARK
);
381 ret
= GP_ReadImage(priv
->io
, callback
);
382 if (errno
== ECANCELED
)
385 GP_IOSeek(priv
->io
, priv
->io
->mark
+ header
.comp_size
, GP_IO_SEEK_SET
);
389 case COMPRESS_DEFLATE
:
390 if ((err
= read_deflate(priv
->io
, &header
, &io
))) {
394 GP_DEBUG(1, "Reading image");
395 ret
= GP_ReadImage(io
, callback
);
396 if (errno
== ECANCELED
)
403 GP_DEBUG(1, "Unimplemented compression %s",
404 compress_method_name(header
.comp_type
));
410 free(header
.file_name
);
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
];
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
))
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
));
451 GP_WARN("Malloc failed :(");
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");
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
);
479 GP_DEBUG(1, "Trying to load next image from ZIP container");
482 offset
= GP_IOTell(priv
->io
);
483 ret
= zip_next_file(priv
, callback
);
484 } while (ret
== NULL
&& errno
== 0);
490 record_offset(priv
, offset
);
494 self
->cur_img
= priv
->cur_pos
;
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
) {
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
;
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
};
537 long offset
= GP_IOTell(priv
->io
);
539 if ((ret
= zip_load_header(priv
->io
, &header
)))
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
);
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
);
561 GP_DEBUG(2, "where %u max %u", where
, max
);
563 /* Move to the max and beyond */
566 if ((err
= load_next_offset(priv
)))
570 priv
->cur_pos
= max
- 1;
574 while (priv
->cur_pos
< where
) {
575 if ((err
= load_next_offset(priv
)))
583 priv
->cur_pos
= where
;
589 static int zip_seek(GP_Container
*self
, int offset
,
590 enum GP_ContainerWhence whence
)
592 struct zip_priv
*priv
= GP_CONTAINER_PRIV(self
);
596 GP_DEBUG(1, "Seek offset=%i whence=%i", offset
, whence
);
600 if (offset
< 0 && priv
->cur_pos
< (unsigned int)-offset
) {
601 GP_WARN("Current position %u offset %i",
602 priv
->cur_pos
, offset
);
605 where
= priv
->cur_pos
+ offset
;
615 ret
= set_cur_pos(priv
, where
);
617 self
->cur_img
= priv
->cur_pos
;
622 static GP_Context
*zip_load(GP_Container
*self
,
623 GP_ProgressCallback
*callback
)
627 img
= zip_load_next(self
, callback
);
632 zip_seek(self
, -1, GP_CONT_CUR
);
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
);
651 static GP_IO
*open_zip(const char *path
)
656 io
= GP_IOFile(path
, GP_IO_RDONLY
);
660 GP_DEBUG(1, "Failed to open '%s': %s", path
, strerror(errno
));
664 /* Check zip local file header and seek back */
665 if (GP_IOMark(io
, GP_IO_MARK
)) {
670 static uint16_t zip_header
[] = {
678 if (GP_IOReadF(io
, zip_header
) != 4) {
679 GP_DEBUG(1, "Invalid zip header");
684 if (GP_IOMark(io
, GP_IO_REWIND
)) {
697 static const struct GP_ContainerOps zip_ops
= {
698 .LoadNext
= zip_load_next
,
705 GP_Container
*GP_OpenZip(const char *path
)
707 struct zip_priv
*priv
;
717 ret
= malloc(sizeof(GP_Container
) + sizeof(struct zip_priv
));
724 GP_DEBUG(1, "ZIP Container initialized");
730 priv
= GP_CONTAINER_PRIV(ret
);
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 */
744 /* Last table, used for insertion */
745 priv
->tables_used
= 0;
746 priv
->table_used
= 0;
747 priv
->last_table
= &priv
->table
;
758 GP_Container
*GP_OpenZip(const char GP_UNUSED(*path
))
760 GP_FATAL("zlib support not compiled in");
765 #endif /* HAVE_ZLIB */
767 int GP_MatchZip(const char *buf
)
769 return !memcmp("PK\03\04", buf
, 4);