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