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) /* {{{ */
  60 {
  61         php_url *resource;
  62         char *arch = NULL, *entry = NULL, *error;
  63         int arch_len, entry_len;
  64 
  65         if (strlen(filename) < 7 || strncasecmp(filename, "phar://", 7)) {
  66                 return NULL;
  67         }
  68         if (mode[0] == 'a') {
  69                 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  70                         php_stream_wrapper_log_error(wrapper, options, "phar error: open mode append not supported");
  71                 }
  72                 return NULL;
  73         }
  74         if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) {
  75                 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  76                         if (arch && !entry) {
  77                                 php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch);
  78                                 arch = NULL;
  79                         } else {
  80                                 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url or non-existent phar \"%s\"", filename);
  81                         }
  82                 }
  83                 return NULL;
  84         }
  85         resource = ecalloc(1, sizeof(php_url));
  86         resource->scheme = estrndup("phar", 4);
  87         resource->host = arch;
  88 
  89         resource->path = entry;
  90 #if MBO_0
  91                 if (resource) {
  92                         fprintf(stderr, "Alias:     %s\n", alias);
  93                         fprintf(stderr, "Scheme:    %s\n", resource->scheme);
  94 /*                      fprintf(stderr, "User:      %s\n", resource->user);*/
  95 /*                      fprintf(stderr, "Pass:      %s\n", resource->pass ? "***" : NULL);*/
  96                         fprintf(stderr, "Host:      %s\n", resource->host);
  97 /*                      fprintf(stderr, "Port:      %d\n", resource->port);*/
  98                         fprintf(stderr, "Path:      %s\n", resource->path);
  99 /*                      fprintf(stderr, "Query:     %s\n", resource->query);*/
 100 /*                      fprintf(stderr, "Fragment:  %s\n", resource->fragment);*/
 101                 }
 102 #endif
 103         if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
 104                 phar_archive_data *pphar = NULL, *phar;
 105 
 106                 if (PHAR_G(request_init) && PHAR_G(phar_fname_map.u.flags) && NULL == (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len))) {
 107                         pphar = NULL;
 108                 }
 109                 if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
 110                         if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 111                                 php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
 112                         }
 113                         php_url_free(resource);
 114                         return NULL;
 115                 }
 116                 if (phar_open_or_create_filename(resource->host, arch_len, NULL, 0, 0, options, &phar, &error) == FAILURE)
 117                 {
 118                         if (error) {
 119                                 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 120                                         php_stream_wrapper_log_error(wrapper, options, "%s", error);
 121                                 }
 122                                 efree(error);
 123                         }
 124                         php_url_free(resource);
 125                         return NULL;
 126                 }
 127                 if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) {
 128                         if (error) {
 129                                 spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", resource->host);
 130                                 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 131                                         php_stream_wrapper_log_error(wrapper, options, "%s", error);
 132                                 }
 133                                 efree(error);
 134                         }
 135                         php_url_free(resource);
 136                         return NULL;
 137                 }
 138         } else {
 139                 if (phar_open_from_filename(resource->host, arch_len, NULL, 0, options, NULL, &error) == FAILURE)
 140                 {
 141                         if (error) {
 142                                 if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 143                                         php_stream_wrapper_log_error(wrapper, options, "%s", error);
 144                                 }
 145                                 efree(error);
 146                         }
 147                         php_url_free(resource);
 148                         return NULL;
 149                 }
 150         }
 151         return resource;
 152 }
 153 /* }}} */
 154 
 155 /**
 156  * used for fopen('phar://...') and company
 157  */
 158 static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
 159 {
 160         phar_archive_data *phar;
 161         phar_entry_data *idata;
 162         char *internal_file;
 163         char *error;
 164         HashTable *pharcontext;
 165         php_url *resource = NULL;
 166         php_stream *fpf;
 167         zval *pzoption, *metadata;
 168         uint host_len;
 169 
 170         if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) {
 171                 return NULL;
 172         }
 173 
 174         /* we must have at the very least phar://alias.phar/internalfile.php */
 175         if (!resource->scheme || !resource->host || !resource->path) {
 176                 php_url_free(resource);
 177                 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", path);
 178                 return NULL;
 179         }
 180 
 181         if (strcasecmp("phar", resource->scheme)) {
 182                 php_url_free(resource);
 183                 php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", path);
 184                 return NULL;
 185         }
 186 
 187         host_len = strlen(resource->host);
 188         phar_request_initialize();
 189 
 190         /* strip leading "/" */
 191         internal_file = estrdup(resource->path + 1);
 192         if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
 193                 if (NULL == (idata = phar_get_or_create_entry_data(resource->host, host_len, internal_file, strlen(internal_file), mode, 0, &error, 1))) {
 194                         if (error) {
 195                                 php_stream_wrapper_log_error(wrapper, options, "%s", error);
 196                                 efree(error);
 197                         } else {
 198                                 php_stream_wrapper_log_error(wrapper, options, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, resource->host);
 199                         }
 200                         efree(internal_file);
 201                         php_url_free(resource);
 202                         return NULL;
 203                 }
 204                 if (error) {
 205                         efree(error);
 206                 }
 207                 fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
 208                 php_url_free(resource);
 209                 efree(internal_file);
 210 
 211                 if (context && Z_TYPE(context->options) != IS_UNDEF && (pzoption = zend_hash_str_find(HASH_OF(&context->options), "phar", sizeof("phar")-1)) != NULL) {
 212                         pharcontext = HASH_OF(pzoption);
 213                         if (idata->internal_file->uncompressed_filesize == 0
 214                                 && idata->internal_file->compressed_filesize == 0
 215                                 && (pzoption = zend_hash_str_find(pharcontext, "compress", sizeof("compress")-1)) != NULL
 216                                 && Z_TYPE_P(pzoption) == IS_LONG
 217                                 && (Z_LVAL_P(pzoption) & ~PHAR_ENT_COMPRESSION_MASK) == 0
 218                         ) {
 219                                 idata->internal_file->flags &= ~PHAR_ENT_COMPRESSION_MASK;
 220                                 idata->internal_file->flags |= Z_LVAL_P(pzoption);
 221                         }
 222                         if ((pzoption = zend_hash_str_find(pharcontext, "metadata", sizeof("metadata")-1)) != NULL) {
 223                                 if (Z_TYPE(idata->internal_file->metadata) != IS_UNDEF) {
 224                                         zval_ptr_dtor(&idata->internal_file->metadata);
 225                                         ZVAL_UNDEF(&idata->internal_file->metadata);
 226                                 }
 227 
 228                                 metadata = pzoption;
 229                                 ZVAL_DEREF(metadata);
 230                                 ZVAL_COPY(&idata->internal_file->metadata, metadata);
 231                                 idata->phar->is_modified = 1;
 232                         }
 233                 }
 234                 if (opened_path) {
 235                         *opened_path = strpprintf(MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
 236                 }
 237                 return fpf;
 238         } else {
 239                 if (!*internal_file && (options & STREAM_OPEN_FOR_INCLUDE)) {
 240                         /* retrieve the stub */
 241                         if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, NULL)) {
 242                                 php_stream_wrapper_log_error(wrapper, options, "file %s is not a valid phar archive", resource->host);
 243                                 efree(internal_file);
 244                                 php_url_free(resource);
 245                                 return NULL;
 246                         }
 247                         if (phar->is_tar || phar->is_zip) {
 248                                 if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, ".phar/stub.php", sizeof(".phar/stub.php")-1, "r", 0, &error, 0)) || !idata) {
 249                                         goto idata_error;
 250                                 }
 251                                 efree(internal_file);
 252                                 if (opened_path) {
 253                                         *opened_path = strpprintf(MAXPATHLEN, "%s", phar->fname);
 254                                 }
 255                                 php_url_free(resource);
 256                                 goto phar_stub;
 257                         } else {
 258                                 phar_entry_info *entry;
 259 
 260                                 entry = (phar_entry_info *) ecalloc(1, sizeof(phar_entry_info));
 261                                 entry->is_temp_dir = 1;
 262                                 entry->filename = estrndup("", 0);
 263                                 entry->filename_len = 0;
 264                                 entry->phar = phar;
 265                                 entry->offset = entry->offset_abs = 0;
 266                                 entry->compressed_filesize = entry->uncompressed_filesize = phar->halt_offset;
 267                                 entry->is_crc_checked = 1;
 268 
 269                                 idata = (phar_entry_data *) ecalloc(1, sizeof(phar_entry_data));
 270                                 idata->fp = phar_get_pharfp(phar);
 271                                 idata->phar = phar;
 272                                 idata->internal_file = entry;
 273                                 if (!phar->is_persistent) {
 274                                         ++(entry->phar->refcount);
 275                                 }
 276                                 ++(entry->fp_refcount);
 277                                 php_url_free(resource);
 278                                 if (opened_path) {
 279                                         *opened_path = strpprintf(MAXPATHLEN, "%s", phar->fname);
 280                                 }
 281                                 efree(internal_file);
 282                                 goto phar_stub;
 283                         }
 284                 }
 285                 /* read-only access is allowed to magic files in .phar directory */
 286                 if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, strlen(internal_file), "r", 0, &error, 0)) || !idata) {
 287 idata_error:
 288                         if (error) {
 289                                 php_stream_wrapper_log_error(wrapper, options, "%s", error);
 290                                 efree(error);
 291                         } else {
 292                                 php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host);
 293                         }
 294                         efree(internal_file);
 295                         php_url_free(resource);
 296                         return NULL;
 297                 }
 298         }
 299         php_url_free(resource);
 300 #if MBO_0
 301                 fprintf(stderr, "Pharname:   %s\n", idata->phar->filename);
 302                 fprintf(stderr, "Filename:   %s\n", internal_file);
 303                 fprintf(stderr, "Entry:      %s\n", idata->internal_file->filename);
 304                 fprintf(stderr, "Size:       %u\n", idata->internal_file->uncompressed_filesize);
 305                 fprintf(stderr, "Compressed: %u\n", idata->internal_file->flags);
 306                 fprintf(stderr, "Offset:     %u\n", idata->internal_file->offset_within_phar);
 307                 fprintf(stderr, "Cached:     %s\n", idata->internal_file->filedata ? "yes" : "no");
 308 #endif
 309 
 310         /* check length, crc32 */
 311         if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) {
 312                 php_stream_wrapper_log_error(wrapper, options, "%s", error);
 313                 efree(error);
 314                 phar_entry_delref(idata);
 315                 efree(internal_file);
 316                 return NULL;
 317         }
 318 
 319         if (!PHAR_G(cwd_init) && options & STREAM_OPEN_FOR_INCLUDE) {
 320                 char *entry = idata->internal_file->filename, *cwd;
 321 
 322                 PHAR_G(cwd_init) = 1;
 323                 if ((idata->phar->is_tar || idata->phar->is_zip) && idata->internal_file->filename_len == sizeof(".phar/stub.php")-1 && !strncmp(idata->internal_file->filename, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
 324                         /* we're executing the stub, which doesn't count as a file */
 325                         PHAR_G(cwd_init) = 0;
 326                 } else if ((cwd = strrchr(entry, '/'))) {
 327                         PHAR_G(cwd_len) = cwd - entry;
 328                         PHAR_G(cwd) = estrndup(entry, PHAR_G(cwd_len));
 329                 } else {
 330                         /* root directory */
 331                         PHAR_G(cwd_len) = 0;
 332                         PHAR_G(cwd) = NULL;
 333                 }
 334         }
 335         if (opened_path) {
 336                 *opened_path = strpprintf(MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
 337         }
 338         efree(internal_file);
 339 phar_stub:
 340         fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
 341         return fpf;
 342 }
 343 /* }}} */
 344 
 345 /**
 346  * Used for fclose($fp) where $fp is a phar archive
 347  */
 348 static int phar_stream_close(php_stream *stream, int close_handle) /* {{{ */
 349 {
 350         /* for some reasons phar needs to be flushed even if there is no write going on */
 351         phar_stream_flush(stream);
 352 
 353         phar_entry_delref((phar_entry_data *)stream->abstract);
 354 
 355         return 0;
 356 }
 357 /* }}} */
 358 
 359 /**
 360  * used for fread($fp) and company on a fopen()ed phar file handle
 361  */
 362 static size_t phar_stream_read(php_stream *stream, char *buf, size_t count) /* {{{ */
 363 {
 364         phar_entry_data *data = (phar_entry_data *)stream->abstract;
 365         size_t got;
 366         phar_entry_info *entry;
 367 
 368         if (data->internal_file->link) {
 369                 entry = phar_get_link_source(data->internal_file);
 370         } else {
 371                 entry = data->internal_file;
 372         }
 373 
 374         if (entry->is_deleted) {
 375                 stream->eof = 1;
 376                 return 0;
 377         }
 378 
 379         /* use our proxy position */
 380         php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
 381 
 382         got = php_stream_read(data->fp, buf, MIN(count, entry->uncompressed_filesize - data->position));
 383         data->position = php_stream_tell(data->fp) - data->zero;
 384         stream->eof = (data->position == (zend_off_t) entry->uncompressed_filesize);
 385 
 386         return got;
 387 }
 388 /* }}} */
 389 
 390 /**
 391  * Used for fseek($fp) on a phar file handle
 392  */
 393 static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
 394 {
 395         phar_entry_data *data = (phar_entry_data *)stream->abstract;
 396         phar_entry_info *entry;
 397         int res;
 398         zend_off_t temp;
 399 
 400         if (data->internal_file->link) {
 401                 entry = phar_get_link_source(data->internal_file);
 402         } else {
 403                 entry = data->internal_file;
 404         }
 405 
 406         switch (whence) {
 407                 case SEEK_END :
 408                         temp = data->zero + entry->uncompressed_filesize + offset;
 409                         break;
 410                 case SEEK_CUR :
 411                         temp = data->zero + data->position + offset;
 412                         break;
 413                 case SEEK_SET :
 414                         temp = data->zero + offset;
 415                         break;
 416                 default:
 417                         temp = 0;
 418         }
 419         if (temp > data->zero + (zend_off_t) entry->uncompressed_filesize) {
 420                 *newoffset = -1;
 421                 return -1;
 422         }
 423         if (temp < data->zero) {
 424                 *newoffset = -1;
 425                 return -1;
 426         }
 427         res = php_stream_seek(data->fp, temp, SEEK_SET);
 428         *newoffset = php_stream_tell(data->fp) - data->zero;
 429         data->position = *newoffset;
 430         return res;
 431 }
 432 /* }}} */
 433 
 434 /**
 435  * Used for writing to a phar file
 436  */
 437 static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
 438 {
 439         phar_entry_data *data = (phar_entry_data *) stream->abstract;
 440 
 441         php_stream_seek(data->fp, data->position, SEEK_SET);
 442         if (count != php_stream_write(data->fp, buf, count)) {
 443                 php_stream_wrapper_log_error(stream->wrapper, stream->flags, "phar error: Could not write %d characters to \"%s\" in phar \"%s\"", (int) count, data->internal_file->filename, data->phar->fname);
 444                 return 0;
 445         }
 446         data->position = php_stream_tell(data->fp);
 447         if (data->position > (zend_off_t)data->internal_file->uncompressed_filesize) {
 448                 data->internal_file->uncompressed_filesize = data->position;
 449         }
 450         data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize;
 451         data->internal_file->old_flags = data->internal_file->flags;
 452         data->internal_file->is_modified = 1;
 453         return count;
 454 }
 455 /* }}} */
 456 
 457 /**
 458  * Used to save work done on a writeable phar
 459  */
 460 static int phar_stream_flush(php_stream *stream) /* {{{ */
 461 {
 462         char *error;
 463         int ret;
 464         phar_entry_data *data = (phar_entry_data *) stream->abstract;
 465 
 466         if (data->internal_file->is_modified) {
 467                 data->internal_file->timestamp = time(0);
 468                 ret = phar_flush(data->phar, 0, 0, 0, &error);
 469                 if (error) {
 470                         php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS, "%s", error);
 471                         efree(error);
 472                 }
 473                 return ret;
 474         } else {
 475                 return EOF;
 476         }
 477 }
 478 /* }}} */
 479 
 480  /* {{{ phar_dostat */
 481 /**
 482  * stat an opened phar file handle stream, used by phar_stat()
 483  */
 484 void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, zend_bool is_temp_dir)
 485 {
 486         memset(ssb, 0, sizeof(php_stream_statbuf));
 487 
 488         if (!is_temp_dir && !data->is_dir) {
 489                 ssb->sb.st_size = data->uncompressed_filesize;
 490                 ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
 491                 ssb->sb.st_mode |= S_IFREG; /* regular file */
 492                 /* timestamp is just the timestamp when this was added to the phar */
 493 #ifdef NETWARE
 494                 ssb->sb.st_mtime.tv_sec = data->timestamp;
 495                 ssb->sb.st_atime.tv_sec = data->timestamp;
 496                 ssb->sb.st_ctime.tv_sec = data->timestamp;
 497 #else
 498                 ssb->sb.st_mtime = data->timestamp;
 499                 ssb->sb.st_atime = data->timestamp;
 500                 ssb->sb.st_ctime = data->timestamp;
 501 #endif
 502         } else if (!is_temp_dir && data->is_dir) {
 503                 ssb->sb.st_size = 0;
 504                 ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
 505                 ssb->sb.st_mode |= S_IFDIR; /* regular directory */
 506                 /* timestamp is just the timestamp when this was added to the phar */
 507 #ifdef NETWARE
 508                 ssb->sb.st_mtime.tv_sec = data->timestamp;
 509                 ssb->sb.st_atime.tv_sec = data->timestamp;
 510                 ssb->sb.st_ctime.tv_sec = data->timestamp;
 511 #else
 512                 ssb->sb.st_mtime = data->timestamp;
 513                 ssb->sb.st_atime = data->timestamp;
 514                 ssb->sb.st_ctime = data->timestamp;
 515 #endif
 516         } else {
 517                 ssb->sb.st_size = 0;
 518                 ssb->sb.st_mode = 0777;
 519                 ssb->sb.st_mode |= S_IFDIR; /* regular directory */
 520 #ifdef NETWARE
 521                 ssb->sb.st_mtime.tv_sec = phar->max_timestamp;
 522                 ssb->sb.st_atime.tv_sec = phar->max_timestamp;
 523                 ssb->sb.st_ctime.tv_sec = phar->max_timestamp;
 524 #else
 525                 ssb->sb.st_mtime = phar->max_timestamp;
 526                 ssb->sb.st_atime = phar->max_timestamp;
 527                 ssb->sb.st_ctime = phar->max_timestamp;
 528 #endif
 529         }
 530         if (!phar->is_writeable) {
 531                 ssb->sb.st_mode = (ssb->sb.st_mode & 0555) | (ssb->sb.st_mode & ~0777);
 532         }
 533 
 534         ssb->sb.st_nlink = 1;
 535         ssb->sb.st_rdev = -1;
 536         /* this is only for APC, so use /dev/null device - no chance of conflict there! */
 537         ssb->sb.st_dev = 0xc;
 538         /* generate unique inode number for alias/filename, so no phars will conflict */
 539         if (!is_temp_dir) {
 540                 ssb->sb.st_ino = data->inode;
 541         }
 542 #ifndef PHP_WIN32
 543         ssb->sb.st_blksize = -1;
 544         ssb->sb.st_blocks = -1;
 545 #endif
 546 }
 547 /* }}}*/
 548 
 549 /**
 550  * Stat an opened phar file handle
 551  */
 552 static int phar_stream_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
 553 {
 554         phar_entry_data *data = (phar_entry_data *)stream->abstract;
 555 
 556         /* If ssb is NULL then someone is misbehaving */
 557         if (!ssb) {
 558                 return -1;
 559         }
 560 
 561         phar_dostat(data->phar, data->internal_file, ssb, 0);
 562         return 0;
 563 }
 564 /* }}} */
 565 
 566 /**
 567  * Stream wrapper stat implementation of stat()
 568  */
 569 static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int flags,
 570                                   php_stream_statbuf *ssb, php_stream_context *context) /* {{{ */
 571 {
 572         php_url *resource = NULL;
 573         char *internal_file, *error;
 574         phar_archive_data *phar;
 575         phar_entry_info *entry;
 576         uint host_len;
 577         int internal_file_len;
 578 
 579         if ((resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
 580                 return FAILURE;
 581         }
 582 
 583         /* we must have at the very least phar://alias.phar/internalfile.php */
 584         if (!resource->scheme || !resource->host || !resource->path) {
 585                 php_url_free(resource);
 586                 return FAILURE;
 587         }
 588 
 589         if (strcasecmp("phar", resource->scheme)) {
 590                 php_url_free(resource);
 591                 return FAILURE;
 592         }
 593 
 594         host_len = strlen(resource->host);
 595         phar_request_initialize();
 596 
 597         internal_file = resource->path + 1; /* strip leading "/" */
 598         /* find the phar in our trusty global hash indexed by alias (host of phar://blah.phar/file.whatever) */
 599         if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, &error)) {
 600                 php_url_free(resource);
 601                 if (error) {
 602                         efree(error);
 603                 }
 604                 return FAILURE;
 605         }
 606         if (error) {
 607                 efree(error);
 608         }
 609         if (*internal_file == '\0') {
 610                 /* root directory requested */
 611                 phar_dostat(phar, NULL, ssb, 1);
 612                 php_url_free(resource);
 613                 return SUCCESS;
 614         }
 615         if (!phar->manifest.u.flags) {
 616                 php_url_free(resource);
 617                 return FAILURE;
 618         }
 619         internal_file_len = strlen(internal_file);
 620         /* search through the manifest of files, and if we have an exact match, it's a file */
 621         if (NULL != (entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len))) {
 622                 phar_dostat(phar, entry, ssb, 0);
 623                 php_url_free(resource);
 624                 return SUCCESS;
 625         }
 626         if (zend_hash_str_exists(&(phar->virtual_dirs), internal_file, internal_file_len)) {
 627                 phar_dostat(phar, NULL, ssb, 1);
 628                 php_url_free(resource);
 629                 return SUCCESS;
 630         }
 631         /* check for mounted directories */
 632         if (phar->mounted_dirs.u.flags && zend_hash_num_elements(&phar->mounted_dirs)) {
 633                 zend_string *str_key;
 634 
 635                 ZEND_HASH_FOREACH_STR_KEY(&phar->mounted_dirs, str_key) {
 636                         if ((int)ZSTR_LEN(str_key) >= internal_file_len || strncmp(ZSTR_VAL(str_key), internal_file, ZSTR_LEN(str_key))) {
 637                                 continue;
 638                         } else {
 639                                 char *test;
 640                                 int test_len;
 641                                 php_stream_statbuf ssbi;
 642 
 643                                 if (NULL == (entry = zend_hash_find_ptr(&phar->manifest, str_key))) {
 644                                         goto free_resource;
 645                                 }
 646                                 if (!entry->tmp || !entry->is_mounted) {
 647                                         goto free_resource;
 648                                 }
 649                                 test_len = spprintf(&test, MAXPATHLEN, "%s%s", entry->tmp, internal_file + ZSTR_LEN(str_key));
 650                                 if (SUCCESS != php_stream_stat_path(test, &ssbi)) {
 651                                         efree(test);
 652                                         continue;
 653                                 }
 654                                 /* mount the file/directory just in time */
 655                                 if (SUCCESS != phar_mount_entry(phar, test, test_len, internal_file, internal_file_len)) {
 656                                         efree(test);
 657                                         goto free_resource;
 658                                 }
 659                                 efree(test);
 660                                 if (NULL == (entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len))) {
 661                                         goto free_resource;
 662                                 }
 663                                 phar_dostat(phar, entry, ssb, 0);
 664                                 php_url_free(resource);
 665                                 return SUCCESS;
 666                         }
 667                 } ZEND_HASH_FOREACH_END();
 668         }
 669 free_resource:
 670         php_url_free(resource);
 671         return FAILURE;
 672 }
 673 /* }}} */
 674 
 675 /**
 676  * Unlink a file within a phar archive
 677  */
 678 static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) /* {{{ */
 679 {
 680         php_url *resource;
 681         char *internal_file, *error;
 682         int internal_file_len;
 683         phar_entry_data *idata;
 684         phar_archive_data *pphar;
 685         uint host_len;
 686 
 687         if ((resource = phar_parse_url(wrapper, url, "rb", options)) == NULL) {
 688                 php_stream_wrapper_log_error(wrapper, options, "phar error: unlink failed");
 689                 return 0;
 690         }
 691 
 692         /* we must have at the very least phar://alias.phar/internalfile.php */
 693         if (!resource->scheme || !resource->host || !resource->path) {
 694                 php_url_free(resource);
 695                 php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
 696                 return 0;
 697         }
 698 
 699         if (strcasecmp("phar", resource->scheme)) {
 700                 php_url_free(resource);
 701                 php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
 702                 return 0;
 703         }
 704 
 705         host_len = strlen(resource->host);
 706         phar_request_initialize();
 707 
 708         pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), resource->host, host_len);
 709         if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
 710                 php_url_free(resource);
 711                 php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
 712                 return 0;
 713         }
 714 
 715         /* need to copy to strip leading "/", will get touched again */
 716         internal_file = estrdup(resource->path + 1);
 717         internal_file_len = strlen(internal_file);
 718         if (FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, internal_file_len, "r", 0, &error, 1)) {
 719                 /* constraints of fp refcount were not met */
 720                 if (error) {
 721                         php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed: %s", url, error);
 722                         efree(error);
 723                 } else {
 724                         php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed, file does not exist", url);
 725                 }
 726                 efree(internal_file);
 727                 php_url_free(resource);
 728                 return 0;
 729         }
 730         if (error) {
 731                 efree(error);
 732         }
 733         if (idata->internal_file->fp_refcount > 1) {
 734                 /* more than just our fp resource is open for this file */
 735                 php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, resource->host);
 736                 efree(internal_file);
 737                 php_url_free(resource);
 738                 phar_entry_delref(idata);
 739                 return 0;
 740         }
 741         php_url_free(resource);
 742         efree(internal_file);
 743         phar_entry_remove(idata, &error);
 744         if (error) {
 745                 php_stream_wrapper_log_error(wrapper, options, "%s", error);
 746                 efree(error);
 747         }
 748         return 1;
 749 }
 750 /* }}} */
 751 
 752 static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context) /* {{{ */
 753 {
 754         php_url *resource_from, *resource_to;
 755         char *error;
 756         phar_archive_data *phar, *pfrom, *pto;
 757         phar_entry_info *entry;
 758         uint host_len;
 759         int is_dir = 0;
 760         int is_modified = 0;
 761 
 762         error = NULL;
 763 
 764         if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
 765                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from);
 766                 return 0;
 767         }
 768         if (SUCCESS != phar_get_archive(&pfrom, resource_from->host, strlen(resource_from->host), NULL, 0, &error)) {
 769                 pfrom = NULL;
 770                 if (error) {
 771                         efree(error);
 772                 }
 773         }
 774         if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
 775                 php_url_free(resource_from);
 776                 php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
 777                 return 0;
 778         }
 779 
 780         if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) {
 781                 php_url_free(resource_from);
 782                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to);
 783                 return 0;
 784         }
 785         if (SUCCESS != phar_get_archive(&pto, resource_to->host, strlen(resource_to->host), NULL, 0, &error)) {
 786                 if (error) {
 787                         efree(error);
 788                 }
 789                 pto = NULL;
 790         }
 791         if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
 792                 php_url_free(resource_from);
 793                 php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
 794                 return 0;
 795         }
 796 
 797         if (strcmp(resource_from->host, resource_to->host)) {
 798                 php_url_free(resource_from);
 799                 php_url_free(resource_to);
 800                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to);
 801                 return 0;
 802         }
 803 
 804         /* we must have at the very least phar://alias.phar/internalfile.php */
 805         if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
 806                 php_url_free(resource_from);
 807                 php_url_free(resource_to);
 808                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from);
 809                 return 0;
 810         }
 811 
 812         if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
 813                 php_url_free(resource_from);
 814                 php_url_free(resource_to);
 815                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
 816                 return 0;
 817         }
 818 
 819         if (strcasecmp("phar", resource_from->scheme)) {
 820                 php_url_free(resource_from);
 821                 php_url_free(resource_to);
 822                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from);
 823                 return 0;
 824         }
 825 
 826         if (strcasecmp("phar", resource_to->scheme)) {
 827                 php_url_free(resource_from);
 828                 php_url_free(resource_to);
 829                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to);
 830                 return 0;
 831         }
 832 
 833         host_len = strlen(resource_from->host);
 834 
 835         if (SUCCESS != phar_get_archive(&phar, resource_from->host, host_len, NULL, 0, &error)) {
 836                 php_url_free(resource_from);
 837                 php_url_free(resource_to);
 838                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 839                 efree(error);
 840                 return 0;
 841         }
 842 
 843         if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) {
 844                 php_url_free(resource_from);
 845                 php_url_free(resource_to);
 846                 php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to);
 847                 return 0;
 848         }
 849 
 850         if (NULL != (entry = zend_hash_str_find_ptr(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1))) {
 851                 phar_entry_info new, *source;
 852 
 853                 /* perform rename magic */
 854                 if (entry->is_deleted) {
 855                         php_url_free(resource_from);
 856                         php_url_free(resource_to);
 857                         php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to);
 858                         return 0;
 859                 }
 860                 /* transfer all data over to the new entry */
 861                 memcpy((void *) &new, (void *) entry, sizeof(phar_entry_info));
 862                 /* mark the old one for deletion */
 863                 entry->is_deleted = 1;
 864                 entry->fp = NULL;
 865                 ZVAL_UNDEF(&entry->metadata);
 866                 entry->link = entry->tmp = NULL;
 867                 source = entry;
 868 
 869                 /* add to the manifest, and then store the pointer to the new guy in entry */
 870                 entry = zend_hash_str_add_mem(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info));
 871 
 872                 entry->filename = estrdup(resource_to->path+1);
 873                 if (FAILURE == phar_copy_entry_fp(source, entry, &error)) {
 874                         php_url_free(resource_from);
 875                         php_url_free(resource_to);
 876                         php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 877                         efree(error);
 878                         zend_hash_str_del(&(phar->manifest), entry->filename, strlen(entry->filename));
 879                         return 0;
 880                 }
 881                 is_modified = 1;
 882                 entry->is_modified = 1;
 883                 entry->filename_len = strlen(entry->filename);
 884                 is_dir = entry->is_dir;
 885         } else {
 886                 is_dir = zend_hash_str_exists(&(phar->virtual_dirs), resource_from->path+1, strlen(resource_from->path)-1);
 887                 if (!is_dir) {
 888                         /* file does not exist */
 889                         php_url_free(resource_from);
 890                         php_url_free(resource_to);
 891                         php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to);
 892                         return 0;
 893 
 894                 }
 895         }
 896 
 897         /* Rename directory. Update all nested paths */
 898         if (is_dir) {
 899                 Bucket *b;
 900                 zend_string *str_key;
 901                 zend_string *new_str_key;
 902                 uint from_len = strlen(resource_from->path+1);
 903                 uint to_len = strlen(resource_to->path+1);
 904 
 905                 ZEND_HASH_FOREACH_BUCKET(&phar->manifest, b) {
 906                         str_key = b->key;
 907                         entry = Z_PTR(b->val);
 908                         if (!entry->is_deleted &&
 909                                 ZSTR_LEN(str_key) > from_len &&
 910                                 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
 911                                 IS_SLASH(ZSTR_VAL(str_key)[from_len])) {
 912 
 913                                 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
 914                                 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
 915                                 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
 916                                 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
 917 
 918                                 is_modified = 1;
 919                                 entry->is_modified = 1;
 920                                 efree(entry->filename);
 921                                 // TODO: avoid reallocation (make entry->filename zend_string*)
 922                                 entry->filename = estrndup(ZSTR_VAL(new_str_key), ZSTR_LEN(new_str_key));
 923                                 entry->filename_len = ZSTR_LEN(new_str_key);
 924 
 925                                 zend_string_release(str_key);
 926                                 b->h = zend_string_hash_val(new_str_key);
 927                                 b->key = new_str_key;
 928                         }
 929                 } ZEND_HASH_FOREACH_END();
 930                 zend_hash_rehash(&phar->manifest);
 931 
 932                 ZEND_HASH_FOREACH_BUCKET(&phar->virtual_dirs, b) {
 933                         str_key = b->key;
 934                         if (ZSTR_LEN(str_key) >= from_len &&
 935                                 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
 936                                 (ZSTR_LEN(str_key) == from_len || IS_SLASH(ZSTR_VAL(str_key)[from_len]))) {
 937 
 938                                 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
 939                                 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
 940                                 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
 941                                 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
 942 
 943                                 zend_string_release(str_key);
 944                                 b->h = zend_string_hash_val(new_str_key);
 945                                 b->key = new_str_key;
 946                         }
 947                 } ZEND_HASH_FOREACH_END();
 948                 zend_hash_rehash(&phar->virtual_dirs);
 949 
 950                 ZEND_HASH_FOREACH_BUCKET(&phar->mounted_dirs, b) {
 951                         str_key = b->key;
 952                         if (ZSTR_LEN(str_key) >= from_len &&
 953                                 memcmp(ZSTR_VAL(str_key), resource_from->path+1, from_len) == 0 &&
 954                                 (ZSTR_LEN(str_key) == from_len || IS_SLASH(ZSTR_VAL(str_key)[from_len]))) {
 955 
 956                                 new_str_key = zend_string_alloc(ZSTR_LEN(str_key) + to_len - from_len, 0);
 957                                 memcpy(ZSTR_VAL(new_str_key), resource_to->path + 1, to_len);
 958                                 memcpy(ZSTR_VAL(new_str_key) + to_len, ZSTR_VAL(str_key) + from_len, ZSTR_LEN(str_key) - from_len);
 959                                 ZSTR_VAL(new_str_key)[ZSTR_LEN(new_str_key)] = 0;
 960 
 961                                 zend_string_release(str_key);
 962                                 b->h = zend_string_hash_val(new_str_key);
 963                                 b->key = new_str_key;
 964                         }
 965                 } ZEND_HASH_FOREACH_END();
 966                 zend_hash_rehash(&phar->mounted_dirs);
 967         }
 968 
 969         if (is_modified) {
 970                 phar_flush(phar, 0, 0, 0, &error);
 971                 if (error) {
 972                         php_url_free(resource_from);
 973                         php_url_free(resource_to);
 974                         php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 975                         efree(error);
 976                         return 0;
 977                 }
 978         }
 979 
 980         php_url_free(resource_from);
 981         php_url_free(resource_to);
 982 
 983         return 1;
 984 }
 985 /* }}} */
 986 
 987 /*
 988  * Local variables:
 989  * tab-width: 4
 990  * c-basic-offset: 4
 991  * End:
 992  * vim600: noet sw=4 ts=4 fdm=marker
 993  * vim<600: noet sw=4 ts=4
 994  */

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