root/main/streams/streams.c

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

DEFINITIONS

This source file includes following definitions.
  1. php_file_le_stream
  2. php_file_le_pstream
  3. php_file_le_stream_filter
  4. _php_stream_get_url_stream_wrappers_hash
  5. php_stream_get_url_stream_wrappers_hash_global
  6. _php_stream_release_context
  7. forget_persistent_resource_id_numbers
  8. PHP_RSHUTDOWN_FUNCTION
  9. php_stream_encloses
  10. php_stream_from_persistent_id
  11. php_get_wrapper_errors_list
  12. php_stream_display_wrapper_errors
  13. php_stream_tidy_wrapper_error_log
  14. wrapper_error_dtor
  15. php_stream_wrapper_log_error
  16. _php_stream_alloc
  17. _php_stream_free_enclosed
  18. _php_stream_pretty_free_options
  19. _php_stream_free_persistent
  20. _php_stream_free
  21. _php_stream_fill_read_buffer
  22. _php_stream_read
  23. _php_stream_eof
  24. _php_stream_putc
  25. _php_stream_getc
  26. _php_stream_puts
  27. _php_stream_stat
  28. php_stream_locate_eol
  29. _php_stream_get_line
  30. _php_stream_search_delim
  31. php_stream_get_record
  32. _php_stream_write_buffer
  33. _php_stream_write_filtered
  34. _php_stream_flush
  35. _php_stream_write
  36. _php_stream_printf
  37. _php_stream_tell
  38. _php_stream_seek
  39. _php_stream_set_option
  40. _php_stream_truncate_set_size
  41. _php_stream_passthru
  42. _php_stream_copy_to_mem
  43. _php_stream_copy_to_stream_ex
  44. _php_stream_copy_to_stream
  45. stream_resource_regular_dtor
  46. stream_resource_persistent_dtor
  47. php_shutdown_stream_hashes
  48. php_init_stream_wrappers
  49. php_shutdown_stream_wrappers
  50. php_stream_wrapper_scheme_validate
  51. php_register_url_stream_wrapper
  52. php_unregister_url_stream_wrapper
  53. clone_wrapper_hash
  54. php_register_url_stream_wrapper_volatile
  55. php_unregister_url_stream_wrapper_volatile
  56. php_stream_locate_url_wrapper
  57. _php_stream_mkdir
  58. _php_stream_rmdir
  59. _php_stream_stat_path
  60. _php_stream_opendir
  61. _php_stream_readdir
  62. _php_stream_open_wrapper_ex
  63. php_stream_context_set
  64. php_stream_notification_notify
  65. php_stream_context_free
  66. php_stream_context_alloc
  67. php_stream_notification_alloc
  68. php_stream_notification_free
  69. php_stream_context_get_option
  70. php_stream_context_set_option
  71. php_stream_dirent_alphasort
  72. php_stream_dirent_alphasortr
  73. _php_stream_scandir

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

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