xref: /php-src/ext/phar/phar.c (revision 792a0e6d)
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