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_Loader.h"
41 #include "loaders/GP_IOZlib.h"
42 #include "loaders/GP_ZIP.h"
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
;
60 /* Current position in zip continer counted in images we found */
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
{
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)",
109 "Reduced with factor 1",
110 "Reduced with factor 2",
111 "Reduced with factor 3",
112 "Reduced with factor 4",
122 static const char *compress_method_name(enum compress_method comp
)
124 if (comp
> COMPRESS_BZIP2
)
127 return compress_method_names
[comp
];
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 */
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
)
156 GP_DEBUG(4, "Moving forward by %"PRIu32
" bytes", bytes
);
158 if (GP_IOSeek(io
, bytes
, GP_IO_SEEK_CUR
) == (off_t
)-1) {
160 GP_DEBUG(1, "Failed to seek: %s", strerror(errno
));
167 static int zip_load_header(GP_IO
*io
, struct zip_local_header
*header
)
172 uint16_t zip_header
[] = {
179 if (GP_IOReadF(io
, zip_header
, &byte
) != 3) {
180 GP_DEBUG(1, "Failed to read header");
185 /* Central directory -> end of archive */
187 GP_DEBUG(1, "Reached end of the archive");
194 GP_DEBUG(1, "Unexpected header PK%x", byte
);
198 uint16_t zip_local_header
[] = {
200 GP_IO_L2
, /* version */
201 GP_IO_L2
, /* bit flags */
202 GP_IO_L2
, /* compression type */
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 */
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
);
218 GP_DEBUG(1, "Failed to read header");
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 */
230 GP_IO_L4
, /* Compressed size */
231 GP_IO_L4
, /* Uncompressed size */
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");
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
};
250 GP_Pixmap
*ret
= NULL
;
253 if ((err
= zip_load_header(priv
->io
, &header
)))
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");
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
) {
280 header
.file_name
[header
.fname_len
] = '\0';
282 if (GP_IORead(priv
->io
, header
.file_name
, header
.fname_len
) != header
.fname_len
) {
283 GP_DEBUG(1, "Failed to read filename");
288 GP_DEBUG(1, "Filename '%s' compressed size=%"PRIu32
289 " uncompressed size=%"PRIu32
,
290 header
.file_name
, header
.comp_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");
304 GP_IOMark(priv
->io
, GP_IO_MARK
);
306 res
= GP_ReadImageEx(priv
->io
, &ret
, storage
, callback
);
307 if (res
&& errno
== ECANCELED
)
310 GP_IOSeek(priv
->io
, priv
->io
->mark
+ header
.comp_size
, GP_IO_SEEK_SET
);
314 case COMPRESS_DEFLATE
:
315 io
= GP_IOZlib(priv
->io
, header
.comp_size
);
321 GP_DEBUG(1, "Reading image");
322 res
= GP_ReadImageEx(io
, &ret
, storage
, callback
);
323 if (res
&& errno
== ECANCELED
)
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");
337 if (header
.flags
& FLAG_DATA_DESC_HEADER
) {
338 if (zip_read_data_desc(priv
->io
, &header
))
345 GP_DEBUG(1, "Unimplemented compression %s",
346 compress_method_name(header
.comp_type
));
352 free(header
.file_name
);
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
];
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
))
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
));
394 GP_WARN("Malloc failed :(");
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");
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
);
422 GP_DEBUG(1, "Trying to load next image from ZIP container");
427 err
= zip_next_file(priv
, img
, storage
, callback
);
428 } while (!*img
&& errno
== 0);
433 record_offset(priv
, GP_IOTell(priv
->io
));
436 self
->cur_img
= priv
->cur_pos
;
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
);
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
) {
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
;
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
};
488 long offset
= GP_IOTell(priv
->io
);
490 if ((ret
= zip_load_header(priv
->io
, &header
)))
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
);
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
);
512 GP_DEBUG(2, "where %u max %u", where
, max
);
514 /* Move to the max and beyond */
517 if ((err
= load_next_offset(priv
)))
521 priv
->cur_pos
= max
- 1;
525 while (priv
->cur_pos
< where
) {
526 if ((err
= load_next_offset(priv
)))
534 priv
->cur_pos
= where
;
540 static int zip_seek(GP_Container
*self
, int offset
,
541 enum GP_ContainerWhence whence
)
543 struct zip_priv
*priv
= GP_CONTAINER_PRIV(self
);
547 GP_DEBUG(1, "Seek offset=%i whence=%i", offset
, whence
);
551 if (offset
< 0 && priv
->cur_pos
< (unsigned int)-offset
) {
552 GP_WARN("Current position %u offset %i",
553 priv
->cur_pos
, offset
);
556 where
= priv
->cur_pos
+ offset
;
566 ret
= set_cur_pos(priv
, where
);
568 self
->cur_img
= priv
->cur_pos
;
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
))
579 zip_seek(self
, -1, GP_CONT_CUR
);
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
);
598 static GP_IO
*open_zip(const char *path
)
603 io
= GP_IOFile(path
, GP_IO_RDONLY
);
607 GP_DEBUG(1, "Failed to open '%s': %s", path
, strerror(errno
));
611 /* Check zip local file header and seek back */
612 if (GP_IOMark(io
, GP_IO_MARK
)) {
617 static uint16_t zip_header
[] = {
625 if (GP_IOReadF(io
, zip_header
) != 4) {
626 GP_DEBUG(1, "Invalid zip header");
631 if (GP_IOMark(io
, GP_IO_REWIND
)) {
644 static const struct GP_ContainerOps zip_ops
= {
645 .LoadNext
= load_next
,
652 GP_Container
*GP_OpenZip(const char *path
)
654 struct zip_priv
*priv
;
664 ret
= malloc(sizeof(GP_Container
) + sizeof(struct zip_priv
));
671 GP_DEBUG(1, "ZIP Container initialized");
677 priv
= GP_CONTAINER_PRIV(ret
);
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 */
691 /* Last table, used for insertion */
692 priv
->tables_used
= 0;
693 priv
->table_used
= 0;
694 priv
->last_table
= &priv
->table
;
705 GP_Container
*GP_OpenZip(const char GP_UNUSED(*path
))
707 GP_FATAL("zlib support not compiled in");
712 #endif /* HAVE_ZLIB */
714 int GP_MatchZip(const char *buf
)
716 return !memcmp("PK\03\04", buf
, 4);