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