]> granicus.if.org Git - zziplib/blob - zzip/fseeko.c
internal
[zziplib] / zzip / fseeko.c
1 /*
2  * NOTE: this is part of libzzipfseeko (i.e. it is not libzzip).
3  *
4  * These routines are fully independent from the traditional zzip
5  * implementation. They assume a readonly seekable stdio handle
6  * representing a complete zip file. The functions show how to 
7  * parse the structure, find files and return a decoded bytestream.
8  *
9  * These routines are a bit simple and really here for documenting
10  * the way to access a zip file. The complexity of zip access comes
11  * from staggered reading of bytes and reposition of a filepointer in
12  * a big archive with lots of files and long compressed datastreams.
13  * Plus varaints of drop-in stdio replacements, obfuscation routines,
14  * auto fileextensions, drop-in dirent replacements, and so on...
15  *
16  * btw, we can _not_ use fgetpos/fsetpos since an fpos_t has no asserted
17  * relation to a linear seek value as specified in zip info headers. In
18  * general it is not a problem if your system has no fseeko/ftello pair
19  * since we can fallback to fseek/ftell which limits the zip disk size
20  * to 2MiBs but the zip-storable seek values are 32bit limited anyway.
21  *
22  * Author: 
23  *      Guido Draheim <guidod@gmx.de>
24  *
25  * Copyright (c) 2003,2004 Guido Draheim
26  *          All rights reserved,
27  *          use under the restrictions of the 
28  *          Lesser GNU General Public License
29  *          or alternatively the restrictions 
30  *          of the Mozilla Public License 1.1
31  */
32
33 #define _LARGEFILE_SOURCE 1
34 #define _ZZIP_ENTRY_STRUCT 1
35
36 #include <zzip/types.h>
37
38 #include <assert.h>
39 #include <stdlib.h>
40 #include <sys/stat.h>
41
42 #if   defined ZZIP_HAVE_STRING_H
43 #include <string.h>
44 #elif defined ZZIP_HAVE_STRINGS_H
45 #include <strings.h>
46 #endif
47
48 #include <zlib.h>
49 #include <zzip/format.h>
50 #include <zzip/fseeko.h>
51 #include <zzip/fetch.h>
52 #include <zzip/__mmap.h>
53 #include <zzip/__fnmatch.h>
54
55 #if __STDC_VERSION__+0 > 199900L
56 #define ___
57 #define ____
58 #else
59 #define ___ {
60 #define ____ }
61 #endif
62
63 #ifndef ZZIP_HAVE_FSEEKO
64 #define fseeko fseek
65 #define ftello ftello
66 #endif
67
68 /* note that the struct zzip_entry inherits the zzip_disk_entry values
69  * and usually carries a copy of its values (in disk format!). To make the
70  * following code more readable, we use a shorthand notation for the
71  * upcast needed in C (not needed in C++) as "disk_(entry)".
72  */
73 #ifdef __zzip_entry_extends_zzip_disk_entry
74 #define disk_(_entry_) _entry_
75 #else
76 #define disk_(_entry_) (& (_entry_)->head)
77 #endif
78
79 /* we try to round all seeks to the pagesize - since we do not use
80  * the sys/mmap interface we have to guess a good value here: */
81 #define PAGESIZE 8192
82
83 /* ====================================================================== */
84 /*                      helper functions                                  */
85
86 /** => zzip_entry_data_offset
87  * This functions read the correspoding struct zzip_file_header from 
88  * the zip disk of the given "entry". The returned off_t points to the
89  * end of the file_header where the current fseek pointer has stopped.
90  * This is used to immediatly parse out any filename/extras block following
91  * the file_header. The return value is null on error.
92  */
93 static zzip_off_t
94 zzip_entry_fread_file_header (ZZIP_ENTRY* entry, 
95                              struct zzip_file_header* file_header)
96 {
97     if (! entry || ! file_header) return 0;
98     ___ zzip_off_t offset = zzip_disk_entry_fileoffset (disk_(entry));
99     if (0 > offset || offset >= entry->disksize) return 0;
100
101     fseeko (entry->diskfile, offset, SEEK_SET);
102     return (fread (file_header, sizeof(*file_header), 1, entry->diskfile)
103             ? offset+sizeof(*file_header) : 0 ); ____;
104 }
105
106 /** helper functions for (fseeko) zip access api
107  *
108  * This functions returns the seekval offset of the data portion of the
109  * file referenced by the given zzip_entry. It requires an intermediate
110  * check of the file_header structure (i.e. it reads it from disk). After 
111  * this call, the contained diskfile readposition is already set to the 
112  * data_offset returned here. On error -1 is returned.
113  */
114 zzip_off_t
115 zzip_entry_data_offset(ZZIP_ENTRY* entry)
116 {
117     struct zzip_file_header file_header;
118     if (! entry) return -1;
119     ___ zzip_off_t offset = 
120         zzip_entry_fread_file_header (entry, & file_header);
121     if (! offset) return -1;
122     offset += zzip_file_header_sizeof_tails (& file_header);
123     fseeko (entry->diskfile, offset, SEEK_SET);
124     return offset; ____;
125 }
126
127 /** => zzip_entry_data_offset
128  * This function is a big helper despite its little name: in a zip file the
129  * encoded filenames are usually NOT zero-terminated but for common usage
130  * with libc we need it that way. Secondly, the filename SHOULD be present
131  * in the zip central directory but if not then we fallback to the filename
132  * given in the file_header of each compressed data portion.
133  */
134 char* _zzip_new
135 zzip_entry_strdup_name(ZZIP_ENTRY* entry)
136 {
137     if (! entry) return 0;
138
139     ___ zzip_size_t len;
140     if ((len = zzip_disk_entry_namlen (disk_(entry)))) {
141         char* name = malloc (len+1);
142         if (! name) return 0;
143         memcpy (name, entry->tail, len);
144         name[len] = '\0';
145         return name;
146     }
147     ___ auto struct zzip_file_header header;
148     if (zzip_entry_fread_file_header (entry, &header) 
149         && ( len = zzip_file_header_namlen(&header) )) {
150         char* name = malloc (len+1);
151         if (! name) return 0;
152         fread (name, 1, len, entry->diskfile);
153         name[len] = '\0';
154         return name;
155     }
156     return 0;
157     ____;____;
158 }
159
160 static int
161 prescan_entry(ZZIP_ENTRY* entry) 
162 {
163     assert (entry);
164     ___ zzip_off_t tailsize = zzip_disk_entry_sizeof_tails (disk_(entry));
165     if (tailsize+1 > entry->tailalloc) {
166         char* newtail = realloc (entry->tail, tailsize+1);
167         if (! newtail) return ENOMEM;
168         entry->tail = newtail;
169         entry->tailalloc = tailsize+1;
170     }
171     fread (entry->tail, 1, tailsize, entry->diskfile);
172     /* name + comment + extras */
173     return 0; ____;
174 }
175
176 static void
177 prescan_clear(ZZIP_ENTRY* entry)
178 {
179     assert (entry);
180     if (entry->tail) free (entry->tail);
181     entry->tail = 0; entry->tailalloc = 0;
182 }
183
184 /* ====================================================================== */
185
186 /** => zzip_entry_findfile
187  *
188  * This function is the first call of all the zip access functions here.
189  * It contains the code to find the first entry of the zip central directory. 
190  * Here we require the stdio handle to represent a real zip file where the
191  * disk_trailer is _last_ in the file area, so that its position would be at 
192  * a fixed offset from the end of the file area if not for the comment field 
193  * allowed to be of variable length (which needs us to do a little search
194  * for the disk_tailer). However, in this simple implementation we disregard
195  * any disk_trailer info telling about multidisk archives, so we just return
196  * a pointer to the first entry in the zip central directory of that file.
197  * 
198  * For an actual means, we are going to search backwards from the end 
199  * of the mmaped block looking for the PK-magic signature of a 
200  * disk_trailer. If we see one then we check the rootseek value to
201  * find the first disk_entry of the root central directory. If we find
202  * the correct PK-magic signature of a disk_entry over there then we 
203  * assume we are done and we are going to return a pointer to that label.
204  *
205  * The return value is a pointer to the first zzip_disk_entry being checked
206  * to be within the bounds of the file area specified by the arguments. If
207  * no disk_trailer was found then null is returned, and likewise we only 
208  * accept a disk_trailer with a seekvalue that points to a disk_entry and 
209  * both parts have valid PK-magic parts. Beyond some sanity check we try to
210  * catch a common brokeness with zip archives that still allows us to find
211  * the start of the zip central directory.
212  */
213 ZZIP_ENTRY* _zzip_new
214 zzip_entry_findfirst(FILE* disk)
215 {
216     if (! disk) return 0;
217     fseeko (disk, 0, SEEK_END);
218     ___ zzip_off_t disksize = ftello (disk);
219     if (disksize < (zzip_off_t) sizeof(struct zzip_disk_trailer)) return 0;
220     /* we read out chunks of 8 KiB in the hope to match disk granularity */
221     ___ zzip_off_t pagesize = PAGESIZE; /* getpagesize() */
222     ___ ZZIP_ENTRY* entry = malloc (sizeof(*entry));  if (! entry) return 0;
223     ___ char* buffer = malloc (pagesize);             if (! buffer) goto nomem;
224
225     assert (pagesize/2 > (zzip_off_t) sizeof (struct zzip_disk_trailer));
226     /* at each step, we will fread a pagesize block which overlaps with the
227      * previous read by means of pagesize/2 step at the end of the while(1) */
228     ___ zzip_off_t mapoffs = disksize &~ (pagesize-1);
229     ___ zzip_off_t mapsize = disksize - mapoffs;
230     if (mapoffs && mapsize < pagesize/2) { 
231         mapoffs -= pagesize/2; mapsize += pagesize/2; }
232     while(1) {
233         fseeko (disk, mapoffs, SEEK_SET);
234         fread (buffer, 1, mapsize, disk);
235         ___ char* p = buffer + mapsize - sizeof(struct zzip_disk_trailer);
236         for (; p >= buffer ; p--)
237         {
238             zzip_off_t root;  /* (struct zzip_disk_entry*) */
239             if (zzip_disk_trailer_check_magic(p)) {
240                 root = zzip_disk_trailer_rootseek (
241                     (struct zzip_disk_trailer*)p);
242                 if (root > disksize - (long)sizeof(struct zzip_disk_trailer)) {
243                     /* first disk_entry is after the disk_trailer? can't be! */
244                     zzip_off_t rootsize = zzip_disk_trailer_rootsize (
245                         (struct zzip_disk_trailer*)p);
246                     if (rootsize > mapoffs) continue;
247                     /* a common brokeness that can be fixed: we just assume the
248                      * central directory was written directly before : */
249                     root = mapoffs - rootsize;
250                 }
251             } else if (zzip_disk64_trailer_check_magic(p)) {
252                 if (sizeof(zzip_off_t) < 8) return 0;
253                 root = zzip_disk64_trailer_rootseek (
254                     (struct zzip_disk64_trailer*)p);
255             } else continue;
256
257             assert (0 <= root && root < mapsize);
258             fseeko (disk, root, SEEK_SET);
259             fread (disk_(entry), 1, sizeof(*disk_(entry)), disk);
260             if (zzip_disk_entry_check_magic(entry)) {
261                 free (buffer);
262                 entry->headseek = root;
263                 entry->diskfile = disk;
264                 entry->disksize = disksize;
265                 if (prescan_entry(entry)) goto nomem;
266                 return entry;
267             }
268         } ____;
269         if (! mapoffs) break;             assert (mapsize >= pagesize/2);
270         mapoffs -= pagesize/2;            /* mapsize += pagesize/2; */
271         mapsize = pagesize;               /* if (mapsize > pagesize) ... */
272         if (disksize - mapoffs > 64*1024) break;
273     }
274     free (buffer);
275  nomem:
276     free (entry);                         ____;____;____;____;____;____;
277     return 0;
278 }
279
280 /** => zzip_entry_findfile
281  *
282  * This function takes an existing "entry" in the central root directory
283  * (e.g. from zzip_entry_findfirst) and moves it to point to the next entry.
284  * On error it returns 0, otherwise the old entry. If no further match is
285  * found then null is returned and the entry already free()d. If you want
286  * to stop searching for matches before that case then please call 
287  * => zzip_entry_free on the cursor struct ZZIP_ENTRY.
288  */
289 ZZIP_ENTRY* _zzip_new
290 zzip_entry_findnext(ZZIP_ENTRY* _zzip_restrict entry)
291 {
292     if (! entry) return entry;
293     if (! zzip_disk_entry_check_magic (entry)) goto err;
294     ___ zzip_off_t seek = 
295         entry->headseek + zzip_disk_entry_sizeto_end (disk_(entry));
296     if (seek + (zzip_off_t) sizeof(*disk_(entry)) > entry->disksize) goto err;
297
298     fseeko (entry->diskfile, seek, SEEK_SET);
299     fread (disk_(entry), 1, sizeof(*disk_(entry)), entry->diskfile);
300     entry->headseek = seek;
301     if (! zzip_disk_entry_check_magic (entry)) goto err;
302     if (prescan_entry(entry)) goto err;
303     return entry;
304  err:
305     zzip_entry_free (entry);
306     return 0; ____;
307 }
308
309 /** => zzip_entry_findfile
310  * this function releases the malloc()ed areas needed for zzip_entry, the
311  * pointer is invalid afterwards. This function has #define synonyms of
312  * zzip_entry_findlast(), zzip_entry_findlastfile(), zzip_entry_findlastmatch()
313  */
314 int
315 zzip_entry_free(ZZIP_ENTRY* entry)
316 {
317     if (! entry) return 0;
318     prescan_clear (entry);
319     free (entry);
320     return 1;
321 }
322
323 /** search for files in the (fseeko) zip central directory
324  *
325  * This function is given a filename as an additional argument, to find the 
326  * disk_entry matching a given filename. The compare-function is usually 
327  * strcmp or strcasecmp or perhaps strcoll, if null then strcmp is used. 
328  * - use null as argument for "old"-entry when searching the first 
329  * matching entry, otherwise the last returned value if you look for other
330  * entries with a special "compare" function (if null then a doubled search
331  * is rather useless with this variant of _findfile). If no further entry is
332  * found then null is returned and any "old"-entry gets already free()d.
333  */
334 ZZIP_ENTRY* _zzip_new
335 zzip_entry_findfile(FILE* disk, char* filename, 
336                     ZZIP_ENTRY* _zzip_restrict entry, 
337                     zzip_strcmp_fn_t compare)
338 {
339     if (! filename || ! disk) return 0;
340     entry = ( ! entry ) ? zzip_entry_findfirst (disk) 
341         : zzip_entry_findnext (entry);
342     if (! compare) compare = (zzip_strcmp_fn_t)(strcmp);
343
344     for (; entry ; entry = zzip_entry_findnext (entry))
345     {   /* filenames within zip files are often not null-terminated! */
346         char* realname = zzip_entry_strdup_name (entry);
347         if (! realname) continue;
348         if (! compare (filename, realname)) {
349             free (realname);    return entry;
350         } else {
351             free (realname);    continue;
352         }
353     }
354     return 0;
355 }
356
357 #ifdef ZZIP_HAVE_FNMATCH_H
358 #define _zzip_fnmatch fnmatch
359 # ifdef FNM_CASEFOLD
360 # define _zzip_fnmatch_CASEFOLD FNM_CASEFOLD
361 # else
362 # define _zzip_fnmatch_CASEFOLD 0
363 # endif
364 #else
365 # define _zzip_fnmatch_CASEFOLD 0
366 /* if your system does not have fnmatch, we fall back to strcmp: */
367 static int _zzip_fnmatch(char* pattern, char* string, int flags)
368
369     puts ("<zzip:strcmp>");
370     return strcmp (pattern, string); 
371 }
372 #endif
373
374 /** => zzip_entry_findfile
375  *
376  * This function uses a compare-function with an additional argument
377  * and it is called just like fnmatch(3) from POSIX.2 AD:1993), i.e.
378  * the argument filespec first and the ziplocal filename second with
379  * the integer-flags put in as third to the indirect call. If the
380  * platform has fnmatch available then null-compare will use that one
381  * and otherwise we fall back to mere strcmp, so if you need fnmatch
382  * searching then please provide an implementation somewhere else.
383  * - use null as argument for "after"-entry when searching the first 
384  * matching entry, or the last disk_entry return-value to find the
385  * next entry matching the given filespec. If no further entry is
386  * found then null is returned and any "old"-entry gets already free()d.
387  */
388 ZZIP_ENTRY* _zzip_new
389 zzip_entry_findmatch(FILE* disk, char* filespec, 
390                      ZZIP_ENTRY* _zzip_restrict entry,
391                      zzip_fnmatch_fn_t compare, int flags)
392 {
393     if (! filespec || ! disk) return 0;
394     entry = ( ! entry ) ? zzip_entry_findfirst (disk) 
395         : zzip_entry_findnext (entry);
396     if (! compare) compare = (zzip_fnmatch_fn_t) _zzip_fnmatch; 
397
398     for (; entry ; entry = zzip_entry_findnext (entry))
399     {   /* filenames within zip files are often not null-terminated! */
400         char* realname = zzip_entry_strdup_name (entry);
401         if (! realname) continue;
402         if (! compare (filespec, realname, flags)) {
403             free (realname);    return entry;
404         } else {
405             free (realname);    continue;
406         }
407     }
408     return 0;
409 }
410
411 /* ====================================================================== */
412
413 /**
414  * typedef struct zzip_disk_file ZZIP_ENTRY_FILE;
415  */
416 struct zzip_entry_file /* : zzip_file_header */
417 {
418     struct zzip_file_header header;    /* fopen detected header */
419     ZZIP_ENTRY* entry;                 /* fopen entry */
420     zzip_off_t  data;                  /* for stored blocks */
421     zzip_size_t avail;                 /* memorized for checks on EOF */
422     zzip_size_t compressed;            /* compressed flag and datasize */
423     zzip_size_t dataoff;               /* offset from data start */
424     z_stream zlib;                     /* for inflated blocks */
425     char     buffer[PAGESIZE];         /* work buffer for inflate algorithm */
426 };
427
428 /** open a file within a zip disk for reading
429  *
430  * This function does take an "entry" argument and copies it (or just takes
431  * it over as owner) to a new ZZIP_ENTRY_FILE handle structure. That
432  * structure contains also a zlib buffer for decoding. This function does
433  * seek to the file_header of the given "entry" and validates it for the
434  * data buffer following it. We do also prefetch some data from the data
435  * buffer thereby trying to match the disk pagesize for faster access later.
436  * The => zzip_entry_fread will then read in chunks of pagesizes which is
437  * the size of the internal readahead buffer. If an error occurs then null
438  * is returned.
439  */
440 ZZIP_ENTRY_FILE* _zzip_new
441 zzip_entry_fopen (ZZIP_ENTRY* entry, int takeover)
442 {
443     if (! entry) return 0;
444     if (! takeover) {
445         ZZIP_ENTRY* found = malloc (sizeof(*entry));
446         if (! found) return 0;
447         memcpy (found, entry, sizeof(*entry));   /* prescan_copy */
448         found->tail = malloc (found->tailalloc);
449         if (! found->tail) { free (found); return 0; }
450         memcpy (found->tail, entry->tail, entry->tailalloc);
451         entry = found;
452     }
453     ___ ZZIP_ENTRY_FILE* file = malloc(sizeof(*file));
454     if (! file) goto fail1;
455     file->entry = entry;
456     if (! zzip_entry_fread_file_header (entry, &file->header))
457         goto fail2;
458     file->avail = zzip_file_header_usize (&file->header);
459     file->data = zzip_entry_data_offset (entry);
460     file->dataoff = 0;
461
462     if (! file->avail || zzip_file_header_data_stored (&file->header))
463     { file->compressed = 0; return file; }
464
465     file->compressed = zzip_file_header_csize (&file->header);
466     file->zlib.opaque = 0;
467     file->zlib.zalloc = Z_NULL;
468     file->zlib.zfree = Z_NULL;
469
470     ___ zzip_off_t seek = file->data;
471     seek += sizeof(file->buffer); seek -= seek & (sizeof(file->buffer)-1);
472     assert (file->data < seek); /* pre-read to next PAGESIZE boundary... */
473     fseeko (file->entry->diskfile, file->data + file->dataoff, SEEK_SET);
474     file->zlib.next_in = file->buffer;
475     file->zlib.avail_in = fread (file->buffer, 1, seek - file->data,
476                                  file->entry->diskfile);
477     file->dataoff += file->zlib.avail_in; ____;
478
479     if (! zzip_file_header_data_deflated (&file->header) 
480         || inflateInit2 (& file->zlib, -MAX_WBITS) != Z_OK) goto fail2;
481
482     return file;
483  fail2:
484     free (file);
485  fail1:
486     zzip_entry_free (entry);
487     return 0; ____;
488 }
489
490 /** => zzip_entry_fopen
491  *
492  * This function opens a file found by name, so it does a search into
493  * the zip central directory with => zzip_entry_findfile and whatever
494  * is found first is given to => zzip_entry_fopen
495  */
496 ZZIP_ENTRY_FILE* _zzip_new
497 zzip_entry_ffile (FILE* disk, char* filename)
498 {
499     ZZIP_ENTRY* entry = zzip_entry_findfile (disk, filename, 0, 0);
500     if (! entry) return 0;
501     return zzip_entry_fopen (entry, 1);
502 }
503
504
505 /** => zzip_entry_fopen
506  *
507  * This function reads more bytes into the output buffer specified as
508  * arguments. The return value is null on eof or error, the stdio-like
509  * interface can not distinguish between these so you need to check
510  * with => zzip_entry_feof for the difference.
511  */
512 zzip_size_t
513 zzip_entry_fread (void* ptr, zzip_size_t sized, zzip_size_t nmemb,
514                   ZZIP_ENTRY_FILE* file)
515 {
516     if (! file) return 0;
517     ___ zzip_size_t size = sized*nmemb;
518     if (! file->compressed) {
519         if (size > file->avail) size = file->avail;
520         fread (ptr, 1, size, file->entry->diskfile);
521         file->dataoff += size;
522         file->avail -= size;
523         return size;
524     }
525     
526     file->zlib.avail_out = size;
527     file->zlib.next_out = ptr;
528     ___ zzip_size_t total_old = file->zlib.total_out;
529     while (1) {
530         if (! file->zlib.avail_in) {   
531             size = file->compressed - file->dataoff;
532             if (size > sizeof(file->buffer)) size = sizeof(file->buffer);
533             /* fseek (file->data + file->dataoff, file->entry->diskfile); */
534             file->zlib.avail_in = fread (file->buffer, 1, size,
535                                          file->entry->diskfile);
536             file->zlib.next_in = file->buffer;
537             file->dataoff += file->zlib.avail_in;
538         }
539         if (! file->zlib.avail_in) return 0;
540         
541         ___ int err = inflate (& file->zlib, Z_NO_FLUSH);
542         if (err == Z_STREAM_END)
543             file->avail = 0;
544         else if (err == Z_OK)
545             file->avail -= file->zlib.total_out - total_old;
546         else
547             return 0;
548         ____;
549         if (file->zlib.avail_out && ! file->zlib.avail_in) continue;
550         return file->zlib.total_out - total_old;
551     }____;____;
552 }
553
554 /** => zzip_entry_fopen
555  * This function releases any zlib decoder info needed for decompression
556  * and dumps the ZZIP_ENTRY_FILE struct then.
557  */
558 int
559 zzip_entry_fclose (ZZIP_ENTRY_FILE* file)
560 {
561     if (! file) return 0;
562     if (file->compressed)
563         inflateEnd (& file->zlib);
564     zzip_entry_free (file->entry);
565     free (file);
566     return 0;
567 }
568
569 /** => zzip_entry_fopen
570  *
571  * This function allows to distinguish an error from an eof condition. 
572  * Actually, if we found an error but we did already reach eof then we
573  * just keep on saying that it was an eof, so the app can just continue.
574  */ 
575 int
576 zzip_entry_feof (ZZIP_ENTRY_FILE* file)
577 {
578     return ! file || ! file->avail;
579 }