1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 5 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2013 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: Wez Furlong <wez@thebrainroom.com> | 16 | Borrowed code from: | 17 | Rasmus Lerdorf <rasmus@lerdorf.on.ca> | 18 | Jim Winstead <jimw@php.net> | 19 +----------------------------------------------------------------------+ 20 */ 21 22/* $Id$ */ 23 24#define _GNU_SOURCE 25#include "php.h" 26#include "php_globals.h" 27#include "php_network.h" 28#include "php_open_temporary_file.h" 29#include "ext/standard/file.h" 30#include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */ 31#include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */ 32#include <stddef.h> 33#include <fcntl.h> 34#include "php_streams_int.h" 35 36/* {{{ resource and registration code */ 37/* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */ 38static HashTable url_stream_wrappers_hash; 39static int le_stream = FAILURE; /* true global */ 40static int le_pstream = FAILURE; /* true global */ 41static int le_stream_filter = FAILURE; /* true global */ 42 43PHPAPI int php_file_le_stream(void) 44{ 45 return le_stream; 46} 47 48PHPAPI int php_file_le_pstream(void) 49{ 50 return le_pstream; 51} 52 53PHPAPI int php_file_le_stream_filter(void) 54{ 55 return le_stream_filter; 56} 57 58PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D) 59{ 60 return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash); 61} 62 63PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void) 64{ 65 return &url_stream_wrappers_hash; 66} 67 68static int _php_stream_release_context(zend_rsrc_list_entry *le, void *pContext TSRMLS_DC) 69{ 70 if (le->ptr == pContext) { 71 return --le->refcount == 0; 72 } 73 return 0; 74} 75 76static int forget_persistent_resource_id_numbers(zend_rsrc_list_entry *rsrc TSRMLS_DC) 77{ 78 php_stream *stream; 79 80 if (Z_TYPE_P(rsrc) != le_pstream) { 81 return 0; 82 } 83 84 stream = (php_stream*)rsrc->ptr; 85 86#if STREAM_DEBUG 87fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream); 88#endif 89 90 stream->rsrc_id = FAILURE; 91 92 if (stream->context) { 93 zend_hash_apply_with_argument(&EG(regular_list), 94 (apply_func_arg_t) _php_stream_release_context, 95 stream->context TSRMLS_CC); 96 stream->context = NULL; 97 } 98 99 return 0; 100} 101 102PHP_RSHUTDOWN_FUNCTION(streams) 103{ 104 zend_hash_apply(&EG(persistent_list), (apply_func_t)forget_persistent_resource_id_numbers TSRMLS_CC); 105 return SUCCESS; 106} 107 108PHPAPI php_stream *php_stream_encloses(php_stream *enclosing, php_stream *enclosed) 109{ 110 php_stream *orig = enclosed->enclosing_stream; 111 112 php_stream_auto_cleanup(enclosed); 113 enclosed->enclosing_stream = enclosing; 114 return orig; 115} 116 117PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC) 118{ 119 zend_rsrc_list_entry *le; 120 121 if (zend_hash_find(&EG(persistent_list), (char*)persistent_id, strlen(persistent_id)+1, (void*) &le) == SUCCESS) { 122 if (Z_TYPE_P(le) == le_pstream) { 123 if (stream) { 124 HashPosition pos; 125 zend_rsrc_list_entry *regentry; 126 ulong index = -1; /* intentional */ 127 128 /* see if this persistent resource already has been loaded to the 129 * regular list; allowing the same resource in several entries in the 130 * regular list causes trouble (see bug #54623) */ 131 zend_hash_internal_pointer_reset_ex(&EG(regular_list), &pos); 132 while (zend_hash_get_current_data_ex(&EG(regular_list), 133 (void **)®entry, &pos) == SUCCESS) { 134 if (regentry->ptr == le->ptr) { 135 zend_hash_get_current_key_ex(&EG(regular_list), NULL, NULL, 136 &index, 0, &pos); 137 break; 138 } 139 zend_hash_move_forward_ex(&EG(regular_list), &pos); 140 } 141 142 *stream = (php_stream*)le->ptr; 143 if (index == -1) { /* not found in regular list */ 144 le->refcount++; 145 (*stream)->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream); 146 } else { 147 regentry->refcount++; 148 (*stream)->rsrc_id = index; 149 } 150 } 151 return PHP_STREAM_PERSISTENT_SUCCESS; 152 } 153 return PHP_STREAM_PERSISTENT_FAILURE; 154 } 155 return PHP_STREAM_PERSISTENT_NOT_EXIST; 156} 157 158/* }}} */ 159 160static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper TSRMLS_DC) 161{ 162 zend_llist *list = NULL; 163 if (!FG(wrapper_errors)) { 164 return NULL; 165 } else { 166 zend_hash_find(FG(wrapper_errors), (const char*)&wrapper, 167 sizeof wrapper, (void**)&list); 168 return list; 169 } 170} 171 172/* {{{ wrapper error reporting */ 173void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC) 174{ 175 char *tmp = estrdup(path); 176 char *msg; 177 int free_msg = 0; 178 179 if (wrapper) { 180 zend_llist *err_list = php_get_wrapper_errors_list(wrapper TSRMLS_CC); 181 if (err_list) { 182 size_t l = 0; 183 int brlen; 184 int i; 185 int count = zend_llist_count(err_list); 186 const char *br; 187 const char **err_buf_p; 188 zend_llist_position pos; 189 190 if (PG(html_errors)) { 191 brlen = 7; 192 br = "<br />\n"; 193 } else { 194 brlen = 1; 195 br = "\n"; 196 } 197 198 for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0; 199 err_buf_p; 200 err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) { 201 l += strlen(*err_buf_p); 202 if (i < count - 1) { 203 l += brlen; 204 } 205 } 206 msg = emalloc(l + 1); 207 msg[0] = '\0'; 208 for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0; 209 err_buf_p; 210 err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) { 211 strcat(msg, *err_buf_p); 212 if (i < count - 1) { 213 strcat(msg, br); 214 } 215 } 216 217 free_msg = 1; 218 } else { 219 if (wrapper == &php_plain_files_wrapper) { 220 msg = strerror(errno); /* TODO: not ts on linux */ 221 } else { 222 msg = "operation failed"; 223 } 224 } 225 } else { 226 msg = "no suitable wrapper could be found"; 227 } 228 229 php_strip_url_passwd(tmp); 230 php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg); 231 efree(tmp); 232 if (free_msg) { 233 efree(msg); 234 } 235} 236 237void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) 238{ 239 if (wrapper && FG(wrapper_errors)) { 240 zend_hash_del(FG(wrapper_errors), (const char*)&wrapper, sizeof wrapper); 241 } 242} 243 244static void wrapper_error_dtor(void *error) 245{ 246 efree(*(char**)error); 247} 248 249PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...) 250{ 251 va_list args; 252 char *buffer = NULL; 253 254 va_start(args, fmt); 255 vspprintf(&buffer, 0, fmt, args); 256 va_end(args); 257 258 if (options & REPORT_ERRORS || wrapper == NULL) { 259 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer); 260 efree(buffer); 261 } else { 262 zend_llist *list = NULL; 263 if (!FG(wrapper_errors)) { 264 ALLOC_HASHTABLE(FG(wrapper_errors)); 265 zend_hash_init(FG(wrapper_errors), 8, NULL, 266 (dtor_func_t)zend_llist_destroy, 0); 267 } else { 268 zend_hash_find(FG(wrapper_errors), (const char*)&wrapper, 269 sizeof wrapper, (void**)&list); 270 } 271 272 if (!list) { 273 zend_llist new_list; 274 zend_llist_init(&new_list, sizeof buffer, wrapper_error_dtor, 0); 275 zend_hash_update(FG(wrapper_errors), (const char*)&wrapper, 276 sizeof wrapper, &new_list, sizeof new_list, (void**)&list); 277 } 278 279 /* append to linked list */ 280 zend_llist_add_element(list, &buffer); 281 } 282} 283 284 285/* }}} */ 286 287/* allocate a new stream for a particular ops */ 288PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */ 289{ 290 php_stream *ret; 291 292 ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0); 293 294 memset(ret, 0, sizeof(php_stream)); 295 296 ret->readfilters.stream = ret; 297 ret->writefilters.stream = ret; 298 299#if STREAM_DEBUG 300fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id); 301#endif 302 303 ret->ops = ops; 304 ret->abstract = abstract; 305 ret->is_persistent = persistent_id ? 1 : 0; 306 ret->chunk_size = FG(def_chunk_size); 307 308#if ZEND_DEBUG 309 ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename; 310 ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno; 311#endif 312 313 if (FG(auto_detect_line_endings)) { 314 ret->flags |= PHP_STREAM_FLAG_DETECT_EOL; 315 } 316 317 if (persistent_id) { 318 zend_rsrc_list_entry le; 319 320 Z_TYPE(le) = le_pstream; 321 le.ptr = ret; 322 le.refcount = 0; 323 324 if (FAILURE == zend_hash_update(&EG(persistent_list), (char *)persistent_id, 325 strlen(persistent_id) + 1, 326 (void *)&le, sizeof(le), NULL)) { 327 328 pefree(ret, 1); 329 return NULL; 330 } 331 } 332 333 ret->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream); 334 strlcpy(ret->mode, mode, sizeof(ret->mode)); 335 336 ret->wrapper = NULL; 337 ret->wrapperthis = NULL; 338 ret->wrapperdata = NULL; 339 ret->stdiocast = NULL; 340 ret->orig_path = NULL; 341 ret->context = NULL; 342 ret->readbuf = NULL; 343 ret->enclosing_stream = NULL; 344 345 return ret; 346} 347/* }}} */ 348 349PHPAPI int _php_stream_free_enclosed(php_stream *stream_enclosed, int close_options TSRMLS_DC) /* {{{ */ 350{ 351 return _php_stream_free(stream_enclosed, 352 close_options | PHP_STREAM_FREE_IGNORE_ENCLOSING TSRMLS_CC); 353} 354/* }}} */ 355 356#if STREAM_DEBUG 357static const char *_php_stream_pretty_free_options(int close_options, char *out) 358{ 359 if (close_options & PHP_STREAM_FREE_CALL_DTOR) 360 strcat(out, "CALL_DTOR, "); 361 if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) 362 strcat(out, "RELEASE_STREAM, "); 363 if (close_options & PHP_STREAM_FREE_PRESERVE_HANDLE) 364 strcat(out, "PREVERSE_HANDLE, "); 365 if (close_options & PHP_STREAM_FREE_RSRC_DTOR) 366 strcat(out, "RSRC_DTOR, "); 367 if (close_options & PHP_STREAM_FREE_PERSISTENT) 368 strcat(out, "PERSISTENT, "); 369 if (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) 370 strcat(out, "IGNORE_ENCLOSING, "); 371 if (out[0] != '\0') 372 out[strlen(out) - 2] = '\0'; 373 return out; 374} 375#endif 376 377static int _php_stream_free_persistent(zend_rsrc_list_entry *le, void *pStream TSRMLS_DC) 378{ 379 return le->ptr == pStream; 380} 381 382 383PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */ 384{ 385 int ret = 1; 386 int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0; 387 int release_cast = 1; 388 php_stream_context *context = NULL; 389 390 /* on an resource list destruction, the context, another resource, may have 391 * already been freed (if it was created after the stream resource), so 392 * don't reference it */ 393 if (EG(active)) { 394 context = stream->context; 395 } 396 397 if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) { 398 preserve_handle = 1; 399 } 400 401#if STREAM_DEBUG 402 { 403 char out[200] = ""; 404 fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%s\n", 405 stream->ops->label, stream, stream->orig_path, stream->in_free, _php_stream_pretty_free_options(close_options, out)); 406 } 407 408#endif 409 410 if (stream->in_free) { 411 /* hopefully called recursively from the enclosing stream; the pointer was NULLed below */ 412 if ((stream->in_free == 1) && (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) && (stream->enclosing_stream == NULL)) { 413 close_options |= PHP_STREAM_FREE_RSRC_DTOR; /* restore flag */ 414 } else { 415 return 1; /* recursion protection */ 416 } 417 } 418 419 stream->in_free++; 420 421 /* force correct order on enclosing/enclosed stream destruction (only from resource 422 * destructor as in when reverse destroying the resource list) */ 423 if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && 424 !(close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) && 425 (close_options & (PHP_STREAM_FREE_CALL_DTOR | PHP_STREAM_FREE_RELEASE_STREAM)) && /* always? */ 426 (stream->enclosing_stream != NULL)) { 427 php_stream *enclosing_stream = stream->enclosing_stream; 428 stream->enclosing_stream = NULL; 429 /* we force PHP_STREAM_CALL_DTOR because that's from where the 430 * enclosing stream can free this stream. We remove rsrc_dtor because 431 * we want the enclosing stream to be deleted from the resource list */ 432 return _php_stream_free(enclosing_stream, 433 (close_options | PHP_STREAM_FREE_CALL_DTOR) & ~PHP_STREAM_FREE_RSRC_DTOR TSRMLS_CC); 434 } 435 436 /* if we are releasing the stream only (and preserving the underlying handle), 437 * we need to do things a little differently. 438 * We are only ever called like this when the stream is cast to a FILE* 439 * for include (or other similar) purposes. 440 * */ 441 if (preserve_handle) { 442 if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) { 443 /* If the stream was fopencookied, we must NOT touch anything 444 * here, as the cookied stream relies on it all. 445 * Instead, mark the stream as OK to auto-clean */ 446 php_stream_auto_cleanup(stream); 447 stream->in_free--; 448 return 0; 449 } 450 /* otherwise, make sure that we don't close the FILE* from a cast */ 451 release_cast = 0; 452 } 453 454#if STREAM_DEBUG 455fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n", 456 stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast, 457 (close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0); 458#endif 459 460 /* make sure everything is saved */ 461 _php_stream_flush(stream, 1 TSRMLS_CC); 462 463 /* If not called from the resource dtor, remove the stream from the resource list. */ 464 if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0) { 465 /* zend_list_delete actually only decreases the refcount; if we're 466 * releasing the stream, we want to actually delete the resource from 467 * the resource list, otherwise the resource will point to invalid memory. 468 * In any case, let's always completely delete it from the resource list, 469 * not only when PHP_STREAM_FREE_RELEASE_STREAM is set */ 470 while (zend_list_delete(stream->rsrc_id) == SUCCESS) {} 471 } 472 473 if (close_options & PHP_STREAM_FREE_CALL_DTOR) { 474 if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) { 475 /* calling fclose on an fopencookied stream will ultimately 476 call this very same function. If we were called via fclose, 477 the cookie_closer unsets the fclose_stdiocast flags, so 478 we can be sure that we only reach here when PHP code calls 479 php_stream_free. 480 Lets let the cookie code clean it all up. 481 */ 482 stream->in_free = 0; 483 return fclose(stream->stdiocast); 484 } 485 486 ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC); 487 stream->abstract = NULL; 488 489 /* tidy up any FILE* that might have been fdopened */ 490 if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) { 491 fclose(stream->stdiocast); 492 stream->stdiocast = NULL; 493 stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE; 494 } 495 } 496 497 if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) { 498 while (stream->readfilters.head) { 499 php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC); 500 } 501 while (stream->writefilters.head) { 502 php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC); 503 } 504 505 if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) { 506 stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC); 507 stream->wrapper = NULL; 508 } 509 510 if (stream->wrapperdata) { 511 zval_ptr_dtor(&stream->wrapperdata); 512 stream->wrapperdata = NULL; 513 } 514 515 if (stream->readbuf) { 516 pefree(stream->readbuf, stream->is_persistent); 517 stream->readbuf = NULL; 518 } 519 520 if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) { 521 /* we don't work with *stream but need its value for comparison */ 522 zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC); 523 } 524#if ZEND_DEBUG 525 if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) { 526 /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it 527 * as leaked; it will log a warning, but lets help it out and display what kind 528 * of stream it was. */ 529 char *leakinfo; 530 spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path); 531 532 if (stream->orig_path) { 533 pefree(stream->orig_path, stream->is_persistent); 534 stream->orig_path = NULL; 535 } 536 537# if defined(PHP_WIN32) 538 OutputDebugString(leakinfo); 539# else 540 fprintf(stderr, "%s", leakinfo); 541# endif 542 efree(leakinfo); 543 } else { 544 if (stream->orig_path) { 545 pefree(stream->orig_path, stream->is_persistent); 546 stream->orig_path = NULL; 547 } 548 549 pefree(stream, stream->is_persistent); 550 } 551#else 552 if (stream->orig_path) { 553 pefree(stream->orig_path, stream->is_persistent); 554 stream->orig_path = NULL; 555 } 556 557 pefree(stream, stream->is_persistent); 558#endif 559 } 560 561 if (context) { 562 zend_list_delete(context->rsrc_id); 563 } 564 565 return ret; 566} 567/* }}} */ 568 569/* {{{ generic stream operations */ 570 571static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC) 572{ 573 /* allocate/fill the buffer */ 574 575 if (stream->readfilters.head) { 576 char *chunk_buf; 577 int err_flag = 0; 578 php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL }; 579 php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap; 580 581 /* Invalidate the existing cache, otherwise reads can fail, see note in 582 main/streams/filter.c::_php_stream_filter_append */ 583 stream->writepos = stream->readpos = 0; 584 585 /* allocate a buffer for reading chunks */ 586 chunk_buf = emalloc(stream->chunk_size); 587 588 while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) { 589 size_t justread = 0; 590 int flags; 591 php_stream_bucket *bucket; 592 php_stream_filter_status_t status = PSFS_ERR_FATAL; 593 php_stream_filter *filter; 594 595 /* read a chunk into a bucket */ 596 justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC); 597 if (justread && justread != (size_t)-1) { 598 bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC); 599 600 /* after this call, bucket is owned by the brigade */ 601 php_stream_bucket_append(brig_inp, bucket TSRMLS_CC); 602 603 flags = PSFS_FLAG_NORMAL; 604 } else { 605 flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC; 606 } 607 608 /* wind the handle... */ 609 for (filter = stream->readfilters.head; filter; filter = filter->next) { 610 status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC); 611 612 if (status != PSFS_PASS_ON) { 613 break; 614 } 615 616 /* brig_out becomes brig_in. 617 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets 618 * to its own brigade */ 619 brig_swap = brig_inp; 620 brig_inp = brig_outp; 621 brig_outp = brig_swap; 622 memset(brig_outp, 0, sizeof(*brig_outp)); 623 } 624 625 switch (status) { 626 case PSFS_PASS_ON: 627 /* we get here when the last filter in the chain has data to pass on. 628 * in this situation, we are passing the brig_in brigade into the 629 * stream read buffer */ 630 while (brig_inp->head) { 631 bucket = brig_inp->head; 632 /* grow buffer to hold this bucket 633 * TODO: this can fail for persistent streams */ 634 if (stream->readbuflen - stream->writepos < bucket->buflen) { 635 stream->readbuflen += bucket->buflen; 636 stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, 637 stream->is_persistent); 638 } 639 memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen); 640 stream->writepos += bucket->buflen; 641 642 php_stream_bucket_unlink(bucket TSRMLS_CC); 643 php_stream_bucket_delref(bucket TSRMLS_CC); 644 } 645 break; 646 647 case PSFS_FEED_ME: 648 /* when a filter needs feeding, there is no brig_out to deal with. 649 * we simply continue the loop; if the caller needs more data, 650 * we will read again, otherwise out job is done here */ 651 if (justread == 0) { 652 /* there is no data */ 653 err_flag = 1; 654 break; 655 } 656 continue; 657 658 case PSFS_ERR_FATAL: 659 /* some fatal error. Theoretically, the stream is borked, so all 660 * further reads should fail. */ 661 err_flag = 1; 662 break; 663 } 664 665 if (justread == 0 || justread == (size_t)-1) { 666 break; 667 } 668 } 669 670 efree(chunk_buf); 671 672 } else { 673 /* is there enough data in the buffer ? */ 674 if (stream->writepos - stream->readpos < (off_t)size) { 675 size_t justread = 0; 676 677 /* reduce buffer memory consumption if possible, to avoid a realloc */ 678 if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) { 679 memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos); 680 stream->writepos -= stream->readpos; 681 stream->readpos = 0; 682 } 683 684 /* grow the buffer if required 685 * TODO: this can fail for persistent streams */ 686 if (stream->readbuflen - stream->writepos < stream->chunk_size) { 687 stream->readbuflen += stream->chunk_size; 688 stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, 689 stream->is_persistent); 690 } 691 692 justread = stream->ops->read(stream, stream->readbuf + stream->writepos, 693 stream->readbuflen - stream->writepos 694 TSRMLS_CC); 695 696 if (justread != (size_t)-1) { 697 stream->writepos += justread; 698 } 699 } 700 } 701} 702 703PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC) 704{ 705 size_t toread = 0, didread = 0; 706 707 while (size > 0) { 708 709 /* take from the read buffer first. 710 * It is possible that a buffered stream was switched to non-buffered, so we 711 * drain the remainder of the buffer before using the "raw" read mode for 712 * the excess */ 713 if (stream->writepos > stream->readpos) { 714 715 toread = stream->writepos - stream->readpos; 716 if (toread > size) { 717 toread = size; 718 } 719 720 memcpy(buf, stream->readbuf + stream->readpos, toread); 721 stream->readpos += toread; 722 size -= toread; 723 buf += toread; 724 didread += toread; 725 } 726 727 /* ignore eof here; the underlying state might have changed */ 728 if (size == 0) { 729 break; 730 } 731 732 if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) { 733 toread = stream->ops->read(stream, buf, size TSRMLS_CC); 734 } else { 735 php_stream_fill_read_buffer(stream, size TSRMLS_CC); 736 737 toread = stream->writepos - stream->readpos; 738 if (toread > size) { 739 toread = size; 740 } 741 742 if (toread > 0) { 743 memcpy(buf, stream->readbuf + stream->readpos, toread); 744 stream->readpos += toread; 745 } 746 } 747 if (toread > 0) { 748 didread += toread; 749 buf += toread; 750 size -= toread; 751 } else { 752 /* EOF, or temporary end of data (for non-blocking mode). */ 753 break; 754 } 755 756 /* just break anyway, to avoid greedy read */ 757 if (stream->wrapper != &php_plain_files_wrapper) { 758 break; 759 } 760 } 761 762 if (didread > 0) { 763 stream->position += didread; 764 } 765 766 return didread; 767} 768 769PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC) 770{ 771 /* if there is data in the buffer, it's not EOF */ 772 if (stream->writepos - stream->readpos > 0) { 773 return 0; 774 } 775 776 /* use the configured timeout when checking eof */ 777 if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR == 778 php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 779 0, NULL)) { 780 stream->eof = 1; 781 } 782 783 return stream->eof; 784} 785 786PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC) 787{ 788 unsigned char buf = c; 789 790 if (php_stream_write(stream, &buf, 1) > 0) { 791 return 1; 792 } 793 return EOF; 794} 795 796PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC) 797{ 798 char buf; 799 800 if (php_stream_read(stream, &buf, 1) > 0) { 801 return buf & 0xff; 802 } 803 return EOF; 804} 805 806PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC) 807{ 808 int len; 809 char newline[2] = "\n"; /* is this OK for Win? */ 810 len = strlen(buf); 811 812 if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) { 813 return 1; 814 } 815 return 0; 816} 817 818PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) 819{ 820 memset(ssb, 0, sizeof(*ssb)); 821 822 /* if the stream was wrapped, allow the wrapper to stat it */ 823 if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) { 824 return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC); 825 } 826 827 /* if the stream doesn't directly support stat-ing, return with failure. 828 * We could try and emulate this by casting to a FD and fstat-ing it, 829 * but since the fd might not represent the actual underlying content 830 * this would give bogus results. */ 831 if (stream->ops->stat == NULL) { 832 return -1; 833 } 834 835 return (stream->ops->stat)(stream, ssb TSRMLS_CC); 836} 837 838PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC) 839{ 840 size_t avail; 841 char *cr, *lf, *eol = NULL; 842 char *readptr; 843 844 if (!buf) { 845 readptr = stream->readbuf + stream->readpos; 846 avail = stream->writepos - stream->readpos; 847 } else { 848 readptr = buf; 849 avail = buf_len; 850 } 851 852 /* Look for EOL */ 853 if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) { 854 cr = memchr(readptr, '\r', avail); 855 lf = memchr(readptr, '\n', avail); 856 857 if (cr && lf != cr + 1 && !(lf && lf < cr)) { 858 /* mac */ 859 stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL; 860 stream->flags |= PHP_STREAM_FLAG_EOL_MAC; 861 eol = cr; 862 } else if ((cr && lf && cr == lf - 1) || (lf)) { 863 /* dos or unix endings */ 864 stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL; 865 eol = lf; 866 } 867 } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) { 868 eol = memchr(readptr, '\r', avail); 869 } else { 870 /* unix (and dos) line endings */ 871 eol = memchr(readptr, '\n', avail); 872 } 873 874 return eol; 875} 876 877/* If buf == NULL, the buffer will be allocated automatically and will be of an 878 * appropriate length to hold the line, regardless of the line length, memory 879 * permitting */ 880PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, 881 size_t *returned_len TSRMLS_DC) 882{ 883 size_t avail = 0; 884 size_t current_buf_size = 0; 885 size_t total_copied = 0; 886 int grow_mode = 0; 887 char *bufstart = buf; 888 889 if (buf == NULL) { 890 grow_mode = 1; 891 } else if (maxlen == 0) { 892 return NULL; 893 } 894 895 /* 896 * If the underlying stream operations block when no new data is readable, 897 * we need to take extra precautions. 898 * 899 * If there is buffered data available, we check for a EOL. If it exists, 900 * we pass the data immediately back to the caller. This saves a call 901 * to the read implementation and will not block where blocking 902 * is not necessary at all. 903 * 904 * If the stream buffer contains more data than the caller requested, 905 * we can also avoid that costly step and simply return that data. 906 */ 907 908 for (;;) { 909 avail = stream->writepos - stream->readpos; 910 911 if (avail > 0) { 912 size_t cpysz = 0; 913 char *readptr; 914 char *eol; 915 int done = 0; 916 917 readptr = stream->readbuf + stream->readpos; 918 eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC); 919 920 if (eol) { 921 cpysz = eol - readptr + 1; 922 done = 1; 923 } else { 924 cpysz = avail; 925 } 926 927 if (grow_mode) { 928 /* allow room for a NUL. If this realloc is really a realloc 929 * (ie: second time around), we get an extra byte. In most 930 * cases, with the default chunk size of 8K, we will only 931 * incur that overhead once. When people have lines longer 932 * than 8K, we waste 1 byte per additional 8K or so. 933 * That seems acceptable to me, to avoid making this code 934 * hard to follow */ 935 bufstart = erealloc(bufstart, current_buf_size + cpysz + 1); 936 current_buf_size += cpysz + 1; 937 buf = bufstart + total_copied; 938 } else { 939 if (cpysz >= maxlen - 1) { 940 cpysz = maxlen - 1; 941 done = 1; 942 } 943 } 944 945 memcpy(buf, readptr, cpysz); 946 947 stream->position += cpysz; 948 stream->readpos += cpysz; 949 buf += cpysz; 950 maxlen -= cpysz; 951 total_copied += cpysz; 952 953 if (done) { 954 break; 955 } 956 } else if (stream->eof) { 957 break; 958 } else { 959 /* XXX: Should be fine to always read chunk_size */ 960 size_t toread; 961 962 if (grow_mode) { 963 toread = stream->chunk_size; 964 } else { 965 toread = maxlen - 1; 966 if (toread > stream->chunk_size) { 967 toread = stream->chunk_size; 968 } 969 } 970 971 php_stream_fill_read_buffer(stream, toread TSRMLS_CC); 972 973 if (stream->writepos - stream->readpos == 0) { 974 break; 975 } 976 } 977 } 978 979 if (total_copied == 0) { 980 if (grow_mode) { 981 assert(bufstart == NULL); 982 } 983 return NULL; 984 } 985 986 buf[0] = '\0'; 987 if (returned_len) { 988 *returned_len = total_copied; 989 } 990 991 return bufstart; 992} 993 994#define STREAM_BUFFERED_AMOUNT(stream) \ 995 ((size_t)(((stream)->writepos) - (stream)->readpos)) 996 997static char *_php_stream_search_delim(php_stream *stream, 998 size_t maxlen, 999 size_t skiplen, 1000 char *delim, /* non-empty! */ 1001 size_t delim_len TSRMLS_DC) 1002{ 1003 size_t seek_len; 1004 1005 /* set the maximum number of bytes we're allowed to read from buffer */ 1006 seek_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen); 1007 if (seek_len <= skiplen) { 1008 return NULL; 1009 } 1010 1011 if (delim_len == 1) { 1012 return memchr(&stream->readbuf[stream->readpos + skiplen], 1013 delim[0], seek_len - skiplen); 1014 } else { 1015 return php_memnstr((char*)&stream->readbuf[stream->readpos + skiplen], 1016 delim, delim_len, 1017 (char*)&stream->readbuf[stream->readpos + seek_len]); 1018 } 1019} 1020 1021PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC) 1022{ 1023 char *ret_buf, /* returned buffer */ 1024 *found_delim = NULL; 1025 size_t buffered_len, 1026 tent_ret_len; /* tentative returned length */ 1027 int has_delim = delim_len > 0; 1028 1029 if (maxlen == 0) { 1030 return NULL; 1031 } 1032 1033 if (has_delim) { 1034 found_delim = _php_stream_search_delim( 1035 stream, maxlen, 0, delim, delim_len TSRMLS_CC); 1036 } 1037 1038 buffered_len = STREAM_BUFFERED_AMOUNT(stream); 1039 /* try to read up to maxlen length bytes while we don't find the delim */ 1040 while (!found_delim && buffered_len < maxlen) { 1041 size_t just_read, 1042 to_read_now; 1043 1044 to_read_now = MIN(maxlen - buffered_len, stream->chunk_size); 1045 1046 php_stream_fill_read_buffer(stream, buffered_len + to_read_now TSRMLS_CC); 1047 1048 just_read = STREAM_BUFFERED_AMOUNT(stream) - buffered_len; 1049 1050 /* Assume the stream is temporarily or permanently out of data */ 1051 if (just_read == 0) { 1052 break; 1053 } 1054 1055 if (has_delim) { 1056 /* search for delimiter, but skip buffered_len (the number of bytes 1057 * buffered before this loop iteration), as they have already been 1058 * searched for the delimiter. 1059 * The left part of the delimiter may still remain in the buffer, 1060 * so subtract up to <delim_len - 1> from buffered_len, which is 1061 * the ammount of data we skip on this search as an optimization 1062 */ 1063 found_delim = _php_stream_search_delim( 1064 stream, maxlen, 1065 buffered_len >= (delim_len - 1) 1066 ? buffered_len - (delim_len - 1) 1067 : 0, 1068 delim, delim_len TSRMLS_CC); 1069 if (found_delim) { 1070 break; 1071 } 1072 } 1073 buffered_len += just_read; 1074 } 1075 1076 if (has_delim && found_delim) { 1077 tent_ret_len = found_delim - (char*)&stream->readbuf[stream->readpos]; 1078 } else if (!has_delim && STREAM_BUFFERED_AMOUNT(stream) >= maxlen) { 1079 tent_ret_len = maxlen; 1080 } else { 1081 /* return with error if the delimiter string (if any) was not found, we 1082 * could not completely fill the read buffer with maxlen bytes and we 1083 * don't know we've reached end of file. Added with non-blocking streams 1084 * in mind, where this situation is frequent */ 1085 if (STREAM_BUFFERED_AMOUNT(stream) < maxlen && !stream->eof) { 1086 return NULL; 1087 } else if (STREAM_BUFFERED_AMOUNT(stream) == 0 && stream->eof) { 1088 /* refuse to return an empty string just because by accident 1089 * we knew of EOF in a read that returned no data */ 1090 return NULL; 1091 } else { 1092 tent_ret_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen); 1093 } 1094 } 1095 1096 ret_buf = emalloc(tent_ret_len + 1); 1097 /* php_stream_read will not call ops->read here because the necessary 1098 * data is guaranteedly buffered */ 1099 *returned_len = php_stream_read(stream, ret_buf, tent_ret_len); 1100 1101 if (found_delim) { 1102 stream->readpos += delim_len; 1103 stream->position += delim_len; 1104 } 1105 ret_buf[*returned_len] = '\0'; 1106 return ret_buf; 1107} 1108 1109/* Writes a buffer directly to a stream, using multiple of the chunk size */ 1110static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC) 1111{ 1112 size_t didwrite = 0, towrite, justwrote; 1113 1114 /* if we have a seekable stream we need to ensure that data is written at the 1115 * current stream->position. This means invalidating the read buffer and then 1116 * performing a low-level seek */ 1117 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) { 1118 stream->readpos = stream->writepos = 0; 1119 1120 stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC); 1121 } 1122 1123 1124 while (count > 0) { 1125 towrite = count; 1126 if (towrite > stream->chunk_size) 1127 towrite = stream->chunk_size; 1128 1129 justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC); 1130 1131 /* convert justwrote to an integer, since normally it is unsigned */ 1132 if ((int)justwrote > 0) { 1133 buf += justwrote; 1134 count -= justwrote; 1135 didwrite += justwrote; 1136 1137 /* Only screw with the buffer if we can seek, otherwise we lose data 1138 * buffered from fifos and sockets */ 1139 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { 1140 stream->position += justwrote; 1141 } 1142 } else { 1143 break; 1144 } 1145 } 1146 return didwrite; 1147 1148} 1149 1150/* push some data through the write filter chain. 1151 * buf may be NULL, if flags are set to indicate a flush. 1152 * This may trigger a real write to the stream. 1153 * Returns the number of bytes consumed from buf by the first filter in the chain. 1154 * */ 1155static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC) 1156{ 1157 size_t consumed = 0; 1158 php_stream_bucket *bucket; 1159 php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL }; 1160 php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap; 1161 php_stream_filter_status_t status = PSFS_ERR_FATAL; 1162 php_stream_filter *filter; 1163 1164 if (buf) { 1165 bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC); 1166 php_stream_bucket_append(&brig_in, bucket TSRMLS_CC); 1167 } 1168 1169 for (filter = stream->writefilters.head; filter; filter = filter->next) { 1170 /* for our return value, we are interested in the number of bytes consumed from 1171 * the first filter in the chain */ 1172 status = filter->fops->filter(stream, filter, brig_inp, brig_outp, 1173 filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC); 1174 1175 if (status != PSFS_PASS_ON) { 1176 break; 1177 } 1178 /* brig_out becomes brig_in. 1179 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets 1180 * to its own brigade */ 1181 brig_swap = brig_inp; 1182 brig_inp = brig_outp; 1183 brig_outp = brig_swap; 1184 memset(brig_outp, 0, sizeof(*brig_outp)); 1185 } 1186 1187 switch (status) { 1188 case PSFS_PASS_ON: 1189 /* filter chain generated some output; push it through to the 1190 * underlying stream */ 1191 while (brig_inp->head) { 1192 bucket = brig_inp->head; 1193 _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC); 1194 /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade 1195 * hanging around and try to write it later. 1196 * At the moment, we just drop it on the floor 1197 * */ 1198 1199 php_stream_bucket_unlink(bucket TSRMLS_CC); 1200 php_stream_bucket_delref(bucket TSRMLS_CC); 1201 } 1202 break; 1203 case PSFS_FEED_ME: 1204 /* need more data before we can push data through to the stream */ 1205 break; 1206 1207 case PSFS_ERR_FATAL: 1208 /* some fatal error. Theoretically, the stream is borked, so all 1209 * further writes should fail. */ 1210 break; 1211 } 1212 1213 return consumed; 1214} 1215 1216PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC) 1217{ 1218 int ret = 0; 1219 1220 if (stream->writefilters.head) { 1221 _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC TSRMLS_CC); 1222 } 1223 1224 if (stream->ops->flush) { 1225 ret = stream->ops->flush(stream TSRMLS_CC); 1226 } 1227 1228 return ret; 1229} 1230 1231PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) 1232{ 1233 if (buf == NULL || count == 0 || stream->ops->write == NULL) { 1234 return 0; 1235 } 1236 1237 if (stream->writefilters.head) { 1238 return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC); 1239 } else { 1240 return _php_stream_write_buffer(stream, buf, count TSRMLS_CC); 1241 } 1242} 1243 1244PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...) 1245{ 1246 size_t count; 1247 char *buf; 1248 va_list ap; 1249 1250 va_start(ap, fmt); 1251 count = vspprintf(&buf, 0, fmt, ap); 1252 va_end(ap); 1253 1254 if (!buf) { 1255 return 0; /* error condition */ 1256 } 1257 1258 count = php_stream_write(stream, buf, count); 1259 efree(buf); 1260 1261 return count; 1262} 1263 1264PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC) 1265{ 1266 return stream->position; 1267} 1268 1269PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC) 1270{ 1271 if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) { 1272 /* flush to commit data written to the fopencookie FILE* */ 1273 fflush(stream->stdiocast); 1274 } 1275 1276 /* handle the case where we are in the buffer */ 1277 if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) { 1278 switch(whence) { 1279 case SEEK_CUR: 1280 if (offset > 0 && offset <= stream->writepos - stream->readpos) { 1281 stream->readpos += offset; /* if offset = ..., then readpos = writepos */ 1282 stream->position += offset; 1283 stream->eof = 0; 1284 return 0; 1285 } 1286 break; 1287 case SEEK_SET: 1288 if (offset > stream->position && 1289 offset <= stream->position + stream->writepos - stream->readpos) { 1290 stream->readpos += offset - stream->position; 1291 stream->position = offset; 1292 stream->eof = 0; 1293 return 0; 1294 } 1295 break; 1296 } 1297 } 1298 1299 1300 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { 1301 int ret; 1302 1303 if (stream->writefilters.head) { 1304 _php_stream_flush(stream, 0 TSRMLS_CC); 1305 } 1306 1307 switch(whence) { 1308 case SEEK_CUR: 1309 offset = stream->position + offset; 1310 whence = SEEK_SET; 1311 break; 1312 } 1313 ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC); 1314 1315 if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) { 1316 if (ret == 0) { 1317 stream->eof = 0; 1318 } 1319 1320 /* invalidate the buffer contents */ 1321 stream->readpos = stream->writepos = 0; 1322 1323 return ret; 1324 } 1325 /* else the stream has decided that it can't support seeking after all; 1326 * fall through to attempt emulation */ 1327 } 1328 1329 /* emulate forward moving seeks with reads */ 1330 if (whence == SEEK_CUR && offset >= 0) { 1331 char tmp[1024]; 1332 size_t didread; 1333 while(offset > 0) { 1334 if ((didread = php_stream_read(stream, tmp, MIN(offset, sizeof(tmp)))) == 0) { 1335 return -1; 1336 } 1337 offset -= didread; 1338 } 1339 stream->eof = 0; 1340 return 0; 1341 } 1342 1343 php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking"); 1344 1345 return -1; 1346} 1347 1348PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) 1349{ 1350 int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL; 1351 1352 if (stream->ops->set_option) { 1353 ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC); 1354 } 1355 1356 if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) { 1357 switch(option) { 1358 case PHP_STREAM_OPTION_SET_CHUNK_SIZE: 1359 ret = stream->chunk_size; 1360 stream->chunk_size = value; 1361 return ret; 1362 1363 case PHP_STREAM_OPTION_READ_BUFFER: 1364 /* try to match the buffer mode as best we can */ 1365 if (value == PHP_STREAM_BUFFER_NONE) { 1366 stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; 1367 } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) { 1368 stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER; 1369 } 1370 ret = PHP_STREAM_OPTION_RETURN_OK; 1371 break; 1372 1373 default: 1374 ; 1375 } 1376 } 1377 1378 return ret; 1379} 1380 1381PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC) 1382{ 1383 return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize); 1384} 1385 1386PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC) 1387{ 1388 size_t bcount = 0; 1389 char buf[8192]; 1390 int b; 1391 1392 if (php_stream_mmap_possible(stream)) { 1393 char *p; 1394 size_t mapped; 1395 1396 p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); 1397 1398 if (p) { 1399 PHPWRITE(p, mapped); 1400 1401 php_stream_mmap_unmap_ex(stream, mapped); 1402 1403 return mapped; 1404 } 1405 } 1406 1407 while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) { 1408 PHPWRITE(buf, b); 1409 bcount += b; 1410 } 1411 1412 return bcount; 1413} 1414 1415 1416PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC) 1417{ 1418 size_t ret = 0; 1419 char *ptr; 1420 size_t len = 0, max_len; 1421 int step = CHUNK_SIZE; 1422 int min_room = CHUNK_SIZE / 4; 1423 php_stream_statbuf ssbuf; 1424 1425 if (maxlen == 0) { 1426 return 0; 1427 } 1428 1429 if (maxlen == PHP_STREAM_COPY_ALL) { 1430 maxlen = 0; 1431 } 1432 1433 if (maxlen > 0) { 1434 ptr = *buf = pemalloc_rel_orig(maxlen + 1, persistent); 1435 while ((len < maxlen) && !php_stream_eof(src)) { 1436 ret = php_stream_read(src, ptr, maxlen - len); 1437 if (!ret) { 1438 break; 1439 } 1440 len += ret; 1441 ptr += ret; 1442 } 1443 if (len) { 1444 *ptr = '\0'; 1445 } else { 1446 pefree(*buf, persistent); 1447 *buf = NULL; 1448 } 1449 return len; 1450 } 1451 1452 /* avoid many reallocs by allocating a good sized chunk to begin with, if 1453 * we can. Note that the stream may be filtered, in which case the stat 1454 * result may be inaccurate, as the filter may inflate or deflate the 1455 * number of bytes that we can read. In order to avoid an upsize followed 1456 * by a downsize of the buffer, overestimate by the step size (which is 1457 * 2K). */ 1458 if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) { 1459 max_len = ssbuf.sb.st_size + step; 1460 } else { 1461 max_len = step; 1462 } 1463 1464 ptr = *buf = pemalloc_rel_orig(max_len, persistent); 1465 1466 while((ret = php_stream_read(src, ptr, max_len - len))) { 1467 len += ret; 1468 if (len + min_room >= max_len) { 1469 *buf = perealloc_rel_orig(*buf, max_len + step, persistent); 1470 max_len += step; 1471 ptr = *buf + len; 1472 } else { 1473 ptr += ret; 1474 } 1475 } 1476 if (len) { 1477 *buf = perealloc_rel_orig(*buf, len + 1, persistent); 1478 (*buf)[len] = '\0'; 1479 } else { 1480 pefree(*buf, persistent); 1481 *buf = NULL; 1482 } 1483 return len; 1484} 1485 1486/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */ 1487PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC) 1488{ 1489 char buf[CHUNK_SIZE]; 1490 size_t readchunk; 1491 size_t haveread = 0; 1492 size_t didread, didwrite, towrite; 1493 size_t dummy; 1494 php_stream_statbuf ssbuf; 1495 1496 if (!len) { 1497 len = &dummy; 1498 } 1499 1500 if (maxlen == 0) { 1501 *len = 0; 1502 return SUCCESS; 1503 } 1504 1505 if (maxlen == PHP_STREAM_COPY_ALL) { 1506 maxlen = 0; 1507 } 1508 1509 if (php_stream_stat(src, &ssbuf) == 0) { 1510 if (ssbuf.sb.st_size == 0 1511#ifdef S_ISREG 1512 && S_ISREG(ssbuf.sb.st_mode) 1513#endif 1514 ) { 1515 *len = 0; 1516 return SUCCESS; 1517 } 1518 } 1519 1520 if (php_stream_mmap_possible(src)) { 1521 char *p; 1522 size_t mapped; 1523 1524 p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); 1525 1526 if (p) { 1527 didwrite = php_stream_write(dest, p, mapped); 1528 1529 php_stream_mmap_unmap_ex(src, mapped); 1530 1531 *len = didwrite; 1532 1533 /* we've got at least 1 byte to read 1534 * less than 1 is an error 1535 * AND read bytes match written */ 1536 if (mapped > 0 && mapped == didwrite) { 1537 return SUCCESS; 1538 } 1539 return FAILURE; 1540 } 1541 } 1542 1543 while(1) { 1544 readchunk = sizeof(buf); 1545 1546 if (maxlen && (maxlen - haveread) < readchunk) { 1547 readchunk = maxlen - haveread; 1548 } 1549 1550 didread = php_stream_read(src, buf, readchunk); 1551 1552 if (didread) { 1553 /* extra paranoid */ 1554 char *writeptr; 1555 1556 towrite = didread; 1557 writeptr = buf; 1558 haveread += didread; 1559 1560 while(towrite) { 1561 didwrite = php_stream_write(dest, writeptr, towrite); 1562 if (didwrite == 0) { 1563 *len = haveread - (didread - towrite); 1564 return FAILURE; 1565 } 1566 1567 towrite -= didwrite; 1568 writeptr += didwrite; 1569 } 1570 } else { 1571 break; 1572 } 1573 1574 if (maxlen - haveread == 0) { 1575 break; 1576 } 1577 } 1578 1579 *len = haveread; 1580 1581 /* we've got at least 1 byte to read. 1582 * less than 1 is an error */ 1583 1584 if (haveread > 0 || src->eof) { 1585 return SUCCESS; 1586 } 1587 return FAILURE; 1588} 1589 1590/* Returns the number of bytes moved. 1591 * Returns 1 when source len is 0. 1592 * Deprecated in favor of php_stream_copy_to_stream_ex() */ 1593ZEND_ATTRIBUTE_DEPRECATED 1594PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC) 1595{ 1596 size_t len; 1597 int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC); 1598 if (ret == SUCCESS && len == 0 && maxlen != 0) { 1599 return 1; 1600 } 1601 return len; 1602} 1603/* }}} */ 1604 1605/* {{{ wrapper init and registration */ 1606 1607static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) 1608{ 1609 php_stream *stream = (php_stream*)rsrc->ptr; 1610 /* set the return value for pclose */ 1611 FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR); 1612} 1613 1614static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) 1615{ 1616 php_stream *stream = (php_stream*)rsrc->ptr; 1617 FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR); 1618} 1619 1620void php_shutdown_stream_hashes(TSRMLS_D) 1621{ 1622 if (FG(stream_wrappers)) { 1623 zend_hash_destroy(FG(stream_wrappers)); 1624 efree(FG(stream_wrappers)); 1625 FG(stream_wrappers) = NULL; 1626 } 1627 1628 if (FG(stream_filters)) { 1629 zend_hash_destroy(FG(stream_filters)); 1630 efree(FG(stream_filters)); 1631 FG(stream_filters) = NULL; 1632 } 1633 1634 if (FG(wrapper_errors)) { 1635 zend_hash_destroy(FG(wrapper_errors)); 1636 efree(FG(wrapper_errors)); 1637 FG(wrapper_errors) = NULL; 1638 } 1639} 1640 1641int php_init_stream_wrappers(int module_number TSRMLS_DC) 1642{ 1643 le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number); 1644 le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number); 1645 1646 /* Filters are cleaned up by the streams they're attached to */ 1647 le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number); 1648 1649 return ( 1650 zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS 1651 && 1652 zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS 1653 && 1654 zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS 1655 && 1656 php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1657 && 1658 php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1659#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)) 1660 && 1661 php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1662 && 1663 php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1664#endif 1665 ) ? SUCCESS : FAILURE; 1666} 1667 1668int php_shutdown_stream_wrappers(int module_number TSRMLS_DC) 1669{ 1670 zend_hash_destroy(&url_stream_wrappers_hash); 1671 zend_hash_destroy(php_get_stream_filters_hash_global()); 1672 zend_hash_destroy(php_stream_xport_get_hash()); 1673 return SUCCESS; 1674} 1675 1676/* Validate protocol scheme names during registration 1677 * Must conform to /^[a-zA-Z0-9+.-]+$/ 1678 */ 1679static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len) 1680{ 1681 int i; 1682 1683 for(i = 0; i < protocol_len; i++) { 1684 if (!isalnum((int)protocol[i]) && 1685 protocol[i] != '+' && 1686 protocol[i] != '-' && 1687 protocol[i] != '.') { 1688 return FAILURE; 1689 } 1690 } 1691 1692 return SUCCESS; 1693} 1694 1695/* API for registering GLOBAL wrappers */ 1696PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC) 1697{ 1698 int protocol_len = strlen(protocol); 1699 1700 if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) { 1701 return FAILURE; 1702 } 1703 1704 return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL); 1705} 1706 1707PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC) 1708{ 1709 return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1); 1710} 1711 1712static void clone_wrapper_hash(TSRMLS_D) 1713{ 1714 php_stream_wrapper *tmp; 1715 1716 ALLOC_HASHTABLE(FG(stream_wrappers)); 1717 zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1); 1718 zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp)); 1719} 1720 1721/* API for registering VOLATILE wrappers */ 1722PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC) 1723{ 1724 int protocol_len = strlen(protocol); 1725 1726 if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) { 1727 return FAILURE; 1728 } 1729 1730 if (!FG(stream_wrappers)) { 1731 clone_wrapper_hash(TSRMLS_C); 1732 } 1733 1734 return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL); 1735} 1736 1737PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC) 1738{ 1739 if (!FG(stream_wrappers)) { 1740 clone_wrapper_hash(TSRMLS_C); 1741 } 1742 1743 return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1); 1744} 1745/* }}} */ 1746 1747/* {{{ php_stream_locate_url_wrapper */ 1748PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC) 1749{ 1750 HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash); 1751 php_stream_wrapper **wrapperpp = NULL; 1752 const char *p, *protocol = NULL; 1753 int n = 0; 1754 1755 if (path_for_open) { 1756 *path_for_open = (char*)path; 1757 } 1758 1759 if (options & IGNORE_URL) { 1760 return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper; 1761 } 1762 1763 for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { 1764 n++; 1765 } 1766 1767 if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) { 1768 protocol = path; 1769 } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) { 1770 /* BC with older php scripts and zlib wrapper */ 1771 protocol = "compress.zlib"; 1772 n = 13; 1773 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead"); 1774 } 1775 1776 if (protocol) { 1777 char *tmp = estrndup(protocol, n); 1778 if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) { 1779 php_strtolower(tmp, n); 1780 if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) { 1781 char wrapper_name[32]; 1782 1783 if (n >= sizeof(wrapper_name)) { 1784 n = sizeof(wrapper_name) - 1; 1785 } 1786 PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n); 1787 1788 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find the wrapper \"%s\" - did you forget to enable it when you configured PHP?", wrapper_name); 1789 1790 wrapperpp = NULL; 1791 protocol = NULL; 1792 } 1793 } 1794 efree(tmp); 1795 } 1796 /* TODO: curl based streams probably support file:// properly */ 1797 if (!protocol || !strncasecmp(protocol, "file", n)) { 1798 /* fall back on regular file access */ 1799 php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper; 1800 1801 if (protocol) { 1802 int localhost = 0; 1803 1804 if (!strncasecmp(path, "file://localhost/", 17)) { 1805 localhost = 1; 1806 } 1807 1808#ifdef PHP_WIN32 1809 if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':') { 1810#else 1811 if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') { 1812#endif 1813 if (options & REPORT_ERRORS) { 1814 php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path); 1815 } 1816 return NULL; 1817 } 1818 1819 if (path_for_open) { 1820 /* skip past protocol and :/, but handle windows correctly */ 1821 *path_for_open = (char*)path + n + 1; 1822 if (localhost == 1) { 1823 (*path_for_open) += 11; 1824 } 1825 while (*(++*path_for_open)=='/'); 1826#ifdef PHP_WIN32 1827 if (*(*path_for_open + 1) != ':') 1828#endif 1829 (*path_for_open)--; 1830 } 1831 } 1832 1833 if (options & STREAM_LOCATE_WRAPPERS_ONLY) { 1834 return NULL; 1835 } 1836 1837 if (FG(stream_wrappers)) { 1838 /* The file:// wrapper may have been disabled/overridden */ 1839 1840 if (wrapperpp) { 1841 /* It was found so go ahead and provide it */ 1842 return *wrapperpp; 1843 } 1844 1845 /* Check again, the original check might have not known the protocol name */ 1846 if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) { 1847 return *wrapperpp; 1848 } 1849 1850 if (options & REPORT_ERRORS) { 1851 php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration"); 1852 } 1853 return NULL; 1854 } 1855 1856 return plain_files_wrapper; 1857 } 1858 1859 if (wrapperpp && (*wrapperpp)->is_url && 1860 (options & STREAM_DISABLE_URL_PROTECTION) == 0 && 1861 (!PG(allow_url_fopen) || 1862 (((options & STREAM_OPEN_FOR_INCLUDE) || 1863 PG(in_user_include)) && !PG(allow_url_include)))) { 1864 if (options & REPORT_ERRORS) { 1865 /* protocol[n] probably isn't '\0' */ 1866 char *protocol_dup = estrndup(protocol, n); 1867 if (!PG(allow_url_fopen)) { 1868 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup); 1869 } else { 1870 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup); 1871 } 1872 efree(protocol_dup); 1873 } 1874 return NULL; 1875 } 1876 1877 return *wrapperpp; 1878} 1879/* }}} */ 1880 1881/* {{{ _php_stream_mkdir 1882 */ 1883PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC) 1884{ 1885 php_stream_wrapper *wrapper = NULL; 1886 1887 wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); 1888 if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) { 1889 return 0; 1890 } 1891 1892 return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC); 1893} 1894/* }}} */ 1895 1896/* {{{ _php_stream_rmdir 1897 */ 1898PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC) 1899{ 1900 php_stream_wrapper *wrapper = NULL; 1901 1902 wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); 1903 if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) { 1904 return 0; 1905 } 1906 1907 return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC); 1908} 1909/* }}} */ 1910 1911/* {{{ _php_stream_stat_path */ 1912PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) 1913{ 1914 php_stream_wrapper *wrapper = NULL; 1915 char *path_to_open = path; 1916 int ret; 1917 1918 /* Try to hit the cache first */ 1919 if (flags & PHP_STREAM_URL_STAT_LINK) { 1920 if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) { 1921 memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf)); 1922 return 0; 1923 } 1924 } else { 1925 if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) { 1926 memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf)); 1927 return 0; 1928 } 1929 } 1930 1931 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC); 1932 if (wrapper && wrapper->wops->url_stat) { 1933 ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC); 1934 if (ret == 0) { 1935 /* Drop into cache */ 1936 if (flags & PHP_STREAM_URL_STAT_LINK) { 1937 if (BG(CurrentLStatFile)) { 1938 efree(BG(CurrentLStatFile)); 1939 } 1940 BG(CurrentLStatFile) = estrdup(path); 1941 memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf)); 1942 } else { 1943 if (BG(CurrentStatFile)) { 1944 efree(BG(CurrentStatFile)); 1945 } 1946 BG(CurrentStatFile) = estrdup(path); 1947 memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf)); 1948 } 1949 } 1950 return ret; 1951 } 1952 return -1; 1953} 1954/* }}} */ 1955 1956/* {{{ php_stream_opendir */ 1957PHPAPI php_stream *_php_stream_opendir(char *path, int options, 1958 php_stream_context *context STREAMS_DC TSRMLS_DC) 1959{ 1960 php_stream *stream = NULL; 1961 php_stream_wrapper *wrapper = NULL; 1962 char *path_to_open; 1963 1964 if (!path || !*path) { 1965 return NULL; 1966 } 1967 1968 path_to_open = path; 1969 1970 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC); 1971 1972 if (wrapper && wrapper->wops->dir_opener) { 1973 stream = wrapper->wops->dir_opener(wrapper, 1974 path_to_open, "r", options ^ REPORT_ERRORS, NULL, 1975 context STREAMS_REL_CC TSRMLS_CC); 1976 1977 if (stream) { 1978 stream->wrapper = wrapper; 1979 stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR; 1980 } 1981 } else if (wrapper) { 1982 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented"); 1983 } 1984 if (stream == NULL && (options & REPORT_ERRORS)) { 1985 php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC); 1986 } 1987 php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); 1988 1989 return stream; 1990} 1991/* }}} */ 1992 1993/* {{{ _php_stream_readdir */ 1994PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC) 1995{ 1996 1997 if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) { 1998 return ent; 1999 } 2000 2001 return NULL; 2002} 2003/* }}} */ 2004 2005/* {{{ php_stream_open_wrapper_ex */ 2006PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options, 2007 char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) 2008{ 2009 php_stream *stream = NULL; 2010 php_stream_wrapper *wrapper = NULL; 2011 char *path_to_open; 2012 int persistent = options & STREAM_OPEN_PERSISTENT; 2013 char *resolved_path = NULL; 2014 char *copy_of_path = NULL; 2015 2016 if (opened_path) { 2017 *opened_path = NULL; 2018 } 2019 2020 if (!path || !*path) { 2021 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty"); 2022 return NULL; 2023 } 2024 2025 if (options & USE_PATH) { 2026 resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC); 2027 if (resolved_path) { 2028 path = resolved_path; 2029 /* we've found this file, don't re-check include_path or run realpath */ 2030 options |= STREAM_ASSUME_REALPATH; 2031 options &= ~USE_PATH; 2032 } 2033 } 2034 2035 path_to_open = path; 2036 2037 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC); 2038 if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) { 2039 php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs"); 2040 if (resolved_path) { 2041 efree(resolved_path); 2042 } 2043 return NULL; 2044 } 2045 2046 if (wrapper) { 2047 if (!wrapper->wops->stream_opener) { 2048 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, 2049 "wrapper does not support stream open"); 2050 } else { 2051 stream = wrapper->wops->stream_opener(wrapper, 2052 path_to_open, mode, options ^ REPORT_ERRORS, 2053 opened_path, context STREAMS_REL_CC TSRMLS_CC); 2054 } 2055 2056 /* if the caller asked for a persistent stream but the wrapper did not 2057 * return one, force an error here */ 2058 if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) { 2059 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, 2060 "wrapper does not support persistent streams"); 2061 php_stream_close(stream); 2062 stream = NULL; 2063 } 2064 2065 if (stream) { 2066 stream->wrapper = wrapper; 2067 } 2068 } 2069 2070 if (stream) { 2071 if (opened_path && !*opened_path && resolved_path) { 2072 *opened_path = resolved_path; 2073 resolved_path = NULL; 2074 } 2075 if (stream->orig_path) { 2076 pefree(stream->orig_path, persistent); 2077 } 2078 copy_of_path = pestrdup(path, persistent); 2079 stream->orig_path = copy_of_path; 2080#if ZEND_DEBUG 2081 stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename; 2082 stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno; 2083#endif 2084 } 2085 2086 if (stream != NULL && (options & STREAM_MUST_SEEK)) { 2087 php_stream *newstream; 2088 2089 switch(php_stream_make_seekable_rel(stream, &newstream, 2090 (options & STREAM_WILL_CAST) 2091 ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) { 2092 case PHP_STREAM_UNCHANGED: 2093 if (resolved_path) { 2094 efree(resolved_path); 2095 } 2096 return stream; 2097 case PHP_STREAM_RELEASED: 2098 if (newstream->orig_path) { 2099 pefree(newstream->orig_path, persistent); 2100 } 2101 newstream->orig_path = pestrdup(path, persistent); 2102 if (resolved_path) { 2103 efree(resolved_path); 2104 } 2105 return newstream; 2106 default: 2107 php_stream_close(stream); 2108 stream = NULL; 2109 if (options & REPORT_ERRORS) { 2110 char *tmp = estrdup(path); 2111 php_strip_url_passwd(tmp); 2112 php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s", 2113 tmp); 2114 efree(tmp); 2115 2116 options ^= REPORT_ERRORS; 2117 } 2118 } 2119 } 2120 2121 if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) { 2122 off_t newpos = 0; 2123 2124 /* if opened for append, we need to revise our idea of the initial file position */ 2125 if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) { 2126 stream->position = newpos; 2127 } 2128 } 2129 2130 if (stream == NULL && (options & REPORT_ERRORS)) { 2131 php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC); 2132 if (opened_path && *opened_path) { 2133 efree(*opened_path); 2134 *opened_path = NULL; 2135 } 2136 } 2137 php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); 2138#if ZEND_DEBUG 2139 if (stream == NULL && copy_of_path != NULL) { 2140 pefree(copy_of_path, persistent); 2141 } 2142#endif 2143 if (resolved_path) { 2144 efree(resolved_path); 2145 } 2146 return stream; 2147} 2148/* }}} */ 2149 2150/* {{{ context API */ 2151PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context) 2152{ 2153 php_stream_context *oldcontext = stream->context; 2154 TSRMLS_FETCH(); 2155 2156 stream->context = context; 2157 2158 if (context) { 2159 zend_list_addref(context->rsrc_id); 2160 } 2161 if (oldcontext) { 2162 zend_list_delete(oldcontext->rsrc_id); 2163 } 2164 2165 return oldcontext; 2166} 2167 2168PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity, 2169 char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC) 2170{ 2171 if (context && context->notifier) 2172 context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC); 2173} 2174 2175PHPAPI void php_stream_context_free(php_stream_context *context) 2176{ 2177 if (context->options) { 2178 zval_ptr_dtor(&context->options); 2179 context->options = NULL; 2180 } 2181 if (context->notifier) { 2182 php_stream_notification_free(context->notifier); 2183 context->notifier = NULL; 2184 } 2185 efree(context); 2186} 2187 2188PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D) 2189{ 2190 php_stream_context *context; 2191 2192 context = ecalloc(1, sizeof(php_stream_context)); 2193 context->notifier = NULL; 2194 MAKE_STD_ZVAL(context->options); 2195 array_init(context->options); 2196 2197 context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C)); 2198 return context; 2199} 2200 2201PHPAPI php_stream_notifier *php_stream_notification_alloc(void) 2202{ 2203 return ecalloc(1, sizeof(php_stream_notifier)); 2204} 2205 2206PHPAPI void php_stream_notification_free(php_stream_notifier *notifier) 2207{ 2208 if (notifier->dtor) { 2209 notifier->dtor(notifier); 2210 } 2211 efree(notifier); 2212} 2213 2214PHPAPI int php_stream_context_get_option(php_stream_context *context, 2215 const char *wrappername, const char *optionname, zval ***optionvalue) 2216{ 2217 zval **wrapperhash; 2218 2219 if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) { 2220 return FAILURE; 2221 } 2222 return zend_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue); 2223} 2224 2225PHPAPI int php_stream_context_set_option(php_stream_context *context, 2226 const char *wrappername, const char *optionname, zval *optionvalue) 2227{ 2228 zval **wrapperhash; 2229 zval *category, *copied_val; 2230 2231 ALLOC_INIT_ZVAL(copied_val); 2232 *copied_val = *optionvalue; 2233 zval_copy_ctor(copied_val); 2234 INIT_PZVAL(copied_val); 2235 2236 if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) { 2237 MAKE_STD_ZVAL(category); 2238 array_init(category); 2239 if (FAILURE == zend_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) { 2240 return FAILURE; 2241 } 2242 2243 wrapperhash = &category; 2244 } 2245 return zend_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL); 2246} 2247/* }}} */ 2248 2249/* {{{ php_stream_dirent_alphasort 2250 */ 2251PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b) 2252{ 2253 return strcoll(*a, *b); 2254} 2255/* }}} */ 2256 2257/* {{{ php_stream_dirent_alphasortr 2258 */ 2259PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b) 2260{ 2261 return strcoll(*b, *a); 2262} 2263/* }}} */ 2264 2265/* {{{ php_stream_scandir 2266 */ 2267PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context, 2268 int (*compare) (const char **a, const char **b) TSRMLS_DC) 2269{ 2270 php_stream *stream; 2271 php_stream_dirent sdp; 2272 char **vector = NULL; 2273 unsigned int vector_size = 0; 2274 unsigned int nfiles = 0; 2275 2276 if (!namelist) { 2277 return FAILURE; 2278 } 2279 2280 stream = php_stream_opendir(dirname, REPORT_ERRORS, context); 2281 if (!stream) { 2282 return FAILURE; 2283 } 2284 2285 while (php_stream_readdir(stream, &sdp)) { 2286 if (nfiles == vector_size) { 2287 if (vector_size == 0) { 2288 vector_size = 10; 2289 } else { 2290 if(vector_size*2 < vector_size) { 2291 /* overflow */ 2292 efree(vector); 2293 return FAILURE; 2294 } 2295 vector_size *= 2; 2296 } 2297 vector = (char **) safe_erealloc(vector, vector_size, sizeof(char *), 0); 2298 } 2299 2300 vector[nfiles] = estrdup(sdp.d_name); 2301 2302 nfiles++; 2303 if(vector_size < 10 || nfiles == 0) { 2304 /* overflow */ 2305 efree(vector); 2306 return FAILURE; 2307 } 2308 } 2309 php_stream_closedir(stream); 2310 2311 *namelist = vector; 2312 2313 if (compare) { 2314 qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare); 2315 } 2316 return nfiles; 2317} 2318/* }}} */ 2319 2320/* 2321 * Local variables: 2322 * tab-width: 4 2323 * c-basic-offset: 4 2324 * End: 2325 * vim600: noet sw=4 ts=4 fdm=marker 2326 * vim<600: noet sw=4 ts=4 2327 */ 2328