xref: /PHP-5.5/ext/phar/stream.c (revision 73c1be26)
1 /*
2   +----------------------------------------------------------------------+
3   | phar:// stream wrapper support                                       |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2005-2015 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,char * filename,char * mode,int options TSRMLS_DC)59 php_url* phar_parse_url(php_stream_wrapper *wrapper, char *filename, char *mode, int options TSRMLS_DC) /* {{{ */
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 TSRMLS_CC, "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) TSRMLS_CC) == FAILURE) {
75 		if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
76 			if (arch && !entry) {
77 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC, "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_GLOBALS->request_init && PHAR_GLOBALS->phar_fname_map.arBuckets && FAILURE == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), arch, arch_len, (void **)&pphar)) {
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 TSRMLS_CC, "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 TSRMLS_CC) == FAILURE)
117 		{
118 			if (error) {
119 				if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
120 					php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%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 TSRMLS_CC)) {
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 TSRMLS_CC, "%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 TSRMLS_CC) == FAILURE)
140 		{
141 			if (error) {
142 				if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
143 					php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%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,char * path,char * mode,int options,char ** opened_path,php_stream_context * context STREAMS_DC TSRMLS_DC)158 static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_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 TSRMLS_CC)) == 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 TSRMLS_CC, "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 TSRMLS_CC, "phar error: not a phar stream url \"%s\"", path);
184 		return NULL;
185 	}
186 
187 	host_len = strlen(resource->host);
188 	phar_request_initialize(TSRMLS_C);
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 TSRMLS_CC))) {
194 			if (error) {
195 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
196 				efree(error);
197 			} else {
198 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 && context->options && zend_hash_find(HASH_OF(context->options), "phar", sizeof("phar"), (void**)&pzoption) == SUCCESS) {
212 			pharcontext = HASH_OF(*pzoption);
213 			if (idata->internal_file->uncompressed_filesize == 0
214 				&& idata->internal_file->compressed_filesize == 0
215 				&& zend_hash_find(pharcontext, "compress", sizeof("compress"), (void**)&pzoption) == SUCCESS
216 				&& Z_TYPE_PP(pzoption) == IS_LONG
217 				&& (Z_LVAL_PP(pzoption) & ~PHAR_ENT_COMPRESSION_MASK) == 0
218 			) {
219 				idata->internal_file->flags &= ~PHAR_ENT_COMPRESSION_MASK;
220 				idata->internal_file->flags |= Z_LVAL_PP(pzoption);
221 			}
222 			if (zend_hash_find(pharcontext, "metadata", sizeof("metadata"), (void**)&pzoption) == SUCCESS) {
223 				if (idata->internal_file->metadata) {
224 					zval_ptr_dtor(&idata->internal_file->metadata);
225 					idata->internal_file->metadata = NULL;
226 				}
227 
228 				MAKE_STD_ZVAL(idata->internal_file->metadata);
229 				metadata = *pzoption;
230 				ZVAL_ZVAL(idata->internal_file->metadata, metadata, 1, 0);
231 				idata->phar->is_modified = 1;
232 			}
233 		}
234 		if (opened_path) {
235 			spprintf(opened_path, 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 TSRMLS_CC)) {
242 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC)) || !idata) {
249 					goto idata_error;
250 				}
251 				efree(internal_file);
252 				if (opened_path) {
253 					spprintf(opened_path, 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 TSRMLS_CC);
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 					spprintf(opened_path, 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 TSRMLS_CC)) || !idata) {
287 idata_error:
288 			if (error) {
289 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
290 				efree(error);
291 			} else {
292 				php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC) != SUCCESS) {
312 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
313 		efree(error);
314 		phar_entry_delref(idata TSRMLS_CC);
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 		spprintf(opened_path, 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 TSRMLS_DC)348 static int phar_stream_close(php_stream *stream, int close_handle TSRMLS_DC) /* {{{ */
349 {
350 	phar_entry_delref((phar_entry_data *)stream->abstract TSRMLS_CC);
351 
352 	return 0;
353 }
354 /* }}} */
355 
356 /**
357  * used for fread($fp) and company on a fopen()ed phar file handle
358  */
phar_stream_read(php_stream * stream,char * buf,size_t count TSRMLS_DC)359 static size_t phar_stream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
360 {
361 	phar_entry_data *data = (phar_entry_data *)stream->abstract;
362 	size_t got;
363 	phar_entry_info *entry;
364 
365 	if (data->internal_file->link) {
366 		entry = phar_get_link_source(data->internal_file TSRMLS_CC);
367 	} else {
368 		entry = data->internal_file;
369 	}
370 
371 	if (entry->is_deleted) {
372 		stream->eof = 1;
373 		return 0;
374 	}
375 
376 	/* use our proxy position */
377 	php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
378 
379 	got = php_stream_read(data->fp, buf, MIN(count, entry->uncompressed_filesize - data->position));
380 	data->position = php_stream_tell(data->fp) - data->zero;
381 	stream->eof = (data->position == (off_t) entry->uncompressed_filesize);
382 
383 	return got;
384 }
385 /* }}} */
386 
387 /**
388  * Used for fseek($fp) on a phar file handle
389  */
phar_stream_seek(php_stream * stream,off_t offset,int whence,off_t * newoffset TSRMLS_DC)390 static int phar_stream_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC) /* {{{ */
391 {
392 	phar_entry_data *data = (phar_entry_data *)stream->abstract;
393 	phar_entry_info *entry;
394 	int res;
395 	off_t temp;
396 
397 	if (data->internal_file->link) {
398 		entry = phar_get_link_source(data->internal_file TSRMLS_CC);
399 	} else {
400 		entry = data->internal_file;
401 	}
402 
403 	switch (whence) {
404 		case SEEK_END :
405 			temp = data->zero + entry->uncompressed_filesize + offset;
406 			break;
407 		case SEEK_CUR :
408 			temp = data->zero + data->position + offset;
409 			break;
410 		case SEEK_SET :
411 			temp = data->zero + offset;
412 			break;
413 		default:
414 			temp = 0;
415 	}
416 	if (temp > data->zero + (off_t) entry->uncompressed_filesize) {
417 		*newoffset = -1;
418 		return -1;
419 	}
420 	if (temp < data->zero) {
421 		*newoffset = -1;
422 		return -1;
423 	}
424 	res = php_stream_seek(data->fp, temp, SEEK_SET);
425 	*newoffset = php_stream_tell(data->fp) - data->zero;
426 	data->position = *newoffset;
427 	return res;
428 }
429 /* }}} */
430 
431 /**
432  * Used for writing to a phar file
433  */
phar_stream_write(php_stream * stream,const char * buf,size_t count TSRMLS_DC)434 static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) /* {{{ */
435 {
436 	phar_entry_data *data = (phar_entry_data *) stream->abstract;
437 
438 	php_stream_seek(data->fp, data->position, SEEK_SET);
439 	if (count != php_stream_write(data->fp, buf, count)) {
440 		php_stream_wrapper_log_error(stream->wrapper, stream->flags TSRMLS_CC, "phar error: Could not write %d characters to \"%s\" in phar \"%s\"", (int) count, data->internal_file->filename, data->phar->fname);
441 		return -1;
442 	}
443 	data->position = php_stream_tell(data->fp);
444 	if (data->position > (off_t)data->internal_file->uncompressed_filesize) {
445 		data->internal_file->uncompressed_filesize = data->position;
446 	}
447 	data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize;
448 	data->internal_file->old_flags = data->internal_file->flags;
449 	data->internal_file->is_modified = 1;
450 	return count;
451 }
452 /* }}} */
453 
454 /**
455  * Used to save work done on a writeable phar
456  */
phar_stream_flush(php_stream * stream TSRMLS_DC)457 static int phar_stream_flush(php_stream *stream TSRMLS_DC) /* {{{ */
458 {
459 	char *error;
460 	int ret;
461 	phar_entry_data *data = (phar_entry_data *) stream->abstract;
462 
463 	if (data->internal_file->is_modified) {
464 		data->internal_file->timestamp = time(0);
465 		ret = phar_flush(data->phar, 0, 0, 0, &error TSRMLS_CC);
466 		if (error) {
467 			php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS TSRMLS_CC, "%s", error);
468 			efree(error);
469 		}
470 		return ret;
471 	} else {
472 		return EOF;
473 	}
474 }
475 /* }}} */
476 
477  /* {{{ phar_dostat */
478 /**
479  * stat an opened phar file handle stream, used by phar_stat()
480  */
phar_dostat(phar_archive_data * phar,phar_entry_info * data,php_stream_statbuf * ssb,zend_bool is_temp_dir TSRMLS_DC)481 void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, zend_bool is_temp_dir TSRMLS_DC)
482 {
483 	memset(ssb, 0, sizeof(php_stream_statbuf));
484 
485 	if (!is_temp_dir && !data->is_dir) {
486 		ssb->sb.st_size = data->uncompressed_filesize;
487 		ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
488 		ssb->sb.st_mode |= S_IFREG; /* regular file */
489 		/* timestamp is just the timestamp when this was added to the phar */
490 #ifdef NETWARE
491 		ssb->sb.st_mtime.tv_sec = data->timestamp;
492 		ssb->sb.st_atime.tv_sec = data->timestamp;
493 		ssb->sb.st_ctime.tv_sec = data->timestamp;
494 #else
495 		ssb->sb.st_mtime = data->timestamp;
496 		ssb->sb.st_atime = data->timestamp;
497 		ssb->sb.st_ctime = data->timestamp;
498 #endif
499 	} else if (!is_temp_dir && data->is_dir) {
500 		ssb->sb.st_size = 0;
501 		ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
502 		ssb->sb.st_mode |= S_IFDIR; /* regular directory */
503 		/* timestamp is just the timestamp when this was added to the phar */
504 #ifdef NETWARE
505 		ssb->sb.st_mtime.tv_sec = data->timestamp;
506 		ssb->sb.st_atime.tv_sec = data->timestamp;
507 		ssb->sb.st_ctime.tv_sec = data->timestamp;
508 #else
509 		ssb->sb.st_mtime = data->timestamp;
510 		ssb->sb.st_atime = data->timestamp;
511 		ssb->sb.st_ctime = data->timestamp;
512 #endif
513 	} else {
514 		ssb->sb.st_size = 0;
515 		ssb->sb.st_mode = 0777;
516 		ssb->sb.st_mode |= S_IFDIR; /* regular directory */
517 #ifdef NETWARE
518 		ssb->sb.st_mtime.tv_sec = phar->max_timestamp;
519 		ssb->sb.st_atime.tv_sec = phar->max_timestamp;
520 		ssb->sb.st_ctime.tv_sec = phar->max_timestamp;
521 #else
522 		ssb->sb.st_mtime = phar->max_timestamp;
523 		ssb->sb.st_atime = phar->max_timestamp;
524 		ssb->sb.st_ctime = phar->max_timestamp;
525 #endif
526 	}
527 	if (!phar->is_writeable) {
528 		ssb->sb.st_mode = (ssb->sb.st_mode & 0555) | (ssb->sb.st_mode & ~0777);
529 	}
530 
531 	ssb->sb.st_nlink = 1;
532 	ssb->sb.st_rdev = -1;
533 	/* this is only for APC, so use /dev/null device - no chance of conflict there! */
534 	ssb->sb.st_dev = 0xc;
535 	/* generate unique inode number for alias/filename, so no phars will conflict */
536 	if (!is_temp_dir) {
537 		ssb->sb.st_ino = data->inode;
538 	}
539 #ifndef PHP_WIN32
540 	ssb->sb.st_blksize = -1;
541 	ssb->sb.st_blocks = -1;
542 #endif
543 }
544 /* }}}*/
545 
546 /**
547  * Stat an opened phar file handle
548  */
phar_stream_stat(php_stream * stream,php_stream_statbuf * ssb TSRMLS_DC)549 static int phar_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
550 {
551 	phar_entry_data *data = (phar_entry_data *)stream->abstract;
552 
553 	/* If ssb is NULL then someone is misbehaving */
554 	if (!ssb) {
555 		return -1;
556 	}
557 
558 	phar_dostat(data->phar, data->internal_file, ssb, 0 TSRMLS_CC);
559 	return 0;
560 }
561 /* }}} */
562 
563 /**
564  * Stream wrapper stat implementation of stat()
565  */
phar_wrapper_stat(php_stream_wrapper * wrapper,char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context TSRMLS_DC)566 static int phar_wrapper_stat(php_stream_wrapper *wrapper, char *url, int flags,
567 				  php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) /* {{{ */
568 {
569 	php_url *resource = NULL;
570 	char *internal_file, *error;
571 	phar_archive_data *phar;
572 	phar_entry_info *entry;
573 	uint host_len;
574 	int internal_file_len;
575 
576 	if ((resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
577 		return FAILURE;
578 	}
579 
580 	/* we must have at the very least phar://alias.phar/internalfile.php */
581 	if (!resource->scheme || !resource->host || !resource->path) {
582 		php_url_free(resource);
583 		return FAILURE;
584 	}
585 
586 	if (strcasecmp("phar", resource->scheme)) {
587 		php_url_free(resource);
588 		return FAILURE;
589 	}
590 
591 	host_len = strlen(resource->host);
592 	phar_request_initialize(TSRMLS_C);
593 
594 	internal_file = resource->path + 1; /* strip leading "/" */
595 	/* find the phar in our trusty global hash indexed by alias (host of phar://blah.phar/file.whatever) */
596 	if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, &error TSRMLS_CC)) {
597 		php_url_free(resource);
598 		if (error) {
599 			efree(error);
600 		}
601 		return FAILURE;
602 	}
603 	if (error) {
604 		efree(error);
605 	}
606 	if (*internal_file == '\0') {
607 		/* root directory requested */
608 		phar_dostat(phar, NULL, ssb, 1 TSRMLS_CC);
609 		php_url_free(resource);
610 		return SUCCESS;
611 	}
612 	if (!phar->manifest.arBuckets) {
613 		php_url_free(resource);
614 		return FAILURE;
615 	}
616 	internal_file_len = strlen(internal_file);
617 	/* search through the manifest of files, and if we have an exact match, it's a file */
618 	if (SUCCESS == zend_hash_find(&phar->manifest, internal_file, internal_file_len, (void**)&entry)) {
619 		phar_dostat(phar, entry, ssb, 0 TSRMLS_CC);
620 		php_url_free(resource);
621 		return SUCCESS;
622 	}
623 	if (zend_hash_exists(&(phar->virtual_dirs), internal_file, internal_file_len)) {
624 		phar_dostat(phar, NULL, ssb, 1 TSRMLS_CC);
625 		php_url_free(resource);
626 		return SUCCESS;
627 	}
628 	/* check for mounted directories */
629 	if (phar->mounted_dirs.arBuckets && zend_hash_num_elements(&phar->mounted_dirs)) {
630 		phar_zstr key;
631 		char *str_key;
632 		ulong unused;
633 		uint keylen;
634 		HashPosition pos;
635 
636 		zend_hash_internal_pointer_reset_ex(&phar->mounted_dirs, &pos);
637 		while (FAILURE != zend_hash_has_more_elements_ex(&phar->mounted_dirs, &pos)) {
638 			if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key_ex(&phar->mounted_dirs, &key, &keylen, &unused, 0, &pos)) {
639 				break;
640 			}
641 			PHAR_STR(key, str_key);
642 			if ((int)keylen >= internal_file_len || strncmp(str_key, internal_file, keylen)) {
643 				zend_hash_move_forward_ex(&phar->mounted_dirs, &pos);
644 				PHAR_STR_FREE(str_key);
645 				continue;
646 			} else {
647 				char *test;
648 				int test_len;
649 				php_stream_statbuf ssbi;
650 
651 				if (SUCCESS != zend_hash_find(&phar->manifest, str_key, keylen, (void **) &entry)) {
652 					PHAR_STR_FREE(str_key);
653 					goto free_resource;
654 				}
655 				PHAR_STR_FREE(str_key);
656 				if (!entry->tmp || !entry->is_mounted) {
657 					goto free_resource;
658 				}
659 				test_len = spprintf(&test, MAXPATHLEN, "%s%s", entry->tmp, internal_file + keylen);
660 				if (SUCCESS != php_stream_stat_path(test, &ssbi)) {
661 					efree(test);
662 					zend_hash_move_forward_ex(&phar->mounted_dirs, &pos);
663 					continue;
664 				}
665 				/* mount the file/directory just in time */
666 				if (SUCCESS != phar_mount_entry(phar, test, test_len, internal_file, internal_file_len TSRMLS_CC)) {
667 					efree(test);
668 					goto free_resource;
669 				}
670 				efree(test);
671 				if (SUCCESS != zend_hash_find(&phar->manifest, internal_file, internal_file_len, (void**)&entry)) {
672 					goto free_resource;
673 				}
674 				phar_dostat(phar, entry, ssb, 0 TSRMLS_CC);
675 				php_url_free(resource);
676 				return SUCCESS;
677 			}
678 		}
679 	}
680 free_resource:
681 	php_url_free(resource);
682 	return FAILURE;
683 }
684 /* }}} */
685 
686 /**
687  * Unlink a file within a phar archive
688  */
phar_wrapper_unlink(php_stream_wrapper * wrapper,char * url,int options,php_stream_context * context TSRMLS_DC)689 static int phar_wrapper_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
690 {
691 	php_url *resource;
692 	char *internal_file, *error;
693 	int internal_file_len;
694 	phar_entry_data *idata;
695 	phar_archive_data **pphar;
696 	uint host_len;
697 
698 	if ((resource = phar_parse_url(wrapper, url, "rb", options TSRMLS_CC)) == NULL) {
699 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: unlink failed");
700 		return 0;
701 	}
702 
703 	/* we must have at the very least phar://alias.phar/internalfile.php */
704 	if (!resource->scheme || !resource->host || !resource->path) {
705 		php_url_free(resource);
706 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: invalid url \"%s\"", url);
707 		return 0;
708 	}
709 
710 	if (strcasecmp("phar", resource->scheme)) {
711 		php_url_free(resource);
712 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: not a phar stream url \"%s\"", url);
713 		return 0;
714 	}
715 
716 	host_len = strlen(resource->host);
717 	phar_request_initialize(TSRMLS_C);
718 
719 	if (FAILURE == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), resource->host, host_len, (void **) &pphar)) {
720 		pphar = NULL;
721 	}
722 	if (PHAR_G(readonly) && (!pphar || !(*pphar)->is_data)) {
723 		php_url_free(resource);
724 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: write operations disabled by the php.ini setting phar.readonly");
725 		return 0;
726 	}
727 
728 	/* need to copy to strip leading "/", will get touched again */
729 	internal_file = estrdup(resource->path + 1);
730 	internal_file_len = strlen(internal_file);
731 	if (FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, internal_file_len, "r", 0, &error, 1 TSRMLS_CC)) {
732 		/* constraints of fp refcount were not met */
733 		if (error) {
734 			php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed: %s", url, error);
735 			efree(error);
736 		} else {
737 			php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed, file does not exist", url);
738 		}
739 		efree(internal_file);
740 		php_url_free(resource);
741 		return 0;
742 	}
743 	if (error) {
744 		efree(error);
745 	}
746 	if (idata->internal_file->fp_refcount > 1) {
747 		/* more than just our fp resource is open for this file */
748 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, resource->host);
749 		efree(internal_file);
750 		php_url_free(resource);
751 		phar_entry_delref(idata TSRMLS_CC);
752 		return 0;
753 	}
754 	php_url_free(resource);
755 	efree(internal_file);
756 	phar_entry_remove(idata, &error TSRMLS_CC);
757 	if (error) {
758 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
759 		efree(error);
760 	}
761 	return 1;
762 }
763 /* }}} */
764 
phar_wrapper_rename(php_stream_wrapper * wrapper,char * url_from,char * url_to,int options,php_stream_context * context TSRMLS_DC)765 static int phar_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
766 {
767 	php_url *resource_from, *resource_to;
768 	char *error;
769 	phar_archive_data *phar, *pfrom, *pto;
770 	phar_entry_info *entry;
771 	uint host_len;
772 	int is_dir = 0;
773 	int is_modified = 0;
774 
775 	error = NULL;
776 
777 	if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
778 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from);
779 		return 0;
780 	}
781 	if (SUCCESS != phar_get_archive(&pfrom, resource_from->host, strlen(resource_from->host), NULL, 0, &error TSRMLS_CC)) {
782 		pfrom = NULL;
783 		if (error) {
784 			efree(error);
785 		}
786 	}
787 	if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
788 		php_url_free(resource_from);
789 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
790 		return 0;
791 	}
792 
793 	if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
794 		php_url_free(resource_from);
795 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to);
796 		return 0;
797 	}
798 	if (SUCCESS != phar_get_archive(&pto, resource_to->host, strlen(resource_to->host), NULL, 0, &error TSRMLS_CC)) {
799 		if (error) {
800 			efree(error);
801 		}
802 		pto = NULL;
803 	}
804 	if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
805 		php_url_free(resource_from);
806 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
807 		return 0;
808 	}
809 
810 	if (strcmp(resource_from->host, resource_to->host)) {
811 		php_url_free(resource_from);
812 		php_url_free(resource_to);
813 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to);
814 		return 0;
815 	}
816 
817 	/* we must have at the very least phar://alias.phar/internalfile.php */
818 	if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
819 		php_url_free(resource_from);
820 		php_url_free(resource_to);
821 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from);
822 		return 0;
823 	}
824 
825 	if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
826 		php_url_free(resource_from);
827 		php_url_free(resource_to);
828 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
829 		return 0;
830 	}
831 
832 	if (strcasecmp("phar", resource_from->scheme)) {
833 		php_url_free(resource_from);
834 		php_url_free(resource_to);
835 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from);
836 		return 0;
837 	}
838 
839 	if (strcasecmp("phar", resource_to->scheme)) {
840 		php_url_free(resource_from);
841 		php_url_free(resource_to);
842 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to);
843 		return 0;
844 	}
845 
846 	host_len = strlen(resource_from->host);
847 
848 	if (SUCCESS != phar_get_archive(&phar, resource_from->host, host_len, NULL, 0, &error TSRMLS_CC)) {
849 		php_url_free(resource_from);
850 		php_url_free(resource_to);
851 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
852 		efree(error);
853 		return 0;
854 	}
855 
856 	if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar TSRMLS_CC)) {
857 		php_url_free(resource_from);
858 		php_url_free(resource_to);
859 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to);
860 		return 0;
861 	}
862 
863 	if (SUCCESS == zend_hash_find(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1, (void **)&entry)) {
864 		phar_entry_info new, *source;
865 
866 		/* perform rename magic */
867 		if (entry->is_deleted) {
868 			php_url_free(resource_from);
869 			php_url_free(resource_to);
870 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to);
871 			return 0;
872 		}
873 		/* transfer all data over to the new entry */
874 		memcpy((void *) &new, (void *) entry, sizeof(phar_entry_info));
875 		/* mark the old one for deletion */
876 		entry->is_deleted = 1;
877 		entry->fp = NULL;
878 		entry->metadata = 0;
879 		entry->link = entry->tmp = NULL;
880 		source = entry;
881 
882 		/* add to the manifest, and then store the pointer to the new guy in entry */
883 		zend_hash_add(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info), (void **) &entry);
884 
885 		entry->filename = estrdup(resource_to->path+1);
886 		if (FAILURE == phar_copy_entry_fp(source, entry, &error TSRMLS_CC)) {
887 			php_url_free(resource_from);
888 			php_url_free(resource_to);
889 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
890 			efree(error);
891 			zend_hash_del(&(phar->manifest), entry->filename, strlen(entry->filename));
892 			return 0;
893 		}
894 		is_modified = 1;
895 		entry->is_modified = 1;
896 		entry->filename_len = strlen(entry->filename);
897 		is_dir = entry->is_dir;
898 	} else {
899 		is_dir = zend_hash_exists(&(phar->virtual_dirs), resource_from->path+1, strlen(resource_from->path)-1);
900 		if (!is_dir) {
901 			/* file does not exist */
902 			php_url_free(resource_from);
903 			php_url_free(resource_to);
904 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to);
905 			return 0;
906 
907 		}
908 	}
909 
910 	/* Rename directory. Update all nested paths */
911 	if (is_dir) {
912 		int key_type;
913 		phar_zstr key, new_key;
914 		char *str_key, *new_str_key;
915 		uint key_len, new_key_len;
916 		ulong unused;
917 		uint from_len = strlen(resource_from->path+1);
918 		uint to_len = strlen(resource_to->path+1);
919 
920 		for (zend_hash_internal_pointer_reset(&phar->manifest);
921 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->manifest, &key, &key_len, &unused, 0, NULL)) &&
922 			SUCCESS == zend_hash_get_current_data(&phar->manifest, (void **) &entry);
923 			zend_hash_move_forward(&phar->manifest)) {
924 
925 			PHAR_STR(key, str_key);
926 
927 			if (!entry->is_deleted &&
928 				key_len > from_len &&
929 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
930 				IS_SLASH(str_key[from_len])) {
931 
932 				new_key_len = key_len + to_len - from_len;
933 				new_str_key = emalloc(new_key_len+1);
934 				memcpy(new_str_key, resource_to->path + 1, to_len);
935 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
936 				new_str_key[new_key_len] = 0;
937 
938 				is_modified = 1;
939 				entry->is_modified = 1;
940 				efree(entry->filename);
941 				entry->filename = new_str_key;
942 				entry->filename_len = new_key_len;
943 
944 				PHAR_ZSTR(new_str_key, new_key);
945 				zend_hash_update_current_key_ex(&phar->manifest, key_type, new_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
946 			}
947 			PHAR_STR_FREE(str_key);
948 		}
949 
950 		for (zend_hash_internal_pointer_reset(&phar->virtual_dirs);
951 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->virtual_dirs, &key, &key_len, &unused, 0, NULL));
952 			zend_hash_move_forward(&phar->virtual_dirs)) {
953 
954 			PHAR_STR(key, str_key);
955 
956 			if (key_len >= from_len &&
957 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
958 				(key_len == from_len || IS_SLASH(str_key[from_len]))) {
959 
960 				new_key_len = key_len + to_len - from_len;
961 				new_str_key = emalloc(new_key_len+1);
962 				memcpy(new_str_key, resource_to->path + 1, to_len);
963 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
964 				new_str_key[new_key_len] = 0;
965 
966 				PHAR_ZSTR(new_str_key, new_key);
967 				zend_hash_update_current_key_ex(&phar->virtual_dirs, key_type, new_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
968 				efree(new_str_key);
969 			}
970 			PHAR_STR_FREE(str_key);
971 		}
972 
973 		for (zend_hash_internal_pointer_reset(&phar->mounted_dirs);
974 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->mounted_dirs, &key, &key_len, &unused, 0, NULL)) &&
975 			SUCCESS == zend_hash_get_current_data(&phar->mounted_dirs, (void **) &entry);
976 			zend_hash_move_forward(&phar->mounted_dirs)) {
977 
978 			PHAR_STR(key, str_key);
979 
980 			if (key_len >= from_len &&
981 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
982 				(key_len == from_len || IS_SLASH(str_key[from_len]))) {
983 
984 				new_key_len = key_len + to_len - from_len;
985 				new_str_key = emalloc(new_key_len+1);
986 				memcpy(new_str_key, resource_to->path + 1, to_len);
987 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
988 				new_str_key[new_key_len] = 0;
989 
990 				PHAR_ZSTR(new_str_key, new_key);
991 				zend_hash_update_current_key_ex(&phar->mounted_dirs, key_type, new_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
992 				efree(new_str_key);
993 			}
994 			PHAR_STR_FREE(str_key);
995 		}
996 	}
997 
998 	if (is_modified) {
999 		phar_flush(phar, 0, 0, 0, &error TSRMLS_CC);
1000 		if (error) {
1001 			php_url_free(resource_from);
1002 			php_url_free(resource_to);
1003 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
1004 			efree(error);
1005 			return 0;
1006 		}
1007 	}
1008 
1009 	php_url_free(resource_from);
1010 	php_url_free(resource_to);
1011 
1012 	return 1;
1013 }
1014 /* }}} */
1015 
1016 /*
1017  * Local variables:
1018  * tab-width: 4
1019  * c-basic-offset: 4
1020  * End:
1021  * vim600: noet sw=4 ts=4 fdm=marker
1022  * vim<600: noet sw=4 ts=4
1023  */
1024