1 /*
2   +----------------------------------------------------------------------+
3   | phar:// stream wrapper support                                       |
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_DIRSTREAM 1
21 #include "phar_internal.h"
22 #include "dirstream.h"
23 
24 void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, bool is_dir);
25 
26 const php_stream_ops phar_dir_ops = {
27 	phar_dir_write, /* write */
28 	phar_dir_read,  /* read  */
29 	phar_dir_close, /* close */
30 	phar_dir_flush, /* flush */
31 	"phar dir",
32 	phar_dir_seek,  /* seek */
33 	NULL,           /* cast */
34 	NULL,           /* stat */
35 	NULL, /* set option */
36 };
37 
38 /**
39  * Used for closedir($fp) where $fp is an opendir('phar://...') directory handle
40  */
phar_dir_close(php_stream * stream,int close_handle)41 static int phar_dir_close(php_stream *stream, int close_handle)  /* {{{ */
42 {
43 	HashTable *data = (HashTable *)stream->abstract;
44 
45 	if (data) {
46 		zend_hash_destroy(data);
47 		FREE_HASHTABLE(data);
48 		stream->abstract = NULL;
49 	}
50 
51 	return 0;
52 }
53 /* }}} */
54 
55 /**
56  * Used for seeking on a phar directory handle
57  */
phar_dir_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)58 static int phar_dir_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
59 {
60 	HashTable *data = (HashTable *)stream->abstract;
61 
62 	if (!data) {
63 		return -1;
64 	}
65 
66 	if (whence == SEEK_END) {
67 		whence = SEEK_SET;
68 		offset = zend_hash_num_elements(data) + offset;
69 	}
70 
71 	if (whence == SEEK_SET) {
72 		zend_hash_internal_pointer_reset(data);
73 	}
74 
75 	if (offset < 0) {
76 		return -1;
77 	} else {
78 		*newoffset = 0;
79 		while (*newoffset < offset && zend_hash_move_forward(data) == SUCCESS) {
80 			++(*newoffset);
81 		}
82 		return 0;
83 	}
84 }
85 /* }}} */
86 
87 /**
88  * Used for readdir() on an opendir()ed phar directory handle
89  */
phar_dir_read(php_stream * stream,char * buf,size_t count)90 static ssize_t phar_dir_read(php_stream *stream, char *buf, size_t count) /* {{{ */
91 {
92 	size_t to_read;
93 	HashTable *data = (HashTable *)stream->abstract;
94 	zend_string *str_key;
95 	zend_ulong unused;
96 
97 	if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key(data, &str_key, &unused)) {
98 		return 0;
99 	}
100 
101 	zend_hash_move_forward(data);
102 	to_read = MIN(ZSTR_LEN(str_key), count);
103 
104 	if (to_read == 0 || count < ZSTR_LEN(str_key)) {
105 		return 0;
106 	}
107 
108 	memset(buf, 0, sizeof(php_stream_dirent));
109 	memcpy(((php_stream_dirent *) buf)->d_name, ZSTR_VAL(str_key), to_read);
110 	((php_stream_dirent *) buf)->d_name[to_read + 1] = '\0';
111 
112 	return sizeof(php_stream_dirent);
113 }
114 /* }}} */
115 
116 /**
117  * Dummy: Used for writing to a phar directory (i.e. not used)
118  */
phar_dir_write(php_stream * stream,const char * buf,size_t count)119 static ssize_t phar_dir_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
120 {
121 	return -1;
122 }
123 /* }}} */
124 
125 /**
126  * Dummy: Used for flushing writes to a phar directory (i.e. not used)
127  */
phar_dir_flush(php_stream * stream)128 static int phar_dir_flush(php_stream *stream) /* {{{ */
129 {
130 	return EOF;
131 }
132 /* }}} */
133 
134 /**
135  * add an empty element with a char * key to a hash table, avoiding duplicates
136  *
137  * This is used to get a unique listing of virtual directories within a phar,
138  * for iterating over opendir()ed phar directories.
139  */
phar_add_empty(HashTable * ht,char * arKey,uint32_t nKeyLength)140 static int phar_add_empty(HashTable *ht, char *arKey, uint32_t nKeyLength)  /* {{{ */
141 {
142 	zval dummy;
143 
144 	ZVAL_NULL(&dummy);
145 	zend_hash_str_update(ht, arKey, nKeyLength, &dummy);
146 	return SUCCESS;
147 }
148 /* }}} */
149 
150 /**
151  * Used for sorting directories alphabetically
152  */
phar_compare_dir_name(Bucket * f,Bucket * s)153 static int phar_compare_dir_name(Bucket *f, Bucket *s)  /* {{{ */
154 {
155 	int result = zend_binary_strcmp(
156 		ZSTR_VAL(f->key), ZSTR_LEN(f->key), ZSTR_VAL(s->key), ZSTR_LEN(s->key));
157 	return ZEND_NORMALIZE_BOOL(result);
158 }
159 /* }}} */
160 
161 /**
162  * Create a opendir() directory stream handle by iterating over each of the
163  * files in a phar and retrieving its relative path.  From this, construct
164  * a list of files/directories that are "in" the directory represented by dir
165  */
phar_make_dirstream(char * dir,HashTable * manifest)166 static php_stream *phar_make_dirstream(char *dir, HashTable *manifest) /* {{{ */
167 {
168 	HashTable *data;
169 	size_t dirlen = strlen(dir);
170 	char *entry, *found, *save;
171 	zend_string *str_key;
172 	size_t keylen;
173 	zend_ulong unused;
174 
175 	ALLOC_HASHTABLE(data);
176 	zend_hash_init(data, 64, NULL, NULL, 0);
177 
178 	if ((*dir == '/' && dirlen == 1 && (manifest->nNumOfElements == 0)) || (dirlen >= sizeof(".phar")-1 && !memcmp(dir, ".phar", sizeof(".phar")-1))) {
179 		/* make empty root directory for empty phar */
180 		/* make empty directory for .phar magic directory */
181 		efree(dir);
182 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
183 	}
184 
185 	zend_hash_internal_pointer_reset(manifest);
186 
187 	while (FAILURE != zend_hash_has_more_elements(manifest)) {
188 		if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key(manifest, &str_key, &unused)) {
189 			break;
190 		}
191 
192 		keylen = ZSTR_LEN(str_key);
193 		if (keylen <= dirlen) {
194 			if (keylen == 0 || keylen < dirlen || !strncmp(ZSTR_VAL(str_key), dir, dirlen)) {
195 				if (SUCCESS != zend_hash_move_forward(manifest)) {
196 					break;
197 				}
198 				continue;
199 			}
200 		}
201 
202 		if (*dir == '/') {
203 			/* root directory */
204 			if (keylen >= sizeof(".phar")-1 && !memcmp(ZSTR_VAL(str_key), ".phar", sizeof(".phar")-1)) {
205 				/* do not add any magic entries to this directory */
206 				if (SUCCESS != zend_hash_move_forward(manifest)) {
207 					break;
208 				}
209 				continue;
210 			}
211 
212 			if (NULL != (found = (char *) memchr(ZSTR_VAL(str_key), '/', keylen))) {
213 				/* the entry has a path separator and is a subdirectory */
214 				entry = (char *) safe_emalloc(found - ZSTR_VAL(str_key), 1, 1);
215 				memcpy(entry, ZSTR_VAL(str_key), found - ZSTR_VAL(str_key));
216 				keylen = found - ZSTR_VAL(str_key);
217 				entry[keylen] = '\0';
218 			} else {
219 				entry = (char *) safe_emalloc(keylen, 1, 1);
220 				memcpy(entry, ZSTR_VAL(str_key), keylen);
221 				entry[keylen] = '\0';
222 			}
223 
224 			goto PHAR_ADD_ENTRY;
225 		} else {
226 			if (0 != memcmp(ZSTR_VAL(str_key), dir, dirlen)) {
227 				/* entry in directory not found */
228 				if (SUCCESS != zend_hash_move_forward(manifest)) {
229 					break;
230 				}
231 				continue;
232 			} else {
233 				if (ZSTR_VAL(str_key)[dirlen] != '/') {
234 					if (SUCCESS != zend_hash_move_forward(manifest)) {
235 						break;
236 					}
237 					continue;
238 				}
239 			}
240 		}
241 
242 		save = ZSTR_VAL(str_key);
243 		save += dirlen + 1; /* seek to just past the path separator */
244 
245 		if (NULL != (found = (char *) memchr(save, '/', keylen - dirlen - 1))) {
246 			/* is subdirectory */
247 			save -= dirlen + 1;
248 			entry = (char *) safe_emalloc(found - save + dirlen, 1, 1);
249 			memcpy(entry, save + dirlen + 1, found - save - dirlen - 1);
250 			keylen = found - save - dirlen - 1;
251 			entry[keylen] = '\0';
252 		} else {
253 			/* is file */
254 			save -= dirlen + 1;
255 			entry = (char *) safe_emalloc(keylen - dirlen, 1, 1);
256 			memcpy(entry, save + dirlen + 1, keylen - dirlen - 1);
257 			entry[keylen - dirlen - 1] = '\0';
258 			keylen = keylen - dirlen - 1;
259 		}
260 PHAR_ADD_ENTRY:
261 		if (keylen) {
262 			phar_add_empty(data, entry, keylen);
263 		}
264 
265 		efree(entry);
266 
267 		if (SUCCESS != zend_hash_move_forward(manifest)) {
268 			break;
269 		}
270 	}
271 
272 	if (FAILURE != zend_hash_has_more_elements(data)) {
273 		efree(dir);
274 		zend_hash_sort(data, phar_compare_dir_name, 0);
275 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
276 	} else {
277 		efree(dir);
278 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
279 	}
280 }
281 /* }}}*/
282 
283 /**
284  * Open a directory handle within a phar archive
285  */
phar_wrapper_open_dir(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)286 php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
287 {
288 	php_url *resource = NULL;
289 	php_stream *ret;
290 	char *internal_file, *error;
291 	zend_string *str_key;
292 	zend_ulong unused;
293 	phar_archive_data *phar;
294 	phar_entry_info *entry = NULL;
295 	uint32_t host_len;
296 
297 	if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) {
298 		php_stream_wrapper_log_error(wrapper, options, "phar url \"%s\" is unknown", path);
299 		return NULL;
300 	}
301 
302 	/* we must have at the very least phar://alias.phar/ */
303 	if (!resource->scheme || !resource->host || !resource->path) {
304 		if (resource->host && !resource->path) {
305 			php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", path, ZSTR_VAL(resource->host));
306 			php_url_free(resource);
307 			return NULL;
308 		}
309 		php_url_free(resource);
310 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\", must have at least phar://%s/", path, path);
311 		return NULL;
312 	}
313 
314 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
315 		php_url_free(resource);
316 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar url \"%s\"", path);
317 		return NULL;
318 	}
319 
320 	host_len = ZSTR_LEN(resource->host);
321 	phar_request_initialize();
322 	internal_file = ZSTR_VAL(resource->path) + 1; /* strip leading "/" */
323 
324 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), host_len, NULL, 0, &error)) {
325 		if (error) {
326 			php_stream_wrapper_log_error(wrapper, options, "%s", error);
327 			efree(error);
328 		} else {
329 			php_stream_wrapper_log_error(wrapper, options, "phar file \"%s\" is unknown", ZSTR_VAL(resource->host));
330 		}
331 		php_url_free(resource);
332 		return NULL;
333 	}
334 
335 	if (error) {
336 		efree(error);
337 	}
338 
339 	if (*internal_file == '\0') {
340 		/* root directory requested */
341 		internal_file = estrndup(internal_file - 1, 1);
342 		ret = phar_make_dirstream(internal_file, &phar->manifest);
343 		php_url_free(resource);
344 		return ret;
345 	}
346 
347 	if (!HT_IS_INITIALIZED(&phar->manifest)) {
348 		php_url_free(resource);
349 		return NULL;
350 	}
351 
352 	if (NULL != (entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, strlen(internal_file))) && !entry->is_dir) {
353 		php_url_free(resource);
354 		return NULL;
355 	} else if (entry && entry->is_dir) {
356 		if (entry->is_mounted) {
357 			php_url_free(resource);
358 			return php_stream_opendir(entry->tmp, options, context);
359 		}
360 		internal_file = estrdup(internal_file);
361 		php_url_free(resource);
362 		return phar_make_dirstream(internal_file, &phar->manifest);
363 	} else {
364 		size_t i_len = strlen(internal_file);
365 
366 		/* search for directory */
367 		zend_hash_internal_pointer_reset(&phar->manifest);
368 		while (FAILURE != zend_hash_has_more_elements(&phar->manifest)) {
369 			if (HASH_KEY_NON_EXISTENT !=
370 					zend_hash_get_current_key(&phar->manifest, &str_key, &unused)) {
371 				if (ZSTR_LEN(str_key) > i_len && 0 == memcmp(ZSTR_VAL(str_key), internal_file, i_len)) {
372 					/* directory found */
373 					internal_file = estrndup(internal_file,
374 							i_len);
375 					php_url_free(resource);
376 					return phar_make_dirstream(internal_file, &phar->manifest);
377 				}
378 			}
379 
380 			if (SUCCESS != zend_hash_move_forward(&phar->manifest)) {
381 				break;
382 			}
383 		}
384 	}
385 
386 	php_url_free(resource);
387 	return NULL;
388 }
389 /* }}} */
390 
391 /**
392  * Make a new directory within a phar archive
393  */
phar_wrapper_mkdir(php_stream_wrapper * wrapper,const char * url_from,int mode,int options,php_stream_context * context)394 int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mode, int options, php_stream_context *context) /* {{{ */
395 {
396 	phar_entry_info entry, *e;
397 	phar_archive_data *phar = NULL;
398 	char *error, *arch, *entry2;
399 	size_t arch_len, entry_len;
400 	php_url *resource = NULL;
401 	uint32_t host_len;
402 
403 	/* pre-readonly check, we need to know if this is a data phar */
404 	if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
405 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
406 		return 0;
407 	}
408 
409 	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
410 		phar = NULL;
411 	}
412 
413 	efree(arch);
414 	efree(entry2);
415 
416 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
417 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from);
418 		return 0;
419 	}
420 
421 	if ((resource = phar_parse_url(wrapper, url_from, "w", options)) == NULL) {
422 		return 0;
423 	}
424 
425 	/* we must have at the very least phar://alias.phar/internalfile.php */
426 	if (!resource->scheme || !resource->host || !resource->path) {
427 		php_url_free(resource);
428 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url_from);
429 		return 0;
430 	}
431 
432 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
433 		php_url_free(resource);
434 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url_from);
435 		return 0;
436 	}
437 
438 	host_len = ZSTR_LEN(resource->host);
439 
440 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), host_len, NULL, 0, &error)) {
441 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error);
442 		efree(error);
443 		php_url_free(resource);
444 		return 0;
445 	}
446 
447 	if ((e = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 2, &error, 1))) {
448 		/* directory exists, or is a subdirectory of an existing file */
449 		if (e->is_temp_dir) {
450 			efree(e->filename);
451 			efree(e);
452 		}
453 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
454 		php_url_free(resource);
455 		return 0;
456 	}
457 
458 	if (error) {
459 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
460 		efree(error);
461 		php_url_free(resource);
462 		return 0;
463 	}
464 
465 	if (phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 0, &error, 1)) {
466 		/* entry exists as a file */
467 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
468 		php_url_free(resource);
469 		return 0;
470 	}
471 
472 	if (error) {
473 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
474 		efree(error);
475 		php_url_free(resource);
476 		return 0;
477 	}
478 
479 	memset((void *) &entry, 0, sizeof(phar_entry_info));
480 
481 	/* strip leading "/" */
482 	if (phar->is_zip) {
483 		entry.is_zip = 1;
484 	}
485 
486 	entry.filename = estrdup(ZSTR_VAL(resource->path) + 1);
487 
488 	if (phar->is_tar) {
489 		entry.is_tar = 1;
490 		entry.tar_type = TAR_DIR;
491 	}
492 
493 	entry.filename_len = ZSTR_LEN(resource->path) - 1;
494 	php_url_free(resource);
495 	entry.is_dir = 1;
496 	entry.phar = phar;
497 	entry.is_modified = 1;
498 	entry.is_crc_checked = 1;
499 	entry.flags = PHAR_ENT_PERM_DEF_DIR;
500 	entry.old_flags = PHAR_ENT_PERM_DEF_DIR;
501 
502 	if (NULL == zend_hash_str_add_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info))) {
503 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", entry.filename, phar->fname);
504 		efree(error);
505 		efree(entry.filename);
506 		return 0;
507 	}
508 
509 	phar_flush(phar, 0, 0, 0, &error);
510 
511 	if (error) {
512 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", entry.filename, phar->fname, error);
513 		zend_hash_str_del(&phar->manifest, entry.filename, entry.filename_len);
514 		efree(error);
515 		return 0;
516 	}
517 
518 	phar_add_virtual_dirs(phar, entry.filename, entry.filename_len);
519 	return 1;
520 }
521 /* }}} */
522 
523 /**
524  * Remove a directory within a phar archive
525  */
phar_wrapper_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)526 int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) /* {{{ */
527 {
528 	phar_entry_info *entry;
529 	phar_archive_data *phar = NULL;
530 	char *error, *arch, *entry2;
531 	size_t arch_len, entry_len;
532 	php_url *resource = NULL;
533 	uint32_t host_len;
534 	zend_string *str_key;
535 	zend_ulong unused;
536 	uint32_t path_len;
537 
538 	/* pre-readonly check, we need to know if this is a data phar */
539 	if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
540 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
541 		return 0;
542 	}
543 
544 	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
545 		phar = NULL;
546 	}
547 
548 	efree(arch);
549 	efree(entry2);
550 
551 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
552 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
553 		return 0;
554 	}
555 
556 	if ((resource = phar_parse_url(wrapper, url, "w", options)) == NULL) {
557 		return 0;
558 	}
559 
560 	/* we must have at the very least phar://alias.phar/internalfile.php */
561 	if (!resource->scheme || !resource->host || !resource->path) {
562 		php_url_free(resource);
563 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
564 		return 0;
565 	}
566 
567 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
568 		php_url_free(resource);
569 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
570 		return 0;
571 	}
572 
573 	host_len = ZSTR_LEN(resource->host);
574 
575 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), host_len, NULL, 0, &error)) {
576 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
577 		efree(error);
578 		php_url_free(resource);
579 		return 0;
580 	}
581 
582 	path_len = ZSTR_LEN(resource->path) - 1;
583 
584 	if (!(entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, 1))) {
585 		if (error) {
586 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
587 			efree(error);
588 		} else {
589 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
590 		}
591 		php_url_free(resource);
592 		return 0;
593 	}
594 
595 	if (!entry->is_deleted) {
596 		for (zend_hash_internal_pointer_reset(&phar->manifest);
597 			HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&phar->manifest, &str_key, &unused);
598 			zend_hash_move_forward(&phar->manifest)
599 		) {
600 			if (ZSTR_LEN(str_key) > path_len &&
601 				memcmp(ZSTR_VAL(str_key), ZSTR_VAL(resource->path)+1, path_len) == 0 &&
602 				IS_SLASH(ZSTR_VAL(str_key)[path_len])) {
603 				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
604 				if (entry->is_temp_dir) {
605 					efree(entry->filename);
606 					efree(entry);
607 				}
608 				php_url_free(resource);
609 				return 0;
610 			}
611 		}
612 
613 		for (zend_hash_internal_pointer_reset(&phar->virtual_dirs);
614 			HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&phar->virtual_dirs, &str_key, &unused);
615 			zend_hash_move_forward(&phar->virtual_dirs)) {
616 
617 			if (ZSTR_LEN(str_key) > path_len &&
618 				memcmp(ZSTR_VAL(str_key), ZSTR_VAL(resource->path)+1, path_len) == 0 &&
619 				IS_SLASH(ZSTR_VAL(str_key)[path_len])) {
620 				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
621 				if (entry->is_temp_dir) {
622 					efree(entry->filename);
623 					efree(entry);
624 				}
625 				php_url_free(resource);
626 				return 0;
627 			}
628 		}
629 	}
630 
631 	if (entry->is_temp_dir) {
632 		zend_hash_str_del(&phar->virtual_dirs, ZSTR_VAL(resource->path)+1, path_len);
633 		efree(entry->filename);
634 		efree(entry);
635 	} else {
636 		entry->is_deleted = 1;
637 		entry->is_modified = 1;
638 		phar_flush(phar, 0, 0, 0, &error);
639 
640 		if (error) {
641 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", entry->filename, phar->fname, error);
642 			php_url_free(resource);
643 			efree(error);
644 			return 0;
645 		}
646 	}
647 
648 	php_url_free(resource);
649 	return 1;
650 }
651 /* }}} */
652