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