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