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