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