1 /*
2 +----------------------------------------------------------------------+
3 | ZIP archive support for Phar |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | https://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Gregory Beaver <cellog@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include "phar_internal.h"
20
21 #define PHAR_GET_16(var) ((uint16_t)((((uint16_t)var[0]) & 0xff) | \
22 (((uint16_t)var[1]) & 0xff) << 8))
23 #define PHAR_GET_32(var) ((uint32_t)((((uint32_t)var[0]) & 0xff) | \
24 (((uint32_t)var[1]) & 0xff) << 8 | \
25 (((uint32_t)var[2]) & 0xff) << 16 | \
26 (((uint32_t)var[3]) & 0xff) << 24))
phar_write_32(char buffer[4],uint32_t value)27 static inline void phar_write_32(char buffer[4], uint32_t value)
28 {
29 buffer[3] = (unsigned char) ((value & 0xff000000) >> 24);
30 buffer[2] = (unsigned char) ((value & 0xff0000) >> 16);
31 buffer[1] = (unsigned char) ((value & 0xff00) >> 8);
32 buffer[0] = (unsigned char) (value & 0xff);
33 }
phar_write_16(char buffer[2],uint32_t value)34 static inline void phar_write_16(char buffer[2], uint32_t value)
35 {
36 buffer[1] = (unsigned char) ((value & 0xff00) >> 8);
37 buffer[0] = (unsigned char) (value & 0xff);
38 }
39 # define PHAR_SET_32(var, value) phar_write_32(var, (uint32_t) (value));
40 # define PHAR_SET_16(var, value) phar_write_16(var, (uint16_t) (value));
41
phar_zip_process_extra(php_stream * fp,phar_entry_info * entry,uint16_t len)42 static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, uint16_t len) /* {{{ */
43 {
44 union {
45 phar_zip_extra_field_header header;
46 phar_zip_unix3 unix3;
47 } h;
48 size_t read;
49
50 do {
51 if (sizeof(h.header) != php_stream_read(fp, (char *) &h.header, sizeof(h.header))) {
52 return FAILURE;
53 }
54
55 if (h.header.tag[0] != 'n' || h.header.tag[1] != 'u') {
56 /* skip to next header */
57 php_stream_seek(fp, PHAR_GET_16(h.header.size), SEEK_CUR);
58 len -= PHAR_GET_16(h.header.size) + 4;
59 continue;
60 }
61
62 /* unix3 header found */
63 read = php_stream_read(fp, (char *) &(h.unix3.crc32), sizeof(h.unix3) - sizeof(h.header));
64 len -= read + 4;
65
66 if (sizeof(h.unix3) - sizeof(h.header) != read) {
67 return FAILURE;
68 }
69
70 if (PHAR_GET_16(h.unix3.size) > sizeof(h.unix3) - 4) {
71 /* skip symlink filename - we may add this support in later */
72 php_stream_seek(fp, PHAR_GET_16(h.unix3.size) - sizeof(h.unix3.size), SEEK_CUR);
73 }
74
75 /* set permissions */
76 entry->flags &= PHAR_ENT_COMPRESSION_MASK;
77
78 if (entry->is_dir) {
79 entry->flags |= PHAR_GET_16(h.unix3.perms) & PHAR_ENT_PERM_MASK;
80 } else {
81 entry->flags |= PHAR_GET_16(h.unix3.perms) & PHAR_ENT_PERM_MASK;
82 }
83
84 } while (len);
85
86 return SUCCESS;
87 }
88 /* }}} */
89
90 /*
91 extracted from libzip
92 zip_dirent.c -- read directory entry (local or central), clean dirent
93 Copyright (C) 1999, 2003, 2004, 2005 Dieter Baron and Thomas Klausner
94
95 This function is part of libzip, a library to manipulate ZIP archives.
96 The authors can be contacted at <nih@giga.or.at>
97
98 Redistribution and use in source and binary forms, with or without
99 modification, are permitted provided that the following conditions
100 are met:
101 1. Redistributions of source code must retain the above copyright
102 notice, this list of conditions and the following disclaimer.
103 2. Redistributions in binary form must reproduce the above copyright
104 notice, this list of conditions and the following disclaimer in
105 the documentation and/or other materials provided with the
106 distribution.
107 3. The names of the authors may not be used to endorse or promote
108 products derived from this software without specific prior
109 written permission.
110
111 THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
112 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
113 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
114 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
115 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
116 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
117 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
118 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
119 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
120 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
121 IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
122 */
phar_zip_d2u_time(char * cdtime,char * cddate)123 static time_t phar_zip_d2u_time(char *cdtime, char *cddate) /* {{{ */
124 {
125 int dtime = PHAR_GET_16(cdtime), ddate = PHAR_GET_16(cddate);
126 struct tm *tm, tmbuf;
127 time_t now;
128
129 now = time(NULL);
130 tm = php_localtime_r(&now, &tmbuf);
131
132 tm->tm_year = ((ddate>>9)&127) + 1980 - 1900;
133 tm->tm_mon = ((ddate>>5)&15) - 1;
134 tm->tm_mday = ddate&31;
135
136 tm->tm_hour = (dtime>>11)&31;
137 tm->tm_min = (dtime>>5)&63;
138 tm->tm_sec = (dtime<<1)&62;
139
140 return mktime(tm);
141 }
142 /* }}} */
143
phar_zip_u2d_time(time_t time,char * dtime,char * ddate)144 static void phar_zip_u2d_time(time_t time, char *dtime, char *ddate) /* {{{ */
145 {
146 uint16_t ctime, cdate;
147 struct tm *tm, tmbuf;
148
149 tm = php_localtime_r(&time, &tmbuf);
150 /* Note: tm_year is the year - 1900 */
151 if (tm->tm_year >= 80) {
152 cdate = ((tm->tm_year+1900-1980)<<9) + ((tm->tm_mon+1)<<5) + tm->tm_mday;
153 ctime = ((tm->tm_hour)<<11) + ((tm->tm_min)<<5) + ((tm->tm_sec)>>1);
154 } else {
155 /* This is the earliest date/time supported by zip. */
156 cdate = (1<<5) + 1; /* 1980-01-01 */
157 ctime = 0; /* 00:00:00 */
158 }
159
160 PHAR_SET_16(dtime, ctime);
161 PHAR_SET_16(ddate, cdate);
162 }
163 /* }}} */
164
phar_find_eocd(const char * s,size_t n)165 static char *phar_find_eocd(const char *s, size_t n)
166 {
167 const char *end = s + n + sizeof("PK\5\6") - 1 - sizeof(phar_zip_dir_end);
168
169 /* search backwards for end of central directory signatures */
170 do {
171 uint16_t comment_len;
172 const char *eocd_start = zend_memnrstr(s, "PK\5\6", sizeof("PK\5\6") - 1, end);
173
174 if (eocd_start == NULL) {
175 return NULL;
176 }
177 ZEND_ASSERT(eocd_start + sizeof(phar_zip_dir_end) <= s + n);
178 comment_len = PHAR_GET_16(((phar_zip_dir_end *) eocd_start)->comment_len);
179 if (eocd_start + sizeof(phar_zip_dir_end) + comment_len == s + n) {
180 /* we can't be sure, but this looks like the proper EOCD signature */
181 return (char *) eocd_start;
182 }
183 end = eocd_start;
184 } while (end > s);
185 return NULL;
186 }
187
188 /**
189 * Does not check for a previously opened phar in the cache.
190 *
191 * Parse a new one and add it to the cache, returning either SUCCESS or
192 * FAILURE, and setting pphar to the pointer to the manifest entry
193 *
194 * This is used by phar_open_from_fp to process a zip-based phar, but can be called
195 * directly.
196 */
phar_parse_zipfile(php_stream * fp,char * fname,size_t fname_len,char * alias,size_t alias_len,phar_archive_data ** pphar,char ** error)197 int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, phar_archive_data** pphar, char **error) /* {{{ */
198 {
199 phar_zip_dir_end locator;
200 char buf[sizeof(locator) + 65536];
201 zend_off_t size;
202 uint16_t i;
203 phar_archive_data *mydata = NULL;
204 phar_entry_info entry = {0};
205 char *p = buf, *ext, *actual_alias = NULL;
206 char *metadata = NULL;
207
208 size = php_stream_tell(fp);
209
210 if (size > sizeof(locator) + 65536) {
211 /* seek to max comment length + end of central directory record */
212 size = sizeof(locator) + 65536;
213 if (FAILURE == php_stream_seek(fp, -size, SEEK_END)) {
214 php_stream_close(fp);
215 if (error) {
216 spprintf(error, 4096, "phar error: unable to search for end of central directory in zip-based phar \"%s\"", fname);
217 }
218 return FAILURE;
219 }
220 } else {
221 php_stream_seek(fp, 0, SEEK_SET);
222 }
223
224 if (!php_stream_read(fp, buf, size)) {
225 php_stream_close(fp);
226 if (error) {
227 spprintf(error, 4096, "phar error: unable to read in data to search for end of central directory in zip-based phar \"%s\"", fname);
228 }
229 return FAILURE;
230 }
231
232 if ((p = phar_find_eocd(buf, size)) != NULL) {
233 memcpy((void *)&locator, (void *) p, sizeof(locator));
234 if (PHAR_GET_16(locator.centraldisk) != 0 || PHAR_GET_16(locator.disknumber) != 0) {
235 /* split archives not handled */
236 php_stream_close(fp);
237 if (error) {
238 spprintf(error, 4096, "phar error: split archives spanning multiple zips cannot be processed in zip-based phar \"%s\"", fname);
239 }
240 return FAILURE;
241 }
242
243 if (PHAR_GET_16(locator.counthere) != PHAR_GET_16(locator.count)) {
244 if (error) {
245 spprintf(error, 4096, "phar error: corrupt zip archive, conflicting file count in end of central directory record in zip-based phar \"%s\"", fname);
246 }
247 php_stream_close(fp);
248 return FAILURE;
249 }
250
251 mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist));
252 mydata->is_persistent = PHAR_G(persist);
253
254 /* read in archive comment, if any */
255 if (PHAR_GET_16(locator.comment_len)) {
256
257 metadata = p + sizeof(locator);
258
259 if (PHAR_GET_16(locator.comment_len) != size - (metadata - buf)) {
260 if (error) {
261 spprintf(error, 4096, "phar error: corrupt zip archive, zip file comment truncated in zip-based phar \"%s\"", fname);
262 }
263 php_stream_close(fp);
264 pefree(mydata, mydata->is_persistent);
265 return FAILURE;
266 }
267
268 phar_parse_metadata_lazy(metadata, &mydata->metadata_tracker, PHAR_GET_16(locator.comment_len), mydata->is_persistent);
269 } else {
270 ZVAL_UNDEF(&mydata->metadata_tracker.val);
271 }
272
273 goto foundit;
274 }
275
276 php_stream_close(fp);
277
278 if (error) {
279 spprintf(error, 4096, "phar error: end of central directory not found in zip-based phar \"%s\"", fname);
280 }
281
282 return FAILURE;
283 foundit:
284 mydata->fname = pestrndup(fname, fname_len, mydata->is_persistent);
285 #ifdef PHP_WIN32
286 phar_unixify_path_separators(mydata->fname, fname_len);
287 #endif
288 mydata->is_zip = 1;
289 mydata->fname_len = fname_len;
290 ext = strrchr(mydata->fname, '/');
291
292 if (ext) {
293 mydata->ext = memchr(ext, '.', (mydata->fname + fname_len) - ext);
294 if (mydata->ext == ext) {
295 mydata->ext = memchr(ext + 1, '.', (mydata->fname + fname_len) - ext - 1);
296 }
297 if (mydata->ext) {
298 mydata->ext_len = (mydata->fname + fname_len) - mydata->ext;
299 }
300 }
301
302 /* clean up on big-endian systems */
303 /* seek to central directory */
304 php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET);
305 /* read in central directory */
306 zend_hash_init(&mydata->manifest, PHAR_GET_16(locator.count),
307 zend_get_hash_value, destroy_phar_manifest_entry, (bool)mydata->is_persistent);
308 zend_hash_init(&mydata->mounted_dirs, 5,
309 zend_get_hash_value, NULL, (bool)mydata->is_persistent);
310 zend_hash_init(&mydata->virtual_dirs, PHAR_GET_16(locator.count) * 2,
311 zend_get_hash_value, NULL, (bool)mydata->is_persistent);
312 entry.phar = mydata;
313 entry.is_zip = 1;
314 entry.fp_type = PHAR_FP;
315 entry.is_persistent = mydata->is_persistent;
316 #define PHAR_ZIP_FAIL_FREE(errmsg, save) \
317 zend_hash_destroy(&mydata->manifest); \
318 HT_INVALIDATE(&mydata->manifest); \
319 zend_hash_destroy(&mydata->mounted_dirs); \
320 HT_INVALIDATE(&mydata->mounted_dirs); \
321 zend_hash_destroy(&mydata->virtual_dirs); \
322 HT_INVALIDATE(&mydata->virtual_dirs); \
323 php_stream_close(fp); \
324 phar_metadata_tracker_free(&mydata->metadata_tracker, mydata->is_persistent); \
325 if (mydata->signature) { \
326 efree(mydata->signature); \
327 } \
328 if (error) { \
329 spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \
330 } \
331 pefree(mydata->fname, mydata->is_persistent); \
332 if (mydata->alias) { \
333 pefree(mydata->alias, mydata->is_persistent); \
334 } \
335 pefree(mydata, mydata->is_persistent); \
336 efree(save); \
337 return FAILURE;
338 #define PHAR_ZIP_FAIL(errmsg) \
339 zend_hash_destroy(&mydata->manifest); \
340 HT_INVALIDATE(&mydata->manifest); \
341 zend_hash_destroy(&mydata->mounted_dirs); \
342 HT_INVALIDATE(&mydata->mounted_dirs); \
343 zend_hash_destroy(&mydata->virtual_dirs); \
344 HT_INVALIDATE(&mydata->virtual_dirs); \
345 php_stream_close(fp); \
346 phar_metadata_tracker_free(&mydata->metadata_tracker, mydata->is_persistent); \
347 if (mydata->signature) { \
348 efree(mydata->signature); \
349 } \
350 if (error) { \
351 spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \
352 } \
353 pefree(mydata->fname, mydata->is_persistent); \
354 if (mydata->alias) { \
355 pefree(mydata->alias, mydata->is_persistent); \
356 } \
357 pefree(mydata, mydata->is_persistent); \
358 return FAILURE;
359
360 /* add each central directory item to the manifest */
361 for (i = 0; i < PHAR_GET_16(locator.count); ++i) {
362 phar_zip_central_dir_file zipentry;
363 zend_off_t beforeus = php_stream_tell(fp);
364
365 ZVAL_UNDEF(&entry.metadata_tracker.val);
366 entry.metadata_tracker.str = NULL;
367
368 if (sizeof(zipentry) != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry))) {
369 PHAR_ZIP_FAIL("unable to read central directory entry, truncated");
370 }
371
372 /* clean up for bigendian systems */
373 if (memcmp("PK\1\2", zipentry.signature, 4)) {
374 /* corrupted entry */
375 PHAR_ZIP_FAIL("corrupted central directory entry, no magic signature");
376 }
377
378 if (entry.is_persistent) {
379 entry.manifest_pos = i;
380 }
381
382 entry.compressed_filesize = PHAR_GET_32(zipentry.compsize);
383 entry.uncompressed_filesize = PHAR_GET_32(zipentry.uncompsize);
384 entry.crc32 = PHAR_GET_32(zipentry.crc32);
385 /* do not PHAR_GET_16 either on the next line */
386 entry.timestamp = phar_zip_d2u_time(zipentry.timestamp, zipentry.datestamp);
387 entry.flags = PHAR_ENT_PERM_DEF_FILE;
388 entry.header_offset = PHAR_GET_32(zipentry.offset);
389
390 if (PHAR_GET_16(zipentry.flags) & PHAR_ZIP_FLAG_ENCRYPTED) {
391 PHAR_ZIP_FAIL("Cannot process encrypted zip files");
392 }
393
394 if (!PHAR_GET_16(zipentry.filename_len)) {
395 PHAR_ZIP_FAIL("Cannot process zips created from stdin (zero-length filename)");
396 }
397
398 entry.filename_len = PHAR_GET_16(zipentry.filename_len);
399 entry.filename = (char *) pemalloc(entry.filename_len + 1, entry.is_persistent);
400
401 if (entry.filename_len != php_stream_read(fp, entry.filename, entry.filename_len)) {
402 pefree(entry.filename, entry.is_persistent);
403 PHAR_ZIP_FAIL("unable to read in filename from central directory, truncated");
404 }
405
406 entry.filename[entry.filename_len] = '\0';
407
408 if (entry.filename[entry.filename_len - 1] == '/') {
409 entry.is_dir = 1;
410 if(entry.filename_len > 1) {
411 entry.filename_len--;
412 }
413 entry.flags |= PHAR_ENT_PERM_DEF_DIR;
414 } else {
415 entry.is_dir = 0;
416 }
417
418 phar_zip_file_header local; /* Warning: only filled in when the entry is not a directory! */
419 if (!entry.is_dir) {
420 /* A file has a central directory entry, and a local file header. Both of these contain the filename
421 * and the extra field data. However, at least the extra field data does not have to match between the two!
422 * This happens for example for the "Extended Timestamp extra field" where the local header has 2 extra fields
423 * in comparison to the central header. So we have to use the local header to find the right offset to the file
424 * contents, otherwise we're reading some garbage bytes before reading the actual file contents. */
425 zend_off_t current_central_dir_pos = php_stream_tell(fp);
426
427 php_stream_seek(fp, entry.header_offset, SEEK_SET);
428 if (sizeof(local) != php_stream_read(fp, (char *) &local, sizeof(local))) {
429 pefree(entry.filename, entry.is_persistent);
430 PHAR_ZIP_FAIL("phar error: internal corruption (cannot read local file header)");
431 }
432 php_stream_seek(fp, current_central_dir_pos, SEEK_SET);
433
434 /* verify local header
435 * Note: normally I'd check the crc32, and file sizes too here, but that breaks tests zip/bug48791.phpt & zip/odt.phpt,
436 * suggesting that something may be wrong with those files or the assumption doesn't hold. Anyway, the other checks
437 * _are_ performed for the alias file as was done in the past too. */
438 if (entry.filename_len != PHAR_GET_16(local.filename_len)) {
439 pefree(entry.filename, entry.is_persistent);
440 PHAR_ZIP_FAIL("phar error: internal corruption (local file header does not match central directory)");
441 }
442
443 entry.offset = entry.offset_abs = entry.header_offset
444 + sizeof(phar_zip_file_header)
445 + entry.filename_len
446 + PHAR_GET_16(local.extra_len);
447 } else {
448 entry.offset = entry.offset_abs = entry.header_offset
449 + sizeof(phar_zip_file_header)
450 + entry.filename_len
451 + PHAR_GET_16(zipentry.extra_len);
452 }
453
454 if (entry.filename_len == sizeof(".phar/signature.bin")-1 && !strncmp(entry.filename, ".phar/signature.bin", sizeof(".phar/signature.bin")-1)) {
455 size_t read;
456 php_stream *sigfile;
457 char *sig;
458 size_t sig_len;
459
460 pefree(entry.filename, entry.is_persistent);
461
462 if (entry.uncompressed_filesize > 0x10000) {
463 PHAR_ZIP_FAIL("signatures larger than 64 KiB are not supported");
464 }
465
466 sigfile = php_stream_fopen_tmpfile();
467 if (!sigfile) {
468 PHAR_ZIP_FAIL("couldn't open temporary file");
469 }
470
471 php_stream_seek(fp, 0, SEEK_SET);
472 /* copy file contents + local headers and zip comment, if any, to be hashed for signature */
473 php_stream_copy_to_stream_ex(fp, sigfile, entry.header_offset, NULL);
474 /* seek to central directory */
475 php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET);
476 /* copy central directory header */
477 php_stream_copy_to_stream_ex(fp, sigfile, beforeus - PHAR_GET_32(locator.cdir_offset), NULL);
478 if (metadata) {
479 php_stream_write(sigfile, metadata, PHAR_GET_16(locator.comment_len));
480 }
481 php_stream_seek(fp, entry.offset, SEEK_SET);
482 sig = (char *) emalloc(entry.uncompressed_filesize);
483 read = php_stream_read(fp, sig, entry.uncompressed_filesize);
484 if (read != entry.uncompressed_filesize || read <= 8) {
485 php_stream_close(sigfile);
486 efree(sig);
487 PHAR_ZIP_FAIL("signature cannot be read");
488 }
489 mydata->sig_flags = PHAR_GET_32(sig);
490 if (FAILURE == phar_verify_signature(sigfile, php_stream_tell(sigfile), mydata->sig_flags, sig + 8, entry.uncompressed_filesize - 8, fname, &mydata->signature, &sig_len, error)) {
491 efree(sig);
492 if (error) {
493 char *save;
494 php_stream_close(sigfile);
495 spprintf(&save, 4096, "signature cannot be verified: %s", *error);
496 efree(*error);
497 PHAR_ZIP_FAIL_FREE(save, save);
498 } else {
499 php_stream_close(sigfile);
500 PHAR_ZIP_FAIL("signature cannot be verified");
501 }
502 }
503 mydata->sig_len = sig_len;
504 php_stream_close(sigfile);
505 efree(sig);
506 /* signature checked out, let's ensure this is the last file in the phar */
507 if (i != PHAR_GET_16(locator.count) - 1) {
508 PHAR_ZIP_FAIL("entries exist after signature, invalid phar");
509 }
510
511 continue;
512 }
513
514 phar_add_virtual_dirs(mydata, entry.filename, entry.filename_len);
515
516 if (PHAR_GET_16(zipentry.extra_len)) {
517 zend_off_t loc = php_stream_tell(fp);
518 if (FAILURE == phar_zip_process_extra(fp, &entry, PHAR_GET_16(zipentry.extra_len))) {
519 pefree(entry.filename, entry.is_persistent);
520 PHAR_ZIP_FAIL("Unable to process extra field header for file in central directory");
521 }
522 php_stream_seek(fp, loc + PHAR_GET_16(zipentry.extra_len), SEEK_SET);
523 }
524
525 switch (PHAR_GET_16(zipentry.compressed)) {
526 case PHAR_ZIP_COMP_NONE :
527 /* compression flag already set */
528 break;
529 case PHAR_ZIP_COMP_DEFLATE :
530 entry.flags |= PHAR_ENT_COMPRESSED_GZ;
531 if (!PHAR_G(has_zlib)) {
532 pefree(entry.filename, entry.is_persistent);
533 PHAR_ZIP_FAIL("zlib extension is required");
534 }
535 break;
536 case PHAR_ZIP_COMP_BZIP2 :
537 entry.flags |= PHAR_ENT_COMPRESSED_BZ2;
538 if (!PHAR_G(has_bz2)) {
539 pefree(entry.filename, entry.is_persistent);
540 PHAR_ZIP_FAIL("bzip2 extension is required");
541 }
542 break;
543 case 1 :
544 pefree(entry.filename, entry.is_persistent);
545 PHAR_ZIP_FAIL("unsupported compression method (Shrunk) used in this zip");
546 case 2 :
547 case 3 :
548 case 4 :
549 case 5 :
550 pefree(entry.filename, entry.is_persistent);
551 PHAR_ZIP_FAIL("unsupported compression method (Reduce) used in this zip");
552 case 6 :
553 pefree(entry.filename, entry.is_persistent);
554 PHAR_ZIP_FAIL("unsupported compression method (Implode) used in this zip");
555 case 7 :
556 pefree(entry.filename, entry.is_persistent);
557 PHAR_ZIP_FAIL("unsupported compression method (Tokenize) used in this zip");
558 case 9 :
559 pefree(entry.filename, entry.is_persistent);
560 PHAR_ZIP_FAIL("unsupported compression method (Deflate64) used in this zip");
561 case 10 :
562 pefree(entry.filename, entry.is_persistent);
563 PHAR_ZIP_FAIL("unsupported compression method (PKWare Implode/old IBM TERSE) used in this zip");
564 case 14 :
565 pefree(entry.filename, entry.is_persistent);
566 PHAR_ZIP_FAIL("unsupported compression method (LZMA) used in this zip");
567 case 18 :
568 pefree(entry.filename, entry.is_persistent);
569 PHAR_ZIP_FAIL("unsupported compression method (IBM TERSE) used in this zip");
570 case 19 :
571 pefree(entry.filename, entry.is_persistent);
572 PHAR_ZIP_FAIL("unsupported compression method (IBM LZ77) used in this zip");
573 case 97 :
574 pefree(entry.filename, entry.is_persistent);
575 PHAR_ZIP_FAIL("unsupported compression method (WavPack) used in this zip");
576 case 98 :
577 pefree(entry.filename, entry.is_persistent);
578 PHAR_ZIP_FAIL("unsupported compression method (PPMd) used in this zip");
579 default :
580 pefree(entry.filename, entry.is_persistent);
581 PHAR_ZIP_FAIL("unsupported compression method (unknown) used in this zip");
582 }
583
584 /* get file metadata */
585 if (PHAR_GET_16(zipentry.comment_len)) {
586 if (PHAR_GET_16(zipentry.comment_len) != php_stream_read(fp, buf, PHAR_GET_16(zipentry.comment_len))) {
587 pefree(entry.filename, entry.is_persistent);
588 PHAR_ZIP_FAIL("unable to read in file comment, truncated");
589 }
590
591 p = buf;
592 phar_parse_metadata_lazy(buf, &entry.metadata_tracker, PHAR_GET_16(zipentry.comment_len), entry.is_persistent);
593 } else {
594 ZVAL_UNDEF(&entry.metadata_tracker.val);
595 }
596
597 if (!actual_alias && entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) {
598 php_stream_filter *filter;
599
600 /* archive alias found */
601
602 /* verify local header */
603 ZEND_ASSERT(!entry.is_dir);
604 if (entry.crc32 != PHAR_GET_32(local.crc32) || entry.uncompressed_filesize != PHAR_GET_32(local.uncompsize) || entry.compressed_filesize != PHAR_GET_32(local.compsize)) {
605 pefree(entry.filename, entry.is_persistent);
606 PHAR_ZIP_FAIL("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)");
607 }
608
609 zend_off_t restore_pos = php_stream_tell(fp);
610 php_stream_seek(fp, entry.offset, SEEK_SET);
611 /* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
612 fp->writepos = 0;
613 fp->readpos = 0;
614 php_stream_seek(fp, entry.offset, SEEK_SET);
615 fp->writepos = 0;
616 fp->readpos = 0;
617 /* the above lines should be for php < 5.2.6 after 5.3 filters are fixed */
618
619 mydata->alias_len = entry.uncompressed_filesize;
620 if (entry.flags & PHAR_ENT_COMPRESSED_GZ) {
621 filter = php_stream_filter_create("zlib.inflate", NULL, php_stream_is_persistent(fp));
622
623 if (!filter) {
624 pefree(entry.filename, entry.is_persistent);
625 PHAR_ZIP_FAIL("unable to decompress alias, zlib filter creation failed");
626 }
627
628 php_stream_filter_append(&fp->readfilters, filter);
629
630 // TODO: refactor to avoid reallocation ???
631 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
632 {
633 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
634 if (str) {
635 entry.uncompressed_filesize = ZSTR_LEN(str);
636 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
637 zend_string_release_ex(str, 0);
638 } else {
639 actual_alias = NULL;
640 entry.uncompressed_filesize = 0;
641 }
642 }
643
644 if (!entry.uncompressed_filesize || !actual_alias) {
645 pefree(entry.filename, entry.is_persistent);
646 PHAR_ZIP_FAIL("unable to read in alias, truncated");
647 }
648
649 php_stream_filter_flush(filter, 1);
650 php_stream_filter_remove(filter, 1);
651
652 } else if (entry.flags & PHAR_ENT_COMPRESSED_BZ2) {
653 filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp));
654
655 if (!filter) {
656 pefree(entry.filename, entry.is_persistent);
657 PHAR_ZIP_FAIL("unable to read in alias, bzip2 filter creation failed");
658 }
659
660 php_stream_filter_append(&fp->readfilters, filter);
661
662 // TODO: refactor to avoid reallocation ???
663 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
664 {
665 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
666 if (str) {
667 entry.uncompressed_filesize = ZSTR_LEN(str);
668 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
669 zend_string_release_ex(str, 0);
670 } else {
671 actual_alias = NULL;
672 entry.uncompressed_filesize = 0;
673 }
674 }
675
676 if (!entry.uncompressed_filesize || !actual_alias) {
677 pefree(entry.filename, entry.is_persistent);
678 PHAR_ZIP_FAIL("unable to read in alias, truncated");
679 }
680
681 php_stream_filter_flush(filter, 1);
682 php_stream_filter_remove(filter, 1);
683 } else {
684 // TODO: refactor to avoid reallocation ???
685 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
686 {
687 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
688 if (str) {
689 entry.uncompressed_filesize = ZSTR_LEN(str);
690 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
691 zend_string_release_ex(str, 0);
692 } else {
693 actual_alias = NULL;
694 entry.uncompressed_filesize = 0;
695 }
696 }
697
698 if (!entry.uncompressed_filesize || !actual_alias) {
699 pefree(entry.filename, entry.is_persistent);
700 PHAR_ZIP_FAIL("unable to read in alias, truncated");
701 }
702 }
703
704 /* return to central directory parsing */
705 php_stream_seek(fp, restore_pos, SEEK_SET);
706 }
707
708 phar_set_inode(&entry);
709 zend_hash_str_add_mem(&mydata->manifest, entry.filename, entry.filename_len, (void *)&entry, sizeof(phar_entry_info));
710 }
711
712 if (zend_hash_str_exists(&(mydata->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
713 mydata->is_data = 0;
714 } else {
715 mydata->is_data = 1;
716 }
717
718 /* ensure signature set */
719 if (!mydata->is_data && PHAR_G(require_hash) && !mydata->signature) {
720 PHAR_ZIP_FAIL("signature is missing");
721 }
722
723 mydata->fp = fp;
724
725 zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata);
726
727 if (actual_alias) {
728 phar_archive_data *fd_ptr;
729
730 if (!phar_validate_alias(actual_alias, mydata->alias_len)) {
731 if (error) {
732 spprintf(error, 4096, "phar error: invalid alias \"%s\" in zip-based phar \"%s\"", actual_alias, fname);
733 }
734 efree(actual_alias);
735 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
736 return FAILURE;
737 }
738
739 mydata->is_temporary_alias = 0;
740
741 if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), actual_alias, mydata->alias_len))) {
742 if (SUCCESS != phar_free_alias(fd_ptr, actual_alias, mydata->alias_len)) {
743 if (error) {
744 spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with implicit alias, alias is already in use", fname);
745 }
746 efree(actual_alias);
747 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
748 return FAILURE;
749 }
750 }
751
752 mydata->alias = entry.is_persistent ? pestrndup(actual_alias, mydata->alias_len, 1) : actual_alias;
753
754 if (entry.is_persistent) {
755 efree(actual_alias);
756 }
757
758 zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), mydata->alias, mydata->alias_len, mydata);
759 } else {
760 phar_archive_data *fd_ptr;
761
762 if (alias_len) {
763 if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) {
764 if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) {
765 if (error) {
766 spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with explicit alias, alias is already in use", fname);
767 }
768 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
769 return FAILURE;
770 }
771 }
772
773 zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata);
774 mydata->alias = pestrndup(alias, alias_len, mydata->is_persistent);
775 mydata->alias_len = alias_len;
776 } else {
777 mydata->alias = pestrndup(mydata->fname, fname_len, mydata->is_persistent);
778 mydata->alias_len = fname_len;
779 }
780
781 mydata->is_temporary_alias = 1;
782 }
783
784 if (pphar) {
785 *pphar = mydata;
786 }
787
788 return SUCCESS;
789 }
790 /* }}} */
791
792 /**
793 * Create or open a zip-based phar for writing
794 */
phar_open_or_create_zip(char * fname,size_t fname_len,char * alias,size_t alias_len,int is_data,uint32_t options,phar_archive_data ** pphar,char ** error)795 int phar_open_or_create_zip(char *fname, size_t fname_len, char *alias, size_t alias_len, int is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
796 {
797 phar_archive_data *phar;
798 int ret = phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, &phar, error);
799
800 if (FAILURE == ret) {
801 return FAILURE;
802 }
803
804 if (pphar) {
805 *pphar = phar;
806 }
807
808 phar->is_data = is_data;
809
810 if (phar->is_zip) {
811 return ret;
812 }
813
814 if (phar->is_brandnew) {
815 phar->internal_file_start = 0;
816 phar->is_zip = 1;
817 phar->is_tar = 0;
818 return SUCCESS;
819 }
820
821 /* we've reached here - the phar exists and is a regular phar */
822 if (error) {
823 spprintf(error, 4096, "phar zip error: phar \"%s\" already exists as a regular phar and must be deleted from disk prior to creating as a zip-based phar", fname);
824 }
825
826 return FAILURE;
827 }
828 /* }}} */
829
830 struct _phar_zip_pass {
831 php_stream *filefp;
832 php_stream *centralfp;
833 php_stream *old;
834 int free_fp;
835 int free_ufp;
836 char **error;
837 };
838 /* perform final modification of zip contents for each file in the manifest before saving */
phar_zip_changed_apply_int(phar_entry_info * entry,void * arg)839 static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ */
840 {
841 phar_zip_file_header local;
842 phar_zip_unix3 perms;
843 phar_zip_central_dir_file central;
844 struct _phar_zip_pass *p;
845 uint32_t newcrc32;
846 zend_off_t offset;
847 int not_really_modified = 0;
848 p = (struct _phar_zip_pass*) arg;
849 uint16_t general_purpose_flags;
850
851 if (entry->is_mounted) {
852 return ZEND_HASH_APPLY_KEEP;
853 }
854
855 if (entry->is_deleted) {
856 if (entry->fp_refcount <= 0) {
857 return ZEND_HASH_APPLY_REMOVE;
858 } else {
859 /* we can't delete this in-memory until it is closed */
860 return ZEND_HASH_APPLY_KEEP;
861 }
862 }
863
864 phar_add_virtual_dirs(entry->phar, entry->filename, entry->filename_len);
865 memset(&local, 0, sizeof(local));
866 memset(¢ral, 0, sizeof(central));
867 memset(&perms, 0, sizeof(perms));
868 memcpy(local.signature, "PK\3\4", 4);
869 memcpy(central.signature, "PK\1\2", 4);
870 PHAR_SET_16(central.extra_len, sizeof(perms));
871 PHAR_SET_16(local.extra_len, sizeof(perms));
872 perms.tag[0] = 'n';
873 perms.tag[1] = 'u';
874 PHAR_SET_16(perms.size, sizeof(perms) - 4);
875 PHAR_SET_16(perms.perms, entry->flags & PHAR_ENT_PERM_MASK);
876 {
877 uint32_t crc = (uint32_t) php_crc32_bulk_init();
878 CRC32(crc, perms.perms[0]);
879 CRC32(crc, perms.perms[1]);
880 PHAR_SET_32(perms.crc32, php_crc32_bulk_end(crc));
881 }
882
883 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
884 PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_DEFLATE);
885 PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_DEFLATE);
886 }
887
888 if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) {
889 PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_BZIP2);
890 PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_BZIP2);
891 }
892
893 /* do not use PHAR_GET_16 on either field of the next line */
894 phar_zip_u2d_time(entry->timestamp, local.timestamp, local.datestamp);
895 memcpy(central.timestamp, local.timestamp, sizeof(local.timestamp));
896 memcpy(central.datestamp, local.datestamp, sizeof(local.datestamp));
897 PHAR_SET_16(central.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
898 PHAR_SET_16(local.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
899 // set language encoding flag (all filenames have to be UTF-8 anyway)
900 general_purpose_flags = PHAR_GET_16(central.flags);
901 PHAR_SET_16(central.flags, general_purpose_flags | (1 << 11));
902 general_purpose_flags = PHAR_GET_16(local.flags);
903 PHAR_SET_16(local.flags, general_purpose_flags | (1 << 11));
904 PHAR_SET_32(central.offset, php_stream_tell(p->filefp));
905
906 /* do extra field for perms later */
907 if (entry->is_modified) {
908 php_stream_filter *filter;
909 php_stream *efp;
910
911 if (entry->is_dir) {
912 entry->is_modified = 0;
913 if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp) {
914 php_stream_close(entry->fp);
915 entry->fp = NULL;
916 entry->fp_type = PHAR_FP;
917 }
918 goto continue_dir;
919 }
920
921 if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
922 spprintf(p->error, 0, "unable to open file contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
923 return ZEND_HASH_APPLY_STOP;
924 }
925
926 /* we can be modified and already be compressed, such as when chmod() is executed */
927 if (entry->flags & PHAR_ENT_COMPRESSION_MASK && (entry->old_flags == entry->flags || !entry->old_flags)) {
928 not_really_modified = 1;
929 goto is_compressed;
930 }
931
932 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
933 spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
934 return ZEND_HASH_APPLY_STOP;
935 }
936
937 efp = phar_get_efp(entry, 0);
938 newcrc32 = php_crc32_bulk_init();
939
940 php_crc32_stream_bulk_update(&newcrc32, efp, entry->uncompressed_filesize);
941
942 entry->crc32 = php_crc32_bulk_end(newcrc32);
943 PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
944 PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
945
946 if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
947 /* not compressed */
948 entry->compressed_filesize = entry->uncompressed_filesize;
949 PHAR_SET_32(central.compsize, entry->uncompressed_filesize);
950 PHAR_SET_32(local.compsize, entry->uncompressed_filesize);
951 goto not_compressed;
952 }
953
954 filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0);
955
956 if (!filter) {
957 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
958 spprintf(p->error, 0, "unable to gzip compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
959 } else {
960 spprintf(p->error, 0, "unable to bzip2 compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
961 }
962 return ZEND_HASH_APPLY_STOP;
963 }
964
965 /* create new file that holds the compressed version */
966 /* work around inability to specify freedom in write and strictness
967 in read count */
968 entry->cfp = php_stream_fopen_tmpfile();
969
970 if (!entry->cfp) {
971 spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
972 return ZEND_HASH_APPLY_STOP;
973 }
974
975 php_stream_flush(efp);
976
977 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
978 spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
979 return ZEND_HASH_APPLY_STOP;
980 }
981
982 php_stream_filter_append((&entry->cfp->writefilters), filter);
983
984 if (SUCCESS != php_stream_copy_to_stream_ex(efp, entry->cfp, entry->uncompressed_filesize, NULL)) {
985 spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname);
986 return ZEND_HASH_APPLY_STOP;
987 }
988
989 php_stream_filter_flush(filter, 1);
990 php_stream_flush(entry->cfp);
991 php_stream_filter_remove(filter, 1);
992 php_stream_seek(entry->cfp, 0, SEEK_END);
993 entry->compressed_filesize = (uint32_t) php_stream_tell(entry->cfp);
994 PHAR_SET_32(central.compsize, entry->compressed_filesize);
995 PHAR_SET_32(local.compsize, entry->compressed_filesize);
996 /* generate crc on compressed file */
997 php_stream_rewind(entry->cfp);
998 entry->old_flags = entry->flags;
999 entry->is_modified = 1;
1000 } else {
1001 is_compressed:
1002 PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
1003 PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
1004 PHAR_SET_32(central.compsize, entry->compressed_filesize);
1005 PHAR_SET_32(local.compsize, entry->compressed_filesize);
1006 if (p->old) {
1007 if (-1 == php_stream_seek(p->old, entry->offset_abs, SEEK_SET)) {
1008 spprintf(p->error, 0, "unable to seek to start of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1009 return ZEND_HASH_APPLY_STOP;
1010 }
1011 }
1012 }
1013 not_compressed:
1014 PHAR_SET_32(central.crc32, entry->crc32);
1015 PHAR_SET_32(local.crc32, entry->crc32);
1016 continue_dir:
1017 /* set file metadata */
1018 if (phar_metadata_tracker_has_data(&entry->metadata_tracker, entry->is_persistent)) {
1019 phar_metadata_tracker_try_ensure_has_serialized_data(&entry->metadata_tracker, entry->is_persistent);
1020 PHAR_SET_16(central.comment_len, entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0);
1021 }
1022
1023 entry->header_offset = php_stream_tell(p->filefp);
1024 offset = entry->header_offset + sizeof(local) + entry->filename_len + (entry->is_dir ? 1 : 0) + sizeof(perms);
1025
1026 if (sizeof(local) != php_stream_write(p->filefp, (char *)&local, sizeof(local))) {
1027 spprintf(p->error, 0, "unable to write local file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1028 return ZEND_HASH_APPLY_STOP;
1029 }
1030
1031 if (sizeof(central) != php_stream_write(p->centralfp, (char *)¢ral, sizeof(central))) {
1032 spprintf(p->error, 0, "unable to write central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1033 return ZEND_HASH_APPLY_STOP;
1034 }
1035
1036 if (entry->is_dir) {
1037 if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
1038 spprintf(p->error, 0, "unable to write filename to local directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1039 return ZEND_HASH_APPLY_STOP;
1040 }
1041
1042 if (1 != php_stream_write(p->filefp, "/", 1)) {
1043 spprintf(p->error, 0, "unable to write filename to local directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1044 return ZEND_HASH_APPLY_STOP;
1045 }
1046
1047 if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1048 spprintf(p->error, 0, "unable to write filename to central directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1049 return ZEND_HASH_APPLY_STOP;
1050 }
1051
1052 if (1 != php_stream_write(p->centralfp, "/", 1)) {
1053 spprintf(p->error, 0, "unable to write filename to central directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1054 return ZEND_HASH_APPLY_STOP;
1055 }
1056 } else {
1057 if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
1058 spprintf(p->error, 0, "unable to write filename to local directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1059 return ZEND_HASH_APPLY_STOP;
1060 }
1061
1062 if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1063 spprintf(p->error, 0, "unable to write filename to central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1064 return ZEND_HASH_APPLY_STOP;
1065 }
1066 }
1067
1068 if (sizeof(perms) != php_stream_write(p->filefp, (char *)&perms, sizeof(perms))) {
1069 spprintf(p->error, 0, "unable to write local extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1070 return ZEND_HASH_APPLY_STOP;
1071 }
1072
1073 if (sizeof(perms) != php_stream_write(p->centralfp, (char *)&perms, sizeof(perms))) {
1074 spprintf(p->error, 0, "unable to write central extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1075 return ZEND_HASH_APPLY_STOP;
1076 }
1077
1078 if (!not_really_modified && entry->is_modified) {
1079 if (entry->cfp) {
1080 if (SUCCESS != php_stream_copy_to_stream_ex(entry->cfp, p->filefp, entry->compressed_filesize, NULL)) {
1081 spprintf(p->error, 0, "unable to write compressed contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1082 return ZEND_HASH_APPLY_STOP;
1083 }
1084
1085 php_stream_close(entry->cfp);
1086 entry->cfp = NULL;
1087 } else {
1088 if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
1089 return ZEND_HASH_APPLY_STOP;
1090 }
1091
1092 phar_seek_efp(entry, 0, SEEK_SET, 0, 0);
1093
1094 if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), p->filefp, entry->uncompressed_filesize, NULL)) {
1095 spprintf(p->error, 0, "unable to write contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1096 return ZEND_HASH_APPLY_STOP;
1097 }
1098 }
1099
1100 if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp && entry->fp_refcount == 0) {
1101 php_stream_close(entry->fp);
1102 }
1103
1104 entry->is_modified = 0;
1105 } else {
1106 entry->is_modified = 0;
1107 if (entry->fp_refcount) {
1108 /* open file pointers refer to this fp, do not free the stream */
1109 switch (entry->fp_type) {
1110 case PHAR_FP:
1111 p->free_fp = 0;
1112 break;
1113 case PHAR_UFP:
1114 p->free_ufp = 0;
1115 default:
1116 break;
1117 }
1118 }
1119
1120 if (!entry->is_dir && entry->compressed_filesize && SUCCESS != php_stream_copy_to_stream_ex(p->old, p->filefp, entry->compressed_filesize, NULL)) {
1121 spprintf(p->error, 0, "unable to copy contents of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1122 return ZEND_HASH_APPLY_STOP;
1123 }
1124 }
1125
1126 entry->fp = NULL;
1127 entry->offset = entry->offset_abs = offset;
1128 entry->fp_type = PHAR_FP;
1129
1130 if (entry->metadata_tracker.str) {
1131 if (ZSTR_LEN(entry->metadata_tracker.str) != php_stream_write(p->centralfp, ZSTR_VAL(entry->metadata_tracker.str), ZSTR_LEN(entry->metadata_tracker.str))) {
1132 spprintf(p->error, 0, "unable to write metadata as file comment for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1133 return ZEND_HASH_APPLY_STOP;
1134 }
1135 }
1136
1137 return ZEND_HASH_APPLY_KEEP;
1138 }
1139 /* }}} */
1140
phar_zip_changed_apply(zval * zv,void * arg)1141 static int phar_zip_changed_apply(zval *zv, void *arg) /* {{{ */
1142 {
1143 return phar_zip_changed_apply_int(Z_PTR_P(zv), arg);
1144 }
1145 /* }}} */
1146
phar_zip_applysignature(phar_archive_data * phar,struct _phar_zip_pass * pass)1147 static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass) /* {{{ */
1148 {
1149 /* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */
1150 if (!phar->is_data || phar->sig_flags) {
1151 size_t signature_length;
1152 char *signature, sigbuf[8];
1153 phar_entry_info entry = {0};
1154 php_stream *newfile;
1155 zend_off_t tell;
1156
1157 newfile = php_stream_fopen_tmpfile();
1158 if (newfile == NULL) {
1159 spprintf(pass->error, 0, "phar error: unable to create temporary file for the signature file");
1160 return FAILURE;
1161 }
1162 tell = php_stream_tell(pass->filefp);
1163 /* copy the local files, central directory, and the zip comment to generate the hash */
1164 php_stream_seek(pass->filefp, 0, SEEK_SET);
1165 php_stream_copy_to_stream_ex(pass->filefp, newfile, tell, NULL);
1166 tell = php_stream_tell(pass->centralfp);
1167 php_stream_seek(pass->centralfp, 0, SEEK_SET);
1168 php_stream_copy_to_stream_ex(pass->centralfp, newfile, tell, NULL);
1169 if (phar->metadata_tracker.str) {
1170 php_stream_write(newfile, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
1171 }
1172
1173 if (FAILURE == phar_create_signature(phar, newfile, &signature, &signature_length, pass->error)) {
1174 if (pass->error) {
1175 char *save = *(pass->error);
1176 spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar: %s", save);
1177 efree(save);
1178 }
1179
1180 php_stream_close(newfile);
1181 return FAILURE;
1182 }
1183
1184 entry.filename = ".phar/signature.bin";
1185 entry.filename_len = sizeof(".phar/signature.bin")-1;
1186 entry.fp = php_stream_fopen_tmpfile();
1187 entry.fp_type = PHAR_MOD;
1188 entry.is_modified = 1;
1189 if (entry.fp == NULL) {
1190 spprintf(pass->error, 0, "phar error: unable to create temporary file for signature");
1191 return FAILURE;
1192 }
1193
1194 PHAR_SET_32(sigbuf, phar->sig_flags);
1195 PHAR_SET_32(sigbuf + 4, signature_length);
1196
1197 if (Z_UL(8) != php_stream_write(entry.fp, sigbuf, 8) || signature_length != php_stream_write(entry.fp, signature, signature_length)) {
1198 efree(signature);
1199 if (pass->error) {
1200 spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar %s", phar->fname);
1201 }
1202
1203 php_stream_close(newfile);
1204 return FAILURE;
1205 }
1206
1207 efree(signature);
1208 entry.uncompressed_filesize = entry.compressed_filesize = signature_length + 8;
1209 entry.phar = phar;
1210 /* throw out return value and write the signature */
1211 phar_zip_changed_apply_int(&entry, (void *)pass);
1212 php_stream_close(newfile);
1213
1214 if (pass->error && *(pass->error)) {
1215 /* error is set by writeheaders */
1216 return FAILURE;
1217 }
1218 } /* signature */
1219 return SUCCESS;
1220 }
1221 /* }}} */
1222
phar_zip_flush(phar_archive_data * phar,char * user_stub,zend_long len,int defaultstub,char ** error)1223 int phar_zip_flush(phar_archive_data *phar, char *user_stub, zend_long len, int defaultstub, char **error) /* {{{ */
1224 {
1225 char *pos;
1226 static const char newstub[] = "<?php // zip-based phar archive stub file\n__HALT_COMPILER();";
1227 char halt_stub[] = "__HALT_COMPILER();";
1228
1229 php_stream *stubfile, *oldfile;
1230 int free_user_stub, closeoldfile = 0;
1231 phar_entry_info entry = {0};
1232 char *temperr = NULL;
1233 struct _phar_zip_pass pass;
1234 phar_zip_dir_end eocd;
1235 uint32_t cdir_size, cdir_offset;
1236
1237 pass.error = &temperr;
1238 entry.flags = PHAR_ENT_PERM_DEF_FILE;
1239 entry.timestamp = time(NULL);
1240 entry.is_modified = 1;
1241 entry.is_zip = 1;
1242 entry.phar = phar;
1243 entry.fp_type = PHAR_MOD;
1244
1245 if (phar->is_persistent) {
1246 if (error) {
1247 spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
1248 }
1249 return EOF;
1250 }
1251
1252 if (phar->is_data) {
1253 goto nostub;
1254 }
1255
1256 /* set alias */
1257 if (!phar->is_temporary_alias && phar->alias_len) {
1258 entry.fp = php_stream_fopen_tmpfile();
1259 if (entry.fp == NULL) {
1260 spprintf(error, 0, "phar error: unable to create temporary file");
1261 return EOF;
1262 }
1263 if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) {
1264 if (error) {
1265 spprintf(error, 0, "unable to set alias in zip-based phar \"%s\"", phar->fname);
1266 }
1267 return EOF;
1268 }
1269
1270 entry.uncompressed_filesize = entry.compressed_filesize = phar->alias_len;
1271 entry.filename = estrndup(".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1272 entry.filename_len = sizeof(".phar/alias.txt")-1;
1273
1274 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1275 } else {
1276 zend_hash_str_del(&phar->manifest, ".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1277 }
1278
1279 /* register alias */
1280 if (phar->alias_len) {
1281 if (FAILURE == phar_get_archive(&phar, phar->fname, phar->fname_len, phar->alias, phar->alias_len, error)) {
1282 return EOF;
1283 }
1284 }
1285
1286 /* set stub */
1287 if (user_stub && !defaultstub) {
1288 if (len < 0) {
1289 /* resource passed in */
1290 if (!(php_stream_from_zval_no_verify(stubfile, (zval *)user_stub))) {
1291 if (error) {
1292 spprintf(error, 0, "unable to access resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1293 }
1294 return EOF;
1295 }
1296
1297 if (len == -1) {
1298 len = PHP_STREAM_COPY_ALL;
1299 } else {
1300 len = -len;
1301 }
1302
1303 user_stub = 0;
1304
1305 // TODO: refactor to avoid reallocation ???
1306 //??? len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)
1307 {
1308 zend_string *str = php_stream_copy_to_mem(stubfile, len, 0);
1309 if (str) {
1310 len = ZSTR_LEN(str);
1311 user_stub = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
1312 zend_string_release_ex(str, 0);
1313 } else {
1314 user_stub = NULL;
1315 len = 0;
1316 }
1317 }
1318
1319 if (!len || !user_stub) {
1320 if (error) {
1321 spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1322 }
1323 return EOF;
1324 }
1325 free_user_stub = 1;
1326 } else {
1327 free_user_stub = 0;
1328 }
1329
1330 if ((pos = php_stristr(user_stub, halt_stub, len, sizeof(halt_stub) - 1)) == NULL) {
1331 if (error) {
1332 spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", phar->fname);
1333 }
1334 if (free_user_stub) {
1335 efree(user_stub);
1336 }
1337 return EOF;
1338 }
1339
1340 len = pos - user_stub + 18;
1341 entry.fp = php_stream_fopen_tmpfile();
1342 if (entry.fp == NULL) {
1343 spprintf(error, 0, "phar error: unable to create temporary file");
1344 return EOF;
1345 }
1346 entry.uncompressed_filesize = len + 5;
1347
1348 if ((size_t)len != php_stream_write(entry.fp, user_stub, len)
1349 || 5 != php_stream_write(entry.fp, " ?>\r\n", 5)) {
1350 if (error) {
1351 spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", phar->fname);
1352 }
1353 if (free_user_stub) {
1354 efree(user_stub);
1355 }
1356 php_stream_close(entry.fp);
1357 return EOF;
1358 }
1359
1360 entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1361 entry.filename_len = sizeof(".phar/stub.php")-1;
1362
1363 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1364
1365 if (free_user_stub) {
1366 efree(user_stub);
1367 }
1368 } else {
1369 /* Either this is a brand new phar (add the stub), or the default stub is required (overwrite the stub) */
1370 entry.fp = php_stream_fopen_tmpfile();
1371 if (entry.fp == NULL) {
1372 spprintf(error, 0, "phar error: unable to create temporary file");
1373 return EOF;
1374 }
1375 if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) {
1376 php_stream_close(entry.fp);
1377 if (error) {
1378 spprintf(error, 0, "unable to %s stub in%szip-based phar \"%s\", failed", user_stub ? "overwrite" : "create", user_stub ? " " : " new ", phar->fname);
1379 }
1380 return EOF;
1381 }
1382
1383 entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1;
1384 entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1385 entry.filename_len = sizeof(".phar/stub.php")-1;
1386
1387 if (!defaultstub) {
1388 if (!zend_hash_str_exists(&phar->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
1389 if (NULL == zend_hash_str_add_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info))) {
1390 php_stream_close(entry.fp);
1391 efree(entry.filename);
1392 if (error) {
1393 spprintf(error, 0, "unable to create stub in zip-based phar \"%s\"", phar->fname);
1394 }
1395 return EOF;
1396 }
1397 } else {
1398 php_stream_close(entry.fp);
1399 efree(entry.filename);
1400 }
1401 } else {
1402 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1403 }
1404 }
1405 nostub:
1406 if (phar->fp && !phar->is_brandnew) {
1407 oldfile = phar->fp;
1408 closeoldfile = 0;
1409 php_stream_rewind(oldfile);
1410 } else {
1411 oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL);
1412 closeoldfile = oldfile != NULL;
1413 }
1414
1415 /* save modified files to the zip */
1416 pass.old = oldfile;
1417 pass.filefp = php_stream_fopen_tmpfile();
1418
1419 if (!pass.filefp) {
1420 fperror:
1421 if (closeoldfile) {
1422 php_stream_close(oldfile);
1423 }
1424 if (error) {
1425 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname);
1426 }
1427 return EOF;
1428 }
1429
1430 pass.centralfp = php_stream_fopen_tmpfile();
1431
1432 if (!pass.centralfp) {
1433 goto fperror;
1434 }
1435
1436 pass.free_fp = pass.free_ufp = 1;
1437 memset(&eocd, 0, sizeof(eocd));
1438
1439 memcpy(eocd.signature, "PK\5\6", 4);
1440 if (!phar->is_data && !phar->sig_flags) {
1441 phar->sig_flags = PHAR_SIG_SHA256;
1442 }
1443 if (phar->sig_flags) {
1444 PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest) + 1);
1445 PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest) + 1);
1446 } else {
1447 PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest));
1448 PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest));
1449 }
1450 zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass);
1451
1452 phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
1453 if (temperr) {
1454 if (error) {
1455 spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr);
1456 }
1457 efree(temperr);
1458 temperror:
1459 php_stream_close(pass.centralfp);
1460 nocentralerror:
1461 php_stream_close(pass.filefp);
1462 if (closeoldfile) {
1463 php_stream_close(oldfile);
1464 }
1465 return EOF;
1466 }
1467
1468 if (FAILURE == phar_zip_applysignature(phar, &pass)) {
1469 goto temperror;
1470 }
1471
1472 /* save zip */
1473 cdir_size = php_stream_tell(pass.centralfp);
1474 cdir_offset = php_stream_tell(pass.filefp);
1475 PHAR_SET_32(eocd.cdir_size, cdir_size);
1476 PHAR_SET_32(eocd.cdir_offset, cdir_offset);
1477 php_stream_seek(pass.centralfp, 0, SEEK_SET);
1478
1479 {
1480 size_t clen;
1481 int ret = php_stream_copy_to_stream_ex(pass.centralfp, pass.filefp, PHP_STREAM_COPY_ALL, &clen);
1482 if (SUCCESS != ret || clen != cdir_size) {
1483 if (error) {
1484 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write central-directory", phar->fname);
1485 }
1486 goto temperror;
1487 }
1488 }
1489
1490 php_stream_close(pass.centralfp);
1491
1492 phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
1493 if (phar->metadata_tracker.str) {
1494 /* set phar metadata */
1495 PHAR_SET_16(eocd.comment_len, ZSTR_LEN(phar->metadata_tracker.str));
1496
1497 if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1498 if (error) {
1499 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1500 }
1501 goto nocentralerror;
1502 }
1503
1504 if (ZSTR_LEN(phar->metadata_tracker.str) != php_stream_write(pass.filefp, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str))) {
1505 if (error) {
1506 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write metadata to zip comment", phar->fname);
1507 }
1508 goto nocentralerror;
1509 }
1510 } else {
1511 if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1512 if (error) {
1513 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1514 }
1515 goto nocentralerror;
1516 }
1517 }
1518
1519 if (phar->fp && pass.free_fp) {
1520 php_stream_close(phar->fp);
1521 }
1522
1523 if (phar->ufp) {
1524 if (pass.free_ufp) {
1525 php_stream_close(phar->ufp);
1526 }
1527 phar->ufp = NULL;
1528 }
1529
1530 /* re-open */
1531 phar->is_brandnew = 0;
1532
1533 if (phar->donotflush) {
1534 /* deferred flush */
1535 phar->fp = pass.filefp;
1536 } else {
1537 phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
1538 if (!phar->fp) {
1539 if (closeoldfile) {
1540 php_stream_close(oldfile);
1541 }
1542 phar->fp = pass.filefp;
1543 if (error) {
1544 spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
1545 }
1546 return EOF;
1547 }
1548 php_stream_rewind(pass.filefp);
1549 php_stream_copy_to_stream_ex(pass.filefp, phar->fp, PHP_STREAM_COPY_ALL, NULL);
1550 /* we could also reopen the file in "rb" mode but there is no need for that */
1551 php_stream_close(pass.filefp);
1552 }
1553
1554 if (closeoldfile) {
1555 php_stream_close(oldfile);
1556 }
1557 return EOF;
1558 }
1559 /* }}} */
1560