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_STREAM 1
21 #include "phar_internal.h"
22 #include "stream.h"
23 #include "dirstream.h"
24
25 php_stream_ops phar_ops = {
26 phar_stream_write, /* write */
27 phar_stream_read, /* read */
28 phar_stream_close, /* close */
29 phar_stream_flush, /* flush */
30 "phar stream",
31 phar_stream_seek, /* seek */
32 NULL, /* cast */
33 phar_stream_stat, /* stat */
34 NULL, /* set option */
35 };
36
37 php_stream_wrapper_ops phar_stream_wops = {
38 phar_wrapper_open_url,
39 NULL, /* phar_wrapper_close */
40 NULL, /* phar_wrapper_stat, */
41 phar_wrapper_stat, /* stat_url */
42 phar_wrapper_open_dir, /* opendir */
43 "phar",
44 phar_wrapper_unlink, /* unlink */
45 phar_wrapper_rename, /* rename */
46 phar_wrapper_mkdir, /* create directory */
47 phar_wrapper_rmdir, /* remove directory */
48 };
49
50 php_stream_wrapper php_stream_phar_wrapper = {
51 &phar_stream_wops,
52 NULL,
53 0 /* is_url */
54 };
55
56 /**
57 * Open a phar file for streams API
58 */
phar_parse_url(php_stream_wrapper * wrapper,const char * filename,const char * mode,int options)59 php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options) /* {{{ */
60 {
61 php_url *resource;
62 char *arch = NULL, *entry = NULL, *error;
63 int arch_len, entry_len;
64
65 if (strlen(filename) < 7 || strncasecmp(filename, "phar://", 7)) {
66 return NULL;
67 }
68 if (mode[0] == 'a') {
69 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
70 php_stream_wrapper_log_error(wrapper, options, "phar error: open mode append not supported");
71 }
72 return NULL;
73 }
74 if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) {
75 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
76 if (arch && !entry) {
77 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)", filename, arch);
78 arch = NULL;
79 } else {
80 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url or non-existent phar \"%s\"", filename);
81 }
82 }
83 return NULL;
84 }
85 resource = ecalloc(1, sizeof(php_url));
86 resource->scheme = estrndup("phar", 4);
87 resource->host = arch;
88
89 resource->path = entry;
90 #if MBO_0
91 if (resource) {
92 fprintf(stderr, "Alias: %s\n", alias);
93 fprintf(stderr, "Scheme: %s\n", resource->scheme);
94 /* fprintf(stderr, "User: %s\n", resource->user);*/
95 /* fprintf(stderr, "Pass: %s\n", resource->pass ? "***" : NULL);*/
96 fprintf(stderr, "Host: %s\n", resource->host);
97 /* fprintf(stderr, "Port: %d\n", resource->port);*/
98 fprintf(stderr, "Path: %s\n", resource->path);
99 /* fprintf(stderr, "Query: %s\n", resource->query);*/
100 /* fprintf(stderr, "Fragment: %s\n", resource->fragment);*/
101 }
102 #endif
103 if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
104 phar_archive_data *pphar = NULL, *phar;
105
106 if (PHAR_G(request_init) && PHAR_G(phar_fname_map.u.flags) && NULL == (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len))) {
107 pphar = NULL;
108 }
109 if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
110 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
111 php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
112 }
113 php_url_free(resource);
114 return NULL;
115 }
116 if (phar_open_or_create_filename(resource->host, arch_len, NULL, 0, 0, options, &phar, &error) == FAILURE)
117 {
118 if (error) {
119 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
120 php_stream_wrapper_log_error(wrapper, options, "%s", error);
121 }
122 efree(error);
123 }
124 php_url_free(resource);
125 return NULL;
126 }
127 if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) {
128 if (error) {
129 spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", resource->host);
130 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
131 php_stream_wrapper_log_error(wrapper, options, "%s", error);
132 }
133 efree(error);
134 }
135 php_url_free(resource);
136 return NULL;
137 }
138 } else {
139 if (phar_open_from_filename(resource->host, arch_len, NULL, 0, options, NULL, &error) == FAILURE)
140 {
141 if (error) {
142 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
143 php_stream_wrapper_log_error(wrapper, options, "%s", error);
144 }
145 efree(error);
146 }
147 php_url_free(resource);
148 return NULL;
149 }
150 }
151 return resource;
152 }
153 /* }}} */
154
155 /**
156 * used for fopen('phar://...') and company
157 */
phar_wrapper_open_url(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)158 static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
159 {
160 phar_archive_data *phar;
161 phar_entry_data *idata;
162 char *internal_file;
163 char *error;
164 HashTable *pharcontext;
165 php_url *resource = NULL;
166 php_stream *fpf;
167 zval *pzoption, *metadata;
168 uint host_len;
169
170 if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) {
171 return NULL;
172 }
173
174 /* we must have at the very least phar://alias.phar/internalfile.php */
175 if (!resource->scheme || !resource->host || !resource->path) {
176 php_url_free(resource);
177 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", path);
178 return NULL;
179 }
180
181 if (strcasecmp("phar", resource->scheme)) {
182 php_url_free(resource);
183 php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", path);
184 return NULL;
185 }
186
187 host_len = strlen(resource->host);
188 phar_request_initialize();
189
190 /* strip leading "/" */
191 internal_file = estrdup(resource->path + 1);
192 if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
193 if (NULL == (idata = phar_get_or_create_entry_data(resource->host, host_len, internal_file, strlen(internal_file), mode, 0, &error, 1))) {
194 if (error) {
195 php_stream_wrapper_log_error(wrapper, options, "%s", error);
196 efree(error);
197 } else {
198 php_stream_wrapper_log_error(wrapper, options, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, resource->host);
199 }
200 efree(internal_file);
201 php_url_free(resource);
202 return NULL;
203 }
204 if (error) {
205 efree(error);
206 }
207 fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
208 php_url_free(resource);
209 efree(internal_file);
210
211 if (context && Z_TYPE(context->options) != IS_UNDEF && (pzoption = zend_hash_str_find(HASH_OF(&context->options), "phar", sizeof("phar")-1)) != NULL) {
212 pharcontext = HASH_OF(pzoption);
213 if (idata->internal_file->uncompressed_filesize == 0
214 && idata->internal_file->compressed_filesize == 0
215 && (pzoption = zend_hash_str_find(pharcontext, "compress", sizeof("compress")-1)) != NULL
216 && Z_TYPE_P(pzoption) == IS_LONG
217 && (Z_LVAL_P(pzoption) & ~PHAR_ENT_COMPRESSION_MASK) == 0
218 ) {
219 idata->internal_file->flags &= ~PHAR_ENT_COMPRESSION_MASK;
220 idata->internal_file->flags |= Z_LVAL_P(pzoption);
221 }
222 if ((pzoption = zend_hash_str_find(pharcontext, "metadata", sizeof("metadata")-1)) != NULL) {
223 if (Z_TYPE(idata->internal_file->metadata) != IS_UNDEF) {
224 zval_ptr_dtor(&idata->internal_file->metadata);
225 ZVAL_UNDEF(&idata->internal_file->metadata);
226 }
227
228 metadata = pzoption;
229 ZVAL_DEREF(metadata);
230 ZVAL_COPY(&idata->internal_file->metadata, metadata);
231 idata->phar->is_modified = 1;
232 }
233 }
234 if (opened_path) {
235 *opened_path = strpprintf(MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
236 }
237 return fpf;
238 } else {
239 if (!*internal_file && (options & STREAM_OPEN_FOR_INCLUDE)) {
240 /* retrieve the stub */
241 if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, NULL)) {
242 php_stream_wrapper_log_error(wrapper, options, "file %s is not a valid phar archive", resource->host);
243 efree(internal_file);
244 php_url_free(resource);
245 return NULL;
246 }
247 if (phar->is_tar || phar->is_zip) {
248 if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, ".phar/stub.php", sizeof(".phar/stub.php")-1, "r", 0, &error, 0)) || !idata) {
249 goto idata_error;
250 }
251 efree(internal_file);
252 if (opened_path) {
253 *opened_path = strpprintf(MAXPATHLEN, "%s", phar->fname);
254 }
255 php_url_free(resource);
256 goto phar_stub;
257 } else {
258 phar_entry_info *entry;
259
260 entry = (phar_entry_info *) ecalloc(1, sizeof(phar_entry_info));
261 entry->is_temp_dir = 1;
262 entry->filename = estrndup("", 0);
263 entry->filename_len = 0;
264 entry->phar = phar;
265 entry->offset = entry->offset_abs = 0;
266 entry->compressed_filesize = entry->uncompressed_filesize = phar->halt_offset;
267 entry->is_crc_checked = 1;
268
269 idata = (phar_entry_data *) ecalloc(1, sizeof(phar_entry_data));
270 idata->fp = phar_get_pharfp(phar);
271 idata->phar = phar;
272 idata->internal_file = entry;
273 if (!phar->is_persistent) {
274 ++(entry->phar->refcount);
275 }
276 ++(entry->fp_refcount);
277 php_url_free(resource);
278 if (opened_path) {
279 *opened_path = strpprintf(MAXPATHLEN, "%s", phar->fname);
280 }
281 efree(internal_file);
282 goto phar_stub;
283 }
284 }
285 /* read-only access is allowed to magic files in .phar directory */
286 if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, strlen(internal_file), "r", 0, &error, 0)) || !idata) {
287 idata_error:
288 if (error) {
289 php_stream_wrapper_log_error(wrapper, options, "%s", error);
290 efree(error);
291 } else {
292 php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host);
293 }
294 efree(internal_file);
295 php_url_free(resource);
296 return NULL;
297 }
298 }
299 php_url_free(resource);
300 #if MBO_0
301 fprintf(stderr, "Pharname: %s\n", idata->phar->filename);
302 fprintf(stderr, "Filename: %s\n", internal_file);
303 fprintf(stderr, "Entry: %s\n", idata->internal_file->filename);
304 fprintf(stderr, "Size: %u\n", idata->internal_file->uncompressed_filesize);
305 fprintf(stderr, "Compressed: %u\n", idata->internal_file->flags);
306 fprintf(stderr, "Offset: %u\n", idata->internal_file->offset_within_phar);
307 fprintf(stderr, "Cached: %s\n", idata->internal_file->filedata ? "yes" : "no");
308 #endif
309
310 /* check length, crc32 */
311 if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) {
312 php_stream_wrapper_log_error(wrapper, options, "%s", error);
313 efree(error);
314 phar_entry_delref(idata);
315 efree(internal_file);
316 return NULL;
317 }
318
319 if (!PHAR_G(cwd_init) && options & STREAM_OPEN_FOR_INCLUDE) {
320 char *entry = idata->internal_file->filename, *cwd;
321
322 PHAR_G(cwd_init) = 1;
323 if ((idata->phar->is_tar || idata->phar->is_zip) && idata->internal_file->filename_len == sizeof(".phar/stub.php")-1 && !strncmp(idata->internal_file->filename, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
324 /* we're executing the stub, which doesn't count as a file */
325 PHAR_G(cwd_init) = 0;
326 } else if ((cwd = strrchr(entry, '/'))) {
327 PHAR_G(cwd_len) = cwd - entry;
328 PHAR_G(cwd) = estrndup(entry, PHAR_G(cwd_len));
329 } else {
330 /* root directory */
331 PHAR_G(cwd_len) = 0;
332 PHAR_G(cwd) = NULL;
333 }
334 }
335 if (opened_path) {
336 *opened_path = strpprintf(MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
337 }
338 efree(internal_file);
339 phar_stub:
340 fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
341 return fpf;
342 }
343 /* }}} */
344
345 /**
346 * Used for fclose($fp) where $fp is a phar archive
347 */
phar_stream_close(php_stream * stream,int close_handle)348 static int phar_stream_close(php_stream *stream, int close_handle) /* {{{ */
349 {
350 /* for some reasons phar needs to be flushed even if there is no write going on */
351 phar_stream_flush(stream);
352
353 phar_entry_delref((phar_entry_data *)stream->abstract);
354
355 return 0;
356 }
357 /* }}} */
358
359 /**
360 * used for fread($fp) and company on a fopen()ed phar file handle
361 */
phar_stream_read(php_stream * stream,char * buf,size_t count)362 static size_t phar_stream_read(php_stream *stream, char *buf, size_t count) /* {{{ */
363 {
364 phar_entry_data *data = (phar_entry_data *)stream->abstract;
365 size_t got;
366 phar_entry_info *entry;
367
368 if (data->internal_file->link) {
369 entry = phar_get_link_source(data->internal_file);
370 } else {
371 entry = data->internal_file;
372 }
373
374 if (entry->is_deleted) {
375 stream->eof = 1;
376 return 0;
377 }
378
379 /* use our proxy position */
380 php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
381
382 got = php_stream_read(data->fp, buf, MIN(count, entry->uncompressed_filesize - data->position));
383 data->position = php_stream_tell(data->fp) - data->zero;
384 stream->eof = (data->position == (zend_off_t) entry->uncompressed_filesize);
385
386 return got;
387 }
388 /* }}} */
389
390 /**
391 * Used for fseek($fp) on a phar file handle
392 */
phar_stream_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)393 static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
394 {
395 phar_entry_data *data = (phar_entry_data *)stream->abstract;
396 phar_entry_info *entry;
397 int res;
398 zend_off_t temp;
399
400 if (data->internal_file->link) {
401 entry = phar_get_link_source(data->internal_file);
402 } else {
403 entry = data->internal_file;
404 }
405
406 switch (whence) {
407 case SEEK_END :
408 temp = data->zero + entry->uncompressed_filesize + offset;
409 break;
410 case SEEK_CUR :
411 temp = data->zero + data->position + offset;
412 break;
413 case SEEK_SET :
414 temp = data->zero + offset;
415 break;
416 default:
417 temp = 0;
418 }
419 if (temp > data->zero + (zend_off_t) entry->uncompressed_filesize) {
420 *newoffset = -1;
421 return -1;
422 }
423 if (temp < data->zero) {
424 *newoffset = -1;
425 return -1;
426 }
427 res = php_stream_seek(data->fp, temp, SEEK_SET);
428 *newoffset = php_stream_tell(data->fp) - data->zero;
429 data->position = *newoffset;
430 return res;
431 }
432 /* }}} */
433
434 /**
435 * Used for writing to a phar file
436 */
phar_stream_write(php_stream * stream,const char * buf,size_t count)437 static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
438 {
439 phar_entry_data *data = (phar_entry_data *) stream->abstract;
440
441 php_stream_seek(data->fp, data->position, SEEK_SET);
442 if (count != php_stream_write(data->fp, buf, count)) {
443 php_stream_wrapper_log_error(stream->wrapper, stream->flags, "phar error: Could not write %d characters to \"%s\" in phar \"%s\"", (int) count, data->internal_file->filename, data->phar->fname);
444 return 0;
445 }
446 data->position = php_stream_tell(data->fp);
447 if (data->position > (zend_off_t)data->internal_file->uncompressed_filesize) {
448 data->internal_file->uncompressed_filesize = data->position;
449 }
450 data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize;
451 data->internal_file->old_flags = data->internal_file->flags;
452 data->internal_file->is_modified = 1;
453 return count;
454 }
455 /* }}} */
456
457 /**
458 * Used to save work done on a writeable phar
459 */
phar_stream_flush(php_stream * stream)460 static int phar_stream_flush(php_stream *stream) /* {{{ */
461 {
462 char *error;
463 int ret;
464 phar_entry_data *data = (phar_entry_data *) stream->abstract;
465
466 if (data->internal_file->is_modified) {
467 data->internal_file->timestamp = time(0);
468 ret = phar_flush(data->phar, 0, 0, 0, &error);
469 if (error) {
470 php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS, "%s", error);
471 efree(error);
472 }
473 return ret;
474 } else {
475 return EOF;
476 }
477 }
478 /* }}} */
479
480 /* {{{ phar_dostat */
481 /**
482 * stat an opened phar file handle stream, used by phar_stat()
483 */
phar_dostat(phar_archive_data * phar,phar_entry_info * data,php_stream_statbuf * ssb,zend_bool is_temp_dir)484 void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, zend_bool is_temp_dir)
485 {
486 memset(ssb, 0, sizeof(php_stream_statbuf));
487
488 if (!is_temp_dir && !data->is_dir) {
489 ssb->sb.st_size = data->uncompressed_filesize;
490 ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
491 ssb->sb.st_mode |= S_IFREG; /* regular file */
492 /* timestamp is just the timestamp when this was added to the phar */
493 #ifdef NETWARE
494 ssb->sb.st_mtime.tv_sec = data->timestamp;
495 ssb->sb.st_atime.tv_sec = data->timestamp;
496 ssb->sb.st_ctime.tv_sec = data->timestamp;
497 #else
498 ssb->sb.st_mtime = data->timestamp;
499 ssb->sb.st_atime = data->timestamp;
500 ssb->sb.st_ctime = data->timestamp;
501 #endif
502 } else if (!is_temp_dir && data->is_dir) {
503 ssb->sb.st_size = 0;
504 ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
505 ssb->sb.st_mode |= S_IFDIR; /* regular directory */
506 /* timestamp is just the timestamp when this was added to the phar */
507 #ifdef NETWARE
508 ssb->sb.st_mtime.tv_sec = data->timestamp;
509 ssb->sb.st_atime.tv_sec = data->timestamp;
510 ssb->sb.st_ctime.tv_sec = data->timestamp;
511 #else
512 ssb->sb.st_mtime = data->timestamp;
513 ssb->sb.st_atime = data->timestamp;
514 ssb->sb.st_ctime = data->timestamp;
515 #endif
516 } else {
517 ssb->sb.st_size = 0;
518 ssb->sb.st_mode = 0777;
519 ssb->sb.st_mode |= S_IFDIR; /* regular directory */
520 #ifdef NETWARE
521 ssb->sb.st_mtime.tv_sec = phar->max_timestamp;
522 ssb->sb.st_atime.tv_sec = phar->max_timestamp;
523 ssb->sb.st_ctime.tv_sec = phar->max_timestamp;
524 #else
525 ssb->sb.st_mtime = phar->max_timestamp;
526 ssb->sb.st_atime = phar->max_timestamp;
527 ssb->sb.st_ctime = phar->max_timestamp;
528 #endif
529 }
530 if (!phar->is_writeable) {
531 ssb->sb.st_mode = (ssb->sb.st_mode & 0555) | (ssb->sb.st_mode & ~0777);
532 }
533
534 ssb->sb.st_nlink = 1;
535 ssb->sb.st_rdev = -1;
536 /* this is only for APC, so use /dev/null device - no chance of conflict there! */
537 ssb->sb.st_dev = 0xc;
538 /* generate unique inode number for alias/filename, so no phars will conflict */
539 if (!is_temp_dir) {
540 ssb->sb.st_ino = data->inode;
541 }
542 #ifndef PHP_WIN32
543 ssb->sb.st_blksize = -1;
544 ssb->sb.st_blocks = -1;
545 #endif
546 }
547 /* }}}*/
548
549 /**
550 * Stat an opened phar file handle
551 */
phar_stream_stat(php_stream * stream,php_stream_statbuf * ssb)552 static int phar_stream_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
553 {
554 phar_entry_data *data = (phar_entry_data *)stream->abstract;
555
556 /* If ssb is NULL then someone is misbehaving */
557 if (!ssb) {
558 return -1;
559 }
560
561 phar_dostat(data->phar, data->internal_file, ssb, 0);
562 return 0;
563 }
564 /* }}} */
565
566 /**
567 * Stream wrapper stat implementation of stat()
568 */
phar_wrapper_stat(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)569 static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int flags,
570 php_stream_statbuf *ssb, php_stream_context *context) /* {{{ */
571 {
572 php_url *resource = NULL;
573 char *internal_file, *error;
574 phar_archive_data *phar;
575 phar_entry_info *entry;
576 uint host_len;
577 int internal_file_len;
578
579 if ((resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
580 return FAILURE;
581 }
582
583 /* we must have at the very least phar://alias.phar/internalfile.php */
584 if (!resource->scheme || !resource->host || !resource->path) {
585 php_url_free(resource);
586 return FAILURE;
587 }
588
589 if (strcasecmp("phar", resource->scheme)) {
590 php_url_free(resource);
591 return FAILURE;
592 }
593
594 host_len = strlen(resource->host);
595 phar_request_initialize();
596
597 internal_file = resource->path + 1; /* strip leading "/" */
598 /* find the phar in our trusty global hash indexed by alias (host of phar://blah.phar/file.whatever) */
599 if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, &error)) {
600 php_url_free(resource);
601 if (error) {
602 efree(error);
603 }
604 return FAILURE;
605 }
606 if (error) {
607 efree(error);
608 }
609 if (*internal_file == '\0') {
610 /* root directory requested */
611 phar_dostat(phar, NULL, ssb, 1);
612 php_url_free(resource);
613 return SUCCESS;
614 }
615 if (!phar->manifest.u.flags) {
616 php_url_free(resource);
617 return FAILURE;
618 }
619 internal_file_len = strlen(internal_file);
620 /* search through the manifest of files, and if we have an exact match, it's a file */
621 if (NULL != (entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len))) {
622 phar_dostat(phar, entry, ssb, 0);
623 php_url_free(resource);
624 return SUCCESS;
625 }
626 if (zend_hash_str_exists(&(phar->virtual_dirs), internal_file, internal_file_len)) {
627 phar_dostat(phar, NULL, ssb, 1);
628 php_url_free(resource);
629 return SUCCESS;
630 }
631 /* check for mounted directories */
632 if (phar->mounted_dirs.u.flags && zend_hash_num_elements(&phar->mounted_dirs)) {
633 zend_string *str_key;
634
635 ZEND_HASH_FOREACH_STR_KEY(&phar->mounted_dirs, str_key) {
636 if ((int)ZSTR_LEN(str_key) >= internal_file_len || strncmp(ZSTR_VAL(str_key), internal_file, ZSTR_LEN(str_key))) {
637 continue;
638 } else {
639 char *test;
640 int test_len;
641 php_stream_statbuf ssbi;
642
643 if (NULL == (entry = zend_hash_find_ptr(&phar->manifest, str_key))) {
644 goto free_resource;
645 }
646 if (!entry->tmp || !entry->is_mounted) {
647 goto free_resource;
648 }
649 test_len = spprintf(&test, MAXPATHLEN, "%s%s", entry->tmp, internal_file + ZSTR_LEN(str_key));
650 if (SUCCESS != php_stream_stat_path(test, &ssbi)) {
651 efree(test);
652 continue;
653 }
654 /* mount the file/directory just in time */
655 if (SUCCESS != phar_mount_entry(phar, test, test_len, internal_file, internal_file_len)) {
656 efree(test);
657 goto free_resource;
658 }
659 efree(test);
660 if (NULL == (entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len))) {
661 goto free_resource;
662 }
663 phar_dostat(phar, entry, ssb, 0);
664 php_url_free(resource);
665 return SUCCESS;
666 }
667 } ZEND_HASH_FOREACH_END();
668 }
669 free_resource:
670 php_url_free(resource);
671 return FAILURE;
672 }
673 /* }}} */
674
675 /**
676 * Unlink a file within a phar archive
677 */
phar_wrapper_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)678 static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) /* {{{ */
679 {
680 php_url *resource;
681 char *internal_file, *error;
682 int internal_file_len;
683 phar_entry_data *idata;
684 phar_archive_data *pphar;
685 uint host_len;
686
687 if ((resource = phar_parse_url(wrapper, url, "rb", options)) == NULL) {
688 php_stream_wrapper_log_error(wrapper, options, "phar error: unlink failed");
689 return 0;
690 }
691
692 /* we must have at the very least phar://alias.phar/internalfile.php */
693 if (!resource->scheme || !resource->host || !resource->path) {
694 php_url_free(resource);
695 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
696 return 0;
697 }
698
699 if (strcasecmp("phar", resource->scheme)) {
700 php_url_free(resource);
701 php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
702 return 0;
703 }
704
705 host_len = strlen(resource->host);
706 phar_request_initialize();
707
708 pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), resource->host, host_len);
709 if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
710 php_url_free(resource);
711 php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
712 return 0;
713 }
714
715 /* need to copy to strip leading "/", will get touched again */
716 internal_file = estrdup(resource->path + 1);
717 internal_file_len = strlen(internal_file);
718 if (FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, internal_file_len, "r", 0, &error, 1)) {
719 /* constraints of fp refcount were not met */
720 if (error) {
721 php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed: %s", url, error);
722 efree(error);
723 } else {
724 php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed, file does not exist", url);
725 }
726 efree(internal_file);
727 php_url_free(resource);
728 return 0;
729 }
730 if (error) {
731 efree(error);
732 }
733 if (idata->internal_file->fp_refcount > 1) {
734 /* more than just our fp resource is open for this file */
735 php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, resource->host);
736 efree(internal_file);
737 php_url_free(resource);
738 phar_entry_delref(idata);
739 return 0;
740 }
741 php_url_free(resource);
742 efree(internal_file);
743 phar_entry_remove(idata, &error);
744 if (error) {
745 php_stream_wrapper_log_error(wrapper, options, "%s", error);
746 efree(error);
747 }
748 return 1;
749 }
750 /* }}} */
751
phar_wrapper_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)752 static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context) /* {{{ */
753 {
754 php_url *resource_from, *resource_to;
755 char *error;
756 phar_archive_data *phar, *pfrom, *pto;
757 phar_entry_info *entry;
758 uint host_len;
759 int is_dir = 0;
760 int is_modified = 0;
761
762 error = NULL;
763
764 if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
765 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from);
766 return 0;
767 }
768 if (SUCCESS != phar_get_archive(&pfrom, resource_from->host, strlen(resource_from->host), NULL, 0, &error)) {
769 pfrom = NULL;
770 if (error) {
771 efree(error);
772 }
773 }
774 if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
775 php_url_free(resource_from);
776 php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
777 return 0;
778 }
779
780 if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
781 php_url_free(resource_from);
782 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to);
783 return 0;
784 }
785 if (SUCCESS != phar_get_archive(&pto, resource_to->host, strlen(resource_to->host), NULL, 0, &error)) {
786 if (error) {
787 efree(error);
788 }
789 pto = NULL;
790 }
791 if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
792 php_url_free(resource_from);
793 php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
794 return 0;
795 }
796
797 if (strcmp(resource_from->host, resource_to->host)) {
798 php_url_free(resource_from);
799 php_url_free(resource_to);
800 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to);
801 return 0;
802 }
803
804 /* we must have at the very least phar://alias.phar/internalfile.php */
805 if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
806 php_url_free(resource_from);
807 php_url_free(resource_to);
808 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from);
809 return 0;
810 }
811
812 if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
813 php_url_free(resource_from);
814 php_url_free(resource_to);
815 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
816 return 0;
817 }
818
819 if (strcasecmp("phar", resource_from->scheme)) {
820 php_url_free(resource_from);
821 php_url_free(resource_to);
822 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from);
823 return 0;
824 }
825
826 if (strcasecmp("phar", resource_to->scheme)) {
827 php_url_free(resource_from);
828 php_url_free(resource_to);
829 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to);
830 return 0;
831 }
832
833 host_len = strlen(resource_from->host);
834
835 if (SUCCESS != phar_get_archive(&phar, resource_from->host, host_len, NULL, 0, &error)) {
836 php_url_free(resource_from);
837 php_url_free(resource_to);
838 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
839 efree(error);
840 return 0;
841 }
842
843 if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) {
844 php_url_free(resource_from);
845 php_url_free(resource_to);
846 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to);
847 return 0;
848 }
849
850 if (NULL != (entry = zend_hash_str_find_ptr(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1))) {
851 phar_entry_info new, *source;
852
853 /* perform rename magic */
854 if (entry->is_deleted) {
855 php_url_free(resource_from);
856 php_url_free(resource_to);
857 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to);
858 return 0;
859 }
860 /* transfer all data over to the new entry */
861 memcpy((void *) &new, (void *) entry, sizeof(phar_entry_info));
862 /* mark the old one for deletion */
863 entry->is_deleted = 1;
864 entry->fp = NULL;
865 ZVAL_UNDEF(&entry->metadata);
866 entry->link = entry->tmp = NULL;
867 source = entry;
868
869 /* add to the manifest, and then store the pointer to the new guy in entry */
870 entry = zend_hash_str_add_mem(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info));
871
872 entry->filename = estrdup(resource_to->path+1);
873 if (FAILURE == phar_copy_entry_fp(source, entry, &error)) {
874 php_url_free(resource_from);
875 php_url_free(resource_to);
876 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
877 efree(error);
878 zend_hash_str_del(&(phar->manifest), entry->filename, strlen(entry->filename));
879 return 0;
880 }
881 is_modified = 1;
882 entry->is_modified = 1;
883 entry->filename_len = strlen(entry->filename);
884 is_dir = entry->is_dir;
885 } else {
886 is_dir = zend_hash_str_exists(&(phar->virtual_dirs), resource_from->path+1, strlen(resource_from->path)-1);
887 if (!is_dir) {
888 /* file does not exist */
889 php_url_free(resource_from);
890 php_url_free(resource_to);
891 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to);
892 return 0;
893
894 }
895 }
896
897 /* Rename directory. Update all nested paths */
898 if (is_dir) {
899 Bucket *b;
900 zend_string *str_key;
901 zend_string *new_str_key;
902 uint from_len = strlen(resource_from->path+1);
903 uint to_len = strlen(resource_to->path+1);
904
905 ZEND_HASH_FOREACH_BUCKET(&phar->manifest, b) {
906 str_key = b->key;
907 entry = Z_PTR(b->val);
908 if (!entry->is_deleted &&
909 ZSTR_LEN(str_key) > from_len &&
910 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
911 IS_SLASH(ZSTR_VAL(str_key)[from_len])) {
912
913 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
914 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
915 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
916 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
917
918 is_modified = 1;
919 entry->is_modified = 1;
920 efree(entry->filename);
921 // TODO: avoid reallocation (make entry->filename zend_string*)
922 entry->filename = estrndup(ZSTR_VAL(new_str_key), ZSTR_LEN(new_str_key));
923 entry->filename_len = ZSTR_LEN(new_str_key);
924
925 zend_string_release(str_key);
926 b->h = zend_string_hash_val(new_str_key);
927 b->key = new_str_key;
928 }
929 } ZEND_HASH_FOREACH_END();
930 zend_hash_rehash(&phar->manifest);
931
932 ZEND_HASH_FOREACH_BUCKET(&phar->virtual_dirs, b) {
933 str_key = b->key;
934 if (ZSTR_LEN(str_key) >= from_len &&
935 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
936 (ZSTR_LEN(str_key) == from_len || IS_SLASH(ZSTR_VAL(str_key)[from_len]))) {
937
938 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
939 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
940 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
941 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
942
943 zend_string_release(str_key);
944 b->h = zend_string_hash_val(new_str_key);
945 b->key = new_str_key;
946 }
947 } ZEND_HASH_FOREACH_END();
948 zend_hash_rehash(&phar->virtual_dirs);
949
950 ZEND_HASH_FOREACH_BUCKET(&phar->mounted_dirs, b) {
951 str_key = b->key;
952 if (ZSTR_LEN(str_key) >= from_len &&
953 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
954 (ZSTR_LEN(str_key) == from_len || IS_SLASH(ZSTR_VAL(str_key)[from_len]))) {
955
956 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
957 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
958 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
959 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
960
961 zend_string_release(str_key);
962 b->h = zend_string_hash_val(new_str_key);
963 b->key = new_str_key;
964 }
965 } ZEND_HASH_FOREACH_END();
966 zend_hash_rehash(&phar->mounted_dirs);
967 }
968
969 if (is_modified) {
970 phar_flush(phar, 0, 0, 0, &error);
971 if (error) {
972 php_url_free(resource_from);
973 php_url_free(resource_to);
974 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
975 efree(error);
976 return 0;
977 }
978 }
979
980 php_url_free(resource_from);
981 php_url_free(resource_to);
982
983 return 1;
984 }
985 /* }}} */
986
987 /*
988 * Local variables:
989 * tab-width: 4
990 * c-basic-offset: 4
991 * End:
992 * vim600: noet sw=4 ts=4 fdm=marker
993 * vim<600: noet sw=4 ts=4
994 */
995