xref: /PHP-7.0/ext/phar/stream.c (revision 478f119a)
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