xref: /PHP-5.6/ext/phar/stream.c (revision 49493a2d)
1 /*
2   +----------------------------------------------------------------------+
3   | phar:// stream wrapper support                                       |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2005-2016 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 TSRMLS_DC)59 php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const 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,const char * path,const 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, const char *path, const 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,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context TSRMLS_DC)566 static int phar_wrapper_stat(php_stream_wrapper *wrapper, const 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 		char *str_key;
631 		ulong unused;
632 		uint keylen;
633 		HashPosition pos;
634 
635 		for (zend_hash_internal_pointer_reset_ex(&phar->mounted_dirs, &pos);
636 			HASH_KEY_NON_EXISTENT != zend_hash_get_current_key_ex(&phar->mounted_dirs, &str_key, &keylen, &unused, 0, &pos);
637 			zend_hash_move_forward_ex(&phar->mounted_dirs, &pos)
638 		) {
639 			if ((int)keylen >= internal_file_len || strncmp(str_key, internal_file, keylen)) {
640 				continue;
641 			} else {
642 				char *test;
643 				int test_len;
644 				php_stream_statbuf ssbi;
645 
646 				if (SUCCESS != zend_hash_find(&phar->manifest, str_key, keylen, (void **) &entry)) {
647 					goto free_resource;
648 				}
649 				if (!entry->tmp || !entry->is_mounted) {
650 					goto free_resource;
651 				}
652 				test_len = spprintf(&test, MAXPATHLEN, "%s%s", entry->tmp, internal_file + keylen);
653 				if (SUCCESS != php_stream_stat_path(test, &ssbi)) {
654 					efree(test);
655 					continue;
656 				}
657 				/* mount the file/directory just in time */
658 				if (SUCCESS != phar_mount_entry(phar, test, test_len, internal_file, internal_file_len TSRMLS_CC)) {
659 					efree(test);
660 					goto free_resource;
661 				}
662 				efree(test);
663 				if (SUCCESS != zend_hash_find(&phar->manifest, internal_file, internal_file_len, (void**)&entry)) {
664 					goto free_resource;
665 				}
666 				phar_dostat(phar, entry, ssb, 0 TSRMLS_CC);
667 				php_url_free(resource);
668 				return SUCCESS;
669 			}
670 		}
671 	}
672 free_resource:
673 	php_url_free(resource);
674 	return FAILURE;
675 }
676 /* }}} */
677 
678 /**
679  * Unlink a file within a phar archive
680  */
phar_wrapper_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context TSRMLS_DC)681 static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
682 {
683 	php_url *resource;
684 	char *internal_file, *error;
685 	int internal_file_len;
686 	phar_entry_data *idata;
687 	phar_archive_data **pphar;
688 	uint host_len;
689 
690 	if ((resource = phar_parse_url(wrapper, url, "rb", options TSRMLS_CC)) == NULL) {
691 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: unlink failed");
692 		return 0;
693 	}
694 
695 	/* we must have at the very least phar://alias.phar/internalfile.php */
696 	if (!resource->scheme || !resource->host || !resource->path) {
697 		php_url_free(resource);
698 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: invalid url \"%s\"", url);
699 		return 0;
700 	}
701 
702 	if (strcasecmp("phar", resource->scheme)) {
703 		php_url_free(resource);
704 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: not a phar stream url \"%s\"", url);
705 		return 0;
706 	}
707 
708 	host_len = strlen(resource->host);
709 	phar_request_initialize(TSRMLS_C);
710 
711 	if (FAILURE == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), resource->host, host_len, (void **) &pphar)) {
712 		pphar = NULL;
713 	}
714 	if (PHAR_G(readonly) && (!pphar || !(*pphar)->is_data)) {
715 		php_url_free(resource);
716 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: write operations disabled by the php.ini setting phar.readonly");
717 		return 0;
718 	}
719 
720 	/* need to copy to strip leading "/", will get touched again */
721 	internal_file = estrdup(resource->path + 1);
722 	internal_file_len = strlen(internal_file);
723 	if (FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, internal_file_len, "r", 0, &error, 1 TSRMLS_CC)) {
724 		/* constraints of fp refcount were not met */
725 		if (error) {
726 			php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed: %s", url, error);
727 			efree(error);
728 		} else {
729 			php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed, file does not exist", url);
730 		}
731 		efree(internal_file);
732 		php_url_free(resource);
733 		return 0;
734 	}
735 	if (error) {
736 		efree(error);
737 	}
738 	if (idata->internal_file->fp_refcount > 1) {
739 		/* more than just our fp resource is open for this file */
740 		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);
741 		efree(internal_file);
742 		php_url_free(resource);
743 		phar_entry_delref(idata TSRMLS_CC);
744 		return 0;
745 	}
746 	php_url_free(resource);
747 	efree(internal_file);
748 	phar_entry_remove(idata, &error TSRMLS_CC);
749 	if (error) {
750 		php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
751 		efree(error);
752 	}
753 	return 1;
754 }
755 /* }}} */
756 
phar_wrapper_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context TSRMLS_DC)757 static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
758 {
759 	php_url *resource_from, *resource_to;
760 	char *error;
761 	phar_archive_data *phar, *pfrom, *pto;
762 	phar_entry_info *entry;
763 	uint host_len;
764 	int is_dir = 0;
765 	int is_modified = 0;
766 
767 	error = NULL;
768 
769 	if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
770 		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);
771 		return 0;
772 	}
773 	if (SUCCESS != phar_get_archive(&pfrom, resource_from->host, strlen(resource_from->host), NULL, 0, &error TSRMLS_CC)) {
774 		pfrom = NULL;
775 		if (error) {
776 			efree(error);
777 		}
778 	}
779 	if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
780 		php_url_free(resource_from);
781 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
782 		return 0;
783 	}
784 
785 	if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
786 		php_url_free(resource_from);
787 		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);
788 		return 0;
789 	}
790 	if (SUCCESS != phar_get_archive(&pto, resource_to->host, strlen(resource_to->host), NULL, 0, &error TSRMLS_CC)) {
791 		if (error) {
792 			efree(error);
793 		}
794 		pto = NULL;
795 	}
796 	if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
797 		php_url_free(resource_from);
798 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
799 		return 0;
800 	}
801 
802 	if (strcmp(resource_from->host, resource_to->host)) {
803 		php_url_free(resource_from);
804 		php_url_free(resource_to);
805 		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);
806 		return 0;
807 	}
808 
809 	/* we must have at the very least phar://alias.phar/internalfile.php */
810 	if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
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\": invalid url \"%s\"", url_from, url_to, url_from);
814 		return 0;
815 	}
816 
817 	if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
818 		php_url_free(resource_from);
819 		php_url_free(resource_to);
820 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
821 		return 0;
822 	}
823 
824 	if (strcasecmp("phar", resource_from->scheme)) {
825 		php_url_free(resource_from);
826 		php_url_free(resource_to);
827 		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);
828 		return 0;
829 	}
830 
831 	if (strcasecmp("phar", resource_to->scheme)) {
832 		php_url_free(resource_from);
833 		php_url_free(resource_to);
834 		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);
835 		return 0;
836 	}
837 
838 	host_len = strlen(resource_from->host);
839 
840 	if (SUCCESS != phar_get_archive(&phar, resource_from->host, host_len, NULL, 0, &error TSRMLS_CC)) {
841 		php_url_free(resource_from);
842 		php_url_free(resource_to);
843 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
844 		efree(error);
845 		return 0;
846 	}
847 
848 	if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar 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\": could not make cached phar writeable", url_from, url_to);
852 		return 0;
853 	}
854 
855 	if (SUCCESS == zend_hash_find(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1, (void **)&entry)) {
856 		phar_entry_info new, *source;
857 
858 		/* perform rename magic */
859 		if (entry->is_deleted) {
860 			php_url_free(resource_from);
861 			php_url_free(resource_to);
862 			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);
863 			return 0;
864 		}
865 		/* transfer all data over to the new entry */
866 		memcpy((void *) &new, (void *) entry, sizeof(phar_entry_info));
867 		/* mark the old one for deletion */
868 		entry->is_deleted = 1;
869 		entry->fp = NULL;
870 		entry->metadata = 0;
871 		entry->link = entry->tmp = NULL;
872 		source = entry;
873 
874 		/* add to the manifest, and then store the pointer to the new guy in entry */
875 		zend_hash_add(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info), (void **) &entry);
876 
877 		entry->filename = estrdup(resource_to->path+1);
878 		if (FAILURE == phar_copy_entry_fp(source, entry, &error TSRMLS_CC)) {
879 			php_url_free(resource_from);
880 			php_url_free(resource_to);
881 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
882 			efree(error);
883 			zend_hash_del(&(phar->manifest), entry->filename, strlen(entry->filename));
884 			return 0;
885 		}
886 		is_modified = 1;
887 		entry->is_modified = 1;
888 		entry->filename_len = strlen(entry->filename);
889 		is_dir = entry->is_dir;
890 	} else {
891 		is_dir = zend_hash_exists(&(phar->virtual_dirs), resource_from->path+1, strlen(resource_from->path)-1);
892 		if (!is_dir) {
893 			/* file does not exist */
894 			php_url_free(resource_from);
895 			php_url_free(resource_to);
896 			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);
897 			return 0;
898 
899 		}
900 	}
901 
902 	/* Rename directory. Update all nested paths */
903 	if (is_dir) {
904 		int key_type;
905 		char *str_key, *new_str_key;
906 		uint key_len, new_key_len;
907 		ulong unused;
908 		uint from_len = strlen(resource_from->path+1);
909 		uint to_len = strlen(resource_to->path+1);
910 
911 		for (zend_hash_internal_pointer_reset(&phar->manifest);
912 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->manifest, &str_key, &key_len, &unused, 0, NULL)) &&
913 			SUCCESS == zend_hash_get_current_data(&phar->manifest, (void **) &entry);
914 			zend_hash_move_forward(&phar->manifest)
915 		) {
916 			if (!entry->is_deleted &&
917 				key_len > from_len &&
918 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
919 				IS_SLASH(str_key[from_len])) {
920 
921 				new_key_len = key_len + to_len - from_len;
922 				new_str_key = emalloc(new_key_len+1);
923 				memcpy(new_str_key, resource_to->path + 1, to_len);
924 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
925 				new_str_key[new_key_len] = 0;
926 
927 				is_modified = 1;
928 				entry->is_modified = 1;
929 				efree(entry->filename);
930 				entry->filename = new_str_key;
931 				entry->filename_len = new_key_len;
932 
933 				zend_hash_update_current_key_ex(&phar->manifest, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
934 			}
935 		}
936 
937 		for (zend_hash_internal_pointer_reset(&phar->virtual_dirs);
938 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->virtual_dirs, &str_key, &key_len, &unused, 0, NULL));
939 			zend_hash_move_forward(&phar->virtual_dirs)
940 		) {
941 			if (key_len >= from_len &&
942 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
943 				(key_len == from_len || IS_SLASH(str_key[from_len]))) {
944 
945 				new_key_len = key_len + to_len - from_len;
946 				new_str_key = emalloc(new_key_len+1);
947 				memcpy(new_str_key, resource_to->path + 1, to_len);
948 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
949 				new_str_key[new_key_len] = 0;
950 
951 				zend_hash_update_current_key_ex(&phar->virtual_dirs, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
952 				efree(new_str_key);
953 			}
954 		}
955 
956 		for (zend_hash_internal_pointer_reset(&phar->mounted_dirs);
957 			HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->mounted_dirs, &str_key, &key_len, &unused, 0, NULL)) &&
958 			SUCCESS == zend_hash_get_current_data(&phar->mounted_dirs, (void **) &entry);
959 			zend_hash_move_forward(&phar->mounted_dirs)
960 		) {
961 			if (key_len >= from_len &&
962 				memcmp(str_key, resource_from->path+1, from_len) == 0 &&
963 				(key_len == from_len || IS_SLASH(str_key[from_len]))) {
964 
965 				new_key_len = key_len + to_len - from_len;
966 				new_str_key = emalloc(new_key_len+1);
967 				memcpy(new_str_key, resource_to->path + 1, to_len);
968 				memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
969 				new_str_key[new_key_len] = 0;
970 
971 				zend_hash_update_current_key_ex(&phar->mounted_dirs, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
972 				efree(new_str_key);
973 			}
974 		}
975 	}
976 
977 	if (is_modified) {
978 		phar_flush(phar, 0, 0, 0, &error TSRMLS_CC);
979 		if (error) {
980 			php_url_free(resource_from);
981 			php_url_free(resource_to);
982 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
983 			efree(error);
984 			return 0;
985 		}
986 	}
987 
988 	php_url_free(resource_from);
989 	php_url_free(resource_to);
990 
991 	return 1;
992 }
993 /* }}} */
994 
995 /*
996  * Local variables:
997  * tab-width: 4
998  * c-basic-offset: 4
999  * End:
1000  * vim600: noet sw=4 ts=4 fdm=marker
1001  * vim<600: noet sw=4 ts=4
1002  */
1003