1 /*
2 +----------------------------------------------------------------------+
3 | phar php single-file executable PHP extension |
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 | Marcus Boerger <helly@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 #define PHAR_MAIN 1
21 #include "phar_internal.h"
22 #include "php_phar.h"
23 #include "SAPI.h"
24 #include "func_interceptors.h"
25 #include "ext/standard/crc32.h"
26 #include "ext/standard/php_var.h"
27 #include "ext/standard/php_string.h" /* For php_stristr() */
28 #include "ext/standard/info.h"
29 #include "zend_smart_str.h"
30
31 static void destroy_phar_data(zval *zv);
32
33 ZEND_DECLARE_MODULE_GLOBALS(phar)
34 static zend_string *(*phar_save_resolve_path)(zend_string *filename);
35
36 /**
37 * set's phar->is_writeable based on the current INI value
38 */
phar_set_writeable_bit(zval * zv,void * argument)39 static int phar_set_writeable_bit(zval *zv, void *argument) /* {{{ */
40 {
41 bool keep = *(bool *)argument;
42 phar_archive_data *phar = (phar_archive_data *)Z_PTR_P(zv);
43
44 if (!phar->is_data) {
45 phar->is_writeable = !keep;
46 }
47
48 return ZEND_HASH_APPLY_KEEP;
49 }
50 /* }}} */
51
52 /* if the original value is 0 (disabled), then allow setting/unsetting at will. Otherwise only allow 1 (enabled), and error on disabling */
ZEND_INI_MH(phar_ini_modify_handler)53 ZEND_INI_MH(phar_ini_modify_handler) /* {{{ */
54 {
55 bool old, ini;
56
57 if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) {
58 old = PHAR_G(readonly_orig);
59 } else {
60 old = PHAR_G(require_hash_orig);
61 }
62
63 ini = zend_ini_parse_bool(new_value);
64
65 /* do not allow unsetting in runtime */
66 if (stage == ZEND_INI_STAGE_STARTUP) {
67 if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) {
68 PHAR_G(readonly_orig) = ini;
69 } else {
70 PHAR_G(require_hash_orig) = ini;
71 }
72 } else if (old && !ini) {
73 return FAILURE;
74 }
75
76 if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) {
77 PHAR_G(readonly) = ini;
78 if (PHAR_G(request_init) && HT_IS_INITIALIZED(&PHAR_G(phar_fname_map))) {
79 zend_hash_apply_with_argument(&(PHAR_G(phar_fname_map)), phar_set_writeable_bit, (void *)&ini);
80 }
81 } else {
82 PHAR_G(require_hash) = ini;
83 }
84
85 return SUCCESS;
86 }
87 /* }}}*/
88
89 /* this global stores the global cached pre-parsed manifests */
90 HashTable cached_phars;
91 HashTable cached_alias;
92
phar_split_cache_list(void)93 static void phar_split_cache_list(void) /* {{{ */
94 {
95 char *tmp;
96 char *key, *lasts, *end;
97 char ds[2];
98 phar_archive_data *phar;
99 uint32_t i = 0;
100
101 if (!PHAR_G(cache_list) || !(PHAR_G(cache_list)[0])) {
102 return;
103 }
104
105 ds[0] = DEFAULT_DIR_SEPARATOR;
106 ds[1] = '\0';
107 tmp = estrdup(PHAR_G(cache_list));
108
109 /* fake request startup */
110 PHAR_G(request_init) = 1;
111 zend_init_rsrc_list();
112 EG(regular_list).nNextFreeElement=1; /* we don't want resource id 0 */
113
114 PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1);
115 PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1);
116 /* these two are dummies and will be destroyed later */
117 zend_hash_init(&cached_phars, sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1);
118 zend_hash_init(&cached_alias, sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1);
119 /* these two are real and will be copied over cached_phars/cached_alias later */
120 zend_hash_init(&(PHAR_G(phar_fname_map)), sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1);
121 zend_hash_init(&(PHAR_G(phar_alias_map)), sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1);
122 PHAR_G(manifest_cached) = 1;
123 PHAR_G(persist) = 1;
124
125 for (key = php_strtok_r(tmp, ds, &lasts);
126 key;
127 key = php_strtok_r(NULL, ds, &lasts)) {
128 size_t len;
129 end = strchr(key, DEFAULT_DIR_SEPARATOR);
130 if (end) {
131 len = end - key;
132 } else {
133 len = strlen(key);
134 }
135
136 if (SUCCESS == phar_open_from_filename(key, len, NULL, 0, 0, &phar, NULL)) {
137 phar->phar_pos = i++;
138 php_stream_close(phar->fp);
139 phar->fp = NULL;
140 } else {
141 PHAR_G(persist) = 0;
142 PHAR_G(manifest_cached) = 0;
143 efree(tmp);
144 zend_hash_destroy(&(PHAR_G(phar_fname_map)));
145 HT_INVALIDATE(&PHAR_G(phar_fname_map));
146 zend_hash_destroy(&(PHAR_G(phar_alias_map)));
147 HT_INVALIDATE(&PHAR_G(phar_alias_map));
148 zend_hash_destroy(&cached_phars);
149 zend_hash_destroy(&cached_alias);
150 zend_hash_graceful_reverse_destroy(&EG(regular_list));
151 memset(&EG(regular_list), 0, sizeof(HashTable));
152 /* free cached manifests */
153 PHAR_G(request_init) = 0;
154 return;
155 }
156 }
157
158 PHAR_G(persist) = 0;
159 PHAR_G(request_init) = 0;
160 /* destroy dummy values from before */
161 zend_hash_destroy(&cached_phars);
162 zend_hash_destroy(&cached_alias);
163 cached_phars = PHAR_G(phar_fname_map);
164 cached_alias = PHAR_G(phar_alias_map);
165 HT_INVALIDATE(&PHAR_G(phar_fname_map));
166 HT_INVALIDATE(&PHAR_G(phar_alias_map));
167 zend_hash_graceful_reverse_destroy(&EG(regular_list));
168 memset(&EG(regular_list), 0, sizeof(HashTable));
169 efree(tmp);
170 }
171 /* }}} */
172
ZEND_INI_MH(phar_ini_cache_list)173 ZEND_INI_MH(phar_ini_cache_list) /* {{{ */
174 {
175 PHAR_G(cache_list) = ZSTR_VAL(new_value);
176
177 if (stage == ZEND_INI_STAGE_STARTUP) {
178 phar_split_cache_list();
179 }
180
181 return SUCCESS;
182 }
183 /* }}} */
184
185 PHP_INI_BEGIN()
186 STD_PHP_INI_BOOLEAN("phar.readonly", "1", PHP_INI_ALL, phar_ini_modify_handler, readonly, zend_phar_globals, phar_globals)
187 STD_PHP_INI_BOOLEAN("phar.require_hash", "1", PHP_INI_ALL, phar_ini_modify_handler, require_hash, zend_phar_globals, phar_globals)
188 STD_PHP_INI_ENTRY("phar.cache_list", "", PHP_INI_SYSTEM, phar_ini_cache_list, cache_list, zend_phar_globals, phar_globals)
PHP_INI_END()189 PHP_INI_END()
190
191 /**
192 * When all uses of a phar have been concluded, this frees the manifest
193 * and the phar slot
194 */
195 void phar_destroy_phar_data(phar_archive_data *phar) /* {{{ */
196 {
197 if (phar->alias && phar->alias != phar->fname) {
198 pefree(phar->alias, phar->is_persistent);
199 phar->alias = NULL;
200 }
201
202 if (phar->fname) {
203 pefree(phar->fname, phar->is_persistent);
204 phar->fname = NULL;
205 }
206
207 if (phar->signature) {
208 pefree(phar->signature, phar->is_persistent);
209 phar->signature = NULL;
210 }
211
212 if (HT_IS_INITIALIZED(&phar->manifest)) {
213 zend_hash_destroy(&phar->manifest);
214 HT_INVALIDATE(&phar->manifest);
215 }
216
217 if (HT_IS_INITIALIZED(&phar->mounted_dirs)) {
218 zend_hash_destroy(&phar->mounted_dirs);
219 HT_INVALIDATE(&phar->mounted_dirs);
220 }
221
222 if (HT_IS_INITIALIZED(&phar->virtual_dirs)) {
223 zend_hash_destroy(&phar->virtual_dirs);
224 HT_INVALIDATE(&phar->virtual_dirs);
225 }
226
227 phar_metadata_tracker_free(&phar->metadata_tracker, phar->is_persistent);
228
229 if (phar->fp) {
230 php_stream_close(phar->fp);
231 phar->fp = 0;
232 }
233
234 if (phar->ufp) {
235 php_stream_close(phar->ufp);
236 phar->ufp = 0;
237 }
238
239 pefree(phar, phar->is_persistent);
240 }
241 /* }}}*/
242
243 /**
244 * Delete refcount and destruct if needed. On destruct return 1 else 0.
245 */
phar_archive_delref(phar_archive_data * phar)246 bool phar_archive_delref(phar_archive_data *phar) /* {{{ */
247 {
248 if (phar->is_persistent) {
249 return 0;
250 }
251
252 if (--phar->refcount < 0) {
253 if (PHAR_G(request_done)
254 || zend_hash_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) {
255 phar_destroy_phar_data(phar);
256 }
257 return 1;
258 } else if (!phar->refcount) {
259 /* invalidate phar cache */
260 PHAR_G(last_phar) = NULL;
261 PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL;
262
263 if (phar->fp && (!(phar->flags & PHAR_FILE_COMPRESSION_MASK) || !phar->alias)) {
264 /* close open file handle - allows removal or rename of
265 the file on windows, which has greedy locking
266 only close if the archive was not already compressed. If it
267 was compressed, then the fp does not refer to the original file.
268 We're also closing compressed files to save resources,
269 but only if the archive isn't aliased. */
270 php_stream_close(phar->fp);
271 phar->fp = NULL;
272 }
273
274 if (!zend_hash_num_elements(&phar->manifest)) {
275 /* this is a new phar that has perhaps had an alias/metadata set, but has never
276 been flushed */
277 if (zend_hash_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) {
278 phar_destroy_phar_data(phar);
279 }
280 return 1;
281 }
282 }
283 return 0;
284 }
285 /* }}}*/
286
287 /**
288 * Destroy phar's in shutdown, here we don't care about aliases
289 */
destroy_phar_data_only(zval * zv)290 static void destroy_phar_data_only(zval *zv) /* {{{ */
291 {
292 phar_archive_data *phar_data = (phar_archive_data *) Z_PTR_P(zv);
293
294 if (EG(exception) || --phar_data->refcount < 0) {
295 phar_destroy_phar_data(phar_data);
296 }
297 }
298 /* }}}*/
299
300 /**
301 * Delete aliases to phar's that got kicked out of the global table
302 */
phar_unalias_apply(zval * zv,void * argument)303 static int phar_unalias_apply(zval *zv, void *argument) /* {{{ */
304 {
305 return Z_PTR_P(zv) == argument ? ZEND_HASH_APPLY_REMOVE : ZEND_HASH_APPLY_KEEP;
306 }
307 /* }}} */
308
309 /**
310 * Delete aliases to phar's that got kicked out of the global table
311 */
phar_tmpclose_apply(zval * zv)312 static int phar_tmpclose_apply(zval *zv) /* {{{ */
313 {
314 phar_entry_info *entry = (phar_entry_info *) Z_PTR_P(zv);
315
316 if (entry->fp_type != PHAR_TMP) {
317 return ZEND_HASH_APPLY_KEEP;
318 }
319
320 if (entry->fp && !entry->fp_refcount) {
321 php_stream_close(entry->fp);
322 entry->fp = NULL;
323 }
324
325 return ZEND_HASH_APPLY_KEEP;
326 }
327 /* }}} */
328
329 /**
330 * Filename map destructor
331 */
destroy_phar_data(zval * zv)332 static void destroy_phar_data(zval *zv) /* {{{ */
333 {
334 phar_archive_data *phar_data = (phar_archive_data *)Z_PTR_P(zv);
335
336 if (PHAR_G(request_ends)) {
337 /* first, iterate over the manifest and close all PHAR_TMP entry fp handles,
338 this prevents unnecessary unfreed stream resources */
339 zend_hash_apply(&(phar_data->manifest), phar_tmpclose_apply);
340 destroy_phar_data_only(zv);
341 return;
342 }
343
344 zend_hash_apply_with_argument(&(PHAR_G(phar_alias_map)), phar_unalias_apply, phar_data);
345
346 if (--phar_data->refcount < 0) {
347 phar_destroy_phar_data(phar_data);
348 }
349 }
350 /* }}}*/
351
352 /**
353 * destructor for the manifest hash, frees each file's entry
354 */
destroy_phar_manifest_entry_int(phar_entry_info * entry)355 void destroy_phar_manifest_entry_int(phar_entry_info *entry) /* {{{ */
356 {
357
358 if (entry->cfp) {
359 php_stream_close(entry->cfp);
360 entry->cfp = 0;
361 }
362
363 if (entry->fp) {
364 php_stream_close(entry->fp);
365 entry->fp = 0;
366 }
367
368 phar_metadata_tracker_free(&entry->metadata_tracker, entry->is_persistent);
369
370 pefree(entry->filename, entry->is_persistent);
371
372 if (entry->link) {
373 pefree(entry->link, entry->is_persistent);
374 entry->link = 0;
375 }
376
377 if (entry->tmp) {
378 pefree(entry->tmp, entry->is_persistent);
379 entry->tmp = 0;
380 }
381 }
382 /* }}} */
383
destroy_phar_manifest_entry(zval * zv)384 void destroy_phar_manifest_entry(zval *zv) /* {{{ */
385 {
386 phar_entry_info *entry = Z_PTR_P(zv);
387 destroy_phar_manifest_entry_int(entry);
388 pefree(entry, entry->is_persistent);
389 }
390 /* }}} */
391
phar_entry_delref(phar_entry_data * idata)392 void phar_entry_delref(phar_entry_data *idata) /* {{{ */
393 {
394 if (idata->internal_file && !idata->internal_file->is_persistent) {
395 if (--idata->internal_file->fp_refcount < 0) {
396 idata->internal_file->fp_refcount = 0;
397 }
398
399 if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) {
400 php_stream_close(idata->fp);
401 }
402 /* if phar_get_or_create_entry_data returns a sub-directory, we have to free it */
403 if (idata->internal_file->is_temp_dir) {
404 destroy_phar_manifest_entry_int(idata->internal_file);
405 efree(idata->internal_file);
406 }
407 }
408
409 phar_archive_delref(idata->phar);
410 efree(idata);
411 }
412 /* }}} */
413
414 /**
415 * Removes an entry, either by actually removing it or by marking it.
416 */
phar_entry_remove(phar_entry_data * idata,char ** error)417 void phar_entry_remove(phar_entry_data *idata, char **error) /* {{{ */
418 {
419 phar_archive_data *phar;
420
421 phar = idata->phar;
422
423 if (idata->internal_file->fp_refcount < 2) {
424 if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) {
425 php_stream_close(idata->fp);
426 }
427 zend_hash_str_del(&idata->phar->manifest, idata->internal_file->filename, idata->internal_file->filename_len);
428 idata->phar->refcount--;
429 efree(idata);
430 } else {
431 idata->internal_file->is_deleted = 1;
432 phar_entry_delref(idata);
433 }
434
435 if (!phar->donotflush) {
436 phar_flush(phar, error);
437 }
438 }
439 /* }}} */
440
441 #define MAPPHAR_ALLOC_FAIL(msg) \
442 if (fp) {\
443 php_stream_close(fp);\
444 }\
445 if (error) {\
446 spprintf(error, 0, msg, fname);\
447 }\
448 return FAILURE;
449
450 #define MAPPHAR_FAIL(msg) \
451 efree(savebuf);\
452 if (mydata) {\
453 phar_destroy_phar_data(mydata);\
454 }\
455 if (signature) {\
456 pefree(signature, PHAR_G(persist));\
457 }\
458 MAPPHAR_ALLOC_FAIL(msg)
459
460 #ifdef WORDS_BIGENDIAN
461 # define PHAR_GET_32(buffer, var) \
462 var = ((uint32_t)(((unsigned char*)(buffer))[3]) << 24) \
463 | ((uint32_t)(((unsigned char*)(buffer))[2]) << 16) \
464 | ((uint32_t)(((unsigned char*)(buffer))[1]) << 8) \
465 | ((uint32_t)((unsigned char*)(buffer))[0]); \
466 (buffer) += 4
467 # define PHAR_GET_16(buffer, var) \
468 var = ((uint16_t)(((unsigned char*)(buffer))[1]) << 8) \
469 | ((uint16_t)((unsigned char*)(buffer))[0]); \
470 (buffer) += 2
471 #else
472 # define PHAR_GET_32(buffer, var) \
473 memcpy(&var, buffer, sizeof(var)); \
474 buffer += 4
475 # define PHAR_GET_16(buffer, var) \
476 var = *(uint16_t*)(buffer); \
477 buffer += 2
478 #endif
479 #define PHAR_ZIP_16(var) ((uint16_t)((((uint16_t)var[0]) & 0xff) | \
480 (((uint16_t)var[1]) & 0xff) << 8))
481 #define PHAR_ZIP_32(var) ((uint32_t)((((uint32_t)var[0]) & 0xff) | \
482 (((uint32_t)var[1]) & 0xff) << 8 | \
483 (((uint32_t)var[2]) & 0xff) << 16 | \
484 (((uint32_t)var[3]) & 0xff) << 24))
485
486 /**
487 * Open an already loaded phar
488 */
phar_open_parsed_phar(char * fname,size_t fname_len,char * alias,size_t alias_len,bool is_data,uint32_t options,phar_archive_data ** pphar,char ** error)489 static zend_result phar_open_parsed_phar(char *fname, size_t fname_len, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
490 {
491 phar_archive_data *phar;
492 #ifdef PHP_WIN32
493 char *save_fname;
494 ALLOCA_FLAG(fname_use_heap)
495 #endif
496
497 if (error) {
498 *error = NULL;
499 }
500 #ifdef PHP_WIN32
501 save_fname = fname;
502 if (memchr(fname, '\\', fname_len)) {
503 fname = do_alloca(fname_len + 1, fname_use_heap);
504 memcpy(fname, save_fname, fname_len);
505 fname[fname_len] = '\0';
506 phar_unixify_path_separators(fname, fname_len);
507 }
508 #endif
509 if (SUCCESS == phar_get_archive(&phar, fname, fname_len, alias, alias_len, error)
510 && ((alias && fname_len == phar->fname_len
511 && !strncmp(fname, phar->fname, fname_len)) || !alias)
512 ) {
513 phar_entry_info *stub;
514 #ifdef PHP_WIN32
515 if (fname != save_fname) {
516 free_alloca(fname, fname_use_heap);
517 fname = save_fname;
518 }
519 #endif
520 /* logic above is as follows:
521 If an explicit alias was requested, ensure the filename passed in
522 matches the phar's filename.
523 If no alias was passed in, then it can match either and be valid
524 */
525
526 if (!is_data) {
527 /* prevent any ".phar" without a stub getting through */
528 if (!phar->halt_offset && !phar->is_brandnew && (phar->is_tar || phar->is_zip)) {
529 if (PHAR_G(readonly) && NULL == (stub = zend_hash_str_find_ptr(&(phar->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) {
530 if (error) {
531 spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname);
532 }
533 return FAILURE;
534 }
535 }
536 }
537
538 if (pphar) {
539 *pphar = phar;
540 }
541
542 return SUCCESS;
543 } else {
544 #ifdef PHP_WIN32
545 if (fname != save_fname) {
546 free_alloca(fname, fname_use_heap);
547 fname = save_fname;
548 }
549 #endif
550 if (pphar) {
551 *pphar = NULL;
552 }
553
554 if (phar && error && !(options & REPORT_ERRORS)) {
555 efree(error);
556 }
557
558 return FAILURE;
559 }
560 }
561 /* }}}*/
562
563 /**
564 * Attempt to serialize the data.
565 * Callers are responsible for handling EG(exception) if one occurs.
566 */
phar_metadata_tracker_try_ensure_has_serialized_data(phar_metadata_tracker * tracker,bool persistent)567 void phar_metadata_tracker_try_ensure_has_serialized_data(phar_metadata_tracker *tracker, bool persistent) /* {{{ */
568 {
569 php_serialize_data_t metadata_hash;
570 smart_str metadata_str = {0};
571 if (tracker->str || Z_ISUNDEF(tracker->val)) {
572 /* Already has serialized the value or there is no value */
573 return;
574 }
575 /* Assert it should not be possible to create raw zvals in a persistent phar (i.e. from cache_list) */
576 ZEND_ASSERT(!persistent);
577
578 PHP_VAR_SERIALIZE_INIT(metadata_hash);
579 php_var_serialize(&metadata_str, &tracker->val, &metadata_hash);
580 PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
581 if (!metadata_str.s) {
582 return;
583 }
584 tracker->str = metadata_str.s;
585 }
586 /* }}} */
587
588 /**
589 * Parse out metadata when phar_metadata_tracker_has_data is true.
590 *
591 * Precondition: phar_metadata_tracker_has_data is true
592 */
phar_metadata_tracker_unserialize_or_copy(phar_metadata_tracker * tracker,zval * metadata,bool persistent,HashTable * unserialize_options,const char * method_name)593 zend_result phar_metadata_tracker_unserialize_or_copy(phar_metadata_tracker *tracker, zval *metadata, bool persistent, HashTable *unserialize_options, const char* method_name) /* {{{ */
594 {
595 const bool has_unserialize_options = unserialize_options != NULL && zend_hash_num_elements(unserialize_options) > 0;
596 /* It should be impossible to create a zval in a persistent phar/entry. */
597 ZEND_ASSERT(!persistent || Z_ISUNDEF(tracker->val));
598
599 if (Z_ISUNDEF(tracker->val) || has_unserialize_options) {
600 if (EG(exception)) {
601 /* Because other parts of the phar code haven't been updated to check for exceptions after doing something that may throw,
602 * check for exceptions before potentially serializing/unserializing instead. */
603 return FAILURE;
604 }
605 /* Persistent phars should always be unserialized. */
606 const char *start;
607 /* Assert it should not be possible to create raw data in a persistent phar (i.e. from cache_list) */
608
609 /* Precondition: This has serialized data, either from setMetadata or the phar file. */
610 ZEND_ASSERT(tracker->str != NULL);
611 ZVAL_NULL(metadata);
612 start = ZSTR_VAL(tracker->str);
613
614 php_unserialize_with_options(metadata, start, ZSTR_LEN(tracker->str), unserialize_options, method_name);
615 if (EG(exception)) {
616 zval_ptr_dtor(metadata);
617 ZVAL_UNDEF(metadata);
618 return FAILURE;
619 }
620 return SUCCESS;
621 } else {
622 /* TODO: what is the current/expected behavior when fetching an object set with setMetadata then getting it
623 * with getMetadata() and modifying a property? Previously, it was underdefined, and probably unimportant to support. */
624 ZVAL_COPY(metadata, &tracker->val);
625 }
626
627 return SUCCESS;
628 }
629 /* }}}*/
630
631 /**
632 * Check if this has any data, serialized or as a raw value.
633 */
phar_metadata_tracker_has_data(const phar_metadata_tracker * tracker,bool persistent)634 bool phar_metadata_tracker_has_data(const phar_metadata_tracker *tracker, bool persistent) /* {{{ */
635 {
636 ZEND_ASSERT(!persistent || Z_ISUNDEF(tracker->val));
637 return !Z_ISUNDEF(tracker->val) || tracker->str != NULL;
638 }
639 /* }}} */
640
641 /**
642 * Free memory used to track the metadata and set all fields to be null/undef.
643 */
phar_metadata_tracker_free(phar_metadata_tracker * tracker,bool persistent)644 void phar_metadata_tracker_free(phar_metadata_tracker *tracker, bool persistent) /* {{{ */
645 {
646 /* Free the string before the zval in case the zval's destructor modifies the metadata */
647 if (tracker->str) {
648 zend_string_release(tracker->str);
649 tracker->str = NULL;
650 }
651 if (!Z_ISUNDEF(tracker->val)) {
652 /* Here, copy the original zval to a different pointer without incrementing the refcount in case something uses the original while it's being freed. */
653 zval zval_copy;
654
655 ZEND_ASSERT(!persistent);
656 ZVAL_COPY_VALUE(&zval_copy, &tracker->val);
657 ZVAL_UNDEF(&tracker->val);
658 zval_ptr_dtor(&zval_copy);
659 }
660 }
661 /* }}} */
662
663 /**
664 * Free memory used to track the metadata and set all fields to be null/undef.
665 */
phar_metadata_tracker_copy(phar_metadata_tracker * dest,const phar_metadata_tracker * source,bool persistent)666 void phar_metadata_tracker_copy(phar_metadata_tracker *dest, const phar_metadata_tracker *source, bool persistent) /* {{{ */
667 {
668 ZEND_ASSERT(dest != source);
669 phar_metadata_tracker_free(dest, persistent);
670
671 if (!Z_ISUNDEF(source->val)) {
672 ZEND_ASSERT(!persistent);
673 ZVAL_COPY(&dest->val, &source->val);
674 }
675 if (source->str) {
676 dest->str = zend_string_copy(source->str);
677 }
678 }
679 /* }}} */
680
681 /**
682 * Copy constructor for a non-persistent clone.
683 */
phar_metadata_tracker_clone(phar_metadata_tracker * tracker)684 void phar_metadata_tracker_clone(phar_metadata_tracker *tracker) /* {{{ */
685 {
686 Z_TRY_ADDREF_P(&tracker->val);
687 if (tracker->str) {
688 /* Duplicate the string, as the original may have been persistent. */
689 tracker->str = zend_string_dup(tracker->str, false);
690 }
691 }
692 /* }}} */
693
694 /**
695 * Parse out metadata from the manifest for a single file, saving it into a string.
696 *
697 * Meta-data is in this format:
698 * [len32][data...]
699 *
700 * data is the serialized zval
701 */
phar_parse_metadata_lazy(const char * buffer,phar_metadata_tracker * tracker,uint32_t zip_metadata_len,bool persistent)702 void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker, uint32_t zip_metadata_len, bool persistent) /* {{{ */
703 {
704 phar_metadata_tracker_free(tracker, persistent);
705 if (zip_metadata_len) {
706 /* lazy init metadata */
707 tracker->str = zend_string_init(buffer, zip_metadata_len, persistent);
708 }
709 }
710 /* }}}*/
711
712 /**
713 * Size of fixed fields in the manifest.
714 * See: https://www.php.net/manual/en/phar.fileformat.phar.php
715 */
716 #define MANIFEST_FIXED_LEN 18
717
718 #define SAFE_PHAR_GET_32(buffer, endbuffer, var) \
719 if (UNEXPECTED(buffer + 4 > endbuffer)) { \
720 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)"); \
721 } \
722 PHAR_GET_32(buffer, var);
723
724 /**
725 * Does not check for a previously opened phar in the cache.
726 *
727 * Parse a new one and add it to the cache, returning either SUCCESS or
728 * FAILURE, and setting pphar to the pointer to the manifest entry
729 *
730 * This is used by phar_open_from_filename to process the manifest, but can be called
731 * directly.
732 */
phar_parse_pharfile(php_stream * fp,char * fname,size_t fname_len,char * alias,size_t alias_len,zend_long halt_offset,phar_archive_data ** pphar,uint32_t compression,char ** error)733 static zend_result phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, zend_long halt_offset, phar_archive_data** pphar, uint32_t compression, char **error) /* {{{ */
734 {
735 char b32[4], *buffer, *endbuffer, *savebuf;
736 phar_archive_data *mydata = NULL;
737 phar_entry_info entry;
738 uint32_t manifest_len, manifest_count, manifest_flags, manifest_index, tmp_len, sig_flags;
739 uint16_t manifest_ver;
740 uint32_t len;
741 zend_long offset;
742 size_t sig_len;
743 int register_alias = 0, temp_alias = 0;
744 char *signature = NULL;
745 zend_string *str;
746
747 if (pphar) {
748 *pphar = NULL;
749 }
750
751 if (error) {
752 *error = NULL;
753 }
754
755 /* check for ?>\n and increment accordingly */
756 if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) {
757 MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"")
758 }
759
760 buffer = b32;
761
762 if (3 != php_stream_read(fp, buffer, 3)) {
763 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
764 }
765
766 if ((*buffer == ' ' || *buffer == '\n') && *(buffer + 1) == '?' && *(buffer + 2) == '>') {
767 int nextchar;
768 halt_offset += 3;
769 if (EOF == (nextchar = php_stream_getc(fp))) {
770 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
771 }
772
773 if ((char) nextchar == '\r') {
774 /* if we have an \r we require an \n as well */
775 if (EOF == (nextchar = php_stream_getc(fp)) || (char)nextchar != '\n') {
776 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)")
777 }
778 ++halt_offset;
779 }
780
781 if ((char) nextchar == '\n') {
782 ++halt_offset;
783 }
784 }
785
786 /* make sure we are at the right location to read the manifest */
787 if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) {
788 MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"")
789 }
790
791 /* read in manifest */
792 buffer = b32;
793
794 if (4 != php_stream_read(fp, buffer, 4)) {
795 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at manifest length)")
796 }
797
798 PHAR_GET_32(buffer, manifest_len);
799
800 if (manifest_len > 1048576 * 100) {
801 /* prevent serious memory issues by limiting manifest to at most 100 MB in length */
802 MAPPHAR_ALLOC_FAIL("manifest cannot be larger than 100 MB in phar \"%s\"")
803 }
804
805 buffer = (char *)emalloc(manifest_len);
806 savebuf = buffer;
807 endbuffer = buffer + manifest_len;
808
809 if (manifest_len < MANIFEST_FIXED_LEN || manifest_len != php_stream_read(fp, buffer, manifest_len)) {
810 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
811 }
812
813 /* extract the number of entries */
814 SAFE_PHAR_GET_32(buffer, endbuffer, manifest_count);
815
816 if (manifest_count == 0) {
817 MAPPHAR_FAIL("in phar \"%s\", manifest claims to have zero entries. Phars must have at least 1 entry");
818 }
819
820 /* extract API version, lowest nibble currently unused */
821 manifest_ver = (((unsigned char)buffer[0]) << 8)
822 + ((unsigned char)buffer[1]);
823 buffer += 2;
824
825 if ((manifest_ver & PHAR_API_VER_MASK) < PHAR_API_MIN_READ) {
826 efree(savebuf);
827 php_stream_close(fp);
828 if (error) {
829 spprintf(error, 0, "phar \"%s\" is API version %1.u.%1.u.%1.u, and cannot be processed", fname, manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0x0F);
830 }
831 return FAILURE;
832 }
833
834 SAFE_PHAR_GET_32(buffer, endbuffer, manifest_flags);
835
836 manifest_flags &= ~PHAR_HDR_COMPRESSION_MASK;
837 manifest_flags &= ~PHAR_FILE_COMPRESSION_MASK;
838 /* remember whether this entire phar was compressed with gz/bzip2 */
839 manifest_flags |= compression;
840
841 /* The lowest nibble contains the phar wide flags. The compression flags can */
842 /* be ignored on reading because it is being generated anyways. */
843 if (manifest_flags & PHAR_HDR_SIGNATURE) {
844 char sig_buf[8], *sig_ptr = sig_buf;
845 zend_off_t read_len;
846 size_t end_of_phar;
847
848 if (-1 == php_stream_seek(fp, -8, SEEK_END)
849 || (read_len = php_stream_tell(fp)) < 20
850 || 8 != php_stream_read(fp, sig_buf, 8)
851 || memcmp(sig_buf+4, "GBMB", 4)) {
852 efree(savebuf);
853 php_stream_close(fp);
854 if (error) {
855 spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
856 }
857 return FAILURE;
858 }
859
860 PHAR_GET_32(sig_ptr, sig_flags);
861
862 switch(sig_flags) {
863 case PHAR_SIG_OPENSSL_SHA512:
864 case PHAR_SIG_OPENSSL_SHA256:
865 case PHAR_SIG_OPENSSL: {
866 uint32_t signature_len;
867 char *sig;
868 zend_off_t whence;
869
870 /* we store the signature followed by the signature length */
871 if (-1 == php_stream_seek(fp, -12, SEEK_CUR)
872 || 4 != php_stream_read(fp, sig_buf, 4)) {
873 efree(savebuf);
874 php_stream_close(fp);
875 if (error) {
876 spprintf(error, 0, "phar \"%s\" openssl signature length could not be read", fname);
877 }
878 return FAILURE;
879 }
880
881 sig_ptr = sig_buf;
882 PHAR_GET_32(sig_ptr, signature_len);
883 sig = (char *) emalloc(signature_len);
884 whence = signature_len + 4;
885 whence = -whence;
886
887 if (-1 == php_stream_seek(fp, whence, SEEK_CUR)
888 || !(end_of_phar = php_stream_tell(fp))
889 || signature_len != php_stream_read(fp, sig, signature_len)) {
890 efree(savebuf);
891 efree(sig);
892 php_stream_close(fp);
893 if (error) {
894 spprintf(error, 0, "phar \"%s\" openssl signature could not be read", fname);
895 }
896 return FAILURE;
897 }
898
899 if (FAILURE == phar_verify_signature(fp, end_of_phar, sig_flags, sig, signature_len, fname, &signature, &sig_len, error)) {
900 efree(savebuf);
901 efree(sig);
902 php_stream_close(fp);
903 if (error) {
904 char *save = *error;
905 spprintf(error, 0, "phar \"%s\" openssl signature could not be verified: %s", fname, *error);
906 efree(save);
907 }
908 return FAILURE;
909 }
910 efree(sig);
911 }
912 break;
913 case PHAR_SIG_SHA512: {
914 unsigned char digest[64];
915
916 php_stream_seek(fp, -(8 + 64), SEEK_END);
917 read_len = php_stream_tell(fp);
918
919 if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
920 efree(savebuf);
921 php_stream_close(fp);
922 if (error) {
923 spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
924 }
925 return FAILURE;
926 }
927
928 if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA512, (char *)digest, 64, fname, &signature, &sig_len, error)) {
929 efree(savebuf);
930 php_stream_close(fp);
931 if (error) {
932 char *save = *error;
933 spprintf(error, 0, "phar \"%s\" SHA512 signature could not be verified: %s", fname, *error);
934 efree(save);
935 }
936 return FAILURE;
937 }
938 break;
939 }
940 case PHAR_SIG_SHA256: {
941 unsigned char digest[32];
942
943 php_stream_seek(fp, -(8 + 32), SEEK_END);
944 read_len = php_stream_tell(fp);
945
946 if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
947 efree(savebuf);
948 php_stream_close(fp);
949 if (error) {
950 spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
951 }
952 return FAILURE;
953 }
954
955 if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA256, (char *)digest, 32, fname, &signature, &sig_len, error)) {
956 efree(savebuf);
957 php_stream_close(fp);
958 if (error) {
959 char *save = *error;
960 spprintf(error, 0, "phar \"%s\" SHA256 signature could not be verified: %s", fname, *error);
961 efree(save);
962 }
963 return FAILURE;
964 }
965 break;
966 }
967 case PHAR_SIG_SHA1: {
968 unsigned char digest[20];
969
970 php_stream_seek(fp, -(8 + 20), SEEK_END);
971 read_len = php_stream_tell(fp);
972
973 if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
974 efree(savebuf);
975 php_stream_close(fp);
976 if (error) {
977 spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
978 }
979 return FAILURE;
980 }
981
982 if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA1, (char *)digest, 20, fname, &signature, &sig_len, error)) {
983 efree(savebuf);
984 php_stream_close(fp);
985 if (error) {
986 char *save = *error;
987 spprintf(error, 0, "phar \"%s\" SHA1 signature could not be verified: %s", fname, *error);
988 efree(save);
989 }
990 return FAILURE;
991 }
992 break;
993 }
994 case PHAR_SIG_MD5: {
995 unsigned char digest[16];
996
997 php_stream_seek(fp, -(8 + 16), SEEK_END);
998 read_len = php_stream_tell(fp);
999
1000 if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) {
1001 efree(savebuf);
1002 php_stream_close(fp);
1003 if (error) {
1004 spprintf(error, 0, "phar \"%s\" has a broken signature", fname);
1005 }
1006 return FAILURE;
1007 }
1008
1009 if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_MD5, (char *)digest, 16, fname, &signature, &sig_len, error)) {
1010 efree(savebuf);
1011 php_stream_close(fp);
1012 if (error) {
1013 char *save = *error;
1014 spprintf(error, 0, "phar \"%s\" MD5 signature could not be verified: %s", fname, *error);
1015 efree(save);
1016 }
1017 return FAILURE;
1018 }
1019 break;
1020 }
1021 default:
1022 efree(savebuf);
1023 php_stream_close(fp);
1024
1025 if (error) {
1026 spprintf(error, 0, "phar \"%s\" has a broken or unsupported signature", fname);
1027 }
1028 return FAILURE;
1029 }
1030 } else if (PHAR_G(require_hash)) {
1031 efree(savebuf);
1032 php_stream_close(fp);
1033
1034 if (error) {
1035 spprintf(error, 0, "phar \"%s\" does not have a signature", fname);
1036 }
1037 return FAILURE;
1038 } else {
1039 sig_flags = 0;
1040 sig_len = 0;
1041 }
1042
1043 /* extract alias */
1044 SAFE_PHAR_GET_32(buffer, endbuffer, tmp_len);
1045
1046 if (buffer + tmp_len > endbuffer) {
1047 MAPPHAR_FAIL("internal corruption of phar \"%s\" (buffer overrun)");
1048 }
1049
1050 if (manifest_len < MANIFEST_FIXED_LEN + tmp_len) {
1051 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)")
1052 }
1053
1054 /* tmp_len = 0 says alias length is 0, which means the alias is not stored in the phar */
1055 if (tmp_len) {
1056 /* if the alias is stored we enforce it (implicit overrides explicit) */
1057 if (alias && alias_len && (alias_len != tmp_len || strncmp(alias, buffer, tmp_len)))
1058 {
1059 php_stream_close(fp);
1060
1061 if (signature) {
1062 efree(signature);
1063 }
1064
1065 if (error) {
1066 spprintf(error, 0, "cannot load phar \"%s\" with implicit alias \"%.*s\" under different alias \"%s\"", fname, tmp_len, buffer, alias);
1067 }
1068
1069 efree(savebuf);
1070 return FAILURE;
1071 }
1072
1073 alias_len = tmp_len;
1074 alias = buffer;
1075 buffer += tmp_len;
1076 register_alias = 1;
1077 } else if (!alias_len || !alias) {
1078 /* if we neither have an explicit nor an implicit alias, we use the filename */
1079 alias = NULL;
1080 alias_len = 0;
1081 register_alias = 0;
1082 } else if (alias_len) {
1083 register_alias = 1;
1084 temp_alias = 1;
1085 }
1086
1087 /* we have 5 32-bit items plus 1 byte at least */
1088 if (manifest_count > ((manifest_len - MANIFEST_FIXED_LEN - tmp_len) / (5 * 4 + 1))) {
1089 /* prevent serious memory issues */
1090 MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)")
1091 }
1092
1093 mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist));
1094 mydata->is_persistent = PHAR_G(persist);
1095 HT_INVALIDATE(&mydata->manifest);
1096 HT_INVALIDATE(&mydata->mounted_dirs);
1097 HT_INVALIDATE(&mydata->virtual_dirs);
1098
1099 /* check whether we have meta data, zero check works regardless of byte order */
1100 SAFE_PHAR_GET_32(buffer, endbuffer, len);
1101 if (mydata->is_persistent) {
1102 if (!len) {
1103 /* FIXME: not sure why this is needed but removing it breaks tests */
1104 SAFE_PHAR_GET_32(buffer, endbuffer, len);
1105 }
1106 }
1107 if(len > (size_t)(endbuffer - buffer)) {
1108 MAPPHAR_FAIL("internal corruption of phar \"%s\" (trying to read past buffer end)");
1109 }
1110 /* Don't implicitly call unserialize() on potentially untrusted input unless getMetadata() is called directly. */
1111 phar_parse_metadata_lazy(buffer, &mydata->metadata_tracker, len, mydata->is_persistent);
1112 buffer += len;
1113
1114 /* set up our manifest */
1115 zend_hash_init(&mydata->manifest, manifest_count,
1116 zend_get_hash_value, destroy_phar_manifest_entry, (bool)mydata->is_persistent);
1117 zend_hash_init(&mydata->mounted_dirs, 5,
1118 zend_get_hash_value, NULL, (bool)mydata->is_persistent);
1119 zend_hash_init(&mydata->virtual_dirs, manifest_count * 2,
1120 zend_get_hash_value, NULL, (bool)mydata->is_persistent);
1121 mydata->fname = pestrndup(fname, fname_len, mydata->is_persistent);
1122 #ifdef PHP_WIN32
1123 phar_unixify_path_separators(mydata->fname, fname_len);
1124 #endif
1125 mydata->fname_len = fname_len;
1126 offset = halt_offset + manifest_len + 4;
1127 memset(&entry, 0, sizeof(phar_entry_info));
1128 entry.phar = mydata;
1129 entry.fp_type = PHAR_FP;
1130 entry.is_persistent = mydata->is_persistent;
1131
1132 for (manifest_index = 0; manifest_index < manifest_count; ++manifest_index) {
1133 if (buffer + 28 > endbuffer) {
1134 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)")
1135 }
1136
1137 PHAR_GET_32(buffer, entry.filename_len);
1138
1139 if (entry.filename_len == 0) {
1140 MAPPHAR_FAIL("zero-length filename encountered in phar \"%s\"");
1141 }
1142
1143 if (entry.is_persistent) {
1144 entry.manifest_pos = manifest_index;
1145 }
1146
1147 if (entry.filename_len > (size_t)(endbuffer - buffer - 24)) {
1148 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)");
1149 }
1150
1151 if ((manifest_ver & PHAR_API_VER_MASK) >= PHAR_API_MIN_DIR && buffer[entry.filename_len - 1] == '/') {
1152 entry.is_dir = 1;
1153 } else {
1154 entry.is_dir = 0;
1155 }
1156
1157 phar_add_virtual_dirs(mydata, buffer, entry.filename_len);
1158 entry.filename = pestrndup(buffer, entry.filename_len, entry.is_persistent);
1159 buffer += entry.filename_len;
1160 PHAR_GET_32(buffer, entry.uncompressed_filesize);
1161 PHAR_GET_32(buffer, entry.timestamp);
1162
1163 if (offset == halt_offset + manifest_len + 4) {
1164 mydata->min_timestamp = entry.timestamp;
1165 mydata->max_timestamp = entry.timestamp;
1166 } else {
1167 if (mydata->min_timestamp > entry.timestamp) {
1168 mydata->min_timestamp = entry.timestamp;
1169 } else if (mydata->max_timestamp < entry.timestamp) {
1170 mydata->max_timestamp = entry.timestamp;
1171 }
1172 }
1173
1174 PHAR_GET_32(buffer, entry.compressed_filesize);
1175 PHAR_GET_32(buffer, entry.crc32);
1176 PHAR_GET_32(buffer, entry.flags);
1177
1178 if (entry.is_dir) {
1179 entry.filename_len--;
1180 entry.flags |= PHAR_ENT_PERM_DEF_DIR;
1181 }
1182
1183 PHAR_GET_32(buffer, len);
1184 if (len > (size_t)(endbuffer - buffer)) {
1185 pefree(entry.filename, entry.is_persistent);
1186 MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)");
1187 }
1188 /* Don't implicitly call unserialize() on potentially untrusted input unless getMetadata() is called directly. */
1189 /* The same local variable entry is reused in a loop, so reset the state before reading data. */
1190 ZVAL_UNDEF(&entry.metadata_tracker.val);
1191 entry.metadata_tracker.str = NULL;
1192 phar_parse_metadata_lazy(buffer, &entry.metadata_tracker, len, entry.is_persistent);
1193 buffer += len;
1194
1195 entry.offset = entry.offset_abs = offset;
1196 offset += entry.compressed_filesize;
1197
1198 switch (entry.flags & PHAR_ENT_COMPRESSION_MASK) {
1199 case PHAR_ENT_COMPRESSED_GZ:
1200 if (!PHAR_G(has_zlib)) {
1201 phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
1202 pefree(entry.filename, entry.is_persistent);
1203 MAPPHAR_FAIL("zlib extension is required for gz compressed .phar file \"%s\"");
1204 }
1205 break;
1206 case PHAR_ENT_COMPRESSED_BZ2:
1207 if (!PHAR_G(has_bz2)) {
1208 phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
1209 pefree(entry.filename, entry.is_persistent);
1210 MAPPHAR_FAIL("bz2 extension is required for bzip2 compressed .phar file \"%s\"");
1211 }
1212 break;
1213 default:
1214 if (entry.uncompressed_filesize != entry.compressed_filesize) {
1215 phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
1216 pefree(entry.filename, entry.is_persistent);
1217 MAPPHAR_FAIL("internal corruption of phar \"%s\" (compressed and uncompressed size does not match for uncompressed entry)");
1218 }
1219 break;
1220 }
1221
1222 manifest_flags |= (entry.flags & PHAR_ENT_COMPRESSION_MASK);
1223 /* if signature matched, no need to check CRC32 for each file */
1224 entry.is_crc_checked = (manifest_flags & PHAR_HDR_SIGNATURE ? 1 : 0);
1225 phar_set_inode(&entry);
1226 if (mydata->is_persistent) {
1227 str = zend_string_init_interned(entry.filename, entry.filename_len, 1);
1228 } else {
1229 str = zend_string_init(entry.filename, entry.filename_len, 0);
1230 }
1231 zend_hash_add_mem(&mydata->manifest, str, (void*)&entry, sizeof(phar_entry_info));
1232 zend_string_release(str);
1233 }
1234
1235 snprintf(mydata->version, sizeof(mydata->version), "%u.%u.%u", manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0xF);
1236 mydata->halt_offset = halt_offset;
1237 mydata->flags = manifest_flags;
1238 endbuffer = strrchr(mydata->fname, '/');
1239
1240 if (endbuffer) {
1241 mydata->ext = memchr(endbuffer, '.', (mydata->fname + fname_len) - endbuffer);
1242 if (mydata->ext == endbuffer) {
1243 mydata->ext = memchr(endbuffer + 1, '.', (mydata->fname + fname_len) - endbuffer - 1);
1244 }
1245 if (mydata->ext) {
1246 mydata->ext_len = (mydata->fname + mydata->fname_len) - mydata->ext;
1247 }
1248 }
1249
1250 mydata->alias = alias ?
1251 pestrndup(alias, alias_len, mydata->is_persistent) :
1252 pestrndup(mydata->fname, fname_len, mydata->is_persistent);
1253 mydata->alias_len = alias ? alias_len : fname_len;
1254 mydata->sig_flags = sig_flags;
1255 mydata->fp = fp;
1256 mydata->sig_len = sig_len;
1257 mydata->signature = signature;
1258 phar_request_initialize();
1259
1260 if (register_alias) {
1261 phar_archive_data *fd_ptr;
1262
1263 mydata->is_temporary_alias = temp_alias;
1264
1265 if (!phar_validate_alias(mydata->alias, mydata->alias_len)) {
1266 signature = NULL;
1267 fp = NULL;
1268 MAPPHAR_FAIL("Cannot open archive \"%s\", invalid alias");
1269 }
1270
1271 if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) {
1272 if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) {
1273 signature = NULL;
1274 fp = NULL;
1275 MAPPHAR_FAIL("Cannot open archive \"%s\", alias is already in use by existing archive");
1276 }
1277 }
1278
1279 if (mydata->is_persistent) {
1280 str = zend_string_init_interned(alias, alias_len, 1);
1281 } else {
1282 str = zend_string_init(alias, alias_len, 0);
1283 }
1284 zend_hash_add_ptr(&(PHAR_G(phar_alias_map)), str, mydata);
1285 zend_string_release(str);
1286 } else {
1287 mydata->is_temporary_alias = 1;
1288 }
1289
1290 if (mydata->is_persistent) {
1291 str = zend_string_init_interned(mydata->fname, fname_len, 1);
1292 } else {
1293 str = zend_string_init(mydata->fname, fname_len, 0);
1294 }
1295 zend_hash_add_ptr(&(PHAR_G(phar_fname_map)), str, mydata);
1296 zend_string_release(str);
1297 efree(savebuf);
1298
1299 if (pphar) {
1300 *pphar = mydata;
1301 }
1302
1303 return SUCCESS;
1304 }
1305 /* }}} */
1306
1307 /**
1308 * Create or open a phar for writing
1309 */
phar_open_or_create_filename(char * fname,size_t fname_len,char * alias,size_t alias_len,bool is_data,uint32_t options,phar_archive_data ** pphar,char ** error)1310 zend_result phar_open_or_create_filename(char *fname, size_t fname_len, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
1311 {
1312 const char *ext_str, *z;
1313 char *my_error;
1314 size_t ext_len;
1315 phar_archive_data **test, *unused = NULL;
1316
1317 test = &unused;
1318
1319 if (error) {
1320 *error = NULL;
1321 }
1322
1323 /* first try to open an existing file */
1324 if (phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 0, 1) == SUCCESS) {
1325 goto check_file;
1326 }
1327
1328 /* next try to create a new file */
1329 if (FAILURE == phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 1, 1)) {
1330 if (error) {
1331 if (ext_len == -2) {
1332 spprintf(error, 0, "Cannot create a phar archive from a URL like \"%s\". Phar objects can only be created from local files", fname);
1333 } else {
1334 spprintf(error, 0, "Cannot create phar '%s', file extension (or combination) not recognised or the directory does not exist", fname);
1335 }
1336 }
1337 return FAILURE;
1338 }
1339 check_file:
1340 if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, test, &my_error) == SUCCESS) {
1341 if (pphar) {
1342 *pphar = *test;
1343 }
1344
1345 if ((*test)->is_data && !(*test)->is_tar && !(*test)->is_zip) {
1346 if (error) {
1347 spprintf(error, 0, "Cannot open '%s' as a PharData object. Use Phar::__construct() for executable archives", fname);
1348 }
1349 return FAILURE;
1350 }
1351
1352 if (PHAR_G(readonly) && !(*test)->is_data && ((*test)->is_tar || (*test)->is_zip)) {
1353 phar_entry_info *stub;
1354 if (NULL == (stub = zend_hash_str_find_ptr(&((*test)->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) {
1355 spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname);
1356 return FAILURE;
1357 }
1358 }
1359
1360 if (!PHAR_G(readonly) || (*test)->is_data) {
1361 (*test)->is_writeable = 1;
1362 }
1363 return SUCCESS;
1364 } else if (my_error) {
1365 if (error) {
1366 *error = my_error;
1367 } else {
1368 efree(my_error);
1369 }
1370 return FAILURE;
1371 }
1372
1373 if (ext_len > 3 && (z = memchr(ext_str, 'z', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ip", 2)) {
1374 /* assume zip-based phar */
1375 return phar_open_or_create_zip(fname, fname_len, alias, alias_len, is_data, options, pphar, error);
1376 }
1377
1378 if (ext_len > 3 && (z = memchr(ext_str, 't', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ar", 2)) {
1379 /* assume tar-based phar */
1380 return phar_open_or_create_tar(fname, fname_len, alias, alias_len, is_data, options, pphar, error);
1381 }
1382
1383 return phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, pphar, error);
1384 }
1385 /* }}} */
1386
1387 static zend_result phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error);
1388
phar_create_or_parse_filename(char * fname,size_t fname_len,char * alias,size_t alias_len,bool is_data,uint32_t options,phar_archive_data ** pphar,char ** error)1389 zend_result phar_create_or_parse_filename(char *fname, size_t fname_len, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
1390 {
1391 phar_archive_data *mydata;
1392 php_stream *fp;
1393 zend_string *actual = NULL;
1394 char *p;
1395
1396 if (!pphar) {
1397 pphar = &mydata;
1398 }
1399 if (php_check_open_basedir(fname)) {
1400 return FAILURE;
1401 }
1402
1403 /* first open readonly so it won't be created if not present */
1404 fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, &actual);
1405
1406 if (actual) {
1407 fname = ZSTR_VAL(actual);
1408 fname_len = ZSTR_LEN(actual);
1409 }
1410
1411 if (fp) {
1412 if (phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error) == SUCCESS) {
1413 if ((*pphar)->is_data || !PHAR_G(readonly)) {
1414 (*pphar)->is_writeable = 1;
1415 }
1416 if (actual) {
1417 zend_string_release_ex(actual, 0);
1418 }
1419 return SUCCESS;
1420 } else {
1421 /* file exists, but is either corrupt or not a phar archive */
1422 if (actual) {
1423 zend_string_release_ex(actual, 0);
1424 }
1425 return FAILURE;
1426 }
1427 }
1428
1429 if (actual) {
1430 zend_string_release_ex(actual, 0);
1431 }
1432
1433 if (PHAR_G(readonly) && !is_data) {
1434 if (options & REPORT_ERRORS) {
1435 if (error) {
1436 spprintf(error, 0, "creating archive \"%s\" disabled by the php.ini setting phar.readonly", fname);
1437 }
1438 }
1439 return FAILURE;
1440 }
1441
1442 /* set up our manifest */
1443 mydata = ecalloc(1, sizeof(phar_archive_data));
1444 mydata->fname = expand_filepath(fname, NULL);
1445 if (mydata->fname == NULL) {
1446 efree(mydata);
1447 return FAILURE;
1448 }
1449 fname_len = strlen(mydata->fname);
1450 #ifdef PHP_WIN32
1451 phar_unixify_path_separators(mydata->fname, fname_len);
1452 #endif
1453 p = strrchr(mydata->fname, '/');
1454
1455 if (p) {
1456 mydata->ext = memchr(p, '.', (mydata->fname + fname_len) - p);
1457 if (mydata->ext == p) {
1458 mydata->ext = memchr(p + 1, '.', (mydata->fname + fname_len) - p - 1);
1459 }
1460 if (mydata->ext) {
1461 mydata->ext_len = (mydata->fname + fname_len) - mydata->ext;
1462 }
1463 }
1464
1465 if (pphar) {
1466 *pphar = mydata;
1467 }
1468
1469 zend_hash_init(&mydata->manifest, sizeof(phar_entry_info),
1470 zend_get_hash_value, destroy_phar_manifest_entry, 0);
1471 zend_hash_init(&mydata->mounted_dirs, sizeof(char *),
1472 zend_get_hash_value, NULL, 0);
1473 zend_hash_init(&mydata->virtual_dirs, sizeof(char *),
1474 zend_get_hash_value, NULL, (bool)mydata->is_persistent);
1475 mydata->fname_len = fname_len;
1476 snprintf(mydata->version, sizeof(mydata->version), "%s", PHP_PHAR_API_VERSION);
1477 mydata->is_temporary_alias = alias ? 0 : 1;
1478 mydata->fp = NULL;
1479 mydata->is_writeable = 1;
1480 mydata->is_brandnew = 1;
1481 phar_request_initialize();
1482 zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata);
1483
1484 if (is_data) {
1485 alias = NULL;
1486 alias_len = 0;
1487 mydata->is_data = 1;
1488 /* assume tar format, PharData can specify other */
1489 mydata->is_tar = 1;
1490 } else {
1491 phar_archive_data *fd_ptr;
1492
1493 if (alias && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) {
1494 if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) {
1495 if (error) {
1496 spprintf(error, 4096, "phar error: phar \"%s\" cannot set alias \"%s\", already in use by another phar archive", mydata->fname, alias);
1497 }
1498
1499 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
1500
1501 if (pphar) {
1502 *pphar = NULL;
1503 }
1504
1505 return FAILURE;
1506 }
1507 }
1508
1509 ZEND_ASSERT(!mydata->is_persistent);
1510 mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len);
1511 mydata->alias_len = alias ? alias_len : fname_len;
1512 }
1513
1514 if (alias_len && alias) {
1515 if (NULL == zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata)) {
1516 if (options & REPORT_ERRORS) {
1517 if (error) {
1518 spprintf(error, 0, "archive \"%s\" cannot be associated with alias \"%s\", already in use", fname, alias);
1519 }
1520 }
1521
1522 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
1523
1524 if (pphar) {
1525 *pphar = NULL;
1526 }
1527
1528 return FAILURE;
1529 }
1530 }
1531
1532 return SUCCESS;
1533 }
1534 /* }}}*/
1535
1536 /**
1537 * Return an already opened filename.
1538 *
1539 * Or scan a phar file for the required __HALT_COMPILER(); ?> token and verify
1540 * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
1541 * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
1542 */
phar_open_from_filename(char * fname,size_t fname_len,char * alias,size_t alias_len,uint32_t options,phar_archive_data ** pphar,char ** error)1543 zend_result phar_open_from_filename(char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
1544 {
1545 php_stream *fp;
1546 zend_string *actual;
1547 bool is_data = false;
1548
1549 if (error) {
1550 *error = NULL;
1551 }
1552
1553 if (!strstr(fname, ".phar")) {
1554 is_data = true;
1555 }
1556
1557 if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, pphar, error) == SUCCESS) {
1558 return SUCCESS;
1559 } else if (error && *error) {
1560 return FAILURE;
1561 }
1562 if (php_check_open_basedir(fname)) {
1563 return FAILURE;
1564 }
1565
1566 fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, &actual);
1567
1568 if (!fp) {
1569 if (options & REPORT_ERRORS) {
1570 if (error) {
1571 spprintf(error, 0, "unable to open phar for reading \"%s\"", fname);
1572 }
1573 }
1574 if (actual) {
1575 zend_string_release_ex(actual, 0);
1576 }
1577 return FAILURE;
1578 }
1579
1580 if (actual) {
1581 fname = ZSTR_VAL(actual);
1582 fname_len = ZSTR_LEN(actual);
1583 }
1584
1585 zend_result ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error);
1586
1587 if (actual) {
1588 zend_string_release_ex(actual, 0);
1589 }
1590
1591 return ret;
1592 }
1593 /* }}}*/
1594
phar_strnstr(const char * buf,int buf_len,const char * search,int search_len)1595 static inline char *phar_strnstr(const char *buf, int buf_len, const char *search, int search_len) /* {{{ */
1596 {
1597 const char *c;
1598 ptrdiff_t so_far = 0;
1599
1600 if (buf_len < search_len) {
1601 return NULL;
1602 }
1603
1604 c = buf - 1;
1605
1606 do {
1607 if (!(c = memchr(c + 1, search[0], buf_len - search_len - so_far))) {
1608 return (char *) NULL;
1609 }
1610
1611 so_far = c - buf;
1612
1613 if (so_far >= (buf_len - search_len)) {
1614 return (char *) NULL;
1615 }
1616
1617 if (!memcmp(c, search, search_len)) {
1618 return (char *) c;
1619 }
1620 } while (1);
1621 }
1622 /* }}} */
1623
1624 /**
1625 * Scan an open fp for the required __HALT_COMPILER(); ?> token and verify
1626 * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
1627 * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
1628 */
phar_open_from_fp(php_stream * fp,char * fname,size_t fname_len,char * alias,size_t alias_len,uint32_t options,phar_archive_data ** pphar,char ** error)1629 static zend_result phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
1630 {
1631 static const char token[] = "__HALT_COMPILER();";
1632 static const char zip_magic[] = "PK\x03\x04";
1633 static const char gz_magic[] = "\x1f\x8b\x08";
1634 static const char bz_magic[] = "BZh";
1635 char *pos, test = '\0';
1636 int recursion_count = 3; // arbitrary limit to avoid too deep or even infinite recursion
1637 const int window_size = 1024;
1638 char buffer[1024 + sizeof(token)]; /* a 1024 byte window + the size of the halt_compiler token (moving window) */
1639 const zend_long readsize = sizeof(buffer) - sizeof(token);
1640 const zend_long tokenlen = sizeof(token) - 1;
1641 zend_long halt_offset;
1642 size_t got;
1643 uint32_t compression = PHAR_FILE_COMPRESSED_NONE;
1644
1645 if (error) {
1646 *error = NULL;
1647 }
1648
1649 if (-1 == php_stream_rewind(fp)) {
1650 MAPPHAR_ALLOC_FAIL("cannot rewind phar \"%s\"")
1651 }
1652
1653 buffer[sizeof(buffer)-1] = '\0';
1654 memset(buffer, 32, sizeof(token));
1655 halt_offset = 0;
1656
1657 /* Maybe it's better to compile the file instead of just searching, */
1658 /* but we only want the offset. So we want a .re scanner to find it. */
1659 while(!php_stream_eof(fp)) {
1660 if ((got = php_stream_read(fp, buffer+tokenlen, readsize)) < (size_t) tokenlen) {
1661 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)")
1662 }
1663
1664 if (!test && recursion_count) {
1665 test = '\1';
1666 pos = buffer+tokenlen;
1667 if (!memcmp(pos, gz_magic, 3)) {
1668 char err = 0;
1669 php_stream_filter *filter;
1670 php_stream *temp;
1671 /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */
1672 zval filterparams;
1673
1674 if (!PHAR_G(has_zlib)) {
1675 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file, enable zlib extension in php.ini")
1676 }
1677 array_init(&filterparams);
1678 /* this is defined in zlib's zconf.h */
1679 #ifndef MAX_WBITS
1680 #define MAX_WBITS 15
1681 #endif
1682 add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS + 32);
1683
1684 /* entire file is gzip-compressed, uncompress to temporary file */
1685 if (!(temp = php_stream_fopen_tmpfile())) {
1686 MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of gzipped phar archive \"%s\"")
1687 }
1688
1689 php_stream_rewind(fp);
1690 filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp));
1691
1692 if (!filter) {
1693 err = 1;
1694 add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS);
1695 filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp));
1696 zend_array_destroy(Z_ARR(filterparams));
1697
1698 if (!filter) {
1699 php_stream_close(temp);
1700 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
1701 }
1702 } else {
1703 zend_array_destroy(Z_ARR(filterparams));
1704 }
1705
1706 php_stream_filter_append(&temp->writefilters, filter);
1707
1708 if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
1709 if (err) {
1710 php_stream_close(temp);
1711 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
1712 }
1713 php_stream_close(temp);
1714 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file")
1715 }
1716
1717 php_stream_filter_flush(filter, 1);
1718 php_stream_filter_remove(filter, 1);
1719 php_stream_close(fp);
1720 fp = temp;
1721 php_stream_rewind(fp);
1722 compression = PHAR_FILE_COMPRESSED_GZ;
1723
1724 /* now, start over */
1725 test = '\0';
1726 if (!--recursion_count) {
1727 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\"");
1728 break;
1729 }
1730 continue;
1731 } else if (!memcmp(pos, bz_magic, 3)) {
1732 php_stream_filter *filter;
1733 php_stream *temp;
1734
1735 if (!PHAR_G(has_bz2)) {
1736 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file, enable bz2 extension in php.ini")
1737 }
1738
1739 /* entire file is bzip-compressed, uncompress to temporary file */
1740 if (!(temp = php_stream_fopen_tmpfile())) {
1741 MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of bzipped phar archive \"%s\"")
1742 }
1743
1744 php_stream_rewind(fp);
1745 filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp));
1746
1747 if (!filter) {
1748 php_stream_close(temp);
1749 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\", filter creation failed")
1750 }
1751
1752 php_stream_filter_append(&temp->writefilters, filter);
1753
1754 if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
1755 php_stream_close(temp);
1756 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file")
1757 }
1758
1759 php_stream_filter_flush(filter, 1);
1760 php_stream_filter_remove(filter, 1);
1761 php_stream_close(fp);
1762 fp = temp;
1763 php_stream_rewind(fp);
1764 compression = PHAR_FILE_COMPRESSED_BZ2;
1765
1766 /* now, start over */
1767 test = '\0';
1768 if (!--recursion_count) {
1769 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\"");
1770 break;
1771 }
1772 continue;
1773 }
1774
1775 if (!memcmp(pos, zip_magic, 4)) {
1776 php_stream_seek(fp, 0, SEEK_END);
1777 return phar_parse_zipfile(fp, fname, fname_len, alias, alias_len, pphar, error);
1778 }
1779
1780 if (got >= 512) {
1781 if (phar_is_tar(pos, fname)) {
1782 php_stream_rewind(fp);
1783 return phar_parse_tarfile(fp, fname, fname_len, alias, alias_len, pphar, compression, error);
1784 }
1785 }
1786 }
1787
1788 if (got > 0 && (pos = phar_strnstr(buffer, got + sizeof(token), token, sizeof(token)-1)) != NULL) {
1789 halt_offset += (pos - buffer); /* no -tokenlen+tokenlen here */
1790 return phar_parse_pharfile(fp, fname, fname_len, alias, alias_len, halt_offset, pphar, compression, error);
1791 }
1792
1793 halt_offset += got;
1794 memmove(buffer, buffer + window_size, tokenlen); /* move the memory buffer by the size of the window */
1795 }
1796
1797 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (__HALT_COMPILER(); not found)")
1798 }
1799 /* }}} */
1800
1801 /*
1802 * given the location of the file extension and the start of the file path,
1803 * determine the end of the portion of the path (i.e. /path/to/file.ext/blah
1804 * grabs "/path/to/file.ext" as does the straight /path/to/file.ext),
1805 * stat it to determine if it exists.
1806 * if so, check to see if it is a directory and fail if so
1807 * if not, check to see if its dirname() exists (i.e. "/path/to") and is a directory
1808 * succeed if we are creating the file, otherwise fail.
1809 */
phar_analyze_path(const char * fname,const char * ext,size_t ext_len,int for_create)1810 static zend_result phar_analyze_path(const char *fname, const char *ext, size_t ext_len, int for_create) /* {{{ */
1811 {
1812 php_stream_statbuf ssb;
1813 char *realpath;
1814 char *filename = estrndup(fname, (ext - fname) + ext_len);
1815
1816 if ((realpath = expand_filepath(filename, NULL))) {
1817 #ifdef PHP_WIN32
1818 phar_unixify_path_separators(realpath, strlen(realpath));
1819 #endif
1820 if (zend_hash_str_exists(&(PHAR_G(phar_fname_map)), realpath, strlen(realpath))) {
1821 efree(realpath);
1822 efree(filename);
1823 return SUCCESS;
1824 }
1825
1826 if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_phars, realpath, strlen(realpath))) {
1827 efree(realpath);
1828 efree(filename);
1829 return SUCCESS;
1830 }
1831 efree(realpath);
1832 }
1833
1834 if (SUCCESS == php_stream_stat_path((char *) filename, &ssb)) {
1835
1836 efree(filename);
1837
1838 if (ssb.sb.st_mode & S_IFDIR) {
1839 return FAILURE;
1840 }
1841
1842 if (for_create == 1) {
1843 return FAILURE;
1844 }
1845
1846 return SUCCESS;
1847 } else {
1848 char *slash;
1849
1850 if (!for_create) {
1851 efree(filename);
1852 return FAILURE;
1853 }
1854
1855 slash = (char *) strrchr(filename, '/');
1856
1857 if (slash) {
1858 *slash = '\0';
1859 }
1860
1861 if (SUCCESS != php_stream_stat_path((char *) filename, &ssb)) {
1862 if (!slash) {
1863 if (!(realpath = expand_filepath(filename, NULL))) {
1864 efree(filename);
1865 return FAILURE;
1866 }
1867 #ifdef PHP_WIN32
1868 phar_unixify_path_separators(realpath, strlen(realpath));
1869 #endif
1870 slash = strstr(realpath, filename);
1871 if (slash) {
1872 slash += ((ext - fname) + ext_len);
1873 *slash = '\0';
1874 }
1875 slash = strrchr(realpath, '/');
1876
1877 if (slash) {
1878 *slash = '\0';
1879 } else {
1880 efree(realpath);
1881 efree(filename);
1882 return FAILURE;
1883 }
1884
1885 if (SUCCESS != php_stream_stat_path(realpath, &ssb)) {
1886 efree(realpath);
1887 efree(filename);
1888 return FAILURE;
1889 }
1890
1891 efree(realpath);
1892
1893 if (ssb.sb.st_mode & S_IFDIR) {
1894 efree(filename);
1895 return SUCCESS;
1896 }
1897 }
1898
1899 efree(filename);
1900 return FAILURE;
1901 }
1902
1903 efree(filename);
1904
1905 if (ssb.sb.st_mode & S_IFDIR) {
1906 return SUCCESS;
1907 }
1908
1909 return FAILURE;
1910 }
1911 }
1912 /* }}} */
1913
1914 /* check for ".phar" in extension */
phar_check_str(const char * fname,const char * ext_str,size_t ext_len,int executable,int for_create)1915 static zend_result phar_check_str(const char *fname, const char *ext_str, size_t ext_len, int executable, int for_create) /* {{{ */
1916 {
1917 const char *pos;
1918
1919 if (ext_len >= 50) {
1920 return FAILURE;
1921 }
1922 if (executable == 1) {
1923 /* executable phars must contain ".phar" as a valid extension (phar://.pharmy/oops is invalid) */
1924 /* (phar://hi/there/.phar/oops is also invalid) */
1925 pos = strstr(ext_str, ".phar");
1926
1927 if (!pos
1928 || (pos != ext_str && (*(pos - 1) == '/'))
1929 || (ext_len - (pos - ext_str)) < 5
1930 || !(pos += 5)
1931 || !(*pos == '\0' || *pos == '/' || *pos == '.')) {
1932 return FAILURE;
1933 }
1934 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1935 }
1936
1937 /* data phars need only contain a single non-"." to be valid */
1938 if (!executable) {
1939 pos = strstr(ext_str, ".phar");
1940 if (!(pos && (*(pos - 1) != '/')
1941 && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) && *(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
1942 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1943 }
1944 } else {
1945 if (*(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
1946 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1947 }
1948 }
1949
1950 return FAILURE;
1951 }
1952 /* }}} */
1953
1954 /*
1955 * if executable is 1, only returns SUCCESS if the extension is one of the tar/zip .phar extensions
1956 * if executable is 0, it returns SUCCESS only if the filename does *not* contain ".phar" anywhere, and treats
1957 * the first extension as the filename extension
1958 *
1959 * if an extension is found, it sets ext_str to the location of the file extension in filename,
1960 * and ext_len to the length of the extension.
1961 * for urls like "phar://alias/oops" it instead sets ext_len to -1 and returns FAILURE, which tells
1962 * the calling function to use "alias" as the phar alias
1963 *
1964 * the last parameter should be set to tell the thing to assume that filename is the full path, and only to check the
1965 * extension rules, not to iterate.
1966 */
phar_detect_phar_fname_ext(const char * filename,size_t filename_len,const char ** ext_str,size_t * ext_len,int executable,int for_create,int is_complete)1967 zend_result phar_detect_phar_fname_ext(const char *filename, size_t filename_len, const char **ext_str, size_t *ext_len, int executable, int for_create, int is_complete) /* {{{ */
1968 {
1969 const char *pos, *slash;
1970
1971 *ext_str = NULL;
1972 *ext_len = 0;
1973
1974 if (filename_len <= 1) {
1975 return FAILURE;
1976 }
1977
1978 phar_request_initialize();
1979 /* first check for alias in first segment */
1980 pos = memchr(filename, '/', filename_len);
1981
1982 if (pos && pos != filename) {
1983 /* check for url like http:// or phar:// */
1984 if (*(pos - 1) == ':' && (size_t)(pos - filename) < filename_len - 1 && *(pos + 1) == '/') {
1985 *ext_len = -2;
1986 *ext_str = NULL;
1987 return FAILURE;
1988 }
1989 if (zend_hash_str_exists(&(PHAR_G(phar_alias_map)), (char *) filename, pos - filename)) {
1990 *ext_str = pos;
1991 *ext_len = -1;
1992 return FAILURE;
1993 }
1994
1995 if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_alias, (char *) filename, pos - filename)) {
1996 *ext_str = pos;
1997 *ext_len = -1;
1998 return FAILURE;
1999 }
2000 }
2001
2002 if (zend_hash_num_elements(&(PHAR_G(phar_fname_map))) || PHAR_G(manifest_cached)) {
2003 phar_archive_data *pphar;
2004
2005 if (is_complete) {
2006 if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), (char *) filename, filename_len))) {
2007 *ext_str = filename + (filename_len - pphar->ext_len);
2008 woohoo:
2009 *ext_len = pphar->ext_len;
2010
2011 if (executable == 2) {
2012 return SUCCESS;
2013 }
2014
2015 if (executable == 1 && !pphar->is_data) {
2016 return SUCCESS;
2017 }
2018
2019 if (!executable && pphar->is_data) {
2020 return SUCCESS;
2021 }
2022
2023 return FAILURE;
2024 }
2025
2026 if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, (char *) filename, filename_len))) {
2027 *ext_str = filename + (filename_len - pphar->ext_len);
2028 goto woohoo;
2029 }
2030 } else {
2031 zend_string *str_key;
2032
2033 ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&PHAR_G(phar_fname_map), str_key, pphar) {
2034 if (ZSTR_LEN(str_key) > filename_len) {
2035 continue;
2036 }
2037
2038 if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && (filename_len == ZSTR_LEN(str_key)
2039 || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) {
2040 *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len);
2041 goto woohoo;
2042 }
2043 } ZEND_HASH_FOREACH_END();
2044
2045 if (PHAR_G(manifest_cached)) {
2046 ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&cached_phars, str_key, pphar) {
2047 if (ZSTR_LEN(str_key) > filename_len) {
2048 continue;
2049 }
2050
2051 if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && (filename_len == ZSTR_LEN(str_key)
2052 || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) {
2053 *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len);
2054 goto woohoo;
2055 }
2056 } ZEND_HASH_FOREACH_END();
2057 }
2058 }
2059 }
2060
2061 // TODO Use some sort of loop here instead of a goto
2062 pos = memchr(filename + 1, '.', filename_len);
2063 next_extension:
2064 if (!pos) {
2065 return FAILURE;
2066 }
2067
2068 while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) {
2069 pos = memchr(pos + 1, '.', filename_len - (pos - filename) - 1);
2070 if (!pos) {
2071 return FAILURE;
2072 }
2073 }
2074
2075 slash = memchr(pos, '/', filename_len - (pos - filename));
2076
2077 if (!slash) {
2078 /* this is a url like "phar://blah.phar" with no directory */
2079 *ext_str = pos;
2080 *ext_len = strlen(pos);
2081
2082 /* file extension must contain "phar" */
2083 return phar_check_str(filename, *ext_str, *ext_len, executable, for_create);
2084 }
2085
2086 /* we've found an extension that ends at a directory separator */
2087 *ext_str = pos;
2088 *ext_len = slash - pos;
2089
2090 if (phar_check_str(filename, *ext_str, *ext_len, executable, for_create) == SUCCESS) {
2091 return SUCCESS;
2092 }
2093
2094 /* look for more extensions */
2095 pos = strchr(pos + 1, '.');
2096 if (pos) {
2097 *ext_str = NULL;
2098 *ext_len = 0;
2099 goto next_extension;
2100 }
2101
2102 return FAILURE;
2103 }
2104 /* }}} */
2105
php_check_dots(const char * element,size_t n)2106 static bool php_check_dots(const char *element, size_t n) /* {{{ */
2107 {
2108 for(n-- ; n != SIZE_MAX; --n) {
2109 if (element[n] != '.') {
2110 return 1;
2111 }
2112 }
2113 return 0;
2114 }
2115 /* }}} */
2116
2117 #define IS_DIRECTORY_UP(element, len) \
2118 (len >= 2 && !php_check_dots(element, len))
2119
2120 #define IS_DIRECTORY_CURRENT(element, len) \
2121 (len == 1 && element[0] == '.')
2122
2123 #define IS_BACKSLASH(c) ((c) == '/')
2124
2125 /**
2126 * Remove .. and . references within a phar filename
2127 */
phar_fix_filepath(char * path,size_t * new_len,int use_cwd)2128 char *phar_fix_filepath(char *path, size_t *new_len, int use_cwd) /* {{{ */
2129 {
2130 char *newpath;
2131 size_t newpath_len;
2132 char *ptr;
2133 char *tok;
2134 size_t ptr_length, path_length = *new_len;
2135
2136 if (PHAR_G(cwd_len) && use_cwd && path_length > 2 && path[0] == '.' && path[1] == '/') {
2137 newpath_len = PHAR_G(cwd_len);
2138 newpath = emalloc(strlen(path) + newpath_len + 1);
2139 memcpy(newpath, PHAR_G(cwd), newpath_len);
2140 } else {
2141 newpath = emalloc(strlen(path) + 2);
2142 newpath[0] = '/';
2143 newpath_len = 1;
2144 }
2145
2146 ptr = path;
2147
2148 if (*ptr == '/') {
2149 ++ptr;
2150 }
2151
2152 tok = ptr;
2153
2154 do {
2155 ptr = memchr(ptr, '/', path_length - (ptr - path));
2156 } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
2157
2158 if (!ptr && (path_length - (tok - path))) {
2159 switch (path_length - (tok - path)) {
2160 case 1:
2161 if (*tok == '.') {
2162 efree(path);
2163 *new_len = 1;
2164 efree(newpath);
2165 return estrndup("/", 1);
2166 }
2167 break;
2168 case 2:
2169 if (tok[0] == '.' && tok[1] == '.') {
2170 efree(path);
2171 *new_len = 1;
2172 efree(newpath);
2173 return estrndup("/", 1);
2174 }
2175 }
2176 efree(newpath);
2177 return path;
2178 }
2179
2180 while (ptr) {
2181 ptr_length = ptr - tok;
2182 last_time:
2183 if (IS_DIRECTORY_UP(tok, ptr_length)) {
2184 while (newpath_len > 1 && !IS_BACKSLASH(newpath[newpath_len - 1])) {
2185 newpath_len--;
2186 }
2187
2188 if (newpath[0] != '/') {
2189 newpath[newpath_len] = '\0';
2190 } else if (newpath_len > 1) {
2191 --newpath_len;
2192 }
2193 } else if (!IS_DIRECTORY_CURRENT(tok, ptr_length)) {
2194 if (newpath_len > 1) {
2195 newpath[newpath_len++] = '/';
2196 memcpy(newpath + newpath_len, tok, ptr_length+1);
2197 } else {
2198 memcpy(newpath + newpath_len, tok, ptr_length+1);
2199 }
2200
2201 newpath_len += ptr_length;
2202 }
2203
2204 if (ptr == path + path_length) {
2205 break;
2206 }
2207
2208 tok = ++ptr;
2209
2210 do {
2211 ptr = memchr(ptr, '/', path_length - (ptr - path));
2212 } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
2213
2214 if (!ptr && (path_length - (tok - path))) {
2215 ptr_length = path_length - (tok - path);
2216 ptr = path + path_length;
2217 goto last_time;
2218 }
2219 }
2220
2221 efree(path);
2222 *new_len = newpath_len;
2223 newpath[newpath_len] = '\0';
2224 return erealloc(newpath, newpath_len + 1);
2225 }
2226 /* }}} */
2227
2228 /**
2229 * Process a phar stream name, ensuring we can handle any of:
2230 *
2231 * - whatever.phar
2232 * - whatever.phar.gz
2233 * - whatever.phar.bz2
2234 * - whatever.phar.php
2235 *
2236 * Optionally the name might start with 'phar://'
2237 *
2238 * This is used by phar_parse_url()
2239 */
phar_split_fname(const char * filename,size_t filename_len,char ** arch,size_t * arch_len,char ** entry,size_t * entry_len,int executable,int for_create)2240 zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, char **entry, size_t *entry_len, int executable, int for_create) /* {{{ */
2241 {
2242 const char *ext_str;
2243 #ifdef PHP_WIN32
2244 char *save;
2245 #endif
2246 size_t ext_len;
2247
2248 if (CHECK_NULL_PATH(filename, filename_len)) {
2249 return FAILURE;
2250 }
2251
2252 if (!strncasecmp(filename, "phar://", 7)) {
2253 filename += 7;
2254 filename_len -= 7;
2255 }
2256
2257 ext_len = 0;
2258 #ifdef PHP_WIN32
2259 save = (char *)filename;
2260 if (memchr(filename, '\\', filename_len)) {
2261 filename = estrndup(filename, filename_len);
2262 phar_unixify_path_separators((char *)filename, filename_len);
2263 }
2264 #endif
2265 if (phar_detect_phar_fname_ext(filename, filename_len, &ext_str, &ext_len, executable, for_create, 0) == FAILURE) {
2266 if (ext_len != -1) {
2267 if (!ext_str) {
2268 /* no / detected, restore arch for error message */
2269 #ifdef PHP_WIN32
2270 *arch = save;
2271 #else
2272 *arch = (char*)filename;
2273 #endif
2274 }
2275
2276 #ifdef PHP_WIN32
2277 if (filename != save) {
2278 efree((char *)filename);
2279 }
2280 #endif
2281 return FAILURE;
2282 }
2283
2284 ext_len = 0;
2285 /* no extension detected - instead we are dealing with an alias */
2286 }
2287
2288 *arch_len = ext_str - filename + ext_len;
2289 *arch = estrndup(filename, *arch_len);
2290
2291 if (ext_str[ext_len]) {
2292 *entry_len = filename_len - *arch_len;
2293 *entry = estrndup(ext_str+ext_len, *entry_len);
2294 #ifdef PHP_WIN32
2295 phar_unixify_path_separators(*entry, *entry_len);
2296 #endif
2297 *entry = phar_fix_filepath(*entry, entry_len, 0);
2298 } else {
2299 *entry_len = 1;
2300 *entry = estrndup("/", 1);
2301 }
2302
2303 #ifdef PHP_WIN32
2304 if (filename != save) {
2305 efree((char *)filename);
2306 }
2307 #endif
2308
2309 return SUCCESS;
2310 }
2311 /* }}} */
2312
2313 /**
2314 * Invoked when a user calls Phar::mapPhar() from within an executing .phar
2315 * to set up its manifest directly
2316 */
phar_open_executed_filename(char * alias,size_t alias_len,char ** error)2317 zend_result phar_open_executed_filename(char *alias, size_t alias_len, char **error) /* {{{ */
2318 {
2319 if (error) {
2320 *error = NULL;
2321 }
2322
2323 zend_string *fname = zend_get_executed_filename_ex();
2324
2325 if (!fname) {
2326 if (error) {
2327 spprintf(error, 0, "cannot initialize a phar outside of PHP execution");
2328 }
2329 return FAILURE;
2330 }
2331
2332 if (phar_open_parsed_phar(ZSTR_VAL(fname), ZSTR_LEN(fname), alias, alias_len, 0, REPORT_ERRORS, NULL, 0) == SUCCESS) {
2333 return SUCCESS;
2334 }
2335
2336 if (0 == zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) {
2337 if (error) {
2338 spprintf(error, 0, "__HALT_COMPILER(); must be declared in a phar");
2339 }
2340 return FAILURE;
2341 }
2342
2343 if (php_check_open_basedir(ZSTR_VAL(fname))) {
2344 return FAILURE;
2345 }
2346
2347 zend_string *actual = NULL;
2348 php_stream *fp;
2349 fp = php_stream_open_wrapper(ZSTR_VAL(fname), "rb", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, &actual);
2350
2351 if (!fp) {
2352 if (error) {
2353 spprintf(error, 0, "unable to open phar for reading \"%s\"", ZSTR_VAL(fname));
2354 }
2355 if (actual) {
2356 zend_string_release_ex(actual, 0);
2357 }
2358 return FAILURE;
2359 }
2360
2361 if (actual) {
2362 fname = actual;
2363 }
2364
2365 zend_result ret = phar_open_from_fp(fp, ZSTR_VAL(fname), ZSTR_LEN(fname), alias, alias_len, REPORT_ERRORS, NULL, error);
2366
2367 if (actual) {
2368 zend_string_release_ex(actual, 0);
2369 }
2370
2371 return ret;
2372 }
2373 /* }}} */
2374
2375 /**
2376 * Validate the CRC32 of a file opened from within the phar
2377 */
phar_postprocess_file(phar_entry_data * idata,uint32_t crc32,char ** error,int process_zip)2378 zend_result phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip) /* {{{ */
2379 {
2380 php_stream *fp = idata->fp;
2381 phar_entry_info *entry = idata->internal_file;
2382
2383 if (error) {
2384 *error = NULL;
2385 }
2386
2387 if (entry->is_zip && process_zip > 0) {
2388 /* verify local file header */
2389 phar_zip_file_header local;
2390 phar_zip_data_desc desc;
2391
2392 if (SUCCESS != phar_open_archive_fp(idata->phar)) {
2393 spprintf(error, 0, "phar error: unable to open zip-based phar archive \"%s\" to verify local file header for file \"%s\"", idata->phar->fname, entry->filename);
2394 return FAILURE;
2395 }
2396 php_stream_seek(phar_get_entrypfp(idata->internal_file), entry->header_offset, SEEK_SET);
2397
2398 if (sizeof(local) != php_stream_read(phar_get_entrypfp(idata->internal_file), (char *) &local, sizeof(local))) {
2399
2400 spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local file header for file \"%s\")", idata->phar->fname, entry->filename);
2401 return FAILURE;
2402 }
2403
2404 /* check for data descriptor */
2405 if (((PHAR_ZIP_16(local.flags)) & 0x8) == 0x8) {
2406 php_stream_seek(phar_get_entrypfp(idata->internal_file),
2407 entry->header_offset + sizeof(local) +
2408 PHAR_ZIP_16(local.filename_len) +
2409 PHAR_ZIP_16(local.extra_len) +
2410 entry->compressed_filesize, SEEK_SET);
2411 if (sizeof(desc) != php_stream_read(phar_get_entrypfp(idata->internal_file),
2412 (char *) &desc, sizeof(desc))) {
2413 spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local data descriptor for file \"%s\")", idata->phar->fname, entry->filename);
2414 return FAILURE;
2415 }
2416 if (desc.signature[0] == 'P' && desc.signature[1] == 'K') {
2417 memcpy(&(local.crc32), &(desc.crc32), 12);
2418 } else {
2419 /* old data descriptors have no signature */
2420 memcpy(&(local.crc32), &desc, 12);
2421 }
2422 }
2423 /* verify local header */
2424 if (entry->filename_len != PHAR_ZIP_16(local.filename_len) || entry->crc32 != PHAR_ZIP_32(local.crc32) || entry->uncompressed_filesize != PHAR_ZIP_32(local.uncompsize) || entry->compressed_filesize != PHAR_ZIP_32(local.compsize)) {
2425 spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (local header of file \"%s\" does not match central directory)", idata->phar->fname, entry->filename);
2426 return FAILURE;
2427 }
2428
2429 /* construct actual offset to file start - local extra_len can be different from central extra_len */
2430 entry->offset = entry->offset_abs =
2431 sizeof(local) + entry->header_offset + PHAR_ZIP_16(local.filename_len) + PHAR_ZIP_16(local.extra_len);
2432
2433 if (idata->zero && idata->zero != entry->offset_abs) {
2434 idata->zero = entry->offset_abs;
2435 }
2436 }
2437
2438 if (process_zip == 1) {
2439 return SUCCESS;
2440 }
2441
2442 php_stream_seek(fp, idata->zero, SEEK_SET);
2443
2444 uint32_t crc = php_crc32_bulk_init();
2445 zend_result ret = php_crc32_stream_bulk_update(&crc, fp, idata->internal_file->uncompressed_filesize);
2446
2447 php_stream_seek(fp, idata->zero, SEEK_SET);
2448
2449 if (SUCCESS == ret && php_crc32_bulk_end(crc) == crc32) {
2450 entry->is_crc_checked = 1;
2451 return SUCCESS;
2452 } else {
2453 spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (crc32 mismatch on file \"%s\")", idata->phar->fname, entry->filename);
2454 return FAILURE;
2455 }
2456 }
2457 /* }}} */
2458
phar_set_32(char * buffer,uint32_t var)2459 static inline void phar_set_32(char *buffer, uint32_t var) /* {{{ */
2460 {
2461 #ifdef WORDS_BIGENDIAN
2462 *((buffer) + 3) = (unsigned char) (((var) >> 24) & 0xFF);
2463 *((buffer) + 2) = (unsigned char) (((var) >> 16) & 0xFF);
2464 *((buffer) + 1) = (unsigned char) (((var) >> 8) & 0xFF);
2465 *((buffer) + 0) = (unsigned char) ((var) & 0xFF);
2466 #else
2467 memcpy(buffer, &var, sizeof(var));
2468 #endif
2469 } /* }}} */
2470
phar_flush_clean_deleted_apply(zval * zv)2471 static int phar_flush_clean_deleted_apply(zval *zv) /* {{{ */
2472 {
2473 phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
2474
2475 if (entry->fp_refcount <= 0 && entry->is_deleted) {
2476 return ZEND_HASH_APPLY_REMOVE;
2477 } else {
2478 return ZEND_HASH_APPLY_KEEP;
2479 }
2480 }
2481 /* }}} */
2482
2483 #include "stub.h" /* Generated phar_get_stub() function from makestub.php script */
2484
phar_create_default_stub(const char * index_php,const char * web_index,char ** error)2485 zend_string *phar_create_default_stub(const char *index_php, const char *web_index, char **error) /* {{{ */
2486 {
2487 size_t index_len, web_len;
2488
2489 if (error) {
2490 *error = NULL;
2491 }
2492
2493 if (!index_php) {
2494 index_php = "index.php";
2495 }
2496
2497 if (!web_index) {
2498 web_index = "index.php";
2499 }
2500
2501 index_len = strlen(index_php);
2502 web_len = strlen(web_index);
2503
2504 if (index_len > 400) {
2505 /* ridiculous size not allowed for index.php startup filename */
2506 if (error) {
2507 spprintf(error, 0, "Illegal filename passed in for stub creation, was %zd characters long, and only 400 or less is allowed", index_len);
2508 return NULL;
2509 }
2510 }
2511
2512 if (web_len > 400) {
2513 /* ridiculous size not allowed for index.php startup filename */
2514 if (error) {
2515 spprintf(error, 0, "Illegal web filename passed in for stub creation, was %zd characters long, and only 400 or less is allowed", web_len);
2516 return NULL;
2517 }
2518 }
2519
2520 return phar_get_stub(index_php, web_index, index_len+1, web_len+1);
2521 }
2522 /* }}} */
2523
phar_flush(phar_archive_data * phar,char ** error)2524 void phar_flush(phar_archive_data *phar, char **error) {
2525 phar_flush_ex(phar, NULL, false, error);
2526 }
2527
2528 /**
2529 * Save phar contents to disk
2530 *
2531 * if user_stub is NULL the default or existing stub should be used
2532 */
phar_flush_ex(phar_archive_data * phar,zend_string * user_stub,bool is_default_stub,char ** error)2533 void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */
2534 {
2535 static const char halt_stub[] = "__HALT_COMPILER();";
2536
2537 phar_entry_info *entry, *newentry;
2538 size_t halt_offset;
2539 int restore_alias_len, global_flags = 0;
2540 bool must_close_old_file = false;
2541 bool has_dirs = false;
2542 char manifest[18], entry_buffer[24];
2543 zend_off_t manifest_ftell;
2544 zend_long offset;
2545 size_t wrote;
2546 uint32_t manifest_len, mytime, new_manifest_count;
2547 uint32_t newcrc32;
2548 php_stream *file, *oldfile, *newfile;
2549 php_stream_filter *filter;
2550 php_serialize_data_t metadata_hash;
2551 smart_str main_metadata_str = {0};
2552 bool free_fp = true;
2553 bool free_ufp = true;
2554 bool manifest_hack = false;
2555 php_stream *shared_cfp = NULL;
2556
2557 if (phar->is_persistent) {
2558 if (error) {
2559 spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
2560 }
2561 return;
2562 }
2563
2564 if (error) {
2565 *error = NULL;
2566 }
2567
2568 if (!zend_hash_num_elements(&phar->manifest) && !user_stub) {
2569 return;
2570 }
2571
2572 zend_hash_clean(&phar->virtual_dirs);
2573
2574 if (phar->is_zip) {
2575 phar_zip_flush(phar, user_stub, is_default_stub, error);
2576 return;
2577 }
2578
2579 if (phar->is_tar) {
2580 phar_tar_flush(phar, user_stub, is_default_stub, error);
2581 return;
2582 }
2583
2584 if (PHAR_G(readonly)) {
2585 return;
2586 }
2587
2588 if (phar->fp && !phar->is_brandnew) {
2589 oldfile = phar->fp;
2590 must_close_old_file = false;
2591 php_stream_rewind(oldfile);
2592 } else {
2593 oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL);
2594 must_close_old_file = oldfile != NULL;
2595 }
2596 newfile = php_stream_fopen_tmpfile();
2597 if (!newfile) {
2598 if (error) {
2599 spprintf(error, 0, "unable to create temporary file");
2600 }
2601 if (must_close_old_file) {
2602 php_stream_close(oldfile);
2603 }
2604 return;
2605 }
2606
2607 if (user_stub) {
2608 char *pos = php_stristr(ZSTR_VAL(user_stub), halt_stub, ZSTR_LEN(user_stub), strlen(halt_stub));
2609
2610 if (pos == NULL) {
2611 if (must_close_old_file) {
2612 php_stream_close(oldfile);
2613 }
2614 php_stream_close(newfile);
2615 if (error) {
2616 spprintf(error, 0, "illegal stub for phar \"%s\" (__HALT_COMPILER(); is missing)", phar->fname);
2617 }
2618 return;
2619 }
2620
2621 size_t len = pos - ZSTR_VAL(user_stub) + strlen(halt_stub);
2622 const char end_sequence[] = " ?>\r\n";
2623 size_t end_sequence_len = strlen(end_sequence);
2624
2625 if (
2626 len != php_stream_write(newfile, ZSTR_VAL(user_stub), len)
2627 || end_sequence_len != php_stream_write(newfile, end_sequence, end_sequence_len)
2628 ) {
2629 if (must_close_old_file) {
2630 php_stream_close(oldfile);
2631 }
2632 php_stream_close(newfile);
2633 if (error) {
2634 spprintf(error, 0, "unable to create stub from string in new phar \"%s\"", phar->fname);
2635 }
2636 return;
2637 }
2638 phar->halt_offset = len + end_sequence_len;
2639 } else {
2640 size_t written;
2641 zend_string *new_stub = NULL;
2642
2643 if (!user_stub && phar->halt_offset && oldfile && !phar->is_brandnew) {
2644 php_stream_copy_to_stream_ex(oldfile, newfile, phar->halt_offset, &written);
2645 } else {
2646 /* this is either a brand new phar or a default stub overwrite */
2647 new_stub = phar_create_default_stub(NULL, NULL, NULL);
2648 phar->halt_offset = ZSTR_LEN(new_stub);
2649 written = php_stream_write(newfile, ZSTR_VAL(new_stub), phar->halt_offset);
2650 }
2651 if (phar->halt_offset != written) {
2652 if (must_close_old_file) {
2653 php_stream_close(oldfile);
2654 }
2655 php_stream_close(newfile);
2656 if (error) {
2657 if (new_stub) {
2658 spprintf(error, 0, "unable to create stub in new phar \"%s\"", phar->fname);
2659 } else {
2660 spprintf(error, 0, "unable to copy stub of old phar to new phar \"%s\"", phar->fname);
2661 }
2662 }
2663 if (new_stub) {
2664 zend_string_free(new_stub);
2665 }
2666 return;
2667 }
2668 if (new_stub) {
2669 zend_string_free(new_stub);
2670 }
2671 }
2672 manifest_ftell = php_stream_tell(newfile);
2673 halt_offset = manifest_ftell;
2674
2675 /* Check whether we can get rid of some of the deleted entries which are
2676 * unused. However some might still be in use so even after this clean-up
2677 * we need to skip entries marked is_deleted. */
2678 zend_hash_apply(&phar->manifest, phar_flush_clean_deleted_apply);
2679
2680 /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */
2681 main_metadata_str.s = NULL;
2682 if (phar->metadata_tracker.str) {
2683 smart_str_appendl(&main_metadata_str, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
2684 } else if (!Z_ISUNDEF(phar->metadata_tracker.val)) {
2685 PHP_VAR_SERIALIZE_INIT(metadata_hash);
2686 php_var_serialize(&main_metadata_str, &phar->metadata_tracker.val, &metadata_hash);
2687 PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
2688 }
2689 new_manifest_count = 0;
2690 offset = 0;
2691 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
2692 if (entry->cfp) {
2693 /* did we forget to get rid of cfp last time? */
2694 php_stream_close(entry->cfp);
2695 entry->cfp = 0;
2696 }
2697 if (entry->is_deleted || entry->is_mounted) {
2698 /* remove this from the new phar */
2699 continue;
2700 }
2701 if (!entry->is_modified && entry->fp_refcount) {
2702 /* open file pointers refer to this fp, do not free the stream */
2703 switch (entry->fp_type) {
2704 case PHAR_FP:
2705 free_fp = false;
2706 break;
2707 case PHAR_UFP:
2708 free_ufp = false;
2709 default:
2710 break;
2711 }
2712 }
2713 /* after excluding deleted files, calculate manifest size in bytes and number of entries */
2714 ++new_manifest_count;
2715 phar_add_virtual_dirs(phar, entry->filename, entry->filename_len);
2716
2717 if (entry->is_dir) {
2718 /* we use this to calculate API version, 1.1.1 is used for phars with directories */
2719 has_dirs = true;
2720 }
2721 if (!Z_ISUNDEF(entry->metadata_tracker.val) && !entry->metadata_tracker.str) {
2722 ZEND_ASSERT(!entry->is_persistent);
2723 /* Assume serialization will succeed. TODO: Set error and throw if EG(exception) != NULL */
2724 smart_str buf = {0};
2725 PHP_VAR_SERIALIZE_INIT(metadata_hash);
2726 php_var_serialize(&buf, &entry->metadata_tracker.val, &metadata_hash);
2727 PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
2728 entry->metadata_tracker.str = buf.s;
2729 }
2730
2731 /* 32 bits for filename length, length of filename, manifest + metadata, and add 1 for trailing / if a directory */
2732 offset += 4 + entry->filename_len + sizeof(entry_buffer) + (entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0) + (entry->is_dir ? 1 : 0);
2733
2734 /* compress and rehash as necessary */
2735 if ((oldfile && !entry->is_modified) || entry->is_dir) {
2736 if (entry->fp_type == PHAR_UFP) {
2737 /* reset so we can copy the compressed data over */
2738 entry->fp_type = PHAR_FP;
2739 }
2740 continue;
2741 }
2742 if (!phar_get_efp(entry, 0)) {
2743 /* re-open internal file pointer just-in-time */
2744 newentry = phar_open_jit(phar, entry, error);
2745 if (!newentry) {
2746 /* major problem re-opening, so we ignore this file and the error */
2747 efree(*error);
2748 *error = NULL;
2749 continue;
2750 }
2751 entry = newentry;
2752 }
2753 file = phar_get_efp(entry, 0);
2754 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 1)) {
2755 if (must_close_old_file) {
2756 php_stream_close(oldfile);
2757 }
2758 php_stream_close(newfile);
2759 if (error) {
2760 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2761 }
2762 return;
2763 }
2764 newcrc32 = php_crc32_bulk_init();
2765 php_crc32_stream_bulk_update(&newcrc32, file, entry->uncompressed_filesize);
2766 entry->crc32 = php_crc32_bulk_end(newcrc32);
2767 entry->is_crc_checked = 1;
2768 if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
2769 /* not compressed */
2770 entry->compressed_filesize = entry->uncompressed_filesize;
2771 continue;
2772 }
2773 filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0);
2774 if (!filter) {
2775 if (must_close_old_file) {
2776 php_stream_close(oldfile);
2777 }
2778 php_stream_close(newfile);
2779 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
2780 if (error) {
2781 spprintf(error, 0, "unable to gzip compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
2782 }
2783 } else {
2784 if (error) {
2785 spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
2786 }
2787 }
2788 return;
2789 }
2790
2791 /* create new file that holds the compressed versions */
2792 /* work around inability to specify freedom in write and strictness
2793 in read count */
2794 if (shared_cfp == NULL) {
2795 shared_cfp = php_stream_fopen_tmpfile();
2796 }
2797 entry->cfp = shared_cfp;
2798 if (!entry->cfp) {
2799 if (error) {
2800 spprintf(error, 0, "unable to create temporary file");
2801 }
2802 if (must_close_old_file) {
2803 php_stream_close(oldfile);
2804 }
2805 php_stream_close(newfile);
2806 goto cleanup;
2807 }
2808 /* for real phars, header_offset is unused; we misuse it here to store the offset in the temp file */
2809 ZEND_ASSERT(entry->header_offset == 0);
2810 entry->header_offset = php_stream_tell(entry->cfp);
2811 php_stream_flush(file);
2812 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
2813 if (must_close_old_file) {
2814 php_stream_close(oldfile);
2815 }
2816 php_stream_close(newfile);
2817 if (error) {
2818 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2819 }
2820 goto cleanup;
2821 }
2822 php_stream_filter_append((&entry->cfp->writefilters), filter);
2823 if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) {
2824 if (must_close_old_file) {
2825 php_stream_close(oldfile);
2826 }
2827 php_stream_close(newfile);
2828 if (error) {
2829 spprintf(error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2830 }
2831 goto cleanup;
2832 }
2833 php_stream_filter_flush(filter, 1);
2834 php_stream_flush(entry->cfp);
2835 php_stream_filter_remove(filter, 1);
2836 php_stream_seek(entry->cfp, 0, SEEK_END);
2837 entry->compressed_filesize = ((uint32_t) php_stream_tell(entry->cfp)) - entry->header_offset;
2838 /* generate crc on compressed file */
2839 entry->old_flags = entry->flags;
2840 entry->is_modified = 1;
2841 global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK);
2842 } ZEND_HASH_FOREACH_END();
2843 global_flags |= PHAR_HDR_SIGNATURE;
2844
2845 /* write out manifest pre-header */
2846 /* 4: manifest length
2847 * 4: manifest entry count
2848 * 2: phar version
2849 * 4: phar global flags
2850 * 4: alias length
2851 * ?: the alias itself
2852 * 4: phar metadata length
2853 * ?: phar metadata
2854 */
2855 restore_alias_len = phar->alias_len;
2856 if (phar->is_temporary_alias) {
2857 phar->alias_len = 0;
2858 }
2859
2860 manifest_len = offset + phar->alias_len + sizeof(manifest) + (main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0);
2861 phar_set_32(manifest, manifest_len);
2862 /* Hack - see bug #65028, add padding byte to the end of the manifest */
2863 if(manifest[0] == '\r' || manifest[0] == '\n') {
2864 manifest_len++;
2865 phar_set_32(manifest, manifest_len);
2866 manifest_hack = true;
2867 }
2868 phar_set_32(manifest+4, new_manifest_count);
2869 if (has_dirs) {
2870 *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION) >> 8) & 0xFF);
2871 *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION) & 0xF0));
2872 } else {
2873 *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION_NODIR) >> 8) & 0xFF);
2874 *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION_NODIR) & 0xF0));
2875 }
2876 phar_set_32(manifest+10, global_flags);
2877 phar_set_32(manifest+14, phar->alias_len);
2878
2879 /* write the manifest header */
2880 if (sizeof(manifest) != php_stream_write(newfile, manifest, sizeof(manifest))
2881 || (size_t)phar->alias_len != php_stream_write(newfile, phar->alias, phar->alias_len)) {
2882
2883 if (must_close_old_file) {
2884 php_stream_close(oldfile);
2885 }
2886
2887 php_stream_close(newfile);
2888 phar->alias_len = restore_alias_len;
2889
2890 if (error) {
2891 spprintf(error, 0, "unable to write manifest header of new phar \"%s\"", phar->fname);
2892 }
2893
2894 goto cleanup;
2895 }
2896
2897 phar->alias_len = restore_alias_len;
2898
2899 phar_set_32(manifest, main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0);
2900 if (4 != php_stream_write(newfile, manifest, 4) || ((main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0)
2901 && ZSTR_LEN(main_metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s)))) {
2902 smart_str_free(&main_metadata_str);
2903
2904 if (must_close_old_file) {
2905 php_stream_close(oldfile);
2906 }
2907
2908 php_stream_close(newfile);
2909 phar->alias_len = restore_alias_len;
2910
2911 if (error) {
2912 spprintf(error, 0, "unable to write manifest meta-data of new phar \"%s\"", phar->fname);
2913 }
2914
2915 goto cleanup;
2916 }
2917 smart_str_free(&main_metadata_str);
2918
2919 /* re-calculate the manifest location to simplify later code */
2920 manifest_ftell = php_stream_tell(newfile);
2921
2922 /* now write the manifest */
2923 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
2924 const zend_string *metadata_str;
2925 if (entry->is_deleted || entry->is_mounted) {
2926 /* remove this from the new phar if deleted, ignore if mounted */
2927 continue;
2928 }
2929
2930 if (entry->is_dir) {
2931 /* add 1 for trailing slash */
2932 phar_set_32(entry_buffer, entry->filename_len + 1);
2933 } else {
2934 phar_set_32(entry_buffer, entry->filename_len);
2935 }
2936
2937 if (4 != php_stream_write(newfile, entry_buffer, 4)
2938 || entry->filename_len != php_stream_write(newfile, entry->filename, entry->filename_len)
2939 || (entry->is_dir && 1 != php_stream_write(newfile, "/", 1))) {
2940 if (must_close_old_file) {
2941 php_stream_close(oldfile);
2942 }
2943 php_stream_close(newfile);
2944 if (error) {
2945 if (entry->is_dir) {
2946 spprintf(error, 0, "unable to write filename of directory \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2947 } else {
2948 spprintf(error, 0, "unable to write filename of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2949 }
2950 }
2951 goto cleanup;
2952 }
2953
2954 /* set the manifest meta-data:
2955 4: uncompressed filesize
2956 4: creation timestamp
2957 4: compressed filesize
2958 4: crc32
2959 4: flags
2960 4: metadata-len
2961 +: metadata
2962 */
2963 mytime = time(NULL);
2964 phar_set_32(entry_buffer, entry->uncompressed_filesize);
2965 phar_set_32(entry_buffer+4, mytime);
2966 phar_set_32(entry_buffer+8, entry->compressed_filesize);
2967 phar_set_32(entry_buffer+12, entry->crc32);
2968 phar_set_32(entry_buffer+16, entry->flags);
2969 metadata_str = entry->metadata_tracker.str;
2970 phar_set_32(entry_buffer+20, metadata_str ? ZSTR_LEN(metadata_str) : 0);
2971
2972 if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer))
2973 || (metadata_str &&
2974 ZSTR_LEN(metadata_str) != php_stream_write(newfile, ZSTR_VAL(metadata_str), ZSTR_LEN(metadata_str)))) {
2975 if (must_close_old_file) {
2976 php_stream_close(oldfile);
2977 }
2978
2979 php_stream_close(newfile);
2980
2981 if (error) {
2982 spprintf(error, 0, "unable to write temporary manifest of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2983 }
2984
2985 goto cleanup;
2986 }
2987 } ZEND_HASH_FOREACH_END();
2988 /* Hack - see bug #65028, add padding byte to the end of the manifest */
2989 if (manifest_hack) {
2990 if(1 != php_stream_write(newfile, manifest, 1)) {
2991 if (must_close_old_file) {
2992 php_stream_close(oldfile);
2993 }
2994
2995 php_stream_close(newfile);
2996
2997 if (error) {
2998 spprintf(error, 0, "unable to write manifest padding byte");
2999 }
3000
3001 goto cleanup;
3002 }
3003 }
3004
3005 /* now copy the actual file data to the new phar */
3006 offset = php_stream_tell(newfile);
3007 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
3008 if (entry->is_deleted || entry->is_dir || entry->is_mounted) {
3009 continue;
3010 }
3011
3012 if (entry->cfp) {
3013 file = entry->cfp;
3014 php_stream_seek(file, entry->header_offset, SEEK_SET);
3015 } else {
3016 file = phar_get_efp(entry, 0);
3017 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
3018 if (must_close_old_file) {
3019 php_stream_close(oldfile);
3020 }
3021 php_stream_close(newfile);
3022 if (error) {
3023 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
3024 }
3025 goto cleanup;
3026 }
3027 }
3028
3029 if (!file) {
3030 if (must_close_old_file) {
3031 php_stream_close(oldfile);
3032 }
3033 php_stream_close(newfile);
3034 if (error) {
3035 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
3036 }
3037 goto cleanup;
3038 }
3039
3040 /* this will have changed for all files that have either changed compression or been modified */
3041 entry->offset = entry->offset_abs = offset;
3042 offset += entry->compressed_filesize;
3043 if (php_stream_copy_to_stream_ex(file, newfile, entry->compressed_filesize, &wrote) == FAILURE) {
3044 if (must_close_old_file) {
3045 php_stream_close(oldfile);
3046 }
3047
3048 php_stream_close(newfile);
3049
3050 if (error) {
3051 spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
3052 }
3053
3054 goto cleanup;
3055 }
3056
3057 entry->is_modified = 0;
3058
3059 if (entry->cfp) {
3060 entry->cfp = NULL;
3061 entry->header_offset = 0;
3062 }
3063
3064 if (entry->fp_type == PHAR_MOD) {
3065 /* this fp is in use by a phar_entry_data returned by phar_get_entry_data, it will be closed when the phar_entry_data is phar_entry_delref'ed */
3066 if (entry->fp_refcount == 0 && entry->fp != phar->fp && entry->fp != phar->ufp) {
3067 php_stream_close(entry->fp);
3068 }
3069
3070 entry->fp = NULL;
3071 entry->fp_type = PHAR_FP;
3072 } else if (entry->fp_type == PHAR_UFP) {
3073 entry->fp_type = PHAR_FP;
3074 }
3075 } ZEND_HASH_FOREACH_END();
3076
3077 if (shared_cfp != NULL) {
3078 php_stream_close(shared_cfp);
3079 shared_cfp = NULL;
3080 }
3081
3082 /* append signature */
3083 if (global_flags & PHAR_HDR_SIGNATURE) {
3084 char sig_buf[4];
3085
3086 php_stream_rewind(newfile);
3087
3088 if (phar->signature) {
3089 efree(phar->signature);
3090 phar->signature = NULL;
3091 }
3092
3093 switch(phar->sig_flags) {
3094 default: {
3095 char *digest = NULL;
3096 size_t digest_len;
3097
3098 if (FAILURE == phar_create_signature(phar, newfile, &digest, &digest_len, error)) {
3099 if (error) {
3100 char *save = *error;
3101 spprintf(error, 0, "phar error: unable to write signature: %s", save);
3102 efree(save);
3103 }
3104 if (digest) {
3105 efree(digest);
3106 }
3107 if (must_close_old_file) {
3108 php_stream_close(oldfile);
3109 }
3110 php_stream_close(newfile);
3111 return;
3112 }
3113
3114 php_stream_write(newfile, digest, digest_len);
3115 efree(digest);
3116 if (phar->sig_flags == PHAR_SIG_OPENSSL ||
3117 phar->sig_flags == PHAR_SIG_OPENSSL_SHA256 ||
3118 phar->sig_flags == PHAR_SIG_OPENSSL_SHA512) {
3119 phar_set_32(sig_buf, digest_len);
3120 php_stream_write(newfile, sig_buf, 4);
3121 }
3122 break;
3123 }
3124 }
3125 phar_set_32(sig_buf, phar->sig_flags);
3126 php_stream_write(newfile, sig_buf, 4);
3127 php_stream_write(newfile, "GBMB", 4);
3128 }
3129
3130 /* finally, close the temp file, rename the original phar,
3131 move the temp to the old phar, unlink the old phar, and reload it into memory
3132 */
3133 if (phar->fp && free_fp) {
3134 php_stream_close(phar->fp);
3135 }
3136
3137 if (phar->ufp) {
3138 if (free_ufp) {
3139 php_stream_close(phar->ufp);
3140 }
3141 phar->ufp = NULL;
3142 }
3143
3144 if (must_close_old_file) {
3145 php_stream_close(oldfile);
3146 }
3147
3148 phar->halt_offset = halt_offset;
3149 phar->is_brandnew = 0;
3150
3151 php_stream_rewind(newfile);
3152
3153 if (phar->donotflush) {
3154 /* deferred flush */
3155 phar->fp = newfile;
3156 } else {
3157 phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
3158 if (!phar->fp) {
3159 phar->fp = newfile;
3160 if (error) {
3161 spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
3162 }
3163 return;
3164 }
3165
3166 if (phar->flags & PHAR_FILE_COMPRESSED_GZ) {
3167 /* to properly compress, we have to tell zlib to add a zlib header */
3168 zval filterparams;
3169
3170 array_init(&filterparams);
3171 add_assoc_long(&filterparams, "window", MAX_WBITS+16);
3172 filter = php_stream_filter_create("zlib.deflate", &filterparams, php_stream_is_persistent(phar->fp));
3173 zend_array_destroy(Z_ARR(filterparams));
3174
3175 if (!filter) {
3176 if (error) {
3177 spprintf(error, 4096, "unable to compress all contents of phar \"%s\" using zlib, PHP versions older than 5.2.6 have a buggy zlib", phar->fname);
3178 }
3179 return;
3180 }
3181
3182 php_stream_filter_append(&phar->fp->writefilters, filter);
3183 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3184 php_stream_filter_flush(filter, 1);
3185 php_stream_filter_remove(filter, 1);
3186 php_stream_close(phar->fp);
3187 /* use the temp stream as our base */
3188 phar->fp = newfile;
3189 } else if (phar->flags & PHAR_FILE_COMPRESSED_BZ2) {
3190 filter = php_stream_filter_create("bzip2.compress", NULL, php_stream_is_persistent(phar->fp));
3191 php_stream_filter_append(&phar->fp->writefilters, filter);
3192 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3193 php_stream_filter_flush(filter, 1);
3194 php_stream_filter_remove(filter, 1);
3195 php_stream_close(phar->fp);
3196 /* use the temp stream as our base */
3197 phar->fp = newfile;
3198 } else {
3199 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3200 /* we could also reopen the file in "rb" mode but there is no need for that */
3201 php_stream_close(newfile);
3202 }
3203 }
3204
3205 if (-1 == php_stream_seek(phar->fp, phar->halt_offset, SEEK_SET)) {
3206 if (error) {
3207 spprintf(error, 0, "unable to seek to __HALT_COMPILER(); in new phar \"%s\"", phar->fname);
3208 }
3209 }
3210
3211 return;
3212
3213 cleanup:
3214 if (shared_cfp != NULL) {
3215 php_stream_close(shared_cfp);
3216 }
3217 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
3218 if (entry->cfp) {
3219 entry->cfp = NULL;
3220 entry->header_offset = 0;
3221 }
3222 } ZEND_HASH_FOREACH_END();
3223 }
3224 /* }}} */
3225
3226 #ifdef COMPILE_DL_PHAR
3227 #ifdef ZTS
3228 ZEND_TSRMLS_CACHE_DEFINE()
3229 #endif
ZEND_GET_MODULE(phar)3230 ZEND_GET_MODULE(phar)
3231 #endif
3232
3233 static ssize_t phar_zend_stream_reader(void *handle, char *buf, size_t len) /* {{{ */
3234 {
3235 return php_stream_read(phar_get_pharfp((phar_archive_data*)handle), buf, len);
3236 }
3237 /* }}} */
3238
phar_zend_stream_fsizer(void * handle)3239 static size_t phar_zend_stream_fsizer(void *handle) /* {{{ */
3240 {
3241 return ((phar_archive_data*)handle)->halt_offset + 32;
3242 } /* }}} */
3243
3244 zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type);
3245
phar_resolve_path(zend_string * filename)3246 static zend_string *phar_resolve_path(zend_string *filename)
3247 {
3248 zend_string *ret = phar_find_in_include_path(filename, NULL);
3249 if (!ret) {
3250 ret = phar_save_resolve_path(filename);
3251 }
3252 return ret;
3253 }
3254
phar_compile_file(zend_file_handle * file_handle,int type)3255 static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type) /* {{{ */
3256 {
3257 zend_op_array *res;
3258 zend_string *name = NULL;
3259 int failed;
3260 phar_archive_data *phar;
3261
3262 if (!file_handle || !file_handle->filename) {
3263 return phar_orig_compile_file(file_handle, type);
3264 }
3265 if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
3266 if (SUCCESS == phar_open_from_filename(ZSTR_VAL(file_handle->filename), ZSTR_LEN(file_handle->filename), NULL, 0, 0, &phar, NULL)) {
3267 if (phar->is_zip || phar->is_tar) {
3268 zend_file_handle f;
3269
3270 /* zip or tar-based phar */
3271 name = zend_strpprintf(4096, "phar://%s/%s", ZSTR_VAL(file_handle->filename), ".phar/stub.php");
3272 zend_stream_init_filename_ex(&f, name);
3273 if (SUCCESS == zend_stream_open_function(&f)) {
3274 zend_string_release(f.filename);
3275 f.filename = file_handle->filename;
3276 if (f.opened_path) {
3277 zend_string_release(f.opened_path);
3278 }
3279 f.opened_path = file_handle->opened_path;
3280
3281 switch (file_handle->type) {
3282 case ZEND_HANDLE_STREAM:
3283 if (file_handle->handle.stream.closer && file_handle->handle.stream.handle) {
3284 file_handle->handle.stream.closer(file_handle->handle.stream.handle);
3285 }
3286 file_handle->handle.stream.handle = NULL;
3287 break;
3288 default:
3289 break;
3290 }
3291 *file_handle = f;
3292 }
3293 } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) {
3294 /* compressed phar */
3295 file_handle->type = ZEND_HANDLE_STREAM;
3296 /* we do our own reading directly from the phar, don't change the next line */
3297 file_handle->handle.stream.handle = phar;
3298 file_handle->handle.stream.reader = phar_zend_stream_reader;
3299 file_handle->handle.stream.closer = NULL;
3300 file_handle->handle.stream.fsizer = phar_zend_stream_fsizer;
3301 file_handle->handle.stream.isatty = 0;
3302 phar->is_persistent ?
3303 php_stream_rewind(PHAR_G(cached_fp)[phar->phar_pos].fp) :
3304 php_stream_rewind(phar->fp);
3305 }
3306 }
3307 }
3308
3309 zend_try {
3310 failed = 0;
3311 CG(zend_lineno) = 0;
3312 res = phar_orig_compile_file(file_handle, type);
3313 } zend_catch {
3314 failed = 1;
3315 res = NULL;
3316 } zend_end_try();
3317
3318 if (name) {
3319 zend_string_release(name);
3320 }
3321
3322 if (failed) {
3323 zend_bailout();
3324 }
3325
3326 return res;
3327 }
3328 /* }}} */
3329
mime_type_dtor(zval * zv)3330 static void mime_type_dtor(zval *zv)
3331 {
3332 free(Z_PTR_P(zv));
3333 }
3334
PHP_GINIT_FUNCTION(phar)3335 PHP_GINIT_FUNCTION(phar) /* {{{ */
3336 {
3337 #if defined(COMPILE_DL_PHAR) && defined(ZTS)
3338 ZEND_TSRMLS_CACHE_UPDATE();
3339 #endif
3340 phar_mime_type mime;
3341
3342 memset(phar_globals, 0, sizeof(zend_phar_globals));
3343 HT_INVALIDATE(&phar_globals->phar_persist_map);
3344 HT_INVALIDATE(&phar_globals->phar_fname_map);
3345 HT_INVALIDATE(&phar_globals->phar_alias_map);
3346 phar_globals->readonly = 1;
3347
3348 zend_hash_init(&phar_globals->mime_types, 0, NULL, mime_type_dtor, 1);
3349
3350 #define PHAR_SET_MIME(mimetype, ret, fileext) \
3351 mime.mime = mimetype; \
3352 mime.len = sizeof((mimetype))+1; \
3353 mime.type = ret; \
3354 zend_hash_str_add_mem(&phar_globals->mime_types, fileext, sizeof(fileext)-1, (void *)&mime, sizeof(phar_mime_type)); \
3355
3356 PHAR_SET_MIME("text/html", PHAR_MIME_PHPS, "phps")
3357 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c")
3358 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cc")
3359 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cpp")
3360 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c++")
3361 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "dtd")
3362 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "h")
3363 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "log")
3364 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "rng")
3365 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "txt")
3366 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "xsd")
3367 PHAR_SET_MIME("", PHAR_MIME_PHP, "php")
3368 PHAR_SET_MIME("", PHAR_MIME_PHP, "inc")
3369 PHAR_SET_MIME("video/avi", PHAR_MIME_OTHER, "avi")
3370 PHAR_SET_MIME("image/bmp", PHAR_MIME_OTHER, "bmp")
3371 PHAR_SET_MIME("text/css", PHAR_MIME_OTHER, "css")
3372 PHAR_SET_MIME("image/gif", PHAR_MIME_OTHER, "gif")
3373 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htm")
3374 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "html")
3375 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htmls")
3376 PHAR_SET_MIME("image/x-ico", PHAR_MIME_OTHER, "ico")
3377 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpe")
3378 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpg")
3379 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpeg")
3380 PHAR_SET_MIME("application/x-javascript", PHAR_MIME_OTHER, "js")
3381 PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "midi")
3382 PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "mid")
3383 PHAR_SET_MIME("audio/mod", PHAR_MIME_OTHER, "mod")
3384 PHAR_SET_MIME("movie/quicktime", PHAR_MIME_OTHER, "mov")
3385 PHAR_SET_MIME("audio/mp3", PHAR_MIME_OTHER, "mp3")
3386 PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpg")
3387 PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpeg")
3388 PHAR_SET_MIME("application/pdf", PHAR_MIME_OTHER, "pdf")
3389 PHAR_SET_MIME("image/png", PHAR_MIME_OTHER, "png")
3390 PHAR_SET_MIME("application/shockwave-flash", PHAR_MIME_OTHER, "swf")
3391 PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tif")
3392 PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tiff")
3393 PHAR_SET_MIME("audio/wav", PHAR_MIME_OTHER, "wav")
3394 PHAR_SET_MIME("image/xbm", PHAR_MIME_OTHER, "xbm")
3395 PHAR_SET_MIME("text/xml", PHAR_MIME_OTHER, "xml")
3396
3397 phar_restore_orig_functions();
3398 }
3399 /* }}} */
3400
PHP_GSHUTDOWN_FUNCTION(phar)3401 PHP_GSHUTDOWN_FUNCTION(phar) /* {{{ */
3402 {
3403 zend_hash_destroy(&phar_globals->mime_types);
3404 }
3405 /* }}} */
3406
PHP_MINIT_FUNCTION(phar)3407 PHP_MINIT_FUNCTION(phar) /* {{{ */
3408 {
3409 REGISTER_INI_ENTRIES();
3410
3411 phar_orig_compile_file = zend_compile_file;
3412 zend_compile_file = phar_compile_file;
3413
3414 phar_save_resolve_path = zend_resolve_path;
3415 zend_resolve_path = phar_resolve_path;
3416
3417 phar_object_init();
3418
3419 phar_intercept_functions_init();
3420 phar_save_orig_functions();
3421
3422 return php_register_url_stream_wrapper("phar", &php_stream_phar_wrapper);
3423 }
3424 /* }}} */
3425
PHP_MSHUTDOWN_FUNCTION(phar)3426 PHP_MSHUTDOWN_FUNCTION(phar) /* {{{ */
3427 {
3428 php_unregister_url_stream_wrapper("phar");
3429
3430 phar_intercept_functions_shutdown();
3431
3432 if (zend_compile_file == phar_compile_file) {
3433 zend_compile_file = phar_orig_compile_file;
3434 }
3435
3436 if (PHAR_G(manifest_cached)) {
3437 zend_hash_destroy(&(cached_phars));
3438 zend_hash_destroy(&(cached_alias));
3439 }
3440
3441 UNREGISTER_INI_ENTRIES();
3442 return SUCCESS;
3443 }
3444 /* }}} */
3445
phar_request_initialize(void)3446 void phar_request_initialize(void) /* {{{ */
3447 {
3448 if (!PHAR_G(request_init))
3449 {
3450 PHAR_G(last_phar) = NULL;
3451 PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL;
3452 PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1);
3453 PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1);
3454 PHAR_G(request_init) = 1;
3455 PHAR_G(request_ends) = 0;
3456 PHAR_G(request_done) = 0;
3457 zend_hash_init(&(PHAR_G(phar_fname_map)), 5, zend_get_hash_value, destroy_phar_data, 0);
3458 zend_hash_init(&(PHAR_G(phar_persist_map)), 5, zend_get_hash_value, NULL, 0);
3459 zend_hash_init(&(PHAR_G(phar_alias_map)), 5, zend_get_hash_value, NULL, 0);
3460
3461 if (PHAR_G(manifest_cached)) {
3462 phar_archive_data *pphar;
3463 phar_entry_fp *stuff = (phar_entry_fp *) ecalloc(zend_hash_num_elements(&cached_phars), sizeof(phar_entry_fp));
3464
3465 ZEND_HASH_MAP_FOREACH_PTR(&cached_phars, pphar) {
3466 stuff[pphar->phar_pos].manifest = (phar_entry_fp_info *) ecalloc( zend_hash_num_elements(&(pphar->manifest)), sizeof(phar_entry_fp_info));
3467 } ZEND_HASH_FOREACH_END();
3468
3469 PHAR_G(cached_fp) = stuff;
3470 }
3471
3472 PHAR_G(phar_SERVER_mung_list) = 0;
3473 PHAR_G(cwd) = NULL;
3474 PHAR_G(cwd_len) = 0;
3475 PHAR_G(cwd_init) = 0;
3476 }
3477 }
3478 /* }}} */
3479
PHP_RSHUTDOWN_FUNCTION(phar)3480 PHP_RSHUTDOWN_FUNCTION(phar) /* {{{ */
3481 {
3482 PHAR_G(request_ends) = 1;
3483
3484 if (PHAR_G(request_init))
3485 {
3486 phar_release_functions();
3487 zend_hash_destroy(&(PHAR_G(phar_alias_map)));
3488 HT_INVALIDATE(&PHAR_G(phar_alias_map));
3489 zend_hash_destroy(&(PHAR_G(phar_fname_map)));
3490 HT_INVALIDATE(&PHAR_G(phar_fname_map));
3491 zend_hash_destroy(&(PHAR_G(phar_persist_map)));
3492 HT_INVALIDATE(&PHAR_G(phar_persist_map));
3493 PHAR_G(phar_SERVER_mung_list) = 0;
3494
3495 if (PHAR_G(cached_fp)) {
3496 for (uint32_t i = 0; i < zend_hash_num_elements(&cached_phars); ++i) {
3497 if (PHAR_G(cached_fp)[i].fp) {
3498 php_stream_close(PHAR_G(cached_fp)[i].fp);
3499 }
3500 if (PHAR_G(cached_fp)[i].ufp) {
3501 php_stream_close(PHAR_G(cached_fp)[i].ufp);
3502 }
3503 efree(PHAR_G(cached_fp)[i].manifest);
3504 }
3505 efree(PHAR_G(cached_fp));
3506 PHAR_G(cached_fp) = 0;
3507 }
3508
3509 PHAR_G(request_init) = 0;
3510
3511 if (PHAR_G(cwd)) {
3512 efree(PHAR_G(cwd));
3513 }
3514
3515 PHAR_G(cwd) = NULL;
3516 PHAR_G(cwd_len) = 0;
3517 PHAR_G(cwd_init) = 0;
3518 }
3519
3520 PHAR_G(request_done) = 1;
3521 return SUCCESS;
3522 }
3523 /* }}} */
3524
PHP_MINFO_FUNCTION(phar)3525 PHP_MINFO_FUNCTION(phar) /* {{{ */
3526 {
3527 phar_request_initialize();
3528 php_info_print_table_start();
3529 php_info_print_table_row(2, "Phar: PHP Archive support", "enabled");
3530 php_info_print_table_row(2, "Phar API version", PHP_PHAR_API_VERSION);
3531 php_info_print_table_row(2, "Phar-based phar archives", "enabled");
3532 php_info_print_table_row(2, "Tar-based phar archives", "enabled");
3533 php_info_print_table_row(2, "ZIP-based phar archives", "enabled");
3534
3535 if (PHAR_G(has_zlib)) {
3536 php_info_print_table_row(2, "gzip compression", "enabled");
3537 } else {
3538 php_info_print_table_row(2, "gzip compression", "disabled (install ext/zlib)");
3539 }
3540
3541 if (PHAR_G(has_bz2)) {
3542 php_info_print_table_row(2, "bzip2 compression", "enabled");
3543 } else {
3544 php_info_print_table_row(2, "bzip2 compression", "disabled (install ext/bz2)");
3545 }
3546 #ifdef PHAR_HAVE_OPENSSL
3547 php_info_print_table_row(2, "Native OpenSSL support", "enabled");
3548 #else
3549 if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) {
3550 php_info_print_table_row(2, "OpenSSL support", "enabled");
3551 } else {
3552 php_info_print_table_row(2, "OpenSSL support", "disabled (install ext/openssl)");
3553 }
3554 #endif
3555 php_info_print_table_end();
3556
3557 php_info_print_box_start(0);
3558 PUTS("Phar based on pear/PHP_Archive, original concept by Davey Shafik.");
3559 PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
3560 PUTS("Phar fully realized by Gregory Beaver and Marcus Boerger.");
3561 PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
3562 PUTS("Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle.");
3563 php_info_print_box_end();
3564
3565 DISPLAY_INI_ENTRIES();
3566 }
3567 /* }}} */
3568
3569 /* {{{ phar_module_entry */
3570 static const zend_module_dep phar_deps[] = {
3571 ZEND_MOD_OPTIONAL("apc")
3572 ZEND_MOD_OPTIONAL("bz2")
3573 ZEND_MOD_OPTIONAL("openssl")
3574 ZEND_MOD_OPTIONAL("zlib")
3575 ZEND_MOD_OPTIONAL("standard")
3576 ZEND_MOD_REQUIRED("hash")
3577 ZEND_MOD_REQUIRED("spl")
3578 ZEND_MOD_END
3579 };
3580
3581 zend_module_entry phar_module_entry = {
3582 STANDARD_MODULE_HEADER_EX, NULL,
3583 phar_deps,
3584 "Phar",
3585 NULL,
3586 PHP_MINIT(phar),
3587 PHP_MSHUTDOWN(phar),
3588 NULL,
3589 PHP_RSHUTDOWN(phar),
3590 PHP_MINFO(phar),
3591 PHP_PHAR_VERSION,
3592 PHP_MODULE_GLOBALS(phar), /* globals descriptor */
3593 PHP_GINIT(phar), /* globals ctor */
3594 PHP_GSHUTDOWN(phar), /* globals dtor */
3595 NULL, /* post deactivate */
3596 STANDARD_MODULE_PROPERTIES_EX
3597 };
3598 /* }}} */
3599