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