xref: /PHP-5.5/ext/zip/lib/zip_close.c (revision 638c4b89)
1 /*
2   zip_close.c -- close zip archive and update changes
3   Copyright (C) 1999-2011 Dieter Baron and Thomas Klausner
4 
5   This file is part of libzip, a library to manipulate ZIP archives.
6   The authors can be contacted at <libzip@nih.at>
7 
8   Redistribution and use in source and binary forms, with or without
9   modification, are permitted provided that the following conditions
10   are met:
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in
15      the documentation and/or other materials provided with the
16      distribution.
17   3. The names of the authors may not be used to endorse or promote
18      products derived from this software without specific prior
19      written permission.
20 
21   THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
25   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29   IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
31   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33 
34 
35 
36 #include "zipint.h"
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <errno.h>
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #ifdef PHP_WIN32
48 #include <io.h>
49 #include <fcntl.h>
50 #endif
51 
52 static int add_data(struct zip *, struct zip_source *, struct zip_dirent *,
53 		    FILE *);
54 static int copy_data(FILE *, off_t, FILE *, struct zip_error *);
55 static int copy_source(struct zip *, struct zip_source *, FILE *);
56 static int write_cdir(struct zip *, struct zip_cdir *, FILE *);
57 static int _zip_cdir_set_comment(struct zip_cdir *, struct zip *);
58 static char *_zip_create_temp_output(struct zip *, FILE **);
59 static int _zip_torrentzip_cmp(const void *, const void *);
60 
61 
62 
63 struct filelist {
64     int idx;
65     const char *name;
66 };
67 
68 
69 
70 ZIP_EXTERN(int)
zip_close(struct zip * za)71 zip_close(struct zip *za)
72 {
73     int survivors;
74     int i, j, error;
75     char *temp;
76     FILE *out;
77 #ifndef PHP_WIN32
78     mode_t mask;
79 #endif
80     struct zip_cdir *cd;
81     struct zip_dirent de;
82     struct filelist *filelist;
83     int reopen_on_error;
84     int new_torrentzip;
85 
86     reopen_on_error = 0;
87 
88     if (za == NULL)
89 	return -1;
90 
91     if (!_zip_changed(za, &survivors)) {
92 	_zip_free(za);
93 	return 0;
94     }
95 
96     /* don't create zip files with no entries */
97     if (survivors == 0) {
98 	if (za->zn && za->zp) {
99 	    if (remove(za->zn) != 0) {
100 		_zip_error_set(&za->error, ZIP_ER_REMOVE, errno);
101 		return -1;
102 	    }
103 	}
104 	_zip_free(za);
105 	return 0;
106     }
107 
108     if ((filelist=(struct filelist *)malloc(sizeof(filelist[0])*survivors))
109 	== NULL)
110 	return -1;
111 
112     if ((cd=_zip_cdir_new(survivors, &za->error)) == NULL) {
113 	free(filelist);
114 	return -1;
115     }
116 
117     for (i=0; i<survivors; i++)
118 	_zip_dirent_init(&cd->entry[i]);
119 
120     /* archive comment is special for torrentzip */
121     if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0)) {
122 	cd->comment = _zip_memdup(TORRENT_SIG "XXXXXXXX",
123 				  TORRENT_SIG_LEN + TORRENT_CRC_LEN,
124 				  &za->error);
125 	if (cd->comment == NULL) {
126 	    _zip_cdir_free(cd);
127 	    free(filelist);
128 	    return -1;
129 	}
130 	cd->comment_len = TORRENT_SIG_LEN + TORRENT_CRC_LEN;
131     }
132     else if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, ZIP_FL_UNCHANGED) == 0) {
133 	if (_zip_cdir_set_comment(cd, za) == -1) {
134 	    _zip_cdir_free(cd);
135 	    free(filelist);
136 	    return -1;
137 	}
138     }
139 
140     if ((temp=_zip_create_temp_output(za, &out)) == NULL) {
141 	_zip_cdir_free(cd);
142 	free(filelist);
143 	return -1;
144     }
145 
146 
147     /* create list of files with index into original archive  */
148     for (i=j=0; i<za->nentry; i++) {
149 	if (za->entry[i].state == ZIP_ST_DELETED)
150 	    continue;
151 
152 	filelist[j].idx = i;
153 	filelist[j].name = zip_get_name(za, i, 0);
154 	j++;
155     }
156     if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
157 	qsort(filelist, survivors, sizeof(filelist[0]),
158 	      _zip_torrentzip_cmp);
159 
160     new_torrentzip = (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 1
161 		      && zip_get_archive_flag(za, ZIP_AFL_TORRENT,
162 					      ZIP_FL_UNCHANGED) == 0);
163     error = 0;
164     for (j=0; j<survivors; j++) {
165 	i = filelist[j].idx;
166 
167 	_zip_dirent_init(&de);
168 
169 	/* create new local directory entry */
170 	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i) || new_torrentzip) {
171 
172 	    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
173 		_zip_dirent_torrent_normalize(&de);
174 
175 	    /* use it as central directory entry */
176 	    memcpy(cd->entry+j, &de, sizeof(cd->entry[j]));
177 
178 	    /* set/update file name */
179 	    if (za->entry[i].ch_filename == NULL) {
180 		if (za->entry[i].state == ZIP_ST_ADDED) {
181 		    de.filename = strdup("-");
182 		    de.filename_len = 1;
183 		    cd->entry[j].filename = "-";
184 		    cd->entry[j].filename_len = 1;
185 		}
186 		else {
187 		    de.filename = strdup(za->cdir->entry[i].filename);
188 		    de.filename_len = strlen(de.filename);
189 		    cd->entry[j].filename = za->cdir->entry[i].filename;
190 		    cd->entry[j].filename_len = de.filename_len;
191 		}
192 	    }
193 	}
194 	else {
195 	    /* copy existing directory entries */
196 	    if ((NULL == za->zp) || (fseeko(za->zp, za->cdir->entry[i].offset, SEEK_SET) != 0)) {
197 		_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
198 		error = 1;
199 		break;
200 	    }
201 	    if (_zip_dirent_read(&de, za->zp, NULL, NULL, 1,
202 				 &za->error) != 0) {
203 		error = 1;
204 		break;
205 	    }
206 	    memcpy(cd->entry+j, za->cdir->entry+i, sizeof(cd->entry[j]));
207 	    if (de.bitflags & ZIP_GPBF_DATA_DESCRIPTOR) {
208 		de.crc = za->cdir->entry[i].crc;
209 		de.comp_size = za->cdir->entry[i].comp_size;
210 		de.uncomp_size = za->cdir->entry[i].uncomp_size;
211 		de.bitflags &= ~ZIP_GPBF_DATA_DESCRIPTOR;
212 	    cd->entry[j].bitflags &= ~ZIP_GPBF_DATA_DESCRIPTOR;
213 		}
214 	}
215 
216 	if (za->entry[i].ch_filename) {
217 	    free(de.filename);
218 	    if ((de.filename=strdup(za->entry[i].ch_filename)) == NULL) {
219 		error = 1;
220 		break;
221 	    }
222 	    de.filename_len = strlen(de.filename);
223 	    cd->entry[j].filename = za->entry[i].ch_filename;
224 	    cd->entry[j].filename_len = de.filename_len;
225 	}
226 
227 	if (za->entry[i].ch_extra_len != -1) {
228 	    free(de.extrafield);
229 	    if ((de.extrafield=malloc(za->entry[i].ch_extra_len)) == NULL) {
230 		error = 1;
231 		break;
232 	    }
233 	    memcpy(de.extrafield, za->entry[i].ch_extra, za->entry[i].ch_extra_len);
234 	    de.extrafield_len = za->entry[i].ch_extra_len;
235 	    /* as the rest of cd entries, its malloc/free is done by za */
236 	    /* TODO unsure if this should also be set in the CD --
237 	     * not done for now
238 	    cd->entry[j].extrafield = za->entry[i].ch_extra;
239 	    cd->entry[j].extrafield_len = za->entry[i].ch_extra_len;
240 	    */
241 	}
242 
243 	if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 0
244 	    && za->entry[i].ch_comment_len != -1) {
245 	    /* as the rest of cd entries, its malloc/free is done by za */
246 	    cd->entry[j].comment = za->entry[i].ch_comment;
247 	    cd->entry[j].comment_len = za->entry[i].ch_comment_len;
248 	}
249 
250 	cd->entry[j].offset = ftello(out);
251 
252 	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i) || new_torrentzip) {
253 	    struct zip_source *zs;
254 
255 	    zs = NULL;
256 	    if (!ZIP_ENTRY_DATA_CHANGED(za->entry+i)) {
257 		if ((zs=zip_source_zip(za, za, i, ZIP_FL_RECOMPRESS, 0, -1))
258 		    == NULL) {
259 		    error = 1;
260 		    break;
261 		}
262 	    }
263 
264 	    if (add_data(za, zs ? zs : za->entry[i].source, &de, out) < 0) {
265 		error = 1;
266 		if (zs)
267 		    zip_source_free(zs);
268 		break;
269 	    }
270 	    if (zs)
271 		zip_source_free(zs);
272 
273 	    cd->entry[j].last_mod = de.last_mod;
274 	    cd->entry[j].comp_method = de.comp_method;
275 	    cd->entry[j].comp_size = de.comp_size;
276 	    cd->entry[j].uncomp_size = de.uncomp_size;
277 	    cd->entry[j].crc = de.crc;
278 	}
279 	else {
280 	    if (_zip_dirent_write(&de, out, 1, &za->error) < 0) {
281 		error = 1;
282 		break;
283 	    }
284 	    /* we just read the local dirent, file is at correct position */
285 	    if (copy_data(za->zp, cd->entry[j].comp_size, out,
286 			  &za->error) < 0) {
287 		error = 1;
288 		break;
289 	    }
290 	}
291 
292 	_zip_dirent_finalize(&de);
293     }
294 
295     free(filelist);
296 
297     if (!error) {
298 	if (write_cdir(za, cd, out) < 0)
299 	    error = 1;
300     }
301 
302     /* pointers in cd entries are owned by za */
303     cd->nentry = 0;
304     _zip_cdir_free(cd);
305 
306     if (error) {
307 	_zip_dirent_finalize(&de);
308 	fclose(out);
309 	remove(temp);
310 	free(temp);
311 	return -1;
312     }
313 
314     if (fclose(out) != 0) {
315 	_zip_error_set(&za->error, ZIP_ER_CLOSE, errno);
316 	remove(temp);
317 	free(temp);
318 	return -1;
319     }
320 
321     if (za->zp) {
322 	fclose(za->zp);
323 	za->zp = NULL;
324 	reopen_on_error = 1;
325     }
326     if (_zip_rename(temp, za->zn) != 0) {
327 	_zip_error_set(&za->error, ZIP_ER_RENAME, errno);
328 	remove(temp);
329 	free(temp);
330 	if (reopen_on_error) {
331 	    /* ignore errors, since we're already in an error case */
332 	    za->zp = fopen(za->zn, "rb");
333 	}
334 	return -1;
335     }
336 #ifndef PHP_WIN32
337     mask = umask(0);
338     umask(mask);
339     chmod(za->zn, 0666&~mask);
340 #endif
341 
342     _zip_free(za);
343 	free(temp);
344 
345     return 0;
346 }
347 
348 
349 
350 static int
add_data(struct zip * za,struct zip_source * src,struct zip_dirent * de,FILE * ft)351 add_data(struct zip *za, struct zip_source *src, struct zip_dirent *de,
352 	 FILE *ft)
353 {
354     off_t offstart, offdata, offend;
355     struct zip_stat st;
356     struct zip_source *s2;
357     zip_compression_implementation comp_impl;
358     int ret;
359 
360     if (zip_source_stat(src, &st) < 0) {
361 	_zip_error_set_from_source(&za->error, src);
362 	return -1;
363     }
364 
365     offstart = ftello(ft);
366 
367     if (_zip_dirent_write(de, ft, 1, &za->error) < 0)
368 	return -1;
369 
370     if ((s2=zip_source_crc(za, src, 0)) == NULL) {
371 	zip_source_pop(s2);
372 	return -1;
373     }
374 
375     /* XXX: deflate 0-byte files for torrentzip? */
376     if (((st.valid & ZIP_STAT_COMP_METHOD) == 0
377 	 || st.comp_method == ZIP_CM_STORE)
378 	&& ((st.valid & ZIP_STAT_SIZE) == 0 || st.size != 0)) {
379 	comp_impl = NULL;
380 	if ((comp_impl=zip_get_compression_implementation(ZIP_CM_DEFLATE))
381 	    == NULL) {
382 	    _zip_error_set(&za->error, ZIP_ER_COMPNOTSUPP, 0);
383 	    zip_source_pop(s2);
384 	    return -1;
385 	}
386 	if ((s2=comp_impl(za, s2, ZIP_CM_DEFLATE, ZIP_CODEC_ENCODE))
387 	    == NULL) {
388 	    /* XXX: set error? */
389 	    zip_source_pop(s2);
390 	    return -1;
391 	}
392     }
393     else
394 	s2 = src;
395 
396     offdata = ftello(ft);
397 
398     ret = copy_source(za, s2, ft);
399 
400     if (zip_source_stat(s2, &st) < 0)
401 	ret = -1;
402 
403     while (s2 != src) {
404 	if ((s2=zip_source_pop(s2)) == NULL) {
405 	    /* XXX: set erorr */
406 	    ret = -1;
407 	    break;
408 	}
409     }
410 
411     if (ret < 0)
412 	return -1;
413 
414     offend = ftello(ft);
415 
416     if (fseeko(ft, offstart, SEEK_SET) < 0) {
417 	_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
418 	return -1;
419     }
420 
421     de->last_mod = st.mtime;
422     de->comp_method = st.comp_method;
423     de->crc = st.crc;
424     de->uncomp_size = st.size;
425     de->comp_size = offend - offdata;
426 
427     if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
428 	_zip_dirent_torrent_normalize(de);
429 
430     if (_zip_dirent_write(de, ft, 1, &za->error) < 0)
431 	return -1;
432 
433     if (fseeko(ft, offend, SEEK_SET) < 0) {
434 	_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
435 	return -1;
436     }
437 
438     return 0;
439 }
440 
441 
442 
443 static int
copy_data(FILE * fs,off_t len,FILE * ft,struct zip_error * error)444 copy_data(FILE *fs, off_t len, FILE *ft, struct zip_error *error)
445 {
446     char buf[BUFSIZE];
447     int n, nn;
448 
449     if (len == 0)
450 	return 0;
451 
452     while (len > 0) {
453 	nn = len > sizeof(buf) ? sizeof(buf) : len;
454 	if ((n=fread(buf, 1, nn, fs)) < 0) {
455 	    _zip_error_set(error, ZIP_ER_READ, errno);
456 	    return -1;
457 	}
458 	else if (n == 0) {
459 	    _zip_error_set(error, ZIP_ER_EOF, 0);
460 	    return -1;
461 	}
462 
463 	if (fwrite(buf, 1, n, ft) != (size_t)n) {
464 	    _zip_error_set(error, ZIP_ER_WRITE, errno);
465 	    return -1;
466 	}
467 
468 	len -= n;
469     }
470 
471     return 0;
472 }
473 
474 
475 
476 static int
copy_source(struct zip * za,struct zip_source * src,FILE * ft)477 copy_source(struct zip *za, struct zip_source *src, FILE *ft)
478 {
479     char buf[BUFSIZE];
480     zip_int64_t n;
481     int ret;
482 
483     if (zip_source_open(src) < 0) {
484 	_zip_error_set_from_source(&za->error, src);
485 	return -1;
486     }
487 
488     ret = 0;
489     while ((n=zip_source_read(src, buf, sizeof(buf))) > 0) {
490 	if (fwrite(buf, 1, n, ft) != (size_t)n) {
491 	    _zip_error_set(&za->error, ZIP_ER_WRITE, errno);
492 	    ret = -1;
493 	    break;
494 	}
495     }
496 
497     if (n < 0) {
498 	if (ret == 0)
499 	    _zip_error_set_from_source(&za->error, src);
500 	ret = -1;
501     }
502 
503     zip_source_close(src);
504 
505     return ret;
506 }
507 
508 
509 
510 static int
write_cdir(struct zip * za,struct zip_cdir * cd,FILE * out)511 write_cdir(struct zip *za, struct zip_cdir *cd, FILE *out)
512 {
513     off_t offset;
514     uLong crc;
515     char buf[TORRENT_CRC_LEN+1];
516 
517     if (_zip_cdir_write(cd, out, &za->error) < 0)
518 	return -1;
519 
520     if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 0)
521 	return 0;
522 
523 
524     /* fix up torrentzip comment */
525 
526     offset = ftello(out);
527 
528     if (_zip_filerange_crc(out, cd->offset, cd->size, &crc, &za->error) < 0)
529 	return -1;
530 
531     snprintf(buf, sizeof(buf), "%08lX", (long)crc);
532 
533     if (fseeko(out, offset-TORRENT_CRC_LEN, SEEK_SET) < 0) {
534 	_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
535 	return -1;
536     }
537 
538     if (fwrite(buf, TORRENT_CRC_LEN, 1, out) != 1) {
539 	_zip_error_set(&za->error, ZIP_ER_WRITE, errno);
540 	return -1;
541     }
542 
543     return 0;
544 }
545 
546 
547 
548 static int
_zip_cdir_set_comment(struct zip_cdir * dest,struct zip * src)549 _zip_cdir_set_comment(struct zip_cdir *dest, struct zip *src)
550 {
551     if (src->ch_comment_len != -1) {
552 	dest->comment = _zip_memdup(src->ch_comment,
553 				    src->ch_comment_len, &src->error);
554 	if (dest->comment == NULL)
555 	    return -1;
556 	dest->comment_len = src->ch_comment_len;
557     } else {
558 	if (src->cdir && src->cdir->comment) {
559 	    dest->comment = _zip_memdup(src->cdir->comment,
560 					src->cdir->comment_len, &src->error);
561 	    if (dest->comment == NULL)
562 		return -1;
563 	    dest->comment_len = src->cdir->comment_len;
564 	}
565     }
566 
567     return 0;
568 }
569 
570 
571 
572 int
_zip_changed(struct zip * za,int * survivorsp)573 _zip_changed(struct zip *za, int *survivorsp)
574 {
575     int changed, i, survivors;
576 
577     changed = survivors = 0;
578 
579     if (za->ch_comment_len != -1
580 	|| za->ch_flags != za->flags)
581 	changed = 1;
582 
583     for (i=0; i<za->nentry; i++) {
584 	if ((za->entry[i].state != ZIP_ST_UNCHANGED)
585 	    || (za->entry[i].ch_extra_len != -1)
586 	    || (za->entry[i].ch_comment_len != -1))
587 	    changed = 1;
588 	if (za->entry[i].state != ZIP_ST_DELETED)
589 	    survivors++;
590     }
591 
592     if (survivorsp)
593 	*survivorsp = survivors;
594 
595     return changed;
596 }
597 
598 
599 
600 static char *
_zip_create_temp_output(struct zip * za,FILE ** outp)601 _zip_create_temp_output(struct zip *za, FILE **outp)
602 {
603     char *temp;
604     int tfd;
605     FILE *tfp;
606 	int len = strlen(za->zn) + 8;
607 
608     if ((temp=(char *)malloc(len)) == NULL) {
609 	_zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
610 	return NULL;
611     }
612 
613 		snprintf(temp, len, "%s.XXXXXX", za->zn);
614 
615     if ((tfd=mkstemp(temp)) == -1) {
616 	_zip_error_set(&za->error, ZIP_ER_TMPOPEN, errno);
617 	free(temp);
618 	return NULL;
619     }
620 
621     if ((tfp=fdopen(tfd, "r+b")) == NULL) {
622 	_zip_error_set(&za->error, ZIP_ER_TMPOPEN, errno);
623 	close(tfd);
624 	remove(temp);
625 	free(temp);
626 	return NULL;
627     }
628 #ifdef PHP_WIN32
629     /*
630       According to Pierre Joye, Windows in some environments per
631       default creates text files, so force binary mode.
632     */
633 	_setmode(_fileno(tfp), _O_BINARY );
634 #endif
635 
636     *outp = tfp;
637     return temp;
638 }
639 
640 
641 
642 static int
_zip_torrentzip_cmp(const void * a,const void * b)643 _zip_torrentzip_cmp(const void *a, const void *b)
644 {
645     return strcasecmp(((const struct filelist *)a)->name,
646 		      ((const struct filelist *)b)->name);
647 }
648