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 /* Remove stream from any context link list */ 474 if (context && context->links) { 475 php_stream_context_del_link(context, stream); 476 } 477 478 if (close_options & PHP_STREAM_FREE_CALL_DTOR) { 479 if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) { 480 /* calling fclose on an fopencookied stream will ultimately 481 call this very same function. If we were called via fclose, 482 the cookie_closer unsets the fclose_stdiocast flags, so 483 we can be sure that we only reach here when PHP code calls 484 php_stream_free. 485 Lets let the cookie code clean it all up. 486 */ 487 stream->in_free = 0; 488 return fclose(stream->stdiocast); 489 } 490 491 ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC); 492 stream->abstract = NULL; 493 494 /* tidy up any FILE* that might have been fdopened */ 495 if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) { 496 fclose(stream->stdiocast); 497 stream->stdiocast = NULL; 498 stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE; 499 } 500 } 501 502 if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) { 503 while (stream->readfilters.head) { 504 php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC); 505 } 506 while (stream->writefilters.head) { 507 php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC); 508 } 509 510 if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) { 511 stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC); 512 stream->wrapper = NULL; 513 } 514 515 if (stream->wrapperdata) { 516 zval_ptr_dtor(&stream->wrapperdata); 517 stream->wrapperdata = NULL; 518 } 519 520 if (stream->readbuf) { 521 pefree(stream->readbuf, stream->is_persistent); 522 stream->readbuf = NULL; 523 } 524 525 if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) { 526 /* we don't work with *stream but need its value for comparison */ 527 zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC); 528 } 529#if ZEND_DEBUG 530 if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) { 531 /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it 532 * as leaked; it will log a warning, but lets help it out and display what kind 533 * of stream it was. */ 534 char *leakinfo; 535 spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path); 536 537 if (stream->orig_path) { 538 pefree(stream->orig_path, stream->is_persistent); 539 stream->orig_path = NULL; 540 } 541 542# if defined(PHP_WIN32) 543 OutputDebugString(leakinfo); 544# else 545 fprintf(stderr, "%s", leakinfo); 546# endif 547 efree(leakinfo); 548 } else { 549 if (stream->orig_path) { 550 pefree(stream->orig_path, stream->is_persistent); 551 stream->orig_path = NULL; 552 } 553 554 pefree(stream, stream->is_persistent); 555 } 556#else 557 if (stream->orig_path) { 558 pefree(stream->orig_path, stream->is_persistent); 559 stream->orig_path = NULL; 560 } 561 562 pefree(stream, stream->is_persistent); 563#endif 564 } 565 566 if (context) { 567 zend_list_delete(context->rsrc_id); 568 } 569 570 return ret; 571} 572/* }}} */ 573 574/* {{{ generic stream operations */ 575 576static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC) 577{ 578 /* allocate/fill the buffer */ 579 580 if (stream->readfilters.head) { 581 char *chunk_buf; 582 int err_flag = 0; 583 php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL }; 584 php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap; 585 586 /* Invalidate the existing cache, otherwise reads can fail, see note in 587 main/streams/filter.c::_php_stream_filter_append */ 588 stream->writepos = stream->readpos = 0; 589 590 /* allocate a buffer for reading chunks */ 591 chunk_buf = emalloc(stream->chunk_size); 592 593 while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) { 594 size_t justread = 0; 595 int flags; 596 php_stream_bucket *bucket; 597 php_stream_filter_status_t status = PSFS_ERR_FATAL; 598 php_stream_filter *filter; 599 600 /* read a chunk into a bucket */ 601 justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC); 602 if (justread && justread != (size_t)-1) { 603 bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC); 604 605 /* after this call, bucket is owned by the brigade */ 606 php_stream_bucket_append(brig_inp, bucket TSRMLS_CC); 607 608 flags = PSFS_FLAG_NORMAL; 609 } else { 610 flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC; 611 } 612 613 /* wind the handle... */ 614 for (filter = stream->readfilters.head; filter; filter = filter->next) { 615 status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC); 616 617 if (status != PSFS_PASS_ON) { 618 break; 619 } 620 621 /* brig_out becomes brig_in. 622 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets 623 * to its own brigade */ 624 brig_swap = brig_inp; 625 brig_inp = brig_outp; 626 brig_outp = brig_swap; 627 memset(brig_outp, 0, sizeof(*brig_outp)); 628 } 629 630 switch (status) { 631 case PSFS_PASS_ON: 632 /* we get here when the last filter in the chain has data to pass on. 633 * in this situation, we are passing the brig_in brigade into the 634 * stream read buffer */ 635 while (brig_inp->head) { 636 bucket = brig_inp->head; 637 /* grow buffer to hold this bucket 638 * TODO: this can fail for persistent streams */ 639 if (stream->readbuflen - stream->writepos < bucket->buflen) { 640 stream->readbuflen += bucket->buflen; 641 stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, 642 stream->is_persistent); 643 } 644 memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen); 645 stream->writepos += bucket->buflen; 646 647 php_stream_bucket_unlink(bucket TSRMLS_CC); 648 php_stream_bucket_delref(bucket TSRMLS_CC); 649 } 650 break; 651 652 case PSFS_FEED_ME: 653 /* when a filter needs feeding, there is no brig_out to deal with. 654 * we simply continue the loop; if the caller needs more data, 655 * we will read again, otherwise out job is done here */ 656 if (justread == 0) { 657 /* there is no data */ 658 err_flag = 1; 659 break; 660 } 661 continue; 662 663 case PSFS_ERR_FATAL: 664 /* some fatal error. Theoretically, the stream is borked, so all 665 * further reads should fail. */ 666 err_flag = 1; 667 break; 668 } 669 670 if (justread == 0 || justread == (size_t)-1) { 671 break; 672 } 673 } 674 675 efree(chunk_buf); 676 677 } else { 678 /* is there enough data in the buffer ? */ 679 if (stream->writepos - stream->readpos < (off_t)size) { 680 size_t justread = 0; 681 682 /* reduce buffer memory consumption if possible, to avoid a realloc */ 683 if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) { 684 memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos); 685 stream->writepos -= stream->readpos; 686 stream->readpos = 0; 687 } 688 689 /* grow the buffer if required 690 * TODO: this can fail for persistent streams */ 691 if (stream->readbuflen - stream->writepos < stream->chunk_size) { 692 stream->readbuflen += stream->chunk_size; 693 stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, 694 stream->is_persistent); 695 } 696 697 justread = stream->ops->read(stream, stream->readbuf + stream->writepos, 698 stream->readbuflen - stream->writepos 699 TSRMLS_CC); 700 701 if (justread != (size_t)-1) { 702 stream->writepos += justread; 703 } 704 } 705 } 706} 707 708PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC) 709{ 710 size_t toread = 0, didread = 0; 711 712 while (size > 0) { 713 714 /* take from the read buffer first. 715 * It is possible that a buffered stream was switched to non-buffered, so we 716 * drain the remainder of the buffer before using the "raw" read mode for 717 * the excess */ 718 if (stream->writepos > stream->readpos) { 719 720 toread = stream->writepos - stream->readpos; 721 if (toread > size) { 722 toread = size; 723 } 724 725 memcpy(buf, stream->readbuf + stream->readpos, toread); 726 stream->readpos += toread; 727 size -= toread; 728 buf += toread; 729 didread += toread; 730 } 731 732 /* ignore eof here; the underlying state might have changed */ 733 if (size == 0) { 734 break; 735 } 736 737 if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) { 738 toread = stream->ops->read(stream, buf, size TSRMLS_CC); 739 } else { 740 php_stream_fill_read_buffer(stream, size TSRMLS_CC); 741 742 toread = stream->writepos - stream->readpos; 743 if (toread > size) { 744 toread = size; 745 } 746 747 if (toread > 0) { 748 memcpy(buf, stream->readbuf + stream->readpos, toread); 749 stream->readpos += toread; 750 } 751 } 752 if (toread > 0) { 753 didread += toread; 754 buf += toread; 755 size -= toread; 756 } else { 757 /* EOF, or temporary end of data (for non-blocking mode). */ 758 break; 759 } 760 761 /* just break anyway, to avoid greedy read */ 762 if (stream->wrapper != &php_plain_files_wrapper) { 763 break; 764 } 765 } 766 767 if (didread > 0) { 768 stream->position += didread; 769 } 770 771 return didread; 772} 773 774PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC) 775{ 776 /* if there is data in the buffer, it's not EOF */ 777 if (stream->writepos - stream->readpos > 0) { 778 return 0; 779 } 780 781 /* use the configured timeout when checking eof */ 782 if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR == 783 php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 784 0, NULL)) { 785 stream->eof = 1; 786 } 787 788 return stream->eof; 789} 790 791PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC) 792{ 793 unsigned char buf = c; 794 795 if (php_stream_write(stream, &buf, 1) > 0) { 796 return 1; 797 } 798 return EOF; 799} 800 801PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC) 802{ 803 char buf; 804 805 if (php_stream_read(stream, &buf, 1) > 0) { 806 return buf & 0xff; 807 } 808 return EOF; 809} 810 811PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC) 812{ 813 int len; 814 char newline[2] = "\n"; /* is this OK for Win? */ 815 len = strlen(buf); 816 817 if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) { 818 return 1; 819 } 820 return 0; 821} 822 823PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) 824{ 825 memset(ssb, 0, sizeof(*ssb)); 826 827 /* if the stream was wrapped, allow the wrapper to stat it */ 828 if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) { 829 return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC); 830 } 831 832 /* if the stream doesn't directly support stat-ing, return with failure. 833 * We could try and emulate this by casting to a FD and fstat-ing it, 834 * but since the fd might not represent the actual underlying content 835 * this would give bogus results. */ 836 if (stream->ops->stat == NULL) { 837 return -1; 838 } 839 840 return (stream->ops->stat)(stream, ssb TSRMLS_CC); 841} 842 843PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC) 844{ 845 size_t avail; 846 char *cr, *lf, *eol = NULL; 847 char *readptr; 848 849 if (!buf) { 850 readptr = stream->readbuf + stream->readpos; 851 avail = stream->writepos - stream->readpos; 852 } else { 853 readptr = buf; 854 avail = buf_len; 855 } 856 857 /* Look for EOL */ 858 if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) { 859 cr = memchr(readptr, '\r', avail); 860 lf = memchr(readptr, '\n', avail); 861 862 if (cr && lf != cr + 1 && !(lf && lf < cr)) { 863 /* mac */ 864 stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL; 865 stream->flags |= PHP_STREAM_FLAG_EOL_MAC; 866 eol = cr; 867 } else if ((cr && lf && cr == lf - 1) || (lf)) { 868 /* dos or unix endings */ 869 stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL; 870 eol = lf; 871 } 872 } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) { 873 eol = memchr(readptr, '\r', avail); 874 } else { 875 /* unix (and dos) line endings */ 876 eol = memchr(readptr, '\n', avail); 877 } 878 879 return eol; 880} 881 882/* If buf == NULL, the buffer will be allocated automatically and will be of an 883 * appropriate length to hold the line, regardless of the line length, memory 884 * permitting */ 885PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, 886 size_t *returned_len TSRMLS_DC) 887{ 888 size_t avail = 0; 889 size_t current_buf_size = 0; 890 size_t total_copied = 0; 891 int grow_mode = 0; 892 char *bufstart = buf; 893 894 if (buf == NULL) { 895 grow_mode = 1; 896 } else if (maxlen == 0) { 897 return NULL; 898 } 899 900 /* 901 * If the underlying stream operations block when no new data is readable, 902 * we need to take extra precautions. 903 * 904 * If there is buffered data available, we check for a EOL. If it exists, 905 * we pass the data immediately back to the caller. This saves a call 906 * to the read implementation and will not block where blocking 907 * is not necessary at all. 908 * 909 * If the stream buffer contains more data than the caller requested, 910 * we can also avoid that costly step and simply return that data. 911 */ 912 913 for (;;) { 914 avail = stream->writepos - stream->readpos; 915 916 if (avail > 0) { 917 size_t cpysz = 0; 918 char *readptr; 919 char *eol; 920 int done = 0; 921 922 readptr = stream->readbuf + stream->readpos; 923 eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC); 924 925 if (eol) { 926 cpysz = eol - readptr + 1; 927 done = 1; 928 } else { 929 cpysz = avail; 930 } 931 932 if (grow_mode) { 933 /* allow room for a NUL. If this realloc is really a realloc 934 * (ie: second time around), we get an extra byte. In most 935 * cases, with the default chunk size of 8K, we will only 936 * incur that overhead once. When people have lines longer 937 * than 8K, we waste 1 byte per additional 8K or so. 938 * That seems acceptable to me, to avoid making this code 939 * hard to follow */ 940 bufstart = erealloc(bufstart, current_buf_size + cpysz + 1); 941 current_buf_size += cpysz + 1; 942 buf = bufstart + total_copied; 943 } else { 944 if (cpysz >= maxlen - 1) { 945 cpysz = maxlen - 1; 946 done = 1; 947 } 948 } 949 950 memcpy(buf, readptr, cpysz); 951 952 stream->position += cpysz; 953 stream->readpos += cpysz; 954 buf += cpysz; 955 maxlen -= cpysz; 956 total_copied += cpysz; 957 958 if (done) { 959 break; 960 } 961 } else if (stream->eof) { 962 break; 963 } else { 964 /* XXX: Should be fine to always read chunk_size */ 965 size_t toread; 966 967 if (grow_mode) { 968 toread = stream->chunk_size; 969 } else { 970 toread = maxlen - 1; 971 if (toread > stream->chunk_size) { 972 toread = stream->chunk_size; 973 } 974 } 975 976 php_stream_fill_read_buffer(stream, toread TSRMLS_CC); 977 978 if (stream->writepos - stream->readpos == 0) { 979 break; 980 } 981 } 982 } 983 984 if (total_copied == 0) { 985 if (grow_mode) { 986 assert(bufstart == NULL); 987 } 988 return NULL; 989 } 990 991 buf[0] = '\0'; 992 if (returned_len) { 993 *returned_len = total_copied; 994 } 995 996 return bufstart; 997} 998 999#define STREAM_BUFFERED_AMOUNT(stream) \ 1000 ((size_t)(((stream)->writepos) - (stream)->readpos)) 1001 1002static char *_php_stream_search_delim(php_stream *stream, 1003 size_t maxlen, 1004 size_t skiplen, 1005 char *delim, /* non-empty! */ 1006 size_t delim_len TSRMLS_DC) 1007{ 1008 size_t seek_len; 1009 1010 /* set the maximum number of bytes we're allowed to read from buffer */ 1011 seek_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen); 1012 if (seek_len <= skiplen) { 1013 return NULL; 1014 } 1015 1016 if (delim_len == 1) { 1017 return memchr(&stream->readbuf[stream->readpos + skiplen], 1018 delim[0], seek_len - skiplen); 1019 } else { 1020 return php_memnstr((char*)&stream->readbuf[stream->readpos + skiplen], 1021 delim, delim_len, 1022 (char*)&stream->readbuf[stream->readpos + seek_len]); 1023 } 1024} 1025 1026PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC) 1027{ 1028 char *ret_buf, /* returned buffer */ 1029 *found_delim = NULL; 1030 size_t buffered_len, 1031 tent_ret_len; /* tentative returned length */ 1032 int has_delim = delim_len > 0; 1033 1034 if (maxlen == 0) { 1035 return NULL; 1036 } 1037 1038 if (has_delim) { 1039 found_delim = _php_stream_search_delim( 1040 stream, maxlen, 0, delim, delim_len TSRMLS_CC); 1041 } 1042 1043 buffered_len = STREAM_BUFFERED_AMOUNT(stream); 1044 /* try to read up to maxlen length bytes while we don't find the delim */ 1045 while (!found_delim && buffered_len < maxlen) { 1046 size_t just_read, 1047 to_read_now; 1048 1049 to_read_now = MIN(maxlen - buffered_len, stream->chunk_size); 1050 1051 php_stream_fill_read_buffer(stream, buffered_len + to_read_now TSRMLS_CC); 1052 1053 just_read = STREAM_BUFFERED_AMOUNT(stream) - buffered_len; 1054 1055 /* Assume the stream is temporarily or permanently out of data */ 1056 if (just_read == 0) { 1057 break; 1058 } 1059 1060 if (has_delim) { 1061 /* search for delimiter, but skip buffered_len (the number of bytes 1062 * buffered before this loop iteration), as they have already been 1063 * searched for the delimiter. 1064 * The left part of the delimiter may still remain in the buffer, 1065 * so subtract up to <delim_len - 1> from buffered_len, which is 1066 * the ammount of data we skip on this search as an optimization 1067 */ 1068 found_delim = _php_stream_search_delim( 1069 stream, maxlen, 1070 buffered_len >= (delim_len - 1) 1071 ? buffered_len - (delim_len - 1) 1072 : 0, 1073 delim, delim_len TSRMLS_CC); 1074 if (found_delim) { 1075 break; 1076 } 1077 } 1078 buffered_len += just_read; 1079 } 1080 1081 if (has_delim && found_delim) { 1082 tent_ret_len = found_delim - (char*)&stream->readbuf[stream->readpos]; 1083 } else if (!has_delim && STREAM_BUFFERED_AMOUNT(stream) >= maxlen) { 1084 tent_ret_len = maxlen; 1085 } else { 1086 /* return with error if the delimiter string (if any) was not found, we 1087 * could not completely fill the read buffer with maxlen bytes and we 1088 * don't know we've reached end of file. Added with non-blocking streams 1089 * in mind, where this situation is frequent */ 1090 if (STREAM_BUFFERED_AMOUNT(stream) < maxlen && !stream->eof) { 1091 return NULL; 1092 } else if (STREAM_BUFFERED_AMOUNT(stream) == 0 && stream->eof) { 1093 /* refuse to return an empty string just because by accident 1094 * we knew of EOF in a read that returned no data */ 1095 return NULL; 1096 } else { 1097 tent_ret_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen); 1098 } 1099 } 1100 1101 ret_buf = emalloc(tent_ret_len + 1); 1102 /* php_stream_read will not call ops->read here because the necessary 1103 * data is guaranteedly buffered */ 1104 *returned_len = php_stream_read(stream, ret_buf, tent_ret_len); 1105 1106 if (found_delim) { 1107 stream->readpos += delim_len; 1108 stream->position += delim_len; 1109 } 1110 ret_buf[*returned_len] = '\0'; 1111 return ret_buf; 1112} 1113 1114/* Writes a buffer directly to a stream, using multiple of the chunk size */ 1115static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC) 1116{ 1117 size_t didwrite = 0, towrite, justwrote; 1118 1119 /* if we have a seekable stream we need to ensure that data is written at the 1120 * current stream->position. This means invalidating the read buffer and then 1121 * performing a low-level seek */ 1122 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) { 1123 stream->readpos = stream->writepos = 0; 1124 1125 stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC); 1126 } 1127 1128 1129 while (count > 0) { 1130 towrite = count; 1131 if (towrite > stream->chunk_size) 1132 towrite = stream->chunk_size; 1133 1134 justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC); 1135 1136 /* convert justwrote to an integer, since normally it is unsigned */ 1137 if ((int)justwrote > 0) { 1138 buf += justwrote; 1139 count -= justwrote; 1140 didwrite += justwrote; 1141 1142 /* Only screw with the buffer if we can seek, otherwise we lose data 1143 * buffered from fifos and sockets */ 1144 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { 1145 stream->position += justwrote; 1146 } 1147 } else { 1148 break; 1149 } 1150 } 1151 return didwrite; 1152 1153} 1154 1155/* push some data through the write filter chain. 1156 * buf may be NULL, if flags are set to indicate a flush. 1157 * This may trigger a real write to the stream. 1158 * Returns the number of bytes consumed from buf by the first filter in the chain. 1159 * */ 1160static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC) 1161{ 1162 size_t consumed = 0; 1163 php_stream_bucket *bucket; 1164 php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL }; 1165 php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap; 1166 php_stream_filter_status_t status = PSFS_ERR_FATAL; 1167 php_stream_filter *filter; 1168 1169 if (buf) { 1170 bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC); 1171 php_stream_bucket_append(&brig_in, bucket TSRMLS_CC); 1172 } 1173 1174 for (filter = stream->writefilters.head; filter; filter = filter->next) { 1175 /* for our return value, we are interested in the number of bytes consumed from 1176 * the first filter in the chain */ 1177 status = filter->fops->filter(stream, filter, brig_inp, brig_outp, 1178 filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC); 1179 1180 if (status != PSFS_PASS_ON) { 1181 break; 1182 } 1183 /* brig_out becomes brig_in. 1184 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets 1185 * to its own brigade */ 1186 brig_swap = brig_inp; 1187 brig_inp = brig_outp; 1188 brig_outp = brig_swap; 1189 memset(brig_outp, 0, sizeof(*brig_outp)); 1190 } 1191 1192 switch (status) { 1193 case PSFS_PASS_ON: 1194 /* filter chain generated some output; push it through to the 1195 * underlying stream */ 1196 while (brig_inp->head) { 1197 bucket = brig_inp->head; 1198 _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC); 1199 /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade 1200 * hanging around and try to write it later. 1201 * At the moment, we just drop it on the floor 1202 * */ 1203 1204 php_stream_bucket_unlink(bucket TSRMLS_CC); 1205 php_stream_bucket_delref(bucket TSRMLS_CC); 1206 } 1207 break; 1208 case PSFS_FEED_ME: 1209 /* need more data before we can push data through to the stream */ 1210 break; 1211 1212 case PSFS_ERR_FATAL: 1213 /* some fatal error. Theoretically, the stream is borked, so all 1214 * further writes should fail. */ 1215 break; 1216 } 1217 1218 return consumed; 1219} 1220 1221PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC) 1222{ 1223 int ret = 0; 1224 1225 if (stream->writefilters.head) { 1226 _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC TSRMLS_CC); 1227 } 1228 1229 if (stream->ops->flush) { 1230 ret = stream->ops->flush(stream TSRMLS_CC); 1231 } 1232 1233 return ret; 1234} 1235 1236PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) 1237{ 1238 if (buf == NULL || count == 0 || stream->ops->write == NULL) { 1239 return 0; 1240 } 1241 1242 if (stream->writefilters.head) { 1243 return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC); 1244 } else { 1245 return _php_stream_write_buffer(stream, buf, count TSRMLS_CC); 1246 } 1247} 1248 1249PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...) 1250{ 1251 size_t count; 1252 char *buf; 1253 va_list ap; 1254 1255 va_start(ap, fmt); 1256 count = vspprintf(&buf, 0, fmt, ap); 1257 va_end(ap); 1258 1259 if (!buf) { 1260 return 0; /* error condition */ 1261 } 1262 1263 count = php_stream_write(stream, buf, count); 1264 efree(buf); 1265 1266 return count; 1267} 1268 1269PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC) 1270{ 1271 return stream->position; 1272} 1273 1274PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC) 1275{ 1276 if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) { 1277 /* flush to commit data written to the fopencookie FILE* */ 1278 fflush(stream->stdiocast); 1279 } 1280 1281 /* handle the case where we are in the buffer */ 1282 if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) { 1283 switch(whence) { 1284 case SEEK_CUR: 1285 if (offset > 0 && offset <= stream->writepos - stream->readpos) { 1286 stream->readpos += offset; /* if offset = ..., then readpos = writepos */ 1287 stream->position += offset; 1288 stream->eof = 0; 1289 return 0; 1290 } 1291 break; 1292 case SEEK_SET: 1293 if (offset > stream->position && 1294 offset <= stream->position + stream->writepos - stream->readpos) { 1295 stream->readpos += offset - stream->position; 1296 stream->position = offset; 1297 stream->eof = 0; 1298 return 0; 1299 } 1300 break; 1301 } 1302 } 1303 1304 1305 if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { 1306 int ret; 1307 1308 if (stream->writefilters.head) { 1309 _php_stream_flush(stream, 0 TSRMLS_CC); 1310 } 1311 1312 switch(whence) { 1313 case SEEK_CUR: 1314 offset = stream->position + offset; 1315 whence = SEEK_SET; 1316 break; 1317 } 1318 ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC); 1319 1320 if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) { 1321 if (ret == 0) { 1322 stream->eof = 0; 1323 } 1324 1325 /* invalidate the buffer contents */ 1326 stream->readpos = stream->writepos = 0; 1327 1328 return ret; 1329 } 1330 /* else the stream has decided that it can't support seeking after all; 1331 * fall through to attempt emulation */ 1332 } 1333 1334 /* emulate forward moving seeks with reads */ 1335 if (whence == SEEK_CUR && offset >= 0) { 1336 char tmp[1024]; 1337 size_t didread; 1338 while(offset > 0) { 1339 if ((didread = php_stream_read(stream, tmp, MIN(offset, sizeof(tmp)))) == 0) { 1340 return -1; 1341 } 1342 offset -= didread; 1343 } 1344 stream->eof = 0; 1345 return 0; 1346 } 1347 1348 php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking"); 1349 1350 return -1; 1351} 1352 1353PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) 1354{ 1355 int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL; 1356 1357 if (stream->ops->set_option) { 1358 ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC); 1359 } 1360 1361 if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) { 1362 switch(option) { 1363 case PHP_STREAM_OPTION_SET_CHUNK_SIZE: 1364 ret = stream->chunk_size; 1365 stream->chunk_size = value; 1366 return ret; 1367 1368 case PHP_STREAM_OPTION_READ_BUFFER: 1369 /* try to match the buffer mode as best we can */ 1370 if (value == PHP_STREAM_BUFFER_NONE) { 1371 stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; 1372 } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) { 1373 stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER; 1374 } 1375 ret = PHP_STREAM_OPTION_RETURN_OK; 1376 break; 1377 1378 default: 1379 ; 1380 } 1381 } 1382 1383 return ret; 1384} 1385 1386PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC) 1387{ 1388 return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize); 1389} 1390 1391PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC) 1392{ 1393 size_t bcount = 0; 1394 char buf[8192]; 1395 int b; 1396 1397 if (php_stream_mmap_possible(stream)) { 1398 char *p; 1399 size_t mapped; 1400 1401 p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); 1402 1403 if (p) { 1404 PHPWRITE(p, mapped); 1405 1406 php_stream_mmap_unmap_ex(stream, mapped); 1407 1408 return mapped; 1409 } 1410 } 1411 1412 while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) { 1413 PHPWRITE(buf, b); 1414 bcount += b; 1415 } 1416 1417 return bcount; 1418} 1419 1420 1421PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC) 1422{ 1423 size_t ret = 0; 1424 char *ptr; 1425 size_t len = 0, max_len; 1426 int step = CHUNK_SIZE; 1427 int min_room = CHUNK_SIZE / 4; 1428 php_stream_statbuf ssbuf; 1429 1430 if (maxlen == 0) { 1431 return 0; 1432 } 1433 1434 if (maxlen == PHP_STREAM_COPY_ALL) { 1435 maxlen = 0; 1436 } 1437 1438 if (maxlen > 0) { 1439 ptr = *buf = pemalloc_rel_orig(maxlen + 1, persistent); 1440 while ((len < maxlen) && !php_stream_eof(src)) { 1441 ret = php_stream_read(src, ptr, maxlen - len); 1442 if (!ret) { 1443 break; 1444 } 1445 len += ret; 1446 ptr += ret; 1447 } 1448 if (len) { 1449 *ptr = '\0'; 1450 } else { 1451 pefree(*buf, persistent); 1452 *buf = NULL; 1453 } 1454 return len; 1455 } 1456 1457 /* avoid many reallocs by allocating a good sized chunk to begin with, if 1458 * we can. Note that the stream may be filtered, in which case the stat 1459 * result may be inaccurate, as the filter may inflate or deflate the 1460 * number of bytes that we can read. In order to avoid an upsize followed 1461 * by a downsize of the buffer, overestimate by the step size (which is 1462 * 2K). */ 1463 if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) { 1464 max_len = ssbuf.sb.st_size + step; 1465 } else { 1466 max_len = step; 1467 } 1468 1469 ptr = *buf = pemalloc_rel_orig(max_len, persistent); 1470 1471 while((ret = php_stream_read(src, ptr, max_len - len))) { 1472 len += ret; 1473 if (len + min_room >= max_len) { 1474 *buf = perealloc_rel_orig(*buf, max_len + step, persistent); 1475 max_len += step; 1476 ptr = *buf + len; 1477 } else { 1478 ptr += ret; 1479 } 1480 } 1481 if (len) { 1482 *buf = perealloc_rel_orig(*buf, len + 1, persistent); 1483 (*buf)[len] = '\0'; 1484 } else { 1485 pefree(*buf, persistent); 1486 *buf = NULL; 1487 } 1488 return len; 1489} 1490 1491/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */ 1492PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC) 1493{ 1494 char buf[CHUNK_SIZE]; 1495 size_t readchunk; 1496 size_t haveread = 0; 1497 size_t didread, didwrite, towrite; 1498 size_t dummy; 1499 php_stream_statbuf ssbuf; 1500 1501 if (!len) { 1502 len = &dummy; 1503 } 1504 1505 if (maxlen == 0) { 1506 *len = 0; 1507 return SUCCESS; 1508 } 1509 1510 if (maxlen == PHP_STREAM_COPY_ALL) { 1511 maxlen = 0; 1512 } 1513 1514 if (php_stream_stat(src, &ssbuf) == 0) { 1515 if (ssbuf.sb.st_size == 0 1516#ifdef S_ISREG 1517 && S_ISREG(ssbuf.sb.st_mode) 1518#endif 1519 ) { 1520 *len = 0; 1521 return SUCCESS; 1522 } 1523 } 1524 1525 if (php_stream_mmap_possible(src)) { 1526 char *p; 1527 size_t mapped; 1528 1529 p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); 1530 1531 if (p) { 1532 didwrite = php_stream_write(dest, p, mapped); 1533 1534 php_stream_mmap_unmap_ex(src, mapped); 1535 1536 *len = didwrite; 1537 1538 /* we've got at least 1 byte to read 1539 * less than 1 is an error 1540 * AND read bytes match written */ 1541 if (mapped > 0 && mapped == didwrite) { 1542 return SUCCESS; 1543 } 1544 return FAILURE; 1545 } 1546 } 1547 1548 while(1) { 1549 readchunk = sizeof(buf); 1550 1551 if (maxlen && (maxlen - haveread) < readchunk) { 1552 readchunk = maxlen - haveread; 1553 } 1554 1555 didread = php_stream_read(src, buf, readchunk); 1556 1557 if (didread) { 1558 /* extra paranoid */ 1559 char *writeptr; 1560 1561 towrite = didread; 1562 writeptr = buf; 1563 haveread += didread; 1564 1565 while(towrite) { 1566 didwrite = php_stream_write(dest, writeptr, towrite); 1567 if (didwrite == 0) { 1568 *len = haveread - (didread - towrite); 1569 return FAILURE; 1570 } 1571 1572 towrite -= didwrite; 1573 writeptr += didwrite; 1574 } 1575 } else { 1576 break; 1577 } 1578 1579 if (maxlen - haveread == 0) { 1580 break; 1581 } 1582 } 1583 1584 *len = haveread; 1585 1586 /* we've got at least 1 byte to read. 1587 * less than 1 is an error */ 1588 1589 if (haveread > 0 || src->eof) { 1590 return SUCCESS; 1591 } 1592 return FAILURE; 1593} 1594 1595/* Returns the number of bytes moved. 1596 * Returns 1 when source len is 0. 1597 * Deprecated in favor of php_stream_copy_to_stream_ex() */ 1598ZEND_ATTRIBUTE_DEPRECATED 1599PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC) 1600{ 1601 size_t len; 1602 int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC); 1603 if (ret == SUCCESS && len == 0 && maxlen != 0) { 1604 return 1; 1605 } 1606 return len; 1607} 1608/* }}} */ 1609 1610/* {{{ wrapper init and registration */ 1611 1612static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) 1613{ 1614 php_stream *stream = (php_stream*)rsrc->ptr; 1615 /* set the return value for pclose */ 1616 FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR); 1617} 1618 1619static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) 1620{ 1621 php_stream *stream = (php_stream*)rsrc->ptr; 1622 FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR); 1623} 1624 1625void php_shutdown_stream_hashes(TSRMLS_D) 1626{ 1627 if (FG(stream_wrappers)) { 1628 zend_hash_destroy(FG(stream_wrappers)); 1629 efree(FG(stream_wrappers)); 1630 FG(stream_wrappers) = NULL; 1631 } 1632 1633 if (FG(stream_filters)) { 1634 zend_hash_destroy(FG(stream_filters)); 1635 efree(FG(stream_filters)); 1636 FG(stream_filters) = NULL; 1637 } 1638 1639 if (FG(wrapper_errors)) { 1640 zend_hash_destroy(FG(wrapper_errors)); 1641 efree(FG(wrapper_errors)); 1642 FG(wrapper_errors) = NULL; 1643 } 1644} 1645 1646int php_init_stream_wrappers(int module_number TSRMLS_DC) 1647{ 1648 le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number); 1649 le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number); 1650 1651 /* Filters are cleaned up by the streams they're attached to */ 1652 le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number); 1653 1654 return ( 1655 zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS 1656 && 1657 zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS 1658 && 1659 zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS 1660 && 1661 php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1662 && 1663 php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1664#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)) 1665 && 1666 php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1667 && 1668 php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS 1669#endif 1670 ) ? SUCCESS : FAILURE; 1671} 1672 1673int php_shutdown_stream_wrappers(int module_number TSRMLS_DC) 1674{ 1675 zend_hash_destroy(&url_stream_wrappers_hash); 1676 zend_hash_destroy(php_get_stream_filters_hash_global()); 1677 zend_hash_destroy(php_stream_xport_get_hash()); 1678 return SUCCESS; 1679} 1680 1681/* Validate protocol scheme names during registration 1682 * Must conform to /^[a-zA-Z0-9+.-]+$/ 1683 */ 1684static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len) 1685{ 1686 int i; 1687 1688 for(i = 0; i < protocol_len; i++) { 1689 if (!isalnum((int)protocol[i]) && 1690 protocol[i] != '+' && 1691 protocol[i] != '-' && 1692 protocol[i] != '.') { 1693 return FAILURE; 1694 } 1695 } 1696 1697 return SUCCESS; 1698} 1699 1700/* API for registering GLOBAL wrappers */ 1701PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC) 1702{ 1703 int protocol_len = strlen(protocol); 1704 1705 if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) { 1706 return FAILURE; 1707 } 1708 1709 return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL); 1710} 1711 1712PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC) 1713{ 1714 return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1); 1715} 1716 1717static void clone_wrapper_hash(TSRMLS_D) 1718{ 1719 php_stream_wrapper *tmp; 1720 1721 ALLOC_HASHTABLE(FG(stream_wrappers)); 1722 zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1); 1723 zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp)); 1724} 1725 1726/* API for registering VOLATILE wrappers */ 1727PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC) 1728{ 1729 int protocol_len = strlen(protocol); 1730 1731 if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) { 1732 return FAILURE; 1733 } 1734 1735 if (!FG(stream_wrappers)) { 1736 clone_wrapper_hash(TSRMLS_C); 1737 } 1738 1739 return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL); 1740} 1741 1742PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC) 1743{ 1744 if (!FG(stream_wrappers)) { 1745 clone_wrapper_hash(TSRMLS_C); 1746 } 1747 1748 return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1); 1749} 1750/* }}} */ 1751 1752/* {{{ php_stream_locate_url_wrapper */ 1753PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC) 1754{ 1755 HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash); 1756 php_stream_wrapper **wrapperpp = NULL; 1757 const char *p, *protocol = NULL; 1758 int n = 0; 1759 1760 if (path_for_open) { 1761 *path_for_open = (char*)path; 1762 } 1763 1764 if (options & IGNORE_URL) { 1765 return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper; 1766 } 1767 1768 for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { 1769 n++; 1770 } 1771 1772 if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) { 1773 protocol = path; 1774 } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) { 1775 /* BC with older php scripts and zlib wrapper */ 1776 protocol = "compress.zlib"; 1777 n = 13; 1778 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead"); 1779 } 1780 1781 if (protocol) { 1782 char *tmp = estrndup(protocol, n); 1783 if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) { 1784 php_strtolower(tmp, n); 1785 if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) { 1786 char wrapper_name[32]; 1787 1788 if (n >= sizeof(wrapper_name)) { 1789 n = sizeof(wrapper_name) - 1; 1790 } 1791 PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n); 1792 1793 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); 1794 1795 wrapperpp = NULL; 1796 protocol = NULL; 1797 } 1798 } 1799 efree(tmp); 1800 } 1801 /* TODO: curl based streams probably support file:// properly */ 1802 if (!protocol || !strncasecmp(protocol, "file", n)) { 1803 /* fall back on regular file access */ 1804 php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper; 1805 1806 if (protocol) { 1807 int localhost = 0; 1808 1809 if (!strncasecmp(path, "file://localhost/", 17)) { 1810 localhost = 1; 1811 } 1812 1813#ifdef PHP_WIN32 1814 if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':') { 1815#else 1816 if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') { 1817#endif 1818 if (options & REPORT_ERRORS) { 1819 php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path); 1820 } 1821 return NULL; 1822 } 1823 1824 if (path_for_open) { 1825 /* skip past protocol and :/, but handle windows correctly */ 1826 *path_for_open = (char*)path + n + 1; 1827 if (localhost == 1) { 1828 (*path_for_open) += 11; 1829 } 1830 while (*(++*path_for_open)=='/'); 1831#ifdef PHP_WIN32 1832 if (*(*path_for_open + 1) != ':') 1833#endif 1834 (*path_for_open)--; 1835 } 1836 } 1837 1838 if (options & STREAM_LOCATE_WRAPPERS_ONLY) { 1839 return NULL; 1840 } 1841 1842 if (FG(stream_wrappers)) { 1843 /* The file:// wrapper may have been disabled/overridden */ 1844 1845 if (wrapperpp) { 1846 /* It was found so go ahead and provide it */ 1847 return *wrapperpp; 1848 } 1849 1850 /* Check again, the original check might have not known the protocol name */ 1851 if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) { 1852 return *wrapperpp; 1853 } 1854 1855 if (options & REPORT_ERRORS) { 1856 php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration"); 1857 } 1858 return NULL; 1859 } 1860 1861 return plain_files_wrapper; 1862 } 1863 1864 if (wrapperpp && (*wrapperpp)->is_url && 1865 (options & STREAM_DISABLE_URL_PROTECTION) == 0 && 1866 (!PG(allow_url_fopen) || 1867 (((options & STREAM_OPEN_FOR_INCLUDE) || 1868 PG(in_user_include)) && !PG(allow_url_include)))) { 1869 if (options & REPORT_ERRORS) { 1870 /* protocol[n] probably isn't '\0' */ 1871 char *protocol_dup = estrndup(protocol, n); 1872 if (!PG(allow_url_fopen)) { 1873 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup); 1874 } else { 1875 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup); 1876 } 1877 efree(protocol_dup); 1878 } 1879 return NULL; 1880 } 1881 1882 return *wrapperpp; 1883} 1884/* }}} */ 1885 1886/* {{{ _php_stream_mkdir 1887 */ 1888PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC) 1889{ 1890 php_stream_wrapper *wrapper = NULL; 1891 1892 wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); 1893 if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) { 1894 return 0; 1895 } 1896 1897 return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC); 1898} 1899/* }}} */ 1900 1901/* {{{ _php_stream_rmdir 1902 */ 1903PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC) 1904{ 1905 php_stream_wrapper *wrapper = NULL; 1906 1907 wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); 1908 if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) { 1909 return 0; 1910 } 1911 1912 return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC); 1913} 1914/* }}} */ 1915 1916/* {{{ _php_stream_stat_path */ 1917PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) 1918{ 1919 php_stream_wrapper *wrapper = NULL; 1920 char *path_to_open = path; 1921 int ret; 1922 1923 /* Try to hit the cache first */ 1924 if (flags & PHP_STREAM_URL_STAT_LINK) { 1925 if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) { 1926 memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf)); 1927 return 0; 1928 } 1929 } else { 1930 if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) { 1931 memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf)); 1932 return 0; 1933 } 1934 } 1935 1936 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC); 1937 if (wrapper && wrapper->wops->url_stat) { 1938 ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC); 1939 if (ret == 0) { 1940 /* Drop into cache */ 1941 if (flags & PHP_STREAM_URL_STAT_LINK) { 1942 if (BG(CurrentLStatFile)) { 1943 efree(BG(CurrentLStatFile)); 1944 } 1945 BG(CurrentLStatFile) = estrdup(path); 1946 memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf)); 1947 } else { 1948 if (BG(CurrentStatFile)) { 1949 efree(BG(CurrentStatFile)); 1950 } 1951 BG(CurrentStatFile) = estrdup(path); 1952 memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf)); 1953 } 1954 } 1955 return ret; 1956 } 1957 return -1; 1958} 1959/* }}} */ 1960 1961/* {{{ php_stream_opendir */ 1962PHPAPI php_stream *_php_stream_opendir(char *path, int options, 1963 php_stream_context *context STREAMS_DC TSRMLS_DC) 1964{ 1965 php_stream *stream = NULL; 1966 php_stream_wrapper *wrapper = NULL; 1967 char *path_to_open; 1968 1969 if (!path || !*path) { 1970 return NULL; 1971 } 1972 1973 path_to_open = path; 1974 1975 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC); 1976 1977 if (wrapper && wrapper->wops->dir_opener) { 1978 stream = wrapper->wops->dir_opener(wrapper, 1979 path_to_open, "r", options ^ REPORT_ERRORS, NULL, 1980 context STREAMS_REL_CC TSRMLS_CC); 1981 1982 if (stream) { 1983 stream->wrapper = wrapper; 1984 stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR; 1985 } 1986 } else if (wrapper) { 1987 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented"); 1988 } 1989 if (stream == NULL && (options & REPORT_ERRORS)) { 1990 php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC); 1991 } 1992 php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); 1993 1994 return stream; 1995} 1996/* }}} */ 1997 1998/* {{{ _php_stream_readdir */ 1999PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC) 2000{ 2001 2002 if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) { 2003 return ent; 2004 } 2005 2006 return NULL; 2007} 2008/* }}} */ 2009 2010/* {{{ php_stream_open_wrapper_ex */ 2011PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options, 2012 char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) 2013{ 2014 php_stream *stream = NULL; 2015 php_stream_wrapper *wrapper = NULL; 2016 char *path_to_open; 2017 int persistent = options & STREAM_OPEN_PERSISTENT; 2018 char *resolved_path = NULL; 2019 char *copy_of_path = NULL; 2020 2021 if (opened_path) { 2022 *opened_path = NULL; 2023 } 2024 2025 if (!path || !*path) { 2026 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty"); 2027 return NULL; 2028 } 2029 2030 if (options & USE_PATH) { 2031 resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC); 2032 if (resolved_path) { 2033 path = resolved_path; 2034 /* we've found this file, don't re-check include_path or run realpath */ 2035 options |= STREAM_ASSUME_REALPATH; 2036 options &= ~USE_PATH; 2037 } 2038 } 2039 2040 path_to_open = path; 2041 2042 wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC); 2043 if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) { 2044 php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs"); 2045 if (resolved_path) { 2046 efree(resolved_path); 2047 } 2048 return NULL; 2049 } 2050 2051 if (wrapper) { 2052 if (!wrapper->wops->stream_opener) { 2053 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, 2054 "wrapper does not support stream open"); 2055 } else { 2056 stream = wrapper->wops->stream_opener(wrapper, 2057 path_to_open, mode, options ^ REPORT_ERRORS, 2058 opened_path, context STREAMS_REL_CC TSRMLS_CC); 2059 } 2060 2061 /* if the caller asked for a persistent stream but the wrapper did not 2062 * return one, force an error here */ 2063 if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) { 2064 php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, 2065 "wrapper does not support persistent streams"); 2066 php_stream_close(stream); 2067 stream = NULL; 2068 } 2069 2070 if (stream) { 2071 stream->wrapper = wrapper; 2072 } 2073 } 2074 2075 if (stream) { 2076 if (opened_path && !*opened_path && resolved_path) { 2077 *opened_path = resolved_path; 2078 resolved_path = NULL; 2079 } 2080 if (stream->orig_path) { 2081 pefree(stream->orig_path, persistent); 2082 } 2083 copy_of_path = pestrdup(path, persistent); 2084 stream->orig_path = copy_of_path; 2085#if ZEND_DEBUG 2086 stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename; 2087 stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno; 2088#endif 2089 } 2090 2091 if (stream != NULL && (options & STREAM_MUST_SEEK)) { 2092 php_stream *newstream; 2093 2094 switch(php_stream_make_seekable_rel(stream, &newstream, 2095 (options & STREAM_WILL_CAST) 2096 ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) { 2097 case PHP_STREAM_UNCHANGED: 2098 if (resolved_path) { 2099 efree(resolved_path); 2100 } 2101 return stream; 2102 case PHP_STREAM_RELEASED: 2103 if (newstream->orig_path) { 2104 pefree(newstream->orig_path, persistent); 2105 } 2106 newstream->orig_path = pestrdup(path, persistent); 2107 if (resolved_path) { 2108 efree(resolved_path); 2109 } 2110 return newstream; 2111 default: 2112 php_stream_close(stream); 2113 stream = NULL; 2114 if (options & REPORT_ERRORS) { 2115 char *tmp = estrdup(path); 2116 php_strip_url_passwd(tmp); 2117 php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s", 2118 tmp); 2119 efree(tmp); 2120 2121 options ^= REPORT_ERRORS; 2122 } 2123 } 2124 } 2125 2126 if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) { 2127 off_t newpos = 0; 2128 2129 /* if opened for append, we need to revise our idea of the initial file position */ 2130 if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) { 2131 stream->position = newpos; 2132 } 2133 } 2134 2135 if (stream == NULL && (options & REPORT_ERRORS)) { 2136 php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC); 2137 if (opened_path && *opened_path) { 2138 efree(*opened_path); 2139 *opened_path = NULL; 2140 } 2141 } 2142 php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); 2143#if ZEND_DEBUG 2144 if (stream == NULL && copy_of_path != NULL) { 2145 pefree(copy_of_path, persistent); 2146 } 2147#endif 2148 if (resolved_path) { 2149 efree(resolved_path); 2150 } 2151 return stream; 2152} 2153/* }}} */ 2154 2155/* {{{ context API */ 2156PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context) 2157{ 2158 php_stream_context *oldcontext = stream->context; 2159 TSRMLS_FETCH(); 2160 2161 stream->context = context; 2162 2163 if (context) { 2164 zend_list_addref(context->rsrc_id); 2165 } 2166 if (oldcontext) { 2167 zend_list_delete(oldcontext->rsrc_id); 2168 } 2169 2170 return oldcontext; 2171} 2172 2173PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity, 2174 char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC) 2175{ 2176 if (context && context->notifier) 2177 context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC); 2178} 2179 2180PHPAPI void php_stream_context_free(php_stream_context *context) 2181{ 2182 if (context->options) { 2183 zval_ptr_dtor(&context->options); 2184 context->options = NULL; 2185 } 2186 if (context->notifier) { 2187 php_stream_notification_free(context->notifier); 2188 context->notifier = NULL; 2189 } 2190 if (context->links) { 2191 zval_ptr_dtor(&context->links); 2192 context->links = NULL; 2193 } 2194 efree(context); 2195} 2196 2197PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D) 2198{ 2199 php_stream_context *context; 2200 2201 context = ecalloc(1, sizeof(php_stream_context)); 2202 context->notifier = NULL; 2203 MAKE_STD_ZVAL(context->options); 2204 array_init(context->options); 2205 2206 context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C)); 2207 return context; 2208} 2209 2210PHPAPI php_stream_notifier *php_stream_notification_alloc(void) 2211{ 2212 return ecalloc(1, sizeof(php_stream_notifier)); 2213} 2214 2215PHPAPI void php_stream_notification_free(php_stream_notifier *notifier) 2216{ 2217 if (notifier->dtor) { 2218 notifier->dtor(notifier); 2219 } 2220 efree(notifier); 2221} 2222 2223PHPAPI int php_stream_context_get_option(php_stream_context *context, 2224 const char *wrappername, const char *optionname, zval ***optionvalue) 2225{ 2226 zval **wrapperhash; 2227 2228 if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) { 2229 return FAILURE; 2230 } 2231 return zend_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue); 2232} 2233 2234PHPAPI int php_stream_context_set_option(php_stream_context *context, 2235 const char *wrappername, const char *optionname, zval *optionvalue) 2236{ 2237 zval **wrapperhash; 2238 zval *category, *copied_val; 2239 2240 ALLOC_INIT_ZVAL(copied_val); 2241 *copied_val = *optionvalue; 2242 zval_copy_ctor(copied_val); 2243 INIT_PZVAL(copied_val); 2244 2245 if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) { 2246 MAKE_STD_ZVAL(category); 2247 array_init(category); 2248 if (FAILURE == zend_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) { 2249 return FAILURE; 2250 } 2251 2252 wrapperhash = &category; 2253 } 2254 return zend_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL); 2255} 2256 2257PHPAPI int php_stream_context_get_link(php_stream_context *context, 2258 const char *hostent, php_stream **stream) 2259{ 2260 php_stream **pstream; 2261 2262 if (!stream || !hostent || !context || !(context->links)) { 2263 return FAILURE; 2264 } 2265 if (SUCCESS == zend_hash_find(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&pstream)) { 2266 *stream = *pstream; 2267 return SUCCESS; 2268 } 2269 return FAILURE; 2270} 2271 2272PHPAPI int php_stream_context_set_link(php_stream_context *context, 2273 const char *hostent, php_stream *stream) 2274{ 2275 if (!context) { 2276 return FAILURE; 2277 } 2278 if (!context->links) { 2279 ALLOC_INIT_ZVAL(context->links); 2280 array_init(context->links); 2281 } 2282 if (!stream) { 2283 /* Delete any entry for <hostent> */ 2284 return zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1); 2285 } 2286 return zend_hash_update(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&stream, sizeof(php_stream *), NULL); 2287} 2288 2289PHPAPI int php_stream_context_del_link(php_stream_context *context, 2290 php_stream *stream) 2291{ 2292 php_stream **pstream; 2293 char *hostent; 2294 int ret = SUCCESS; 2295 2296 if (!context || !context->links || !stream) { 2297 return FAILURE; 2298 } 2299 2300 for(zend_hash_internal_pointer_reset(Z_ARRVAL_P(context->links)); 2301 SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(context->links), (void**)&pstream); 2302 zend_hash_move_forward(Z_ARRVAL_P(context->links))) { 2303 if (*pstream == stream) { 2304 if (SUCCESS == zend_hash_get_current_key(Z_ARRVAL_P(context->links), &hostent, NULL, 0)) { 2305 if (FAILURE == zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1)) { 2306 ret = FAILURE; 2307 } 2308 } else { 2309 ret = FAILURE; 2310 } 2311 } 2312 } 2313 2314 return ret; 2315} 2316/* }}} */ 2317 2318/* {{{ php_stream_dirent_alphasort 2319 */ 2320PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b) 2321{ 2322 return strcoll(*a, *b); 2323} 2324/* }}} */ 2325 2326/* {{{ php_stream_dirent_alphasortr 2327 */ 2328PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b) 2329{ 2330 return strcoll(*b, *a); 2331} 2332/* }}} */ 2333 2334/* {{{ php_stream_scandir 2335 */ 2336PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context, 2337 int (*compare) (const char **a, const char **b) TSRMLS_DC) 2338{ 2339 php_stream *stream; 2340 php_stream_dirent sdp; 2341 char **vector = NULL; 2342 unsigned int vector_size = 0; 2343 unsigned int nfiles = 0; 2344 2345 if (!namelist) { 2346 return FAILURE; 2347 } 2348 2349 stream = php_stream_opendir(dirname, REPORT_ERRORS, context); 2350 if (!stream) { 2351 return FAILURE; 2352 } 2353 2354 while (php_stream_readdir(stream, &sdp)) { 2355 if (nfiles == vector_size) { 2356 if (vector_size == 0) { 2357 vector_size = 10; 2358 } else { 2359 if(vector_size*2 < vector_size) { 2360 /* overflow */ 2361 efree(vector); 2362 return FAILURE; 2363 } 2364 vector_size *= 2; 2365 } 2366 vector = (char **) safe_erealloc(vector, vector_size, sizeof(char *), 0); 2367 } 2368 2369 vector[nfiles] = estrdup(sdp.d_name); 2370 2371 nfiles++; 2372 if(vector_size < 10 || nfiles == 0) { 2373 /* overflow */ 2374 efree(vector); 2375 return FAILURE; 2376 } 2377 } 2378 php_stream_closedir(stream); 2379 2380 *namelist = vector; 2381 2382 if (compare) { 2383 qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare); 2384 } 2385 return nfiles; 2386} 2387/* }}} */ 2388 2389/* 2390 * Local variables: 2391 * tab-width: 4 2392 * c-basic-offset: 4 2393 * End: 2394 * vim600: noet sw=4 ts=4 fdm=marker 2395 * vim<600: noet sw=4 ts=4 2396 */ 2397