root/ext/phar/stream.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. phar_parse_url
  2. phar_wrapper_open_url
  3. phar_stream_close
  4. phar_stream_read
  5. phar_stream_seek
  6. phar_stream_write
  7. phar_stream_flush
  8. phar_dostat
  9. phar_stream_stat
  10. phar_wrapper_stat
  11. phar_wrapper_unlink
  12. phar_wrapper_rename

   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  */
  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  */
 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  */
 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  */
 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  */
 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  */
 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  */
 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  */
 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  */
 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  */
 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  */
 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 
 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  */

/* [<][>][^][v][top][bottom][index][help] */