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