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 mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len);
1510 mydata->alias_len = alias ? alias_len : fname_len;
1511 }
1512
1513 if (alias_len && alias) {
1514 if (NULL == zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata)) {
1515 if (options & REPORT_ERRORS) {
1516 if (error) {
1517 spprintf(error, 0, "archive \"%s\" cannot be associated with alias \"%s\", already in use", fname, alias);
1518 }
1519 }
1520
1521 zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
1522
1523 if (pphar) {
1524 *pphar = NULL;
1525 }
1526
1527 return FAILURE;
1528 }
1529 }
1530
1531 return SUCCESS;
1532 }
1533 /* }}}*/
1534
1535 /**
1536 * Return an already opened filename.
1537 *
1538 * Or scan a phar file for the required __HALT_COMPILER(); ?> token and verify
1539 * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
1540 * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
1541 */
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)1542 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) /* {{{ */
1543 {
1544 php_stream *fp;
1545 zend_string *actual;
1546 bool is_data = false;
1547
1548 if (error) {
1549 *error = NULL;
1550 }
1551
1552 if (!strstr(fname, ".phar")) {
1553 is_data = true;
1554 }
1555
1556 if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, pphar, error) == SUCCESS) {
1557 return SUCCESS;
1558 } else if (error && *error) {
1559 return FAILURE;
1560 }
1561 if (php_check_open_basedir(fname)) {
1562 return FAILURE;
1563 }
1564
1565 fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, &actual);
1566
1567 if (!fp) {
1568 if (options & REPORT_ERRORS) {
1569 if (error) {
1570 spprintf(error, 0, "unable to open phar for reading \"%s\"", fname);
1571 }
1572 }
1573 if (actual) {
1574 zend_string_release_ex(actual, 0);
1575 }
1576 return FAILURE;
1577 }
1578
1579 if (actual) {
1580 fname = ZSTR_VAL(actual);
1581 fname_len = ZSTR_LEN(actual);
1582 }
1583
1584 zend_result ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, error);
1585
1586 if (actual) {
1587 zend_string_release_ex(actual, 0);
1588 }
1589
1590 return ret;
1591 }
1592 /* }}}*/
1593
phar_strnstr(const char * buf,int buf_len,const char * search,int search_len)1594 static inline char *phar_strnstr(const char *buf, int buf_len, const char *search, int search_len) /* {{{ */
1595 {
1596 const char *c;
1597 ptrdiff_t so_far = 0;
1598
1599 if (buf_len < search_len) {
1600 return NULL;
1601 }
1602
1603 c = buf - 1;
1604
1605 do {
1606 if (!(c = memchr(c + 1, search[0], buf_len - search_len - so_far))) {
1607 return (char *) NULL;
1608 }
1609
1610 so_far = c - buf;
1611
1612 if (so_far >= (buf_len - search_len)) {
1613 return (char *) NULL;
1614 }
1615
1616 if (!memcmp(c, search, search_len)) {
1617 return (char *) c;
1618 }
1619 } while (1);
1620 }
1621 /* }}} */
1622
1623 /**
1624 * Scan an open fp for the required __HALT_COMPILER(); ?> token and verify
1625 * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS
1626 * or FAILURE is returned and pphar is set to a pointer to the phar's manifest
1627 */
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)1628 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) /* {{{ */
1629 {
1630 static const char token[] = "__HALT_COMPILER();";
1631 static const char zip_magic[] = "PK\x03\x04";
1632 static const char gz_magic[] = "\x1f\x8b\x08";
1633 static const char bz_magic[] = "BZh";
1634 char *pos, test = '\0';
1635 int recursion_count = 3; // arbitrary limit to avoid too deep or even infinite recursion
1636 const int window_size = 1024;
1637 char buffer[1024 + sizeof(token)]; /* a 1024 byte window + the size of the halt_compiler token (moving window) */
1638 const zend_long readsize = sizeof(buffer) - sizeof(token);
1639 const zend_long tokenlen = sizeof(token) - 1;
1640 zend_long halt_offset;
1641 size_t got;
1642 uint32_t compression = PHAR_FILE_COMPRESSED_NONE;
1643
1644 if (error) {
1645 *error = NULL;
1646 }
1647
1648 if (-1 == php_stream_rewind(fp)) {
1649 MAPPHAR_ALLOC_FAIL("cannot rewind phar \"%s\"")
1650 }
1651
1652 buffer[sizeof(buffer)-1] = '\0';
1653 memset(buffer, 32, sizeof(token));
1654 halt_offset = 0;
1655
1656 /* Maybe it's better to compile the file instead of just searching, */
1657 /* but we only want the offset. So we want a .re scanner to find it. */
1658 while(!php_stream_eof(fp)) {
1659 if ((got = php_stream_read(fp, buffer+tokenlen, readsize)) < (size_t) tokenlen) {
1660 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)")
1661 }
1662
1663 if (!test && recursion_count) {
1664 test = '\1';
1665 pos = buffer+tokenlen;
1666 if (!memcmp(pos, gz_magic, 3)) {
1667 char err = 0;
1668 php_stream_filter *filter;
1669 php_stream *temp;
1670 /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */
1671 zval filterparams;
1672
1673 if (!PHAR_G(has_zlib)) {
1674 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file, enable zlib extension in php.ini")
1675 }
1676 array_init(&filterparams);
1677 /* this is defined in zlib's zconf.h */
1678 #ifndef MAX_WBITS
1679 #define MAX_WBITS 15
1680 #endif
1681 add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS + 32);
1682
1683 /* entire file is gzip-compressed, uncompress to temporary file */
1684 if (!(temp = php_stream_fopen_tmpfile())) {
1685 MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of gzipped phar archive \"%s\"")
1686 }
1687
1688 php_stream_rewind(fp);
1689 filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp));
1690
1691 if (!filter) {
1692 err = 1;
1693 add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS);
1694 filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp));
1695 zend_array_destroy(Z_ARR(filterparams));
1696
1697 if (!filter) {
1698 php_stream_close(temp);
1699 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
1700 }
1701 } else {
1702 zend_array_destroy(Z_ARR(filterparams));
1703 }
1704
1705 php_stream_filter_append(&temp->writefilters, filter);
1706
1707 if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
1708 if (err) {
1709 php_stream_close(temp);
1710 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6")
1711 }
1712 php_stream_close(temp);
1713 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file")
1714 }
1715
1716 php_stream_filter_flush(filter, 1);
1717 php_stream_filter_remove(filter, 1);
1718 php_stream_close(fp);
1719 fp = temp;
1720 php_stream_rewind(fp);
1721 compression = PHAR_FILE_COMPRESSED_GZ;
1722
1723 /* now, start over */
1724 test = '\0';
1725 if (!--recursion_count) {
1726 MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\"");
1727 break;
1728 }
1729 continue;
1730 } else if (!memcmp(pos, bz_magic, 3)) {
1731 php_stream_filter *filter;
1732 php_stream *temp;
1733
1734 if (!PHAR_G(has_bz2)) {
1735 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file, enable bz2 extension in php.ini")
1736 }
1737
1738 /* entire file is bzip-compressed, uncompress to temporary file */
1739 if (!(temp = php_stream_fopen_tmpfile())) {
1740 MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of bzipped phar archive \"%s\"")
1741 }
1742
1743 php_stream_rewind(fp);
1744 filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp));
1745
1746 if (!filter) {
1747 php_stream_close(temp);
1748 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\", filter creation failed")
1749 }
1750
1751 php_stream_filter_append(&temp->writefilters, filter);
1752
1753 if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) {
1754 php_stream_close(temp);
1755 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file")
1756 }
1757
1758 php_stream_filter_flush(filter, 1);
1759 php_stream_filter_remove(filter, 1);
1760 php_stream_close(fp);
1761 fp = temp;
1762 php_stream_rewind(fp);
1763 compression = PHAR_FILE_COMPRESSED_BZ2;
1764
1765 /* now, start over */
1766 test = '\0';
1767 if (!--recursion_count) {
1768 MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\"");
1769 break;
1770 }
1771 continue;
1772 }
1773
1774 if (!memcmp(pos, zip_magic, 4)) {
1775 php_stream_seek(fp, 0, SEEK_END);
1776 return phar_parse_zipfile(fp, fname, fname_len, alias, alias_len, pphar, error);
1777 }
1778
1779 if (got >= 512) {
1780 if (phar_is_tar(pos, fname)) {
1781 php_stream_rewind(fp);
1782 return phar_parse_tarfile(fp, fname, fname_len, alias, alias_len, pphar, compression, error);
1783 }
1784 }
1785 }
1786
1787 if (got > 0 && (pos = phar_strnstr(buffer, got + sizeof(token), token, sizeof(token)-1)) != NULL) {
1788 halt_offset += (pos - buffer); /* no -tokenlen+tokenlen here */
1789 return phar_parse_pharfile(fp, fname, fname_len, alias, alias_len, halt_offset, pphar, compression, error);
1790 }
1791
1792 halt_offset += got;
1793 memmove(buffer, buffer + window_size, tokenlen); /* move the memory buffer by the size of the window */
1794 }
1795
1796 MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (__HALT_COMPILER(); not found)")
1797 }
1798 /* }}} */
1799
1800 /*
1801 * given the location of the file extension and the start of the file path,
1802 * determine the end of the portion of the path (i.e. /path/to/file.ext/blah
1803 * grabs "/path/to/file.ext" as does the straight /path/to/file.ext),
1804 * stat it to determine if it exists.
1805 * if so, check to see if it is a directory and fail if so
1806 * if not, check to see if its dirname() exists (i.e. "/path/to") and is a directory
1807 * succeed if we are creating the file, otherwise fail.
1808 */
phar_analyze_path(const char * fname,const char * ext,size_t ext_len,int for_create)1809 static zend_result phar_analyze_path(const char *fname, const char *ext, size_t ext_len, int for_create) /* {{{ */
1810 {
1811 php_stream_statbuf ssb;
1812 char *realpath;
1813 char *filename = estrndup(fname, (ext - fname) + ext_len);
1814
1815 if ((realpath = expand_filepath(filename, NULL))) {
1816 #ifdef PHP_WIN32
1817 phar_unixify_path_separators(realpath, strlen(realpath));
1818 #endif
1819 if (zend_hash_str_exists(&(PHAR_G(phar_fname_map)), realpath, strlen(realpath))) {
1820 efree(realpath);
1821 efree(filename);
1822 return SUCCESS;
1823 }
1824
1825 if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_phars, realpath, strlen(realpath))) {
1826 efree(realpath);
1827 efree(filename);
1828 return SUCCESS;
1829 }
1830 efree(realpath);
1831 }
1832
1833 if (SUCCESS == php_stream_stat_path((char *) filename, &ssb)) {
1834
1835 efree(filename);
1836
1837 if (ssb.sb.st_mode & S_IFDIR) {
1838 return FAILURE;
1839 }
1840
1841 if (for_create == 1) {
1842 return FAILURE;
1843 }
1844
1845 return SUCCESS;
1846 } else {
1847 char *slash;
1848
1849 if (!for_create) {
1850 efree(filename);
1851 return FAILURE;
1852 }
1853
1854 slash = (char *) strrchr(filename, '/');
1855
1856 if (slash) {
1857 *slash = '\0';
1858 }
1859
1860 if (SUCCESS != php_stream_stat_path((char *) filename, &ssb)) {
1861 if (!slash) {
1862 if (!(realpath = expand_filepath(filename, NULL))) {
1863 efree(filename);
1864 return FAILURE;
1865 }
1866 #ifdef PHP_WIN32
1867 phar_unixify_path_separators(realpath, strlen(realpath));
1868 #endif
1869 slash = strstr(realpath, filename);
1870 if (slash) {
1871 slash += ((ext - fname) + ext_len);
1872 *slash = '\0';
1873 }
1874 slash = strrchr(realpath, '/');
1875
1876 if (slash) {
1877 *slash = '\0';
1878 } else {
1879 efree(realpath);
1880 efree(filename);
1881 return FAILURE;
1882 }
1883
1884 if (SUCCESS != php_stream_stat_path(realpath, &ssb)) {
1885 efree(realpath);
1886 efree(filename);
1887 return FAILURE;
1888 }
1889
1890 efree(realpath);
1891
1892 if (ssb.sb.st_mode & S_IFDIR) {
1893 efree(filename);
1894 return SUCCESS;
1895 }
1896 }
1897
1898 efree(filename);
1899 return FAILURE;
1900 }
1901
1902 efree(filename);
1903
1904 if (ssb.sb.st_mode & S_IFDIR) {
1905 return SUCCESS;
1906 }
1907
1908 return FAILURE;
1909 }
1910 }
1911 /* }}} */
1912
1913 /* check for ".phar" in extension */
phar_check_str(const char * fname,const char * ext_str,size_t ext_len,int executable,int for_create)1914 static zend_result phar_check_str(const char *fname, const char *ext_str, size_t ext_len, int executable, int for_create) /* {{{ */
1915 {
1916 const char *pos;
1917
1918 if (ext_len >= 50) {
1919 return FAILURE;
1920 }
1921 if (executable == 1) {
1922 /* executable phars must contain ".phar" as a valid extension (phar://.pharmy/oops is invalid) */
1923 /* (phar://hi/there/.phar/oops is also invalid) */
1924 pos = strstr(ext_str, ".phar");
1925
1926 if (!pos
1927 || (pos != ext_str && (*(pos - 1) == '/'))
1928 || (ext_len - (pos - ext_str)) < 5
1929 || !(pos += 5)
1930 || !(*pos == '\0' || *pos == '/' || *pos == '.')) {
1931 return FAILURE;
1932 }
1933 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1934 }
1935
1936 /* data phars need only contain a single non-"." to be valid */
1937 if (!executable) {
1938 pos = strstr(ext_str, ".phar");
1939 if (!(pos && (*(pos - 1) != '/')
1940 && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) && *(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
1941 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1942 }
1943 } else {
1944 if (*(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') {
1945 return phar_analyze_path(fname, ext_str, ext_len, for_create);
1946 }
1947 }
1948
1949 return FAILURE;
1950 }
1951 /* }}} */
1952
1953 /*
1954 * if executable is 1, only returns SUCCESS if the extension is one of the tar/zip .phar extensions
1955 * if executable is 0, it returns SUCCESS only if the filename does *not* contain ".phar" anywhere, and treats
1956 * the first extension as the filename extension
1957 *
1958 * if an extension is found, it sets ext_str to the location of the file extension in filename,
1959 * and ext_len to the length of the extension.
1960 * for urls like "phar://alias/oops" it instead sets ext_len to -1 and returns FAILURE, which tells
1961 * the calling function to use "alias" as the phar alias
1962 *
1963 * the last parameter should be set to tell the thing to assume that filename is the full path, and only to check the
1964 * extension rules, not to iterate.
1965 */
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)1966 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) /* {{{ */
1967 {
1968 const char *pos, *slash;
1969
1970 *ext_str = NULL;
1971 *ext_len = 0;
1972
1973 if (filename_len <= 1) {
1974 return FAILURE;
1975 }
1976
1977 phar_request_initialize();
1978 /* first check for alias in first segment */
1979 pos = memchr(filename, '/', filename_len);
1980
1981 if (pos && pos != filename) {
1982 /* check for url like http:// or phar:// */
1983 if (*(pos - 1) == ':' && (size_t)(pos - filename) < filename_len - 1 && *(pos + 1) == '/') {
1984 *ext_len = -2;
1985 *ext_str = NULL;
1986 return FAILURE;
1987 }
1988 if (zend_hash_str_exists(&(PHAR_G(phar_alias_map)), (char *) filename, pos - filename)) {
1989 *ext_str = pos;
1990 *ext_len = -1;
1991 return FAILURE;
1992 }
1993
1994 if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_alias, (char *) filename, pos - filename)) {
1995 *ext_str = pos;
1996 *ext_len = -1;
1997 return FAILURE;
1998 }
1999 }
2000
2001 if (zend_hash_num_elements(&(PHAR_G(phar_fname_map))) || PHAR_G(manifest_cached)) {
2002 phar_archive_data *pphar;
2003
2004 if (is_complete) {
2005 if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), (char *) filename, filename_len))) {
2006 *ext_str = filename + (filename_len - pphar->ext_len);
2007 woohoo:
2008 *ext_len = pphar->ext_len;
2009
2010 if (executable == 2) {
2011 return SUCCESS;
2012 }
2013
2014 if (executable == 1 && !pphar->is_data) {
2015 return SUCCESS;
2016 }
2017
2018 if (!executable && pphar->is_data) {
2019 return SUCCESS;
2020 }
2021
2022 return FAILURE;
2023 }
2024
2025 if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, (char *) filename, filename_len))) {
2026 *ext_str = filename + (filename_len - pphar->ext_len);
2027 goto woohoo;
2028 }
2029 } else {
2030 zend_string *str_key;
2031
2032 ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&PHAR_G(phar_fname_map), str_key, pphar) {
2033 if (ZSTR_LEN(str_key) > filename_len) {
2034 continue;
2035 }
2036
2037 if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && (filename_len == ZSTR_LEN(str_key)
2038 || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) {
2039 *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len);
2040 goto woohoo;
2041 }
2042 } ZEND_HASH_FOREACH_END();
2043
2044 if (PHAR_G(manifest_cached)) {
2045 ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&cached_phars, str_key, pphar) {
2046 if (ZSTR_LEN(str_key) > filename_len) {
2047 continue;
2048 }
2049
2050 if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && (filename_len == ZSTR_LEN(str_key)
2051 || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) {
2052 *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len);
2053 goto woohoo;
2054 }
2055 } ZEND_HASH_FOREACH_END();
2056 }
2057 }
2058 }
2059
2060 // TODO Use some sort of loop here instead of a goto
2061 pos = memchr(filename + 1, '.', filename_len);
2062 next_extension:
2063 if (!pos) {
2064 return FAILURE;
2065 }
2066
2067 while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) {
2068 pos = memchr(pos + 1, '.', filename_len - (pos - filename) - 1);
2069 if (!pos) {
2070 return FAILURE;
2071 }
2072 }
2073
2074 slash = memchr(pos, '/', filename_len - (pos - filename));
2075
2076 if (!slash) {
2077 /* this is a url like "phar://blah.phar" with no directory */
2078 *ext_str = pos;
2079 *ext_len = strlen(pos);
2080
2081 /* file extension must contain "phar" */
2082 return phar_check_str(filename, *ext_str, *ext_len, executable, for_create);
2083 }
2084
2085 /* we've found an extension that ends at a directory separator */
2086 *ext_str = pos;
2087 *ext_len = slash - pos;
2088
2089 if (phar_check_str(filename, *ext_str, *ext_len, executable, for_create) == SUCCESS) {
2090 return SUCCESS;
2091 }
2092
2093 /* look for more extensions */
2094 pos = strchr(pos + 1, '.');
2095 if (pos) {
2096 *ext_str = NULL;
2097 *ext_len = 0;
2098 goto next_extension;
2099 }
2100
2101 return FAILURE;
2102 }
2103 /* }}} */
2104
php_check_dots(const char * element,size_t n)2105 static bool php_check_dots(const char *element, size_t n) /* {{{ */
2106 {
2107 for(n-- ; n != SIZE_MAX; --n) {
2108 if (element[n] != '.') {
2109 return 1;
2110 }
2111 }
2112 return 0;
2113 }
2114 /* }}} */
2115
2116 #define IS_DIRECTORY_UP(element, len) \
2117 (len >= 2 && !php_check_dots(element, len))
2118
2119 #define IS_DIRECTORY_CURRENT(element, len) \
2120 (len == 1 && element[0] == '.')
2121
2122 #define IS_BACKSLASH(c) ((c) == '/')
2123
2124 /**
2125 * Remove .. and . references within a phar filename
2126 */
phar_fix_filepath(char * path,size_t * new_len,int use_cwd)2127 char *phar_fix_filepath(char *path, size_t *new_len, int use_cwd) /* {{{ */
2128 {
2129 char *newpath;
2130 size_t newpath_len;
2131 char *ptr;
2132 char *tok;
2133 size_t ptr_length, path_length = *new_len;
2134
2135 if (PHAR_G(cwd_len) && use_cwd && path_length > 2 && path[0] == '.' && path[1] == '/') {
2136 newpath_len = PHAR_G(cwd_len);
2137 newpath = emalloc(strlen(path) + newpath_len + 1);
2138 memcpy(newpath, PHAR_G(cwd), newpath_len);
2139 } else {
2140 newpath = emalloc(strlen(path) + 2);
2141 newpath[0] = '/';
2142 newpath_len = 1;
2143 }
2144
2145 ptr = path;
2146
2147 if (*ptr == '/') {
2148 ++ptr;
2149 }
2150
2151 tok = ptr;
2152
2153 do {
2154 ptr = memchr(ptr, '/', path_length - (ptr - path));
2155 } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
2156
2157 if (!ptr && (path_length - (tok - path))) {
2158 switch (path_length - (tok - path)) {
2159 case 1:
2160 if (*tok == '.') {
2161 efree(path);
2162 *new_len = 1;
2163 efree(newpath);
2164 return estrndup("/", 1);
2165 }
2166 break;
2167 case 2:
2168 if (tok[0] == '.' && tok[1] == '.') {
2169 efree(path);
2170 *new_len = 1;
2171 efree(newpath);
2172 return estrndup("/", 1);
2173 }
2174 }
2175 efree(newpath);
2176 return path;
2177 }
2178
2179 while (ptr) {
2180 ptr_length = ptr - tok;
2181 last_time:
2182 if (IS_DIRECTORY_UP(tok, ptr_length)) {
2183 while (newpath_len > 1 && !IS_BACKSLASH(newpath[newpath_len - 1])) {
2184 newpath_len--;
2185 }
2186
2187 if (newpath[0] != '/') {
2188 newpath[newpath_len] = '\0';
2189 } else if (newpath_len > 1) {
2190 --newpath_len;
2191 }
2192 } else if (!IS_DIRECTORY_CURRENT(tok, ptr_length)) {
2193 if (newpath_len > 1) {
2194 newpath[newpath_len++] = '/';
2195 memcpy(newpath + newpath_len, tok, ptr_length+1);
2196 } else {
2197 memcpy(newpath + newpath_len, tok, ptr_length+1);
2198 }
2199
2200 newpath_len += ptr_length;
2201 }
2202
2203 if (ptr == path + path_length) {
2204 break;
2205 }
2206
2207 tok = ++ptr;
2208
2209 do {
2210 ptr = memchr(ptr, '/', path_length - (ptr - path));
2211 } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok);
2212
2213 if (!ptr && (path_length - (tok - path))) {
2214 ptr_length = path_length - (tok - path);
2215 ptr = path + path_length;
2216 goto last_time;
2217 }
2218 }
2219
2220 efree(path);
2221 *new_len = newpath_len;
2222 newpath[newpath_len] = '\0';
2223 return erealloc(newpath, newpath_len + 1);
2224 }
2225 /* }}} */
2226
2227 /**
2228 * Process a phar stream name, ensuring we can handle any of:
2229 *
2230 * - whatever.phar
2231 * - whatever.phar.gz
2232 * - whatever.phar.bz2
2233 * - whatever.phar.php
2234 *
2235 * Optionally the name might start with 'phar://'
2236 *
2237 * This is used by phar_parse_url()
2238 */
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)2239 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) /* {{{ */
2240 {
2241 const char *ext_str;
2242 #ifdef PHP_WIN32
2243 char *save;
2244 #endif
2245 size_t ext_len;
2246
2247 if (CHECK_NULL_PATH(filename, filename_len)) {
2248 return FAILURE;
2249 }
2250
2251 if (!strncasecmp(filename, "phar://", 7)) {
2252 filename += 7;
2253 filename_len -= 7;
2254 }
2255
2256 ext_len = 0;
2257 #ifdef PHP_WIN32
2258 save = (char *)filename;
2259 if (memchr(filename, '\\', filename_len)) {
2260 filename = estrndup(filename, filename_len);
2261 phar_unixify_path_separators((char *)filename, filename_len);
2262 }
2263 #endif
2264 if (phar_detect_phar_fname_ext(filename, filename_len, &ext_str, &ext_len, executable, for_create, 0) == FAILURE) {
2265 if (ext_len != -1) {
2266 if (!ext_str) {
2267 /* no / detected, restore arch for error message */
2268 #ifdef PHP_WIN32
2269 *arch = save;
2270 #else
2271 *arch = (char*)filename;
2272 #endif
2273 }
2274
2275 #ifdef PHP_WIN32
2276 if (filename != save) {
2277 efree((char *)filename);
2278 }
2279 #endif
2280 return FAILURE;
2281 }
2282
2283 ext_len = 0;
2284 /* no extension detected - instead we are dealing with an alias */
2285 }
2286
2287 *arch_len = ext_str - filename + ext_len;
2288 *arch = estrndup(filename, *arch_len);
2289
2290 if (ext_str[ext_len]) {
2291 *entry_len = filename_len - *arch_len;
2292 *entry = estrndup(ext_str+ext_len, *entry_len);
2293 #ifdef PHP_WIN32
2294 phar_unixify_path_separators(*entry, *entry_len);
2295 #endif
2296 *entry = phar_fix_filepath(*entry, entry_len, 0);
2297 } else {
2298 *entry_len = 1;
2299 *entry = estrndup("/", 1);
2300 }
2301
2302 #ifdef PHP_WIN32
2303 if (filename != save) {
2304 efree((char *)filename);
2305 }
2306 #endif
2307
2308 return SUCCESS;
2309 }
2310 /* }}} */
2311
2312 /**
2313 * Invoked when a user calls Phar::mapPhar() from within an executing .phar
2314 * to set up its manifest directly
2315 */
phar_open_executed_filename(char * alias,size_t alias_len,char ** error)2316 zend_result phar_open_executed_filename(char *alias, size_t alias_len, char **error) /* {{{ */
2317 {
2318 if (error) {
2319 *error = NULL;
2320 }
2321
2322 zend_string *fname = zend_get_executed_filename_ex();
2323
2324 if (!fname) {
2325 if (error) {
2326 spprintf(error, 0, "cannot initialize a phar outside of PHP execution");
2327 }
2328 return FAILURE;
2329 }
2330
2331 if (phar_open_parsed_phar(ZSTR_VAL(fname), ZSTR_LEN(fname), alias, alias_len, 0, REPORT_ERRORS, NULL, 0) == SUCCESS) {
2332 return SUCCESS;
2333 }
2334
2335 if (0 == zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) {
2336 if (error) {
2337 spprintf(error, 0, "__HALT_COMPILER(); must be declared in a phar");
2338 }
2339 return FAILURE;
2340 }
2341
2342 if (php_check_open_basedir(ZSTR_VAL(fname))) {
2343 return FAILURE;
2344 }
2345
2346 zend_string *actual = NULL;
2347 php_stream *fp;
2348 fp = php_stream_open_wrapper(ZSTR_VAL(fname), "rb", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, &actual);
2349
2350 if (!fp) {
2351 if (error) {
2352 spprintf(error, 0, "unable to open phar for reading \"%s\"", ZSTR_VAL(fname));
2353 }
2354 if (actual) {
2355 zend_string_release_ex(actual, 0);
2356 }
2357 return FAILURE;
2358 }
2359
2360 if (actual) {
2361 fname = actual;
2362 }
2363
2364 zend_result ret = phar_open_from_fp(fp, ZSTR_VAL(fname), ZSTR_LEN(fname), alias, alias_len, REPORT_ERRORS, NULL, error);
2365
2366 if (actual) {
2367 zend_string_release_ex(actual, 0);
2368 }
2369
2370 return ret;
2371 }
2372 /* }}} */
2373
2374 /**
2375 * Validate the CRC32 of a file opened from within the phar
2376 */
phar_postprocess_file(phar_entry_data * idata,uint32_t crc32,char ** error,int process_zip)2377 zend_result phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip) /* {{{ */
2378 {
2379 php_stream *fp = idata->fp;
2380 phar_entry_info *entry = idata->internal_file;
2381
2382 if (error) {
2383 *error = NULL;
2384 }
2385
2386 if (entry->is_zip && process_zip > 0) {
2387 /* verify local file header */
2388 phar_zip_file_header local;
2389 phar_zip_data_desc desc;
2390
2391 if (SUCCESS != phar_open_archive_fp(idata->phar)) {
2392 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);
2393 return FAILURE;
2394 }
2395 php_stream_seek(phar_get_entrypfp(idata->internal_file), entry->header_offset, SEEK_SET);
2396
2397 if (sizeof(local) != php_stream_read(phar_get_entrypfp(idata->internal_file), (char *) &local, sizeof(local))) {
2398
2399 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);
2400 return FAILURE;
2401 }
2402
2403 /* check for data descriptor */
2404 if (((PHAR_ZIP_16(local.flags)) & 0x8) == 0x8) {
2405 php_stream_seek(phar_get_entrypfp(idata->internal_file),
2406 entry->header_offset + sizeof(local) +
2407 PHAR_ZIP_16(local.filename_len) +
2408 PHAR_ZIP_16(local.extra_len) +
2409 entry->compressed_filesize, SEEK_SET);
2410 if (sizeof(desc) != php_stream_read(phar_get_entrypfp(idata->internal_file),
2411 (char *) &desc, sizeof(desc))) {
2412 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);
2413 return FAILURE;
2414 }
2415 if (desc.signature[0] == 'P' && desc.signature[1] == 'K') {
2416 memcpy(&(local.crc32), &(desc.crc32), 12);
2417 } else {
2418 /* old data descriptors have no signature */
2419 memcpy(&(local.crc32), &desc, 12);
2420 }
2421 }
2422 /* verify local header */
2423 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)) {
2424 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);
2425 return FAILURE;
2426 }
2427
2428 /* construct actual offset to file start - local extra_len can be different from central extra_len */
2429 entry->offset = entry->offset_abs =
2430 sizeof(local) + entry->header_offset + PHAR_ZIP_16(local.filename_len) + PHAR_ZIP_16(local.extra_len);
2431
2432 if (idata->zero && idata->zero != entry->offset_abs) {
2433 idata->zero = entry->offset_abs;
2434 }
2435 }
2436
2437 if (process_zip == 1) {
2438 return SUCCESS;
2439 }
2440
2441 php_stream_seek(fp, idata->zero, SEEK_SET);
2442
2443 uint32_t crc = php_crc32_bulk_init();
2444 zend_result ret = php_crc32_stream_bulk_update(&crc, fp, idata->internal_file->uncompressed_filesize);
2445
2446 php_stream_seek(fp, idata->zero, SEEK_SET);
2447
2448 if (SUCCESS == ret && php_crc32_bulk_end(crc) == crc32) {
2449 entry->is_crc_checked = 1;
2450 return SUCCESS;
2451 } else {
2452 spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (crc32 mismatch on file \"%s\")", idata->phar->fname, entry->filename);
2453 return FAILURE;
2454 }
2455 }
2456 /* }}} */
2457
phar_set_32(char * buffer,uint32_t var)2458 static inline void phar_set_32(char *buffer, uint32_t var) /* {{{ */
2459 {
2460 #ifdef WORDS_BIGENDIAN
2461 *((buffer) + 3) = (unsigned char) (((var) >> 24) & 0xFF);
2462 *((buffer) + 2) = (unsigned char) (((var) >> 16) & 0xFF);
2463 *((buffer) + 1) = (unsigned char) (((var) >> 8) & 0xFF);
2464 *((buffer) + 0) = (unsigned char) ((var) & 0xFF);
2465 #else
2466 memcpy(buffer, &var, sizeof(var));
2467 #endif
2468 } /* }}} */
2469
phar_flush_clean_deleted_apply(zval * zv)2470 static int phar_flush_clean_deleted_apply(zval *zv) /* {{{ */
2471 {
2472 phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
2473
2474 if (entry->fp_refcount <= 0 && entry->is_deleted) {
2475 return ZEND_HASH_APPLY_REMOVE;
2476 } else {
2477 return ZEND_HASH_APPLY_KEEP;
2478 }
2479 }
2480 /* }}} */
2481
2482 #include "stub.h" /* Generated phar_get_stub() function from makestub.php script */
2483
phar_create_default_stub(const char * index_php,const char * web_index,char ** error)2484 zend_string *phar_create_default_stub(const char *index_php, const char *web_index, char **error) /* {{{ */
2485 {
2486 size_t index_len, web_len;
2487
2488 if (error) {
2489 *error = NULL;
2490 }
2491
2492 if (!index_php) {
2493 index_php = "index.php";
2494 }
2495
2496 if (!web_index) {
2497 web_index = "index.php";
2498 }
2499
2500 index_len = strlen(index_php);
2501 web_len = strlen(web_index);
2502
2503 if (index_len > 400) {
2504 /* ridiculous size not allowed for index.php startup filename */
2505 if (error) {
2506 spprintf(error, 0, "Illegal filename passed in for stub creation, was %zd characters long, and only 400 or less is allowed", index_len);
2507 return NULL;
2508 }
2509 }
2510
2511 if (web_len > 400) {
2512 /* ridiculous size not allowed for index.php startup filename */
2513 if (error) {
2514 spprintf(error, 0, "Illegal web filename passed in for stub creation, was %zd characters long, and only 400 or less is allowed", web_len);
2515 return NULL;
2516 }
2517 }
2518
2519 return phar_get_stub(index_php, web_index, index_len+1, web_len+1);
2520 }
2521 /* }}} */
2522
phar_flush(phar_archive_data * phar,char ** error)2523 void phar_flush(phar_archive_data *phar, char **error) {
2524 phar_flush_ex(phar, NULL, false, error);
2525 }
2526
2527 /**
2528 * Save phar contents to disk
2529 *
2530 * if user_stub is NULL the default or existing stub should be used
2531 */
phar_flush_ex(phar_archive_data * phar,zend_string * user_stub,bool is_default_stub,char ** error)2532 void phar_flush_ex(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error) /* {{{ */
2533 {
2534 static const char halt_stub[] = "__HALT_COMPILER();";
2535
2536 phar_entry_info *entry, *newentry;
2537 size_t halt_offset;
2538 int restore_alias_len, global_flags = 0;
2539 bool must_close_old_file = false;
2540 bool has_dirs = false;
2541 char manifest[18], entry_buffer[24];
2542 zend_off_t manifest_ftell;
2543 zend_long offset;
2544 size_t wrote;
2545 uint32_t manifest_len, mytime, new_manifest_count;
2546 uint32_t newcrc32;
2547 php_stream *file, *oldfile, *newfile;
2548 php_stream_filter *filter;
2549 php_serialize_data_t metadata_hash;
2550 smart_str main_metadata_str = {0};
2551 bool free_fp = true;
2552 bool free_ufp = true;
2553 bool manifest_hack = false;
2554 php_stream *shared_cfp = NULL;
2555
2556 if (phar->is_persistent) {
2557 if (error) {
2558 spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
2559 }
2560 return;
2561 }
2562
2563 if (error) {
2564 *error = NULL;
2565 }
2566
2567 if (!zend_hash_num_elements(&phar->manifest) && !user_stub) {
2568 return;
2569 }
2570
2571 zend_hash_clean(&phar->virtual_dirs);
2572
2573 if (phar->is_zip) {
2574 phar_zip_flush(phar, user_stub, is_default_stub, error);
2575 return;
2576 }
2577
2578 if (phar->is_tar) {
2579 phar_tar_flush(phar, user_stub, is_default_stub, error);
2580 return;
2581 }
2582
2583 if (PHAR_G(readonly)) {
2584 return;
2585 }
2586
2587 if (phar->fp && !phar->is_brandnew) {
2588 oldfile = phar->fp;
2589 must_close_old_file = false;
2590 php_stream_rewind(oldfile);
2591 } else {
2592 oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL);
2593 must_close_old_file = oldfile != NULL;
2594 }
2595 newfile = php_stream_fopen_tmpfile();
2596 if (!newfile) {
2597 if (error) {
2598 spprintf(error, 0, "unable to create temporary file");
2599 }
2600 if (must_close_old_file) {
2601 php_stream_close(oldfile);
2602 }
2603 return;
2604 }
2605
2606 if (user_stub) {
2607 char *pos = php_stristr(ZSTR_VAL(user_stub), halt_stub, ZSTR_LEN(user_stub), strlen(halt_stub));
2608
2609 if (pos == NULL) {
2610 if (must_close_old_file) {
2611 php_stream_close(oldfile);
2612 }
2613 php_stream_close(newfile);
2614 if (error) {
2615 spprintf(error, 0, "illegal stub for phar \"%s\" (__HALT_COMPILER(); is missing)", phar->fname);
2616 }
2617 return;
2618 }
2619
2620 size_t len = pos - ZSTR_VAL(user_stub) + strlen(halt_stub);
2621 const char end_sequence[] = " ?>\r\n";
2622 size_t end_sequence_len = strlen(end_sequence);
2623
2624 if (
2625 len != php_stream_write(newfile, ZSTR_VAL(user_stub), len)
2626 || end_sequence_len != php_stream_write(newfile, end_sequence, end_sequence_len)
2627 ) {
2628 if (must_close_old_file) {
2629 php_stream_close(oldfile);
2630 }
2631 php_stream_close(newfile);
2632 if (error) {
2633 spprintf(error, 0, "unable to create stub from string in new phar \"%s\"", phar->fname);
2634 }
2635 return;
2636 }
2637 phar->halt_offset = len + end_sequence_len;
2638 } else {
2639 size_t written;
2640 zend_string *new_stub = NULL;
2641
2642 if (!user_stub && phar->halt_offset && oldfile && !phar->is_brandnew) {
2643 php_stream_copy_to_stream_ex(oldfile, newfile, phar->halt_offset, &written);
2644 } else {
2645 /* this is either a brand new phar or a default stub overwrite */
2646 new_stub = phar_create_default_stub(NULL, NULL, NULL);
2647 phar->halt_offset = ZSTR_LEN(new_stub);
2648 written = php_stream_write(newfile, ZSTR_VAL(new_stub), phar->halt_offset);
2649 }
2650 if (phar->halt_offset != written) {
2651 if (must_close_old_file) {
2652 php_stream_close(oldfile);
2653 }
2654 php_stream_close(newfile);
2655 if (error) {
2656 if (new_stub) {
2657 spprintf(error, 0, "unable to create stub in new phar \"%s\"", phar->fname);
2658 } else {
2659 spprintf(error, 0, "unable to copy stub of old phar to new phar \"%s\"", phar->fname);
2660 }
2661 }
2662 if (new_stub) {
2663 zend_string_free(new_stub);
2664 }
2665 return;
2666 }
2667 if (new_stub) {
2668 zend_string_free(new_stub);
2669 }
2670 }
2671 manifest_ftell = php_stream_tell(newfile);
2672 halt_offset = manifest_ftell;
2673
2674 /* Check whether we can get rid of some of the deleted entries which are
2675 * unused. However some might still be in use so even after this clean-up
2676 * we need to skip entries marked is_deleted. */
2677 zend_hash_apply(&phar->manifest, phar_flush_clean_deleted_apply);
2678
2679 /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */
2680 main_metadata_str.s = NULL;
2681 if (phar->metadata_tracker.str) {
2682 smart_str_appendl(&main_metadata_str, ZSTR_VAL(phar->metadata_tracker.str), ZSTR_LEN(phar->metadata_tracker.str));
2683 } else if (!Z_ISUNDEF(phar->metadata_tracker.val)) {
2684 PHP_VAR_SERIALIZE_INIT(metadata_hash);
2685 php_var_serialize(&main_metadata_str, &phar->metadata_tracker.val, &metadata_hash);
2686 PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
2687 }
2688 new_manifest_count = 0;
2689 offset = 0;
2690 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
2691 if (entry->cfp) {
2692 /* did we forget to get rid of cfp last time? */
2693 php_stream_close(entry->cfp);
2694 entry->cfp = 0;
2695 }
2696 if (entry->is_deleted || entry->is_mounted) {
2697 /* remove this from the new phar */
2698 continue;
2699 }
2700 if (!entry->is_modified && entry->fp_refcount) {
2701 /* open file pointers refer to this fp, do not free the stream */
2702 switch (entry->fp_type) {
2703 case PHAR_FP:
2704 free_fp = false;
2705 break;
2706 case PHAR_UFP:
2707 free_ufp = false;
2708 default:
2709 break;
2710 }
2711 }
2712 /* after excluding deleted files, calculate manifest size in bytes and number of entries */
2713 ++new_manifest_count;
2714 phar_add_virtual_dirs(phar, entry->filename, entry->filename_len);
2715
2716 if (entry->is_dir) {
2717 /* we use this to calculate API version, 1.1.1 is used for phars with directories */
2718 has_dirs = true;
2719 }
2720 if (!Z_ISUNDEF(entry->metadata_tracker.val) && !entry->metadata_tracker.str) {
2721 ZEND_ASSERT(!entry->is_persistent);
2722 /* Assume serialization will succeed. TODO: Set error and throw if EG(exception) != NULL */
2723 smart_str buf = {0};
2724 PHP_VAR_SERIALIZE_INIT(metadata_hash);
2725 php_var_serialize(&buf, &entry->metadata_tracker.val, &metadata_hash);
2726 PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
2727 entry->metadata_tracker.str = buf.s;
2728 }
2729
2730 /* 32 bits for filename length, length of filename, manifest + metadata, and add 1 for trailing / if a directory */
2731 offset += 4 + entry->filename_len + sizeof(entry_buffer) + (entry->metadata_tracker.str ? ZSTR_LEN(entry->metadata_tracker.str) : 0) + (entry->is_dir ? 1 : 0);
2732
2733 /* compress and rehash as necessary */
2734 if ((oldfile && !entry->is_modified) || entry->is_dir) {
2735 if (entry->fp_type == PHAR_UFP) {
2736 /* reset so we can copy the compressed data over */
2737 entry->fp_type = PHAR_FP;
2738 }
2739 continue;
2740 }
2741 if (!phar_get_efp(entry, 0)) {
2742 /* re-open internal file pointer just-in-time */
2743 newentry = phar_open_jit(phar, entry, error);
2744 if (!newentry) {
2745 /* major problem re-opening, so we ignore this file and the error */
2746 efree(*error);
2747 *error = NULL;
2748 continue;
2749 }
2750 entry = newentry;
2751 }
2752 file = phar_get_efp(entry, 0);
2753 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 1)) {
2754 if (must_close_old_file) {
2755 php_stream_close(oldfile);
2756 }
2757 php_stream_close(newfile);
2758 if (error) {
2759 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2760 }
2761 return;
2762 }
2763 newcrc32 = php_crc32_bulk_init();
2764 php_crc32_stream_bulk_update(&newcrc32, file, entry->uncompressed_filesize);
2765 entry->crc32 = php_crc32_bulk_end(newcrc32);
2766 entry->is_crc_checked = 1;
2767 if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
2768 /* not compressed */
2769 entry->compressed_filesize = entry->uncompressed_filesize;
2770 continue;
2771 }
2772 filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0);
2773 if (!filter) {
2774 if (must_close_old_file) {
2775 php_stream_close(oldfile);
2776 }
2777 php_stream_close(newfile);
2778 if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
2779 if (error) {
2780 spprintf(error, 0, "unable to gzip compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
2781 }
2782 } else {
2783 if (error) {
2784 spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
2785 }
2786 }
2787 return;
2788 }
2789
2790 /* create new file that holds the compressed versions */
2791 /* work around inability to specify freedom in write and strictness
2792 in read count */
2793 if (shared_cfp == NULL) {
2794 shared_cfp = php_stream_fopen_tmpfile();
2795 }
2796 entry->cfp = shared_cfp;
2797 if (!entry->cfp) {
2798 if (error) {
2799 spprintf(error, 0, "unable to create temporary file");
2800 }
2801 if (must_close_old_file) {
2802 php_stream_close(oldfile);
2803 }
2804 php_stream_close(newfile);
2805 goto cleanup;
2806 }
2807 /* for real phars, header_offset is unused; we misuse it here to store the offset in the temp file */
2808 ZEND_ASSERT(entry->header_offset == 0);
2809 entry->header_offset = php_stream_tell(entry->cfp);
2810 php_stream_flush(file);
2811 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
2812 if (must_close_old_file) {
2813 php_stream_close(oldfile);
2814 }
2815 php_stream_close(newfile);
2816 if (error) {
2817 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2818 }
2819 goto cleanup;
2820 }
2821 php_stream_filter_append((&entry->cfp->writefilters), filter);
2822 if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) {
2823 if (must_close_old_file) {
2824 php_stream_close(oldfile);
2825 }
2826 php_stream_close(newfile);
2827 if (error) {
2828 spprintf(error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
2829 }
2830 goto cleanup;
2831 }
2832 php_stream_filter_flush(filter, 1);
2833 php_stream_flush(entry->cfp);
2834 php_stream_filter_remove(filter, 1);
2835 php_stream_seek(entry->cfp, 0, SEEK_END);
2836 entry->compressed_filesize = ((uint32_t) php_stream_tell(entry->cfp)) - entry->header_offset;
2837 /* generate crc on compressed file */
2838 entry->old_flags = entry->flags;
2839 entry->is_modified = 1;
2840 global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK);
2841 } ZEND_HASH_FOREACH_END();
2842 global_flags |= PHAR_HDR_SIGNATURE;
2843
2844 /* write out manifest pre-header */
2845 /* 4: manifest length
2846 * 4: manifest entry count
2847 * 2: phar version
2848 * 4: phar global flags
2849 * 4: alias length
2850 * ?: the alias itself
2851 * 4: phar metadata length
2852 * ?: phar metadata
2853 */
2854 restore_alias_len = phar->alias_len;
2855 if (phar->is_temporary_alias) {
2856 phar->alias_len = 0;
2857 }
2858
2859 manifest_len = offset + phar->alias_len + sizeof(manifest) + (main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0);
2860 phar_set_32(manifest, manifest_len);
2861 /* Hack - see bug #65028, add padding byte to the end of the manifest */
2862 if(manifest[0] == '\r' || manifest[0] == '\n') {
2863 manifest_len++;
2864 phar_set_32(manifest, manifest_len);
2865 manifest_hack = true;
2866 }
2867 phar_set_32(manifest+4, new_manifest_count);
2868 if (has_dirs) {
2869 *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION) >> 8) & 0xFF);
2870 *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION) & 0xF0));
2871 } else {
2872 *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION_NODIR) >> 8) & 0xFF);
2873 *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION_NODIR) & 0xF0));
2874 }
2875 phar_set_32(manifest+10, global_flags);
2876 phar_set_32(manifest+14, phar->alias_len);
2877
2878 /* write the manifest header */
2879 if (sizeof(manifest) != php_stream_write(newfile, manifest, sizeof(manifest))
2880 || (size_t)phar->alias_len != php_stream_write(newfile, phar->alias, phar->alias_len)) {
2881
2882 if (must_close_old_file) {
2883 php_stream_close(oldfile);
2884 }
2885
2886 php_stream_close(newfile);
2887 phar->alias_len = restore_alias_len;
2888
2889 if (error) {
2890 spprintf(error, 0, "unable to write manifest header of new phar \"%s\"", phar->fname);
2891 }
2892
2893 goto cleanup;
2894 }
2895
2896 phar->alias_len = restore_alias_len;
2897
2898 phar_set_32(manifest, main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0);
2899 if (4 != php_stream_write(newfile, manifest, 4) || ((main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0)
2900 && ZSTR_LEN(main_metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s)))) {
2901 smart_str_free(&main_metadata_str);
2902
2903 if (must_close_old_file) {
2904 php_stream_close(oldfile);
2905 }
2906
2907 php_stream_close(newfile);
2908 phar->alias_len = restore_alias_len;
2909
2910 if (error) {
2911 spprintf(error, 0, "unable to write manifest meta-data of new phar \"%s\"", phar->fname);
2912 }
2913
2914 goto cleanup;
2915 }
2916 smart_str_free(&main_metadata_str);
2917
2918 /* re-calculate the manifest location to simplify later code */
2919 manifest_ftell = php_stream_tell(newfile);
2920
2921 /* now write the manifest */
2922 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
2923 const zend_string *metadata_str;
2924 if (entry->is_deleted || entry->is_mounted) {
2925 /* remove this from the new phar if deleted, ignore if mounted */
2926 continue;
2927 }
2928
2929 if (entry->is_dir) {
2930 /* add 1 for trailing slash */
2931 phar_set_32(entry_buffer, entry->filename_len + 1);
2932 } else {
2933 phar_set_32(entry_buffer, entry->filename_len);
2934 }
2935
2936 if (4 != php_stream_write(newfile, entry_buffer, 4)
2937 || entry->filename_len != php_stream_write(newfile, entry->filename, entry->filename_len)
2938 || (entry->is_dir && 1 != php_stream_write(newfile, "/", 1))) {
2939 if (must_close_old_file) {
2940 php_stream_close(oldfile);
2941 }
2942 php_stream_close(newfile);
2943 if (error) {
2944 if (entry->is_dir) {
2945 spprintf(error, 0, "unable to write filename of directory \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2946 } else {
2947 spprintf(error, 0, "unable to write filename of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2948 }
2949 }
2950 goto cleanup;
2951 }
2952
2953 /* set the manifest meta-data:
2954 4: uncompressed filesize
2955 4: creation timestamp
2956 4: compressed filesize
2957 4: crc32
2958 4: flags
2959 4: metadata-len
2960 +: metadata
2961 */
2962 mytime = time(NULL);
2963 phar_set_32(entry_buffer, entry->uncompressed_filesize);
2964 phar_set_32(entry_buffer+4, mytime);
2965 phar_set_32(entry_buffer+8, entry->compressed_filesize);
2966 phar_set_32(entry_buffer+12, entry->crc32);
2967 phar_set_32(entry_buffer+16, entry->flags);
2968 metadata_str = entry->metadata_tracker.str;
2969 phar_set_32(entry_buffer+20, metadata_str ? ZSTR_LEN(metadata_str) : 0);
2970
2971 if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer))
2972 || (metadata_str &&
2973 ZSTR_LEN(metadata_str) != php_stream_write(newfile, ZSTR_VAL(metadata_str), ZSTR_LEN(metadata_str)))) {
2974 if (must_close_old_file) {
2975 php_stream_close(oldfile);
2976 }
2977
2978 php_stream_close(newfile);
2979
2980 if (error) {
2981 spprintf(error, 0, "unable to write temporary manifest of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname);
2982 }
2983
2984 goto cleanup;
2985 }
2986 } ZEND_HASH_FOREACH_END();
2987 /* Hack - see bug #65028, add padding byte to the end of the manifest */
2988 if (manifest_hack) {
2989 if(1 != php_stream_write(newfile, manifest, 1)) {
2990 if (must_close_old_file) {
2991 php_stream_close(oldfile);
2992 }
2993
2994 php_stream_close(newfile);
2995
2996 if (error) {
2997 spprintf(error, 0, "unable to write manifest padding byte");
2998 }
2999
3000 goto cleanup;
3001 }
3002 }
3003
3004 /* now copy the actual file data to the new phar */
3005 offset = php_stream_tell(newfile);
3006 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
3007 if (entry->is_deleted || entry->is_dir || entry->is_mounted) {
3008 continue;
3009 }
3010
3011 if (entry->cfp) {
3012 file = entry->cfp;
3013 php_stream_seek(file, entry->header_offset, SEEK_SET);
3014 } else {
3015 file = phar_get_efp(entry, 0);
3016 if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
3017 if (must_close_old_file) {
3018 php_stream_close(oldfile);
3019 }
3020 php_stream_close(newfile);
3021 if (error) {
3022 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
3023 }
3024 goto cleanup;
3025 }
3026 }
3027
3028 if (!file) {
3029 if (must_close_old_file) {
3030 php_stream_close(oldfile);
3031 }
3032 php_stream_close(newfile);
3033 if (error) {
3034 spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname);
3035 }
3036 goto cleanup;
3037 }
3038
3039 /* this will have changed for all files that have either changed compression or been modified */
3040 entry->offset = entry->offset_abs = offset;
3041 offset += entry->compressed_filesize;
3042 if (php_stream_copy_to_stream_ex(file, newfile, entry->compressed_filesize, &wrote) == FAILURE) {
3043 if (must_close_old_file) {
3044 php_stream_close(oldfile);
3045 }
3046
3047 php_stream_close(newfile);
3048
3049 if (error) {
3050 spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\"", entry->filename, phar->fname);
3051 }
3052
3053 goto cleanup;
3054 }
3055
3056 entry->is_modified = 0;
3057
3058 if (entry->cfp) {
3059 entry->cfp = NULL;
3060 entry->header_offset = 0;
3061 }
3062
3063 if (entry->fp_type == PHAR_MOD) {
3064 /* 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 */
3065 if (entry->fp_refcount == 0 && entry->fp != phar->fp && entry->fp != phar->ufp) {
3066 php_stream_close(entry->fp);
3067 }
3068
3069 entry->fp = NULL;
3070 entry->fp_type = PHAR_FP;
3071 } else if (entry->fp_type == PHAR_UFP) {
3072 entry->fp_type = PHAR_FP;
3073 }
3074 } ZEND_HASH_FOREACH_END();
3075
3076 if (shared_cfp != NULL) {
3077 php_stream_close(shared_cfp);
3078 shared_cfp = NULL;
3079 }
3080
3081 /* append signature */
3082 if (global_flags & PHAR_HDR_SIGNATURE) {
3083 char sig_buf[4];
3084
3085 php_stream_rewind(newfile);
3086
3087 if (phar->signature) {
3088 efree(phar->signature);
3089 phar->signature = NULL;
3090 }
3091
3092 switch(phar->sig_flags) {
3093 default: {
3094 char *digest = NULL;
3095 size_t digest_len;
3096
3097 if (FAILURE == phar_create_signature(phar, newfile, &digest, &digest_len, error)) {
3098 if (error) {
3099 char *save = *error;
3100 spprintf(error, 0, "phar error: unable to write signature: %s", save);
3101 efree(save);
3102 }
3103 if (digest) {
3104 efree(digest);
3105 }
3106 if (must_close_old_file) {
3107 php_stream_close(oldfile);
3108 }
3109 php_stream_close(newfile);
3110 return;
3111 }
3112
3113 php_stream_write(newfile, digest, digest_len);
3114 efree(digest);
3115 if (phar->sig_flags == PHAR_SIG_OPENSSL ||
3116 phar->sig_flags == PHAR_SIG_OPENSSL_SHA256 ||
3117 phar->sig_flags == PHAR_SIG_OPENSSL_SHA512) {
3118 phar_set_32(sig_buf, digest_len);
3119 php_stream_write(newfile, sig_buf, 4);
3120 }
3121 break;
3122 }
3123 }
3124 phar_set_32(sig_buf, phar->sig_flags);
3125 php_stream_write(newfile, sig_buf, 4);
3126 php_stream_write(newfile, "GBMB", 4);
3127 }
3128
3129 /* finally, close the temp file, rename the original phar,
3130 move the temp to the old phar, unlink the old phar, and reload it into memory
3131 */
3132 if (phar->fp && free_fp) {
3133 php_stream_close(phar->fp);
3134 }
3135
3136 if (phar->ufp) {
3137 if (free_ufp) {
3138 php_stream_close(phar->ufp);
3139 }
3140 phar->ufp = NULL;
3141 }
3142
3143 if (must_close_old_file) {
3144 php_stream_close(oldfile);
3145 }
3146
3147 phar->halt_offset = halt_offset;
3148 phar->is_brandnew = 0;
3149
3150 php_stream_rewind(newfile);
3151
3152 if (phar->donotflush) {
3153 /* deferred flush */
3154 phar->fp = newfile;
3155 } else {
3156 phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
3157 if (!phar->fp) {
3158 phar->fp = newfile;
3159 if (error) {
3160 spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
3161 }
3162 return;
3163 }
3164
3165 if (phar->flags & PHAR_FILE_COMPRESSED_GZ) {
3166 /* to properly compress, we have to tell zlib to add a zlib header */
3167 zval filterparams;
3168
3169 array_init(&filterparams);
3170 add_assoc_long(&filterparams, "window", MAX_WBITS+16);
3171 filter = php_stream_filter_create("zlib.deflate", &filterparams, php_stream_is_persistent(phar->fp));
3172 zend_array_destroy(Z_ARR(filterparams));
3173
3174 if (!filter) {
3175 if (error) {
3176 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);
3177 }
3178 return;
3179 }
3180
3181 php_stream_filter_append(&phar->fp->writefilters, filter);
3182 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3183 php_stream_filter_flush(filter, 1);
3184 php_stream_filter_remove(filter, 1);
3185 php_stream_close(phar->fp);
3186 /* use the temp stream as our base */
3187 phar->fp = newfile;
3188 } else if (phar->flags & PHAR_FILE_COMPRESSED_BZ2) {
3189 filter = php_stream_filter_create("bzip2.compress", NULL, php_stream_is_persistent(phar->fp));
3190 php_stream_filter_append(&phar->fp->writefilters, filter);
3191 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3192 php_stream_filter_flush(filter, 1);
3193 php_stream_filter_remove(filter, 1);
3194 php_stream_close(phar->fp);
3195 /* use the temp stream as our base */
3196 phar->fp = newfile;
3197 } else {
3198 php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL);
3199 /* we could also reopen the file in "rb" mode but there is no need for that */
3200 php_stream_close(newfile);
3201 }
3202 }
3203
3204 if (-1 == php_stream_seek(phar->fp, phar->halt_offset, SEEK_SET)) {
3205 if (error) {
3206 spprintf(error, 0, "unable to seek to __HALT_COMPILER(); in new phar \"%s\"", phar->fname);
3207 }
3208 }
3209
3210 return;
3211
3212 cleanup:
3213 if (shared_cfp != NULL) {
3214 php_stream_close(shared_cfp);
3215 }
3216 ZEND_HASH_MAP_FOREACH_PTR(&phar->manifest, entry) {
3217 if (entry->cfp) {
3218 entry->cfp = NULL;
3219 entry->header_offset = 0;
3220 }
3221 } ZEND_HASH_FOREACH_END();
3222 }
3223 /* }}} */
3224
3225 #ifdef COMPILE_DL_PHAR
3226 #ifdef ZTS
3227 ZEND_TSRMLS_CACHE_DEFINE()
3228 #endif
ZEND_GET_MODULE(phar)3229 ZEND_GET_MODULE(phar)
3230 #endif
3231
3232 static ssize_t phar_zend_stream_reader(void *handle, char *buf, size_t len) /* {{{ */
3233 {
3234 return php_stream_read(phar_get_pharfp((phar_archive_data*)handle), buf, len);
3235 }
3236 /* }}} */
3237
phar_zend_stream_fsizer(void * handle)3238 static size_t phar_zend_stream_fsizer(void *handle) /* {{{ */
3239 {
3240 return ((phar_archive_data*)handle)->halt_offset + 32;
3241 } /* }}} */
3242
3243 zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type);
3244
phar_resolve_path(zend_string * filename)3245 static zend_string *phar_resolve_path(zend_string *filename)
3246 {
3247 zend_string *ret = phar_find_in_include_path(filename, NULL);
3248 if (!ret) {
3249 ret = phar_save_resolve_path(filename);
3250 }
3251 return ret;
3252 }
3253
phar_compile_file(zend_file_handle * file_handle,int type)3254 static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type) /* {{{ */
3255 {
3256 zend_op_array *res;
3257 zend_string *name = NULL;
3258 int failed;
3259 phar_archive_data *phar;
3260
3261 if (!file_handle || !file_handle->filename) {
3262 return phar_orig_compile_file(file_handle, type);
3263 }
3264 if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
3265 if (SUCCESS == phar_open_from_filename(ZSTR_VAL(file_handle->filename), ZSTR_LEN(file_handle->filename), NULL, 0, 0, &phar, NULL)) {
3266 if (phar->is_zip || phar->is_tar) {
3267 zend_file_handle f;
3268
3269 /* zip or tar-based phar */
3270 name = zend_strpprintf(4096, "phar://%s/%s", ZSTR_VAL(file_handle->filename), ".phar/stub.php");
3271 zend_stream_init_filename_ex(&f, name);
3272 if (SUCCESS == zend_stream_open_function(&f)) {
3273 zend_string_release(f.filename);
3274 f.filename = file_handle->filename;
3275 if (f.opened_path) {
3276 zend_string_release(f.opened_path);
3277 }
3278 f.opened_path = file_handle->opened_path;
3279
3280 switch (file_handle->type) {
3281 case ZEND_HANDLE_STREAM:
3282 if (file_handle->handle.stream.closer && file_handle->handle.stream.handle) {
3283 file_handle->handle.stream.closer(file_handle->handle.stream.handle);
3284 }
3285 file_handle->handle.stream.handle = NULL;
3286 break;
3287 default:
3288 break;
3289 }
3290 *file_handle = f;
3291 }
3292 } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) {
3293 /* compressed phar */
3294 file_handle->type = ZEND_HANDLE_STREAM;
3295 /* we do our own reading directly from the phar, don't change the next line */
3296 file_handle->handle.stream.handle = phar;
3297 file_handle->handle.stream.reader = phar_zend_stream_reader;
3298 file_handle->handle.stream.closer = NULL;
3299 file_handle->handle.stream.fsizer = phar_zend_stream_fsizer;
3300 file_handle->handle.stream.isatty = 0;
3301 phar->is_persistent ?
3302 php_stream_rewind(PHAR_G(cached_fp)[phar->phar_pos].fp) :
3303 php_stream_rewind(phar->fp);
3304 }
3305 }
3306 }
3307
3308 zend_try {
3309 failed = 0;
3310 CG(zend_lineno) = 0;
3311 res = phar_orig_compile_file(file_handle, type);
3312 } zend_catch {
3313 failed = 1;
3314 res = NULL;
3315 } zend_end_try();
3316
3317 if (name) {
3318 zend_string_release(name);
3319 }
3320
3321 if (failed) {
3322 zend_bailout();
3323 }
3324
3325 return res;
3326 }
3327 /* }}} */
3328
mime_type_dtor(zval * zv)3329 static void mime_type_dtor(zval *zv)
3330 {
3331 free(Z_PTR_P(zv));
3332 }
3333
PHP_GINIT_FUNCTION(phar)3334 PHP_GINIT_FUNCTION(phar) /* {{{ */
3335 {
3336 #if defined(COMPILE_DL_PHAR) && defined(ZTS)
3337 ZEND_TSRMLS_CACHE_UPDATE();
3338 #endif
3339 phar_mime_type mime;
3340
3341 memset(phar_globals, 0, sizeof(zend_phar_globals));
3342 HT_INVALIDATE(&phar_globals->phar_persist_map);
3343 HT_INVALIDATE(&phar_globals->phar_fname_map);
3344 HT_INVALIDATE(&phar_globals->phar_alias_map);
3345 phar_globals->readonly = 1;
3346
3347 zend_hash_init(&phar_globals->mime_types, 0, NULL, mime_type_dtor, 1);
3348
3349 #define PHAR_SET_MIME(mimetype, ret, fileext) \
3350 mime.mime = mimetype; \
3351 mime.len = sizeof((mimetype))+1; \
3352 mime.type = ret; \
3353 zend_hash_str_add_mem(&phar_globals->mime_types, fileext, sizeof(fileext)-1, (void *)&mime, sizeof(phar_mime_type)); \
3354
3355 PHAR_SET_MIME("text/html", PHAR_MIME_PHPS, "phps")
3356 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c")
3357 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cc")
3358 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cpp")
3359 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c++")
3360 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "dtd")
3361 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "h")
3362 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "log")
3363 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "rng")
3364 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "txt")
3365 PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "xsd")
3366 PHAR_SET_MIME("", PHAR_MIME_PHP, "php")
3367 PHAR_SET_MIME("", PHAR_MIME_PHP, "inc")
3368 PHAR_SET_MIME("video/avi", PHAR_MIME_OTHER, "avi")
3369 PHAR_SET_MIME("image/bmp", PHAR_MIME_OTHER, "bmp")
3370 PHAR_SET_MIME("text/css", PHAR_MIME_OTHER, "css")
3371 PHAR_SET_MIME("image/gif", PHAR_MIME_OTHER, "gif")
3372 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htm")
3373 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "html")
3374 PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htmls")
3375 PHAR_SET_MIME("image/x-ico", PHAR_MIME_OTHER, "ico")
3376 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpe")
3377 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpg")
3378 PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpeg")
3379 PHAR_SET_MIME("application/x-javascript", PHAR_MIME_OTHER, "js")
3380 PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "midi")
3381 PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "mid")
3382 PHAR_SET_MIME("audio/mod", PHAR_MIME_OTHER, "mod")
3383 PHAR_SET_MIME("movie/quicktime", PHAR_MIME_OTHER, "mov")
3384 PHAR_SET_MIME("audio/mp3", PHAR_MIME_OTHER, "mp3")
3385 PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpg")
3386 PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpeg")
3387 PHAR_SET_MIME("application/pdf", PHAR_MIME_OTHER, "pdf")
3388 PHAR_SET_MIME("image/png", PHAR_MIME_OTHER, "png")
3389 PHAR_SET_MIME("application/shockwave-flash", PHAR_MIME_OTHER, "swf")
3390 PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tif")
3391 PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tiff")
3392 PHAR_SET_MIME("audio/wav", PHAR_MIME_OTHER, "wav")
3393 PHAR_SET_MIME("image/xbm", PHAR_MIME_OTHER, "xbm")
3394 PHAR_SET_MIME("text/xml", PHAR_MIME_OTHER, "xml")
3395
3396 phar_restore_orig_functions();
3397 }
3398 /* }}} */
3399
PHP_GSHUTDOWN_FUNCTION(phar)3400 PHP_GSHUTDOWN_FUNCTION(phar) /* {{{ */
3401 {
3402 zend_hash_destroy(&phar_globals->mime_types);
3403 }
3404 /* }}} */
3405
PHP_MINIT_FUNCTION(phar)3406 PHP_MINIT_FUNCTION(phar) /* {{{ */
3407 {
3408 REGISTER_INI_ENTRIES();
3409
3410 phar_orig_compile_file = zend_compile_file;
3411 zend_compile_file = phar_compile_file;
3412
3413 phar_save_resolve_path = zend_resolve_path;
3414 zend_resolve_path = phar_resolve_path;
3415
3416 phar_object_init();
3417
3418 phar_intercept_functions_init();
3419 phar_save_orig_functions();
3420
3421 return php_register_url_stream_wrapper("phar", &php_stream_phar_wrapper);
3422 }
3423 /* }}} */
3424
PHP_MSHUTDOWN_FUNCTION(phar)3425 PHP_MSHUTDOWN_FUNCTION(phar) /* {{{ */
3426 {
3427 php_unregister_url_stream_wrapper("phar");
3428
3429 phar_intercept_functions_shutdown();
3430
3431 if (zend_compile_file == phar_compile_file) {
3432 zend_compile_file = phar_orig_compile_file;
3433 }
3434
3435 if (PHAR_G(manifest_cached)) {
3436 zend_hash_destroy(&(cached_phars));
3437 zend_hash_destroy(&(cached_alias));
3438 }
3439
3440 UNREGISTER_INI_ENTRIES();
3441 return SUCCESS;
3442 }
3443 /* }}} */
3444
phar_request_initialize(void)3445 void phar_request_initialize(void) /* {{{ */
3446 {
3447 if (!PHAR_G(request_init))
3448 {
3449 PHAR_G(last_phar) = NULL;
3450 PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL;
3451 PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1);
3452 PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1);
3453 PHAR_G(request_init) = 1;
3454 PHAR_G(request_ends) = 0;
3455 PHAR_G(request_done) = 0;
3456 zend_hash_init(&(PHAR_G(phar_fname_map)), 5, zend_get_hash_value, destroy_phar_data, 0);
3457 zend_hash_init(&(PHAR_G(phar_persist_map)), 5, zend_get_hash_value, NULL, 0);
3458 zend_hash_init(&(PHAR_G(phar_alias_map)), 5, zend_get_hash_value, NULL, 0);
3459
3460 if (PHAR_G(manifest_cached)) {
3461 phar_archive_data *pphar;
3462 phar_entry_fp *stuff = (phar_entry_fp *) ecalloc(zend_hash_num_elements(&cached_phars), sizeof(phar_entry_fp));
3463
3464 ZEND_HASH_MAP_FOREACH_PTR(&cached_phars, pphar) {
3465 stuff[pphar->phar_pos].manifest = (phar_entry_fp_info *) ecalloc( zend_hash_num_elements(&(pphar->manifest)), sizeof(phar_entry_fp_info));
3466 } ZEND_HASH_FOREACH_END();
3467
3468 PHAR_G(cached_fp) = stuff;
3469 }
3470
3471 PHAR_G(phar_SERVER_mung_list) = 0;
3472 PHAR_G(cwd) = NULL;
3473 PHAR_G(cwd_len) = 0;
3474 PHAR_G(cwd_init) = 0;
3475 }
3476 }
3477 /* }}} */
3478
PHP_RSHUTDOWN_FUNCTION(phar)3479 PHP_RSHUTDOWN_FUNCTION(phar) /* {{{ */
3480 {
3481 PHAR_G(request_ends) = 1;
3482
3483 if (PHAR_G(request_init))
3484 {
3485 phar_release_functions();
3486 zend_hash_destroy(&(PHAR_G(phar_alias_map)));
3487 HT_INVALIDATE(&PHAR_G(phar_alias_map));
3488 zend_hash_destroy(&(PHAR_G(phar_fname_map)));
3489 HT_INVALIDATE(&PHAR_G(phar_fname_map));
3490 zend_hash_destroy(&(PHAR_G(phar_persist_map)));
3491 HT_INVALIDATE(&PHAR_G(phar_persist_map));
3492 PHAR_G(phar_SERVER_mung_list) = 0;
3493
3494 if (PHAR_G(cached_fp)) {
3495 for (uint32_t i = 0; i < zend_hash_num_elements(&cached_phars); ++i) {
3496 if (PHAR_G(cached_fp)[i].fp) {
3497 php_stream_close(PHAR_G(cached_fp)[i].fp);
3498 }
3499 if (PHAR_G(cached_fp)[i].ufp) {
3500 php_stream_close(PHAR_G(cached_fp)[i].ufp);
3501 }
3502 efree(PHAR_G(cached_fp)[i].manifest);
3503 }
3504 efree(PHAR_G(cached_fp));
3505 PHAR_G(cached_fp) = 0;
3506 }
3507
3508 PHAR_G(request_init) = 0;
3509
3510 if (PHAR_G(cwd)) {
3511 efree(PHAR_G(cwd));
3512 }
3513
3514 PHAR_G(cwd) = NULL;
3515 PHAR_G(cwd_len) = 0;
3516 PHAR_G(cwd_init) = 0;
3517 }
3518
3519 PHAR_G(request_done) = 1;
3520 return SUCCESS;
3521 }
3522 /* }}} */
3523
PHP_MINFO_FUNCTION(phar)3524 PHP_MINFO_FUNCTION(phar) /* {{{ */
3525 {
3526 phar_request_initialize();
3527 php_info_print_table_start();
3528 php_info_print_table_row(2, "Phar: PHP Archive support", "enabled");
3529 php_info_print_table_row(2, "Phar API version", PHP_PHAR_API_VERSION);
3530 php_info_print_table_row(2, "Phar-based phar archives", "enabled");
3531 php_info_print_table_row(2, "Tar-based phar archives", "enabled");
3532 php_info_print_table_row(2, "ZIP-based phar archives", "enabled");
3533
3534 if (PHAR_G(has_zlib)) {
3535 php_info_print_table_row(2, "gzip compression", "enabled");
3536 } else {
3537 php_info_print_table_row(2, "gzip compression", "disabled (install ext/zlib)");
3538 }
3539
3540 if (PHAR_G(has_bz2)) {
3541 php_info_print_table_row(2, "bzip2 compression", "enabled");
3542 } else {
3543 php_info_print_table_row(2, "bzip2 compression", "disabled (install ext/bz2)");
3544 }
3545 #ifdef PHAR_HAVE_OPENSSL
3546 php_info_print_table_row(2, "Native OpenSSL support", "enabled");
3547 #else
3548 if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) {
3549 php_info_print_table_row(2, "OpenSSL support", "enabled");
3550 } else {
3551 php_info_print_table_row(2, "OpenSSL support", "disabled (install ext/openssl)");
3552 }
3553 #endif
3554 php_info_print_table_end();
3555
3556 php_info_print_box_start(0);
3557 PUTS("Phar based on pear/PHP_Archive, original concept by Davey Shafik.");
3558 PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
3559 PUTS("Phar fully realized by Gregory Beaver and Marcus Boerger.");
3560 PUTS(!sapi_module.phpinfo_as_text?"<br />":"\n");
3561 PUTS("Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle.");
3562 php_info_print_box_end();
3563
3564 DISPLAY_INI_ENTRIES();
3565 }
3566 /* }}} */
3567
3568 /* {{{ phar_module_entry */
3569 static const zend_module_dep phar_deps[] = {
3570 ZEND_MOD_OPTIONAL("apc")
3571 ZEND_MOD_OPTIONAL("bz2")
3572 ZEND_MOD_OPTIONAL("openssl")
3573 ZEND_MOD_OPTIONAL("zlib")
3574 ZEND_MOD_OPTIONAL("standard")
3575 ZEND_MOD_REQUIRED("hash")
3576 ZEND_MOD_REQUIRED("spl")
3577 ZEND_MOD_END
3578 };
3579
3580 zend_module_entry phar_module_entry = {
3581 STANDARD_MODULE_HEADER_EX, NULL,
3582 phar_deps,
3583 "Phar",
3584 NULL,
3585 PHP_MINIT(phar),
3586 PHP_MSHUTDOWN(phar),
3587 NULL,
3588 PHP_RSHUTDOWN(phar),
3589 PHP_MINFO(phar),
3590 PHP_PHAR_VERSION,
3591 PHP_MODULE_GLOBALS(phar), /* globals descriptor */
3592 PHP_GINIT(phar), /* globals ctor */
3593 PHP_GSHUTDOWN(phar), /* globals dtor */
3594 NULL, /* post deactivate */
3595 STANDARD_MODULE_PROPERTIES_EX
3596 };
3597 /* }}} */
3598