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