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 php_stream_tell(fp);
467 sigfile = php_stream_fopen_tmpfile();
468 if (!sigfile) {
469 PHAR_ZIP_FAIL("couldn't open temporary file");
470 }
471
472 php_stream_seek(fp, 0, SEEK_SET);
473 /* copy file contents + local headers and zip comment, if any, to be hashed for signature */
474 php_stream_copy_to_stream_ex(fp, sigfile, entry.header_offset, NULL);
475 /* seek to central directory */
476 php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET);
477 /* copy central directory header */
478 php_stream_copy_to_stream_ex(fp, sigfile, beforeus - PHAR_GET_32(locator.cdir_offset), NULL);
479 if (metadata) {
480 php_stream_write(sigfile, metadata, PHAR_GET_16(locator.comment_len));
481 }
482 php_stream_seek(fp, entry.offset, SEEK_SET);
483 sig = (char *) emalloc(entry.uncompressed_filesize);
484 read = php_stream_read(fp, sig, entry.uncompressed_filesize);
485 if (read != entry.uncompressed_filesize || read <= 8) {
486 php_stream_close(sigfile);
487 efree(sig);
488 PHAR_ZIP_FAIL("signature cannot be read");
489 }
490 mydata->sig_flags = PHAR_GET_32(sig);
491 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)) {
492 efree(sig);
493 if (error) {
494 char *save;
495 php_stream_close(sigfile);
496 spprintf(&save, 4096, "signature cannot be verified: %s", *error);
497 efree(*error);
498 PHAR_ZIP_FAIL_FREE(save, save);
499 } else {
500 php_stream_close(sigfile);
501 PHAR_ZIP_FAIL("signature cannot be verified");
502 }
503 }
504 mydata->sig_len = sig_len;
505 php_stream_close(sigfile);
506 efree(sig);
507 /* signature checked out, let's ensure this is the last file in the phar */
508 if (i != PHAR_GET_16(locator.count) - 1) {
509 PHAR_ZIP_FAIL("entries exist after signature, invalid phar");
510 }
511
512 continue;
513 }
514
515 phar_add_virtual_dirs(mydata, entry.filename, entry.filename_len);
516
517 if (PHAR_GET_16(zipentry.extra_len)) {
518 zend_off_t loc = php_stream_tell(fp);
519 if (FAILURE == phar_zip_process_extra(fp, &entry, PHAR_GET_16(zipentry.extra_len))) {
520 pefree(entry.filename, entry.is_persistent);
521 PHAR_ZIP_FAIL("Unable to process extra field header for file in central directory");
522 }
523 php_stream_seek(fp, loc + PHAR_GET_16(zipentry.extra_len), SEEK_SET);
524 }
525
526 switch (PHAR_GET_16(zipentry.compressed)) {
527 case PHAR_ZIP_COMP_NONE :
528 /* compression flag already set */
529 break;
530 case PHAR_ZIP_COMP_DEFLATE :
531 entry.flags |= PHAR_ENT_COMPRESSED_GZ;
532 if (!PHAR_G(has_zlib)) {
533 pefree(entry.filename, entry.is_persistent);
534 PHAR_ZIP_FAIL("zlib extension is required");
535 }
536 break;
537 case PHAR_ZIP_COMP_BZIP2 :
538 entry.flags |= PHAR_ENT_COMPRESSED_BZ2;
539 if (!PHAR_G(has_bz2)) {
540 pefree(entry.filename, entry.is_persistent);
541 PHAR_ZIP_FAIL("bzip2 extension is required");
542 }
543 break;
544 case 1 :
545 pefree(entry.filename, entry.is_persistent);
546 PHAR_ZIP_FAIL("unsupported compression method (Shrunk) used in this zip");
547 case 2 :
548 case 3 :
549 case 4 :
550 case 5 :
551 pefree(entry.filename, entry.is_persistent);
552 PHAR_ZIP_FAIL("unsupported compression method (Reduce) used in this zip");
553 case 6 :
554 pefree(entry.filename, entry.is_persistent);
555 PHAR_ZIP_FAIL("unsupported compression method (Implode) used in this zip");
556 case 7 :
557 pefree(entry.filename, entry.is_persistent);
558 PHAR_ZIP_FAIL("unsupported compression method (Tokenize) used in this zip");
559 case 9 :
560 pefree(entry.filename, entry.is_persistent);
561 PHAR_ZIP_FAIL("unsupported compression method (Deflate64) used in this zip");
562 case 10 :
563 pefree(entry.filename, entry.is_persistent);
564 PHAR_ZIP_FAIL("unsupported compression method (PKWare Implode/old IBM TERSE) used in this zip");
565 case 14 :
566 pefree(entry.filename, entry.is_persistent);
567 PHAR_ZIP_FAIL("unsupported compression method (LZMA) used in this zip");
568 case 18 :
569 pefree(entry.filename, entry.is_persistent);
570 PHAR_ZIP_FAIL("unsupported compression method (IBM TERSE) used in this zip");
571 case 19 :
572 pefree(entry.filename, entry.is_persistent);
573 PHAR_ZIP_FAIL("unsupported compression method (IBM LZ77) used in this zip");
574 case 97 :
575 pefree(entry.filename, entry.is_persistent);
576 PHAR_ZIP_FAIL("unsupported compression method (WavPack) used in this zip");
577 case 98 :
578 pefree(entry.filename, entry.is_persistent);
579 PHAR_ZIP_FAIL("unsupported compression method (PPMd) used in this zip");
580 default :
581 pefree(entry.filename, entry.is_persistent);
582 PHAR_ZIP_FAIL("unsupported compression method (unknown) used in this zip");
583 }
584
585 /* get file metadata */
586 if (PHAR_GET_16(zipentry.comment_len)) {
587 if (PHAR_GET_16(zipentry.comment_len) != php_stream_read(fp, buf, PHAR_GET_16(zipentry.comment_len))) {
588 pefree(entry.filename, entry.is_persistent);
589 PHAR_ZIP_FAIL("unable to read in file comment, truncated");
590 }
591
592 p = buf;
593 phar_parse_metadata_lazy(buf, &entry.metadata_tracker, PHAR_GET_16(zipentry.comment_len), entry.is_persistent);
594 } else {
595 ZVAL_UNDEF(&entry.metadata_tracker.val);
596 }
597
598 if (!actual_alias && entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) {
599 php_stream_filter *filter;
600
601 /* archive alias found */
602
603 /* verify local header */
604 ZEND_ASSERT(!entry.is_dir);
605 if (entry.crc32 != PHAR_GET_32(local.crc32) || entry.uncompressed_filesize != PHAR_GET_32(local.uncompsize) || entry.compressed_filesize != PHAR_GET_32(local.compsize)) {
606 pefree(entry.filename, entry.is_persistent);
607 PHAR_ZIP_FAIL("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)");
608 }
609
610 zend_off_t restore_pos = php_stream_tell(fp);
611 php_stream_seek(fp, entry.offset, SEEK_SET);
612 /* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
613 fp->writepos = 0;
614 fp->readpos = 0;
615 php_stream_seek(fp, entry.offset, SEEK_SET);
616 fp->writepos = 0;
617 fp->readpos = 0;
618 /* the above lines should be for php < 5.2.6 after 5.3 filters are fixed */
619
620 mydata->alias_len = entry.uncompressed_filesize;
621 if (entry.flags & PHAR_ENT_COMPRESSED_GZ) {
622 filter = php_stream_filter_create("zlib.inflate", NULL, php_stream_is_persistent(fp));
623
624 if (!filter) {
625 pefree(entry.filename, entry.is_persistent);
626 PHAR_ZIP_FAIL("unable to decompress alias, zlib filter creation failed");
627 }
628
629 php_stream_filter_append(&fp->readfilters, filter);
630
631 // TODO: refactor to avoid reallocation ???
632 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
633 {
634 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
635 if (str) {
636 entry.uncompressed_filesize = ZSTR_LEN(str);
637 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
638 zend_string_release_ex(str, 0);
639 } else {
640 actual_alias = NULL;
641 entry.uncompressed_filesize = 0;
642 }
643 }
644
645 if (!entry.uncompressed_filesize || !actual_alias) {
646 pefree(entry.filename, entry.is_persistent);
647 PHAR_ZIP_FAIL("unable to read in alias, truncated");
648 }
649
650 php_stream_filter_flush(filter, 1);
651 php_stream_filter_remove(filter, 1);
652
653 } else if (entry.flags & PHAR_ENT_COMPRESSED_BZ2) {
654 filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp));
655
656 if (!filter) {
657 pefree(entry.filename, entry.is_persistent);
658 PHAR_ZIP_FAIL("unable to read in alias, bzip2 filter creation failed");
659 }
660
661 php_stream_filter_append(&fp->readfilters, filter);
662
663 // TODO: refactor to avoid reallocation ???
664 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
665 {
666 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
667 if (str) {
668 entry.uncompressed_filesize = ZSTR_LEN(str);
669 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
670 zend_string_release_ex(str, 0);
671 } else {
672 actual_alias = NULL;
673 entry.uncompressed_filesize = 0;
674 }
675 }
676
677 if (!entry.uncompressed_filesize || !actual_alias) {
678 pefree(entry.filename, entry.is_persistent);
679 PHAR_ZIP_FAIL("unable to read in alias, truncated");
680 }
681
682 php_stream_filter_flush(filter, 1);
683 php_stream_filter_remove(filter, 1);
684 } else {
685 // TODO: refactor to avoid reallocation ???
686 //??? entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
687 {
688 zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
689 if (str) {
690 entry.uncompressed_filesize = ZSTR_LEN(str);
691 actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
692 zend_string_release_ex(str, 0);
693 } else {
694 actual_alias = NULL;
695 entry.uncompressed_filesize = 0;
696 }
697 }
698
699 if (!entry.uncompressed_filesize || !actual_alias) {
700 pefree(entry.filename, entry.is_persistent);
701 PHAR_ZIP_FAIL("unable to read in alias, truncated");
702 }
703 }
704
705 /* return to central directory parsing */
706 php_stream_seek(fp, restore_pos, SEEK_SET);
707 }
708
709 phar_set_inode(&entry);
710 zend_hash_str_add_mem(&mydata->manifest, entry.filename, entry.filename_len, (void *)&entry, sizeof(phar_entry_info));
711 }
712
713 if (zend_hash_str_exists(&(mydata->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
714 mydata->is_data = 0;
715 } else {
716 mydata->is_data = 1;
717 }
718
719 /* ensure signature set */
720 if (!mydata->is_data && PHAR_G(require_hash) && !mydata->signature) {
721 PHAR_ZIP_FAIL("signature is missing");
722 }
723
724 mydata->fp = fp;
725
726 zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata);
727
728 if (actual_alias) {
729 phar_archive_data *fd_ptr;
730
731 if (!phar_validate_alias(actual_alias, mydata->alias_len)) {
732 if (error) {
733 spprintf(error, 4096, "phar error: invalid alias \"%s\" in zip-based phar \"%s\"", actual_alias, fname);
734 }
735 efree(actual_alias);
736 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
737 return FAILURE;
738 }
739
740 mydata->is_temporary_alias = 0;
741
742 if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), actual_alias, mydata->alias_len))) {
743 if (SUCCESS != phar_free_alias(fd_ptr, actual_alias, mydata->alias_len)) {
744 if (error) {
745 spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with implicit alias, alias is already in use", fname);
746 }
747 efree(actual_alias);
748 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
749 return FAILURE;
750 }
751 }
752
753 mydata->alias = entry.is_persistent ? pestrndup(actual_alias, mydata->alias_len, 1) : actual_alias;
754
755 if (entry.is_persistent) {
756 efree(actual_alias);
757 }
758
759 zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), mydata->alias, mydata->alias_len, mydata);
760 } else {
761 phar_archive_data *fd_ptr;
762
763 if (alias_len) {
764 if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) {
765 if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) {
766 if (error) {
767 spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with explicit alias, alias is already in use", fname);
768 }
769 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
770 return FAILURE;
771 }
772 }
773
774 zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata);
775 mydata->alias = pestrndup(alias, alias_len, mydata->is_persistent);
776 mydata->alias_len = alias_len;
777 } else {
778 mydata->alias = pestrndup(mydata->fname, fname_len, mydata->is_persistent);
779 mydata->alias_len = fname_len;
780 }
781
782 mydata->is_temporary_alias = 1;
783 }
784
785 if (pphar) {
786 *pphar = mydata;
787 }
788
789 return SUCCESS;
790 }
791 /* }}} */
792
793 /**
794 * Create or open a zip-based phar for writing
795 */
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 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) /* {{{ */
797 {
798 phar_archive_data *phar;
799 int ret = phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, &phar, error);
800
801 if (FAILURE == ret) {
802 return FAILURE;
803 }
804
805 if (pphar) {
806 *pphar = phar;
807 }
808
809 phar->is_data = is_data;
810
811 if (phar->is_zip) {
812 return ret;
813 }
814
815 if (phar->is_brandnew) {
816 phar->internal_file_start = 0;
817 phar->is_zip = 1;
818 phar->is_tar = 0;
819 return SUCCESS;
820 }
821
822 /* we've reached here - the phar exists and is a regular phar */
823 if (error) {
824 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);
825 }
826
827 return FAILURE;
828 }
829 /* }}} */
830
831 struct _phar_zip_pass {
832 php_stream *filefp;
833 php_stream *centralfp;
834 php_stream *old;
835 int free_fp;
836 int free_ufp;
837 char **error;
838 };
839 /* 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)840 static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ */
841 {
842 phar_zip_file_header local;
843 phar_zip_unix3 perms;
844 phar_zip_central_dir_file central;
845 struct _phar_zip_pass *p;
846 uint32_t newcrc32;
847 zend_off_t offset;
848 int not_really_modified = 0;
849 p = (struct _phar_zip_pass*) arg;
850 uint16_t general_purpose_flags;
851
852 if (entry->is_mounted) {
853 return ZEND_HASH_APPLY_KEEP;
854 }
855
856 if (entry->is_deleted) {
857 if (entry->fp_refcount <= 0) {
858 return ZEND_HASH_APPLY_REMOVE;
859 } else {
860 /* we can't delete this in-memory until it is closed */
861 return ZEND_HASH_APPLY_KEEP;
862 }
863 }
864
865 phar_add_virtual_dirs(entry->phar, entry->filename, entry->filename_len);
866 memset(&local, 0, sizeof(local));
867 memset(¢ral, 0, sizeof(central));
868 memset(&perms, 0, sizeof(perms));
869 memcpy(local.signature, "PK\3\4", 4);
870 memcpy(central.signature, "PK\1\2", 4);
871 PHAR_SET_16(central.extra_len, sizeof(perms));
872 PHAR_SET_16(local.extra_len, sizeof(perms));
873 perms.tag[0] = 'n';
874 perms.tag[1] = 'u';
875 PHAR_SET_16(perms.size, sizeof(perms) - 4);
876 PHAR_SET_16(perms.perms, entry->flags & PHAR_ENT_PERM_MASK);
877 {
878 uint32_t crc = (uint32_t) php_crc32_bulk_init();
879 CRC32(crc, perms.perms[0]);
880 CRC32(crc, perms.perms[1]);
881 PHAR_SET_32(perms.crc32, php_crc32_bulk_end(crc));
882 }
883
884 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
885 PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_DEFLATE);
886 PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_DEFLATE);
887 }
888
889 if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) {
890 PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_BZIP2);
891 PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_BZIP2);
892 }
893
894 /* do not use PHAR_GET_16 on either field of the next line */
895 phar_zip_u2d_time(entry->timestamp, local.timestamp, local.datestamp);
896 memcpy(central.timestamp, local.timestamp, sizeof(local.timestamp));
897 memcpy(central.datestamp, local.datestamp, sizeof(local.datestamp));
898 PHAR_SET_16(central.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
899 PHAR_SET_16(local.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
900 // set language encoding flag (all filenames have to be UTF-8 anyway)
901 general_purpose_flags = PHAR_GET_16(central.flags);
902 PHAR_SET_16(central.flags, general_purpose_flags | (1 << 11));
903 general_purpose_flags = PHAR_GET_16(local.flags);
904 PHAR_SET_16(local.flags, general_purpose_flags | (1 << 11));
905 PHAR_SET_32(central.offset, php_stream_tell(p->filefp));
906
907 /* do extra field for perms later */
908 if (entry->is_modified) {
909 php_stream_filter *filter;
910 php_stream *efp;
911
912 if (entry->is_dir) {
913 entry->is_modified = 0;
914 if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp) {
915 php_stream_close(entry->fp);
916 entry->fp = NULL;
917 entry->fp_type = PHAR_FP;
918 }
919 goto continue_dir;
920 }
921
922 if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
923 spprintf(p->error, 0, "unable to open file contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
924 return ZEND_HASH_APPLY_STOP;
925 }
926
927 /* we can be modified and already be compressed, such as when chmod() is executed */
928 if (entry->flags & PHAR_ENT_COMPRESSION_MASK && (entry->old_flags == entry->flags || !entry->old_flags)) {
929 not_really_modified = 1;
930 goto is_compressed;
931 }
932
933 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
934 spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
935 return ZEND_HASH_APPLY_STOP;
936 }
937
938 efp = phar_get_efp(entry, 0);
939 newcrc32 = php_crc32_bulk_init();
940
941 php_crc32_stream_bulk_update(&newcrc32, efp, entry->uncompressed_filesize);
942
943 entry->crc32 = php_crc32_bulk_end(newcrc32);
944 PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
945 PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
946
947 if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
948 /* not compressed */
949 entry->compressed_filesize = entry->uncompressed_filesize;
950 PHAR_SET_32(central.compsize, entry->uncompressed_filesize);
951 PHAR_SET_32(local.compsize, entry->uncompressed_filesize);
952 goto not_compressed;
953 }
954
955 filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0);
956
957 if (!filter) {
958 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
959 spprintf(p->error, 0, "unable to gzip compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
960 } else {
961 spprintf(p->error, 0, "unable to bzip2 compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
962 }
963 return ZEND_HASH_APPLY_STOP;
964 }
965
966 /* create new file that holds the compressed version */
967 /* work around inability to specify freedom in write and strictness
968 in read count */
969 entry->cfp = php_stream_fopen_tmpfile();
970
971 if (!entry->cfp) {
972 spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
973 return ZEND_HASH_APPLY_STOP;
974 }
975
976 php_stream_flush(efp);
977
978 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
979 spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
980 return ZEND_HASH_APPLY_STOP;
981 }
982
983 php_stream_filter_append((&entry->cfp->writefilters), filter);
984
985 if (SUCCESS != php_stream_copy_to_stream_ex(efp, entry->cfp, entry->uncompressed_filesize, NULL)) {
986 spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname);
987 return ZEND_HASH_APPLY_STOP;
988 }
989
990 php_stream_filter_flush(filter, 1);
991 php_stream_flush(entry->cfp);
992 php_stream_filter_remove(filter, 1);
993 php_stream_seek(entry->cfp, 0, SEEK_END);
994 entry->compressed_filesize = (uint32_t) php_stream_tell(entry->cfp);
995 PHAR_SET_32(central.compsize, entry->compressed_filesize);
996 PHAR_SET_32(local.compsize, entry->compressed_filesize);
997 /* generate crc on compressed file */
998 php_stream_rewind(entry->cfp);
999 entry->old_flags = entry->flags;
1000 entry->is_modified = 1;
1001 } else {
1002 is_compressed:
1003 PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
1004 PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
1005 PHAR_SET_32(central.compsize, entry->compressed_filesize);
1006 PHAR_SET_32(local.compsize, entry->compressed_filesize);
1007 if (p->old) {
1008 if (-1 == php_stream_seek(p->old, entry->offset_abs, SEEK_SET)) {
1009 spprintf(p->error, 0, "unable to seek to start of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1010 return ZEND_HASH_APPLY_STOP;
1011 }
1012 }
1013 }
1014 not_compressed:
1015 PHAR_SET_32(central.crc32, entry->crc32);
1016 PHAR_SET_32(local.crc32, entry->crc32);
1017 continue_dir:
1018 /* set file metadata */
1019 if (phar_metadata_tracker_has_data(&entry->metadata_tracker, entry->is_persistent)) {
1020 phar_metadata_tracker_try_ensure_has_serialized_data(&entry->metadata_tracker, entry->is_persistent);
1021 PHAR_SET_16(central.comment_len, entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0);
1022 }
1023
1024 entry->header_offset = php_stream_tell(p->filefp);
1025 offset = entry->header_offset + sizeof(local) + entry->filename_len + (entry->is_dir ? 1 : 0) + sizeof(perms);
1026
1027 if (sizeof(local) != php_stream_write(p->filefp, (char *)&local, sizeof(local))) {
1028 spprintf(p->error, 0, "unable to write local file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1029 return ZEND_HASH_APPLY_STOP;
1030 }
1031
1032 if (sizeof(central) != php_stream_write(p->centralfp, (char *)¢ral, sizeof(central))) {
1033 spprintf(p->error, 0, "unable to write central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1034 return ZEND_HASH_APPLY_STOP;
1035 }
1036
1037 if (entry->is_dir) {
1038 if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
1039 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);
1040 return ZEND_HASH_APPLY_STOP;
1041 }
1042
1043 if (1 != php_stream_write(p->filefp, "/", 1)) {
1044 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);
1045 return ZEND_HASH_APPLY_STOP;
1046 }
1047
1048 if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1049 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);
1050 return ZEND_HASH_APPLY_STOP;
1051 }
1052
1053 if (1 != php_stream_write(p->centralfp, "/", 1)) {
1054 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);
1055 return ZEND_HASH_APPLY_STOP;
1056 }
1057 } else {
1058 if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
1059 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);
1060 return ZEND_HASH_APPLY_STOP;
1061 }
1062
1063 if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1064 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);
1065 return ZEND_HASH_APPLY_STOP;
1066 }
1067 }
1068
1069 if (sizeof(perms) != php_stream_write(p->filefp, (char *)&perms, sizeof(perms))) {
1070 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);
1071 return ZEND_HASH_APPLY_STOP;
1072 }
1073
1074 if (sizeof(perms) != php_stream_write(p->centralfp, (char *)&perms, sizeof(perms))) {
1075 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);
1076 return ZEND_HASH_APPLY_STOP;
1077 }
1078
1079 if (!not_really_modified && entry->is_modified) {
1080 if (entry->cfp) {
1081 if (SUCCESS != php_stream_copy_to_stream_ex(entry->cfp, p->filefp, entry->compressed_filesize, NULL)) {
1082 spprintf(p->error, 0, "unable to write compressed contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1083 return ZEND_HASH_APPLY_STOP;
1084 }
1085
1086 php_stream_close(entry->cfp);
1087 entry->cfp = NULL;
1088 } else {
1089 if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
1090 return ZEND_HASH_APPLY_STOP;
1091 }
1092
1093 phar_seek_efp(entry, 0, SEEK_SET, 0, 0);
1094
1095 if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), p->filefp, entry->uncompressed_filesize, NULL)) {
1096 spprintf(p->error, 0, "unable to write contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1097 return ZEND_HASH_APPLY_STOP;
1098 }
1099 }
1100
1101 if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp && entry->fp_refcount == 0) {
1102 php_stream_close(entry->fp);
1103 }
1104
1105 entry->is_modified = 0;
1106 } else {
1107 entry->is_modified = 0;
1108 if (entry->fp_refcount) {
1109 /* open file pointers refer to this fp, do not free the stream */
1110 switch (entry->fp_type) {
1111 case PHAR_FP:
1112 p->free_fp = 0;
1113 break;
1114 case PHAR_UFP:
1115 p->free_ufp = 0;
1116 default:
1117 break;
1118 }
1119 }
1120
1121 if (!entry->is_dir && entry->compressed_filesize && SUCCESS != php_stream_copy_to_stream_ex(p->old, p->filefp, entry->compressed_filesize, NULL)) {
1122 spprintf(p->error, 0, "unable to copy contents of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1123 return ZEND_HASH_APPLY_STOP;
1124 }
1125 }
1126
1127 entry->fp = NULL;
1128 entry->offset = entry->offset_abs = offset;
1129 entry->fp_type = PHAR_FP;
1130
1131 if (entry->metadata_tracker.str) {
1132 if (ZSTR_LEN(entry->metadata_tracker.str) != php_stream_write(p->centralfp, ZSTR_VAL(entry->metadata_tracker.str), ZSTR_LEN(entry->metadata_tracker.str))) {
1133 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);
1134 return ZEND_HASH_APPLY_STOP;
1135 }
1136 }
1137
1138 return ZEND_HASH_APPLY_KEEP;
1139 }
1140 /* }}} */
1141
phar_zip_changed_apply(zval * zv,void * arg)1142 static int phar_zip_changed_apply(zval *zv, void *arg) /* {{{ */
1143 {
1144 return phar_zip_changed_apply_int(Z_PTR_P(zv), arg);
1145 }
1146 /* }}} */
1147
phar_zip_applysignature(phar_archive_data * phar,struct _phar_zip_pass * pass)1148 static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass) /* {{{ */
1149 {
1150 /* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */
1151 if (!phar->is_data || phar->sig_flags) {
1152 size_t signature_length;
1153 char *signature, sigbuf[8];
1154 phar_entry_info entry = {0};
1155 php_stream *newfile;
1156 zend_off_t tell;
1157
1158 newfile = php_stream_fopen_tmpfile();
1159 if (newfile == NULL) {
1160 spprintf(pass->error, 0, "phar error: unable to create temporary file for the signature file");
1161 return FAILURE;
1162 }
1163 tell = php_stream_tell(pass->filefp);
1164 /* copy the local files, central directory, and the zip comment to generate the hash */
1165 php_stream_seek(pass->filefp, 0, SEEK_SET);
1166 php_stream_copy_to_stream_ex(pass->filefp, newfile, tell, NULL);
1167 tell = php_stream_tell(pass->centralfp);
1168 php_stream_seek(pass->centralfp, 0, SEEK_SET);
1169 php_stream_copy_to_stream_ex(pass->centralfp, newfile, tell, NULL);
1170 if (phar->metadata_tracker.str) {
1171 php_stream_write(newfile, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
1172 }
1173
1174 if (FAILURE == phar_create_signature(phar, newfile, &signature, &signature_length, pass->error)) {
1175 if (pass->error) {
1176 char *save = *(pass->error);
1177 spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar: %s", save);
1178 efree(save);
1179 }
1180
1181 php_stream_close(newfile);
1182 return FAILURE;
1183 }
1184
1185 entry.filename = ".phar/signature.bin";
1186 entry.filename_len = sizeof(".phar/signature.bin")-1;
1187 entry.fp = php_stream_fopen_tmpfile();
1188 entry.fp_type = PHAR_MOD;
1189 entry.is_modified = 1;
1190 if (entry.fp == NULL) {
1191 spprintf(pass->error, 0, "phar error: unable to create temporary file for signature");
1192 return FAILURE;
1193 }
1194
1195 PHAR_SET_32(sigbuf, phar->sig_flags);
1196 PHAR_SET_32(sigbuf + 4, signature_length);
1197
1198 if (Z_UL(8) != php_stream_write(entry.fp, sigbuf, 8) || signature_length != php_stream_write(entry.fp, signature, signature_length)) {
1199 efree(signature);
1200 if (pass->error) {
1201 spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar %s", phar->fname);
1202 }
1203
1204 php_stream_close(newfile);
1205 return FAILURE;
1206 }
1207
1208 efree(signature);
1209 entry.uncompressed_filesize = entry.compressed_filesize = signature_length + 8;
1210 entry.phar = phar;
1211 /* throw out return value and write the signature */
1212 phar_zip_changed_apply_int(&entry, (void *)pass);
1213 php_stream_close(newfile);
1214
1215 if (pass->error && *(pass->error)) {
1216 /* error is set by writeheaders */
1217 return FAILURE;
1218 }
1219 } /* signature */
1220 return SUCCESS;
1221 }
1222 /* }}} */
1223
phar_zip_flush(phar_archive_data * phar,char * user_stub,zend_long len,int defaultstub,char ** error)1224 int phar_zip_flush(phar_archive_data *phar, char *user_stub, zend_long len, int defaultstub, char **error) /* {{{ */
1225 {
1226 char *pos;
1227 static const char newstub[] = "<?php // zip-based phar archive stub file\n__HALT_COMPILER();";
1228 char halt_stub[] = "__HALT_COMPILER();";
1229
1230 php_stream *stubfile, *oldfile;
1231 int free_user_stub, closeoldfile = 0;
1232 phar_entry_info entry = {0};
1233 char *temperr = NULL;
1234 struct _phar_zip_pass pass;
1235 phar_zip_dir_end eocd;
1236 uint32_t cdir_size, cdir_offset;
1237
1238 pass.error = &temperr;
1239 entry.flags = PHAR_ENT_PERM_DEF_FILE;
1240 entry.timestamp = time(NULL);
1241 entry.is_modified = 1;
1242 entry.is_zip = 1;
1243 entry.phar = phar;
1244 entry.fp_type = PHAR_MOD;
1245
1246 if (phar->is_persistent) {
1247 if (error) {
1248 spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
1249 }
1250 return EOF;
1251 }
1252
1253 if (phar->is_data) {
1254 goto nostub;
1255 }
1256
1257 /* set alias */
1258 if (!phar->is_temporary_alias && phar->alias_len) {
1259 entry.fp = php_stream_fopen_tmpfile();
1260 if (entry.fp == NULL) {
1261 spprintf(error, 0, "phar error: unable to create temporary file");
1262 return EOF;
1263 }
1264 if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) {
1265 if (error) {
1266 spprintf(error, 0, "unable to set alias in zip-based phar \"%s\"", phar->fname);
1267 }
1268 return EOF;
1269 }
1270
1271 entry.uncompressed_filesize = entry.compressed_filesize = phar->alias_len;
1272 entry.filename = estrndup(".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1273 entry.filename_len = sizeof(".phar/alias.txt")-1;
1274
1275 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1276 } else {
1277 zend_hash_str_del(&phar->manifest, ".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1278 }
1279
1280 /* register alias */
1281 if (phar->alias_len) {
1282 if (FAILURE == phar_get_archive(&phar, phar->fname, phar->fname_len, phar->alias, phar->alias_len, error)) {
1283 return EOF;
1284 }
1285 }
1286
1287 /* set stub */
1288 if (user_stub && !defaultstub) {
1289 if (len < 0) {
1290 /* resource passed in */
1291 if (!(php_stream_from_zval_no_verify(stubfile, (zval *)user_stub))) {
1292 if (error) {
1293 spprintf(error, 0, "unable to access resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1294 }
1295 return EOF;
1296 }
1297
1298 if (len == -1) {
1299 len = PHP_STREAM_COPY_ALL;
1300 } else {
1301 len = -len;
1302 }
1303
1304 user_stub = 0;
1305
1306 // TODO: refactor to avoid reallocation ???
1307 //??? len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)
1308 {
1309 zend_string *str = php_stream_copy_to_mem(stubfile, len, 0);
1310 if (str) {
1311 len = ZSTR_LEN(str);
1312 user_stub = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
1313 zend_string_release_ex(str, 0);
1314 } else {
1315 user_stub = NULL;
1316 len = 0;
1317 }
1318 }
1319
1320 if (!len || !user_stub) {
1321 if (error) {
1322 spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1323 }
1324 return EOF;
1325 }
1326 free_user_stub = 1;
1327 } else {
1328 free_user_stub = 0;
1329 }
1330
1331 if ((pos = php_stristr(user_stub, halt_stub, len, sizeof(halt_stub) - 1)) == NULL) {
1332 if (error) {
1333 spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", phar->fname);
1334 }
1335 if (free_user_stub) {
1336 efree(user_stub);
1337 }
1338 return EOF;
1339 }
1340
1341 len = pos - user_stub + 18;
1342 entry.fp = php_stream_fopen_tmpfile();
1343 if (entry.fp == NULL) {
1344 spprintf(error, 0, "phar error: unable to create temporary file");
1345 return EOF;
1346 }
1347 entry.uncompressed_filesize = len + 5;
1348
1349 if ((size_t)len != php_stream_write(entry.fp, user_stub, len)
1350 || 5 != php_stream_write(entry.fp, " ?>\r\n", 5)) {
1351 if (error) {
1352 spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", phar->fname);
1353 }
1354 if (free_user_stub) {
1355 efree(user_stub);
1356 }
1357 php_stream_close(entry.fp);
1358 return EOF;
1359 }
1360
1361 entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1362 entry.filename_len = sizeof(".phar/stub.php")-1;
1363
1364 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1365
1366 if (free_user_stub) {
1367 efree(user_stub);
1368 }
1369 } else {
1370 /* Either this is a brand new phar (add the stub), or the default stub is required (overwrite the stub) */
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 if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) {
1377 php_stream_close(entry.fp);
1378 if (error) {
1379 spprintf(error, 0, "unable to %s stub in%szip-based phar \"%s\", failed", user_stub ? "overwrite" : "create", user_stub ? " " : " new ", phar->fname);
1380 }
1381 return EOF;
1382 }
1383
1384 entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1;
1385 entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1386 entry.filename_len = sizeof(".phar/stub.php")-1;
1387
1388 if (!defaultstub) {
1389 if (!zend_hash_str_exists(&phar->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
1390 if (NULL == zend_hash_str_add_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info))) {
1391 php_stream_close(entry.fp);
1392 efree(entry.filename);
1393 if (error) {
1394 spprintf(error, 0, "unable to create stub in zip-based phar \"%s\"", phar->fname);
1395 }
1396 return EOF;
1397 }
1398 } else {
1399 php_stream_close(entry.fp);
1400 efree(entry.filename);
1401 }
1402 } else {
1403 zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1404 }
1405 }
1406 nostub:
1407 if (phar->fp && !phar->is_brandnew) {
1408 oldfile = phar->fp;
1409 closeoldfile = 0;
1410 php_stream_rewind(oldfile);
1411 } else {
1412 oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL);
1413 closeoldfile = oldfile != NULL;
1414 }
1415
1416 /* save modified files to the zip */
1417 pass.old = oldfile;
1418 pass.filefp = php_stream_fopen_tmpfile();
1419
1420 if (!pass.filefp) {
1421 fperror:
1422 if (closeoldfile) {
1423 php_stream_close(oldfile);
1424 }
1425 if (error) {
1426 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname);
1427 }
1428 return EOF;
1429 }
1430
1431 pass.centralfp = php_stream_fopen_tmpfile();
1432
1433 if (!pass.centralfp) {
1434 goto fperror;
1435 }
1436
1437 pass.free_fp = pass.free_ufp = 1;
1438 memset(&eocd, 0, sizeof(eocd));
1439
1440 memcpy(eocd.signature, "PK\5\6", 4);
1441 if (!phar->is_data && !phar->sig_flags) {
1442 phar->sig_flags = PHAR_SIG_SHA256;
1443 }
1444 if (phar->sig_flags) {
1445 PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest) + 1);
1446 PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest) + 1);
1447 } else {
1448 PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest));
1449 PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest));
1450 }
1451 zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass);
1452
1453 phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
1454 if (temperr) {
1455 if (error) {
1456 spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr);
1457 }
1458 efree(temperr);
1459 temperror:
1460 php_stream_close(pass.centralfp);
1461 nocentralerror:
1462 php_stream_close(pass.filefp);
1463 if (closeoldfile) {
1464 php_stream_close(oldfile);
1465 }
1466 return EOF;
1467 }
1468
1469 if (FAILURE == phar_zip_applysignature(phar, &pass)) {
1470 goto temperror;
1471 }
1472
1473 /* save zip */
1474 cdir_size = php_stream_tell(pass.centralfp);
1475 cdir_offset = php_stream_tell(pass.filefp);
1476 PHAR_SET_32(eocd.cdir_size, cdir_size);
1477 PHAR_SET_32(eocd.cdir_offset, cdir_offset);
1478 php_stream_seek(pass.centralfp, 0, SEEK_SET);
1479
1480 {
1481 size_t clen;
1482 int ret = php_stream_copy_to_stream_ex(pass.centralfp, pass.filefp, PHP_STREAM_COPY_ALL, &clen);
1483 if (SUCCESS != ret || clen != cdir_size) {
1484 if (error) {
1485 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write central-directory", phar->fname);
1486 }
1487 goto temperror;
1488 }
1489 }
1490
1491 php_stream_close(pass.centralfp);
1492
1493 phar_metadata_tracker_try_ensure_has_serialized_data(&phar->metadata_tracker, phar->is_persistent);
1494 if (phar->metadata_tracker.str) {
1495 /* set phar metadata */
1496 PHAR_SET_16(eocd.comment_len, ZSTR_LEN(phar->metadata_tracker.str));
1497
1498 if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1499 if (error) {
1500 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1501 }
1502 goto nocentralerror;
1503 }
1504
1505 if (ZSTR_LEN(phar->metadata_tracker.str) != php_stream_write(pass.filefp, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str))) {
1506 if (error) {
1507 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write metadata to zip comment", phar->fname);
1508 }
1509 goto nocentralerror;
1510 }
1511 } else {
1512 if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1513 if (error) {
1514 spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1515 }
1516 goto nocentralerror;
1517 }
1518 }
1519
1520 if (phar->fp && pass.free_fp) {
1521 php_stream_close(phar->fp);
1522 }
1523
1524 if (phar->ufp) {
1525 if (pass.free_ufp) {
1526 php_stream_close(phar->ufp);
1527 }
1528 phar->ufp = NULL;
1529 }
1530
1531 /* re-open */
1532 phar->is_brandnew = 0;
1533
1534 if (phar->donotflush) {
1535 /* deferred flush */
1536 phar->fp = pass.filefp;
1537 } else {
1538 phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
1539 if (!phar->fp) {
1540 if (closeoldfile) {
1541 php_stream_close(oldfile);
1542 }
1543 phar->fp = pass.filefp;
1544 if (error) {
1545 spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
1546 }
1547 return EOF;
1548 }
1549 php_stream_rewind(pass.filefp);
1550 php_stream_copy_to_stream_ex(pass.filefp, phar->fp, PHP_STREAM_COPY_ALL, NULL);
1551 /* we could also reopen the file in "rb" mode but there is no need for that */
1552 php_stream_close(pass.filefp);
1553 }
1554
1555 if (closeoldfile) {
1556 php_stream_close(oldfile);
1557 }
1558 return EOF;
1559 }
1560 /* }}} */
1561