Back to index

php5  5.3.10
streams.c
Go to the documentation of this file.
00001 /*
00002    +----------------------------------------------------------------------+
00003    | PHP Version 5                                                        |
00004    +----------------------------------------------------------------------+
00005    | Copyright (c) 1997-2012 The PHP Group                                |
00006    +----------------------------------------------------------------------+
00007    | This source file is subject to version 3.01 of the PHP license,      |
00008    | that is bundled with this package in the file LICENSE, and is        |
00009    | available through the world-wide-web at the following url:           |
00010    | http://www.php.net/license/3_01.txt                                  |
00011    | If you did not receive a copy of the PHP license and are unable to   |
00012    | obtain it through the world-wide-web, please send a note to          |
00013    | license@php.net so we can mail you a copy immediately.               |
00014    +----------------------------------------------------------------------+
00015    | Authors: Wez Furlong <wez@thebrainroom.com>                          |
00016    | Borrowed code from:                                                  |
00017    |          Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
00018    |          Jim Winstead <jimw@php.net>                                 |
00019    +----------------------------------------------------------------------+
00020  */
00021 
00022 /* $Id: streams.c 321634 2012-01-01 13:15:04Z felipe $ */
00023 
00024 #define _GNU_SOURCE
00025 #include "php.h"
00026 #include "php_globals.h"
00027 #include "php_network.h"
00028 #include "php_open_temporary_file.h"
00029 #include "ext/standard/file.h"
00030 #include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */
00031 #include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */
00032 #include <stddef.h>
00033 #include <fcntl.h>
00034 #include "php_streams_int.h"
00035 
00036 /* {{{ resource and registration code */
00037 /* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */
00038 static HashTable url_stream_wrappers_hash;
00039 static int le_stream = FAILURE; /* true global */
00040 static int le_pstream = FAILURE; /* true global */
00041 static int le_stream_filter = FAILURE; /* true global */
00042 
00043 PHPAPI int php_file_le_stream(void)
00044 {
00045        return le_stream;
00046 }
00047 
00048 PHPAPI int php_file_le_pstream(void)
00049 {
00050        return le_pstream;
00051 }
00052 
00053 PHPAPI int php_file_le_stream_filter(void)
00054 {
00055        return le_stream_filter;
00056 }
00057 
00058 PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)
00059 {
00060        return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
00061 }
00062 
00063 PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void)
00064 {
00065        return &url_stream_wrappers_hash;
00066 }
00067 
00068 static int _php_stream_release_context(zend_rsrc_list_entry *le, void *pContext TSRMLS_DC)
00069 {
00070        if (le->ptr == pContext) {
00071               return --le->refcount == 0;
00072        }
00073        return 0;
00074 }
00075 
00076 static int forget_persistent_resource_id_numbers(zend_rsrc_list_entry *rsrc TSRMLS_DC)
00077 {
00078        php_stream *stream;
00079 
00080        if (Z_TYPE_P(rsrc) != le_pstream) {
00081               return 0;
00082        }
00083 
00084        stream = (php_stream*)rsrc->ptr;
00085 
00086 #if STREAM_DEBUG
00087 fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
00088 #endif
00089 
00090        stream->rsrc_id = FAILURE;
00091 
00092        if (stream->context) {
00093               zend_hash_apply_with_argument(&EG(regular_list),
00094                             (apply_func_arg_t) _php_stream_release_context,
00095                             stream->context TSRMLS_CC);
00096               stream->context = NULL;
00097        }
00098 
00099        return 0;
00100 }
00101 
00102 PHP_RSHUTDOWN_FUNCTION(streams)
00103 {
00104        zend_hash_apply(&EG(persistent_list), (apply_func_t)forget_persistent_resource_id_numbers TSRMLS_CC);
00105        return SUCCESS;
00106 }
00107 
00108 PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC)
00109 {
00110        zend_rsrc_list_entry *le;
00111 
00112        if (zend_hash_find(&EG(persistent_list), (char*)persistent_id, strlen(persistent_id)+1, (void*) &le) == SUCCESS) {
00113               if (Z_TYPE_P(le) == le_pstream) {
00114                      if (stream) {
00115                             HashPosition pos;
00116                             zend_rsrc_list_entry *regentry;
00117                             ulong index = -1; /* intentional */
00118 
00119                             /* see if this persistent resource already has been loaded to the
00120                              * regular list; allowing the same resource in several entries in the
00121                              * regular list causes trouble (see bug #54623) */
00122                             zend_hash_internal_pointer_reset_ex(&EG(regular_list), &pos);
00123                             while (zend_hash_get_current_data_ex(&EG(regular_list),
00124                                           (void **)&regentry, &pos) == SUCCESS) {
00125                                    if (regentry->ptr == le->ptr) {
00126                                           zend_hash_get_current_key_ex(&EG(regular_list), NULL, NULL,
00127                                                  &index, 0, &pos);
00128                                           break;
00129                                    }
00130                                    zend_hash_move_forward_ex(&EG(regular_list), &pos);
00131                             }
00132                             
00133                             *stream = (php_stream*)le->ptr;
00134                             if (index == -1) { /* not found in regular list */
00135                                    le->refcount++;
00136                                    (*stream)->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream);
00137                             } else {
00138                                    regentry->refcount++;
00139                                    (*stream)->rsrc_id = index;
00140                             }
00141                      }
00142                      return PHP_STREAM_PERSISTENT_SUCCESS;
00143               }
00144               return PHP_STREAM_PERSISTENT_FAILURE;
00145        }
00146        return PHP_STREAM_PERSISTENT_NOT_EXIST;
00147 }
00148 
00149 /* }}} */
00150 
00151 /* {{{ wrapper error reporting */
00152 void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC)
00153 {
00154        char *tmp = estrdup(path);
00155        char *msg;
00156        int free_msg = 0;
00157        php_stream_wrapper orig_wrapper;
00158 
00159        if (wrapper) {
00160               if (wrapper->err_count > 0) {
00161                      int i;
00162                      size_t l;
00163                      int brlen;
00164                      char *br;
00165 
00166                      if (PG(html_errors)) {
00167                             brlen = 7;
00168                             br = "<br />\n";
00169                      } else {
00170                             brlen = 1;
00171                             br = "\n";
00172                      }
00173 
00174                      for (i = 0, l = 0; i < wrapper->err_count; i++) {
00175                             l += strlen(wrapper->err_stack[i]);
00176                             if (i < wrapper->err_count - 1) {
00177                                    l += brlen;
00178                             }
00179                      }
00180                      msg = emalloc(l + 1);
00181                      msg[0] = '\0';
00182                      for (i = 0; i < wrapper->err_count; i++) {
00183                             strcat(msg, wrapper->err_stack[i]);
00184                             if (i < wrapper->err_count - 1) {
00185                                    strcat(msg, br);
00186                             }
00187                      }
00188 
00189                      free_msg = 1;
00190               } else {
00191                      if (wrapper == &php_plain_files_wrapper) {
00192                             msg = strerror(errno);
00193                      } else {
00194                             msg = "operation failed";
00195                      }
00196               }
00197        } else {
00198               msg = "no suitable wrapper could be found";
00199        }
00200 
00201        php_strip_url_passwd(tmp);
00202        if (wrapper) {
00203               /* see bug #52935 */
00204               orig_wrapper = *wrapper;
00205               wrapper->err_stack = NULL;
00206               wrapper->err_count = 0;
00207        }
00208        php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg);
00209        if (wrapper) {
00210               *wrapper = orig_wrapper;
00211        }
00212        efree(tmp);
00213        if (free_msg) {
00214               efree(msg);
00215        }
00216 }
00217 
00218 void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC)
00219 {
00220        if (wrapper) {
00221               /* tidy up the error stack */
00222               int i;
00223 
00224               for (i = 0; i < wrapper->err_count; i++) {
00225                      efree(wrapper->err_stack[i]);
00226               }
00227               if (wrapper->err_stack) {
00228                      efree(wrapper->err_stack);
00229               }
00230               wrapper->err_stack = NULL;
00231               wrapper->err_count = 0;
00232        }
00233 }
00234 
00235 PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...)
00236 {
00237        va_list args;
00238        char *buffer = NULL;
00239 
00240        va_start(args, fmt);
00241        vspprintf(&buffer, 0, fmt, args);
00242        va_end(args);
00243 
00244        if (options & REPORT_ERRORS || wrapper == NULL) {
00245               php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer);
00246               efree(buffer);
00247        } else {
00248               /* append to stack */
00249               wrapper->err_stack = erealloc(wrapper->err_stack, (wrapper->err_count + 1) * sizeof(char *));
00250               if (wrapper->err_stack) {
00251                      wrapper->err_stack[wrapper->err_count++] = buffer;
00252               }
00253        }
00254 }
00255 
00256 
00257 /* }}} */
00258 
00259 /* allocate a new stream for a particular ops */
00260 PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */
00261 {
00262        php_stream *ret;
00263 
00264        ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0);
00265 
00266        memset(ret, 0, sizeof(php_stream));
00267 
00268        ret->readfilters.stream = ret;
00269        ret->writefilters.stream = ret;
00270 
00271 #if STREAM_DEBUG
00272 fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id);
00273 #endif
00274 
00275        ret->ops = ops;
00276        ret->abstract = abstract;
00277        ret->is_persistent = persistent_id ? 1 : 0;
00278        ret->chunk_size = FG(def_chunk_size);
00279 
00280 #if ZEND_DEBUG
00281        ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
00282        ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
00283 #endif
00284 
00285        if (FG(auto_detect_line_endings)) {
00286               ret->flags |= PHP_STREAM_FLAG_DETECT_EOL;
00287        }
00288 
00289        if (persistent_id) {
00290               zend_rsrc_list_entry le;
00291 
00292               Z_TYPE(le) = le_pstream;
00293               le.ptr = ret;
00294               le.refcount = 0;
00295 
00296               if (FAILURE == zend_hash_update(&EG(persistent_list), (char *)persistent_id,
00297                                    strlen(persistent_id) + 1,
00298                                    (void *)&le, sizeof(le), NULL)) {
00299 
00300                      pefree(ret, 1);
00301                      return NULL;
00302               }
00303        }
00304 
00305        ret->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream);
00306        strlcpy(ret->mode, mode, sizeof(ret->mode));
00307 
00308        return ret;
00309 }
00310 /* }}} */
00311 
00312 static int _php_stream_free_persistent(zend_rsrc_list_entry *le, void *pStream TSRMLS_DC)
00313 {
00314        return le->ptr == pStream;
00315 }
00316 
00317 PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */
00318 {
00319        int ret = 1;
00320        int remove_rsrc = 1;
00321        int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0;
00322        int release_cast = 1;
00323        php_stream_context *context = stream->context;
00324 
00325        if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) {
00326               preserve_handle = 1;
00327        }
00328 
00329 #if STREAM_DEBUG
00330 fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%08x\n", stream->ops->label, stream, stream->orig_path, stream->in_free, close_options);
00331 #endif
00332 
00333        /* recursion protection */
00334        if (stream->in_free) {
00335               return 1;
00336        }
00337 
00338        stream->in_free++;
00339 
00340        /* if we are releasing the stream only (and preserving the underlying handle),
00341         * we need to do things a little differently.
00342         * We are only ever called like this when the stream is cast to a FILE*
00343         * for include (or other similar) purposes.
00344         * */
00345        if (preserve_handle) {
00346               if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
00347                      /* If the stream was fopencookied, we must NOT touch anything
00348                       * here, as the cookied stream relies on it all.
00349                       * Instead, mark the stream as OK to auto-clean */
00350                      php_stream_auto_cleanup(stream);
00351                      stream->in_free--;
00352                      return 0;
00353               }
00354               /* otherwise, make sure that we don't close the FILE* from a cast */
00355               release_cast = 0;
00356        }
00357 
00358 #if STREAM_DEBUG
00359 fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n",
00360               stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast, remove_rsrc);
00361 #endif
00362 
00363        /* make sure everything is saved */
00364        _php_stream_flush(stream, 1 TSRMLS_CC);
00365 
00366        /* If not called from the resource dtor, remove the stream from the resource list. */
00367        if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0 && remove_rsrc) {
00368               /* zend_list_delete actually only decreases the refcount; if we're
00369                * releasing the stream, we want to actually delete the resource from
00370                * the resource list, otherwise the resource will point to invalid memory.
00371                * In any case, let's always completely delete it from the resource list,
00372                * not only when PHP_STREAM_FREE_RELEASE_STREAM is set */
00373               while (zend_list_delete(stream->rsrc_id) == SUCCESS) {}
00374        }
00375 
00376        /* Remove stream from any context link list */
00377        if (stream->context && stream->context->links) {
00378               php_stream_context_del_link(stream->context, stream);
00379        }
00380 
00381        if (close_options & PHP_STREAM_FREE_CALL_DTOR) {
00382               if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
00383                      /* calling fclose on an fopencookied stream will ultimately
00384                             call this very same function.  If we were called via fclose,
00385                             the cookie_closer unsets the fclose_stdiocast flags, so
00386                             we can be sure that we only reach here when PHP code calls
00387                             php_stream_free.
00388                             Lets let the cookie code clean it all up.
00389                       */
00390                      stream->in_free = 0;
00391                      return fclose(stream->stdiocast);
00392               }
00393 
00394               ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
00395               stream->abstract = NULL;
00396 
00397               /* tidy up any FILE* that might have been fdopened */
00398               if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) {
00399                      fclose(stream->stdiocast);
00400                      stream->stdiocast = NULL;
00401                      stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
00402               }
00403        }
00404 
00405        if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
00406               while (stream->readfilters.head) {
00407                      php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC);
00408               }
00409               while (stream->writefilters.head) {
00410                      php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC);
00411               }
00412 
00413               if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
00414                      stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
00415                      stream->wrapper = NULL;
00416               }
00417 
00418               if (stream->wrapperdata) {
00419                      zval_ptr_dtor(&stream->wrapperdata);
00420                      stream->wrapperdata = NULL;
00421               }
00422 
00423               if (stream->readbuf) {
00424                      pefree(stream->readbuf, stream->is_persistent);
00425                      stream->readbuf = NULL;
00426               }
00427 
00428               if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) {
00429                      /* we don't work with *stream but need its value for comparison */
00430                      zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC);
00431               }
00432 #if ZEND_DEBUG
00433               if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) {
00434                      /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it
00435                       * as leaked; it will log a warning, but lets help it out and display what kind
00436                       * of stream it was. */
00437                      char *leakinfo;
00438                      spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path);
00439 
00440                      if (stream->orig_path) {
00441                             pefree(stream->orig_path, stream->is_persistent);
00442                             stream->orig_path = NULL;
00443                      }
00444 
00445 # if defined(PHP_WIN32)
00446                      OutputDebugString(leakinfo);
00447 # else
00448                      fprintf(stderr, "%s", leakinfo);
00449 # endif
00450                      efree(leakinfo);
00451               } else {
00452                      if (stream->orig_path) {
00453                             pefree(stream->orig_path, stream->is_persistent);
00454                             stream->orig_path = NULL;
00455                      }
00456 
00457                      pefree(stream, stream->is_persistent);
00458               }
00459 #else
00460               if (stream->orig_path) {
00461                      pefree(stream->orig_path, stream->is_persistent);
00462                      stream->orig_path = NULL;
00463               }
00464 
00465               pefree(stream, stream->is_persistent);
00466 #endif
00467        }
00468 
00469        if (context) {
00470               zend_list_delete(context->rsrc_id);
00471        }
00472 
00473        return ret;
00474 }
00475 /* }}} */
00476 
00477 /* {{{ generic stream operations */
00478 
00479 static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC)
00480 {
00481        /* allocate/fill the buffer */
00482 
00483        if (stream->readfilters.head) {
00484               char *chunk_buf;
00485               int err_flag = 0;
00486               php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
00487               php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
00488 
00489               /* Invalidate the existing cache, otherwise reads can fail, see note in
00490                  main/streams/filter.c::_php_stream_filter_append */
00491               stream->writepos = stream->readpos = 0;
00492 
00493               /* allocate a buffer for reading chunks */
00494               chunk_buf = emalloc(stream->chunk_size);
00495 
00496               while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) {
00497                      size_t justread = 0;
00498                      int flags;
00499                      php_stream_bucket *bucket;
00500                      php_stream_filter_status_t status = PSFS_ERR_FATAL;
00501                      php_stream_filter *filter;
00502 
00503                      /* read a chunk into a bucket */
00504                      justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);
00505                      if (justread && justread != (size_t)-1) {
00506                             bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC);
00507 
00508                             /* after this call, bucket is owned by the brigade */
00509                             php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
00510 
00511                             flags = PSFS_FLAG_NORMAL;
00512                      } else {
00513                             flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
00514                      }
00515 
00516                      /* wind the handle... */
00517                      for (filter = stream->readfilters.head; filter; filter = filter->next) {
00518                             status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC);
00519 
00520                             if (status != PSFS_PASS_ON) {
00521                                    break;
00522                             }
00523 
00524                             /* brig_out becomes brig_in.
00525                              * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
00526                              * to its own brigade */
00527                             brig_swap = brig_inp;
00528                             brig_inp = brig_outp;
00529                             brig_outp = brig_swap;
00530                             memset(brig_outp, 0, sizeof(*brig_outp));
00531                      }
00532 
00533                      switch (status) {
00534                             case PSFS_PASS_ON:
00535                                    /* we get here when the last filter in the chain has data to pass on.
00536                                     * in this situation, we are passing the brig_in brigade into the
00537                                     * stream read buffer */
00538                                    while (brig_inp->head) {
00539                                           bucket = brig_inp->head;
00540                                           /* grow buffer to hold this bucket
00541                                            * TODO: this can fail for persistent streams */
00542                                           if (stream->readbuflen - stream->writepos < bucket->buflen) {
00543                                                  stream->readbuflen += bucket->buflen;
00544                                                  stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
00545                                                                stream->is_persistent);
00546                                           }
00547                                           memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
00548                                           stream->writepos += bucket->buflen;
00549 
00550                                           php_stream_bucket_unlink(bucket TSRMLS_CC);
00551                                           php_stream_bucket_delref(bucket TSRMLS_CC);
00552                                    }
00553                                    break;
00554 
00555                             case PSFS_FEED_ME:
00556                                    /* when a filter needs feeding, there is no brig_out to deal with.
00557                                     * we simply continue the loop; if the caller needs more data,
00558                                     * we will read again, otherwise out job is done here */
00559                                    if (justread == 0) {
00560                                           /* there is no data */
00561                                           err_flag = 1;
00562                                           break;
00563                                    }
00564                                    continue;
00565 
00566                             case PSFS_ERR_FATAL:
00567                                    /* some fatal error. Theoretically, the stream is borked, so all
00568                                     * further reads should fail. */
00569                                    err_flag = 1;
00570                                    break;
00571                      }
00572 
00573                      if (justread == 0 || justread == (size_t)-1) {
00574                             break;
00575                      }
00576               }
00577 
00578               efree(chunk_buf);
00579 
00580        } else {
00581               /* is there enough data in the buffer ? */
00582               if (stream->writepos - stream->readpos < (off_t)size) {
00583                      size_t justread = 0;
00584 
00585                      /* reduce buffer memory consumption if possible, to avoid a realloc */
00586                      if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) {
00587                             memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos);
00588                             stream->writepos -= stream->readpos;
00589                             stream->readpos = 0;
00590                      }
00591 
00592                      /* grow the buffer if required
00593                       * TODO: this can fail for persistent streams */
00594                      if (stream->readbuflen - stream->writepos < stream->chunk_size) {
00595                             stream->readbuflen += stream->chunk_size;
00596                             stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
00597                                           stream->is_persistent);
00598                      }
00599 
00600                      justread = stream->ops->read(stream, stream->readbuf + stream->writepos,
00601                                    stream->readbuflen - stream->writepos
00602                                    TSRMLS_CC);
00603 
00604                      if (justread != (size_t)-1) {
00605                             stream->writepos += justread;
00606                      }
00607               }
00608        }
00609 }
00610 
00611 PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
00612 {
00613        size_t toread = 0, didread = 0;
00614 
00615        while (size > 0) {
00616 
00617               /* take from the read buffer first.
00618                * It is possible that a buffered stream was switched to non-buffered, so we
00619                * drain the remainder of the buffer before using the "raw" read mode for
00620                * the excess */
00621               if (stream->writepos > stream->readpos) {
00622 
00623                      toread = stream->writepos - stream->readpos;
00624                      if (toread > size) {
00625                             toread = size;
00626                      }
00627 
00628                      memcpy(buf, stream->readbuf + stream->readpos, toread);
00629                      stream->readpos += toread;
00630                      size -= toread;
00631                      buf += toread;
00632                      didread += toread;
00633               }
00634 
00635               /* ignore eof here; the underlying state might have changed */
00636               if (size == 0) {
00637                      break;
00638               }
00639 
00640               if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
00641                      toread = stream->ops->read(stream, buf, size TSRMLS_CC);
00642               } else {
00643                      php_stream_fill_read_buffer(stream, size TSRMLS_CC);
00644 
00645                      toread = stream->writepos - stream->readpos;
00646                      if (toread > size) {
00647                             toread = size;
00648                      }
00649 
00650                      if (toread > 0) {
00651                             memcpy(buf, stream->readbuf + stream->readpos, toread);
00652                             stream->readpos += toread;
00653                      }
00654               }
00655               if (toread > 0) {
00656                      didread += toread;
00657                      buf += toread;
00658                      size -= toread;
00659               } else {
00660                      /* EOF, or temporary end of data (for non-blocking mode). */
00661                      break;
00662               }
00663 
00664               /* just break anyway, to avoid greedy read */
00665               if (stream->wrapper != &php_plain_files_wrapper) {
00666                      break;
00667               }
00668        }
00669 
00670        if (didread > 0) {
00671               stream->position += didread;
00672        }
00673 
00674        return didread;
00675 }
00676 
00677 PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
00678 {
00679        /* if there is data in the buffer, it's not EOF */
00680        if (stream->writepos - stream->readpos > 0) {
00681               return 0;
00682        }
00683 
00684        /* use the configured timeout when checking eof */
00685        if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR ==
00686                      php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
00687                      0, NULL)) {
00688               stream->eof = 1;
00689        }
00690 
00691        return stream->eof;
00692 }
00693 
00694 PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
00695 {
00696        unsigned char buf = c;
00697 
00698        if (php_stream_write(stream, &buf, 1) > 0) {
00699               return 1;
00700        }
00701        return EOF;
00702 }
00703 
00704 PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC)
00705 {
00706        char buf;
00707 
00708        if (php_stream_read(stream, &buf, 1) > 0) {
00709               return buf & 0xff;
00710        }
00711        return EOF;
00712 }
00713 
00714 PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC)
00715 {
00716        int len;
00717        char newline[2] = "\n"; /* is this OK for Win? */
00718        len = strlen(buf);
00719 
00720        if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) {
00721               return 1;
00722        }
00723        return 0;
00724 }
00725 
00726 PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
00727 {
00728        memset(ssb, 0, sizeof(*ssb));
00729 
00730        /* if the stream was wrapped, allow the wrapper to stat it */
00731        if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) {
00732               return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC);
00733        }
00734 
00735        /* if the stream doesn't directly support stat-ing, return with failure.
00736         * We could try and emulate this by casting to a FD and fstat-ing it,
00737         * but since the fd might not represent the actual underlying content
00738         * this would give bogus results. */
00739        if (stream->ops->stat == NULL) {
00740               return -1;
00741        }
00742 
00743        return (stream->ops->stat)(stream, ssb TSRMLS_CC);
00744 }
00745 
00746 PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC)
00747 {
00748        size_t avail;
00749        char *cr, *lf, *eol = NULL;
00750        char *readptr;
00751 
00752        if (!buf) {
00753               readptr = stream->readbuf + stream->readpos;
00754               avail = stream->writepos - stream->readpos;
00755        } else {
00756               readptr = buf;
00757               avail = buf_len;
00758        }
00759 
00760        /* Look for EOL */
00761        if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
00762               cr = memchr(readptr, '\r', avail);
00763               lf = memchr(readptr, '\n', avail);
00764 
00765               if (cr && lf != cr + 1 && !(lf && lf < cr)) {
00766                      /* mac */
00767                      stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
00768                      stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
00769                      eol = cr;
00770               } else if ((cr && lf && cr == lf - 1) || (lf)) {
00771                      /* dos or unix endings */
00772                      stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
00773                      eol = lf;
00774               }
00775        } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
00776               eol = memchr(readptr, '\r', avail);
00777        } else {
00778               /* unix (and dos) line endings */
00779               eol = memchr(readptr, '\n', avail);
00780        }
00781 
00782        return eol;
00783 }
00784 
00785 /* If buf == NULL, the buffer will be allocated automatically and will be of an
00786  * appropriate length to hold the line, regardless of the line length, memory
00787  * permitting */
00788 PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
00789               size_t *returned_len TSRMLS_DC)
00790 {
00791        size_t avail = 0;
00792        size_t current_buf_size = 0;
00793        size_t total_copied = 0;
00794        int grow_mode = 0;
00795        char *bufstart = buf;
00796 
00797        if (buf == NULL) {
00798               grow_mode = 1;
00799        } else if (maxlen == 0) {
00800               return NULL;
00801        }
00802 
00803        /*
00804         * If the underlying stream operations block when no new data is readable,
00805         * we need to take extra precautions.
00806         *
00807         * If there is buffered data available, we check for a EOL. If it exists,
00808         * we pass the data immediately back to the caller. This saves a call
00809         * to the read implementation and will not block where blocking
00810         * is not necessary at all.
00811         *
00812         * If the stream buffer contains more data than the caller requested,
00813         * we can also avoid that costly step and simply return that data.
00814         */
00815 
00816        for (;;) {
00817               avail = stream->writepos - stream->readpos;
00818 
00819               if (avail > 0) {
00820                      size_t cpysz = 0;
00821                      char *readptr;
00822                      char *eol;
00823                      int done = 0;
00824 
00825                      readptr = stream->readbuf + stream->readpos;
00826                      eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC);
00827 
00828                      if (eol) {
00829                             cpysz = eol - readptr + 1;
00830                             done = 1;
00831                      } else {
00832                             cpysz = avail;
00833                      }
00834 
00835                      if (grow_mode) {
00836                             /* allow room for a NUL. If this realloc is really a realloc
00837                              * (ie: second time around), we get an extra byte. In most
00838                              * cases, with the default chunk size of 8K, we will only
00839                              * incur that overhead once.  When people have lines longer
00840                              * than 8K, we waste 1 byte per additional 8K or so.
00841                              * That seems acceptable to me, to avoid making this code
00842                              * hard to follow */
00843                             bufstart = erealloc(bufstart, current_buf_size + cpysz + 1);
00844                             current_buf_size += cpysz + 1;
00845                             buf = bufstart + total_copied;
00846                      } else {
00847                             if (cpysz >= maxlen - 1) {
00848                                    cpysz = maxlen - 1;
00849                                    done = 1;
00850                             }
00851                      }
00852 
00853                      memcpy(buf, readptr, cpysz);
00854 
00855                      stream->position += cpysz;
00856                      stream->readpos += cpysz;
00857                      buf += cpysz;
00858                      maxlen -= cpysz;
00859                      total_copied += cpysz;
00860 
00861                      if (done) {
00862                             break;
00863                      }
00864               } else if (stream->eof) {
00865                      break;
00866               } else {
00867                      /* XXX: Should be fine to always read chunk_size */
00868                      size_t toread;
00869 
00870                      if (grow_mode) {
00871                             toread = stream->chunk_size;
00872                      } else {
00873                             toread = maxlen - 1;
00874                             if (toread > stream->chunk_size) {
00875                                    toread = stream->chunk_size;
00876                             }
00877                      }
00878 
00879                      php_stream_fill_read_buffer(stream, toread TSRMLS_CC);
00880 
00881                      if (stream->writepos - stream->readpos == 0) {
00882                             break;
00883                      }
00884               }
00885        }
00886 
00887        if (total_copied == 0) {
00888               if (grow_mode) {
00889                      assert(bufstart == NULL);
00890               }
00891               return NULL;
00892        }
00893 
00894        buf[0] = '\0';
00895        if (returned_len) {
00896               *returned_len = total_copied;
00897        }
00898 
00899        return bufstart;
00900 }
00901 
00902 PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC)
00903 {
00904        char *e, *buf;
00905        size_t toread, len;
00906        int skip = 0;
00907 
00908        len = stream->writepos - stream->readpos;
00909 
00910        /* make sure the stream read buffer has maxlen bytes */
00911        while (len < maxlen) {
00912 
00913               size_t just_read;
00914               toread = MIN(maxlen - len, stream->chunk_size);
00915 
00916               php_stream_fill_read_buffer(stream, len + toread TSRMLS_CC);
00917 
00918               just_read = (stream->writepos - stream->readpos) - len;
00919               len += just_read;
00920 
00921               /* Assume the stream is temporarily or permanently out of data */
00922               if (just_read == 0) {
00923                      break;
00924               }
00925        }
00926 
00927        if (delim_len == 0 || !delim) {
00928               toread = maxlen;
00929        } else {
00930               size_t seek_len;
00931 
00932               /* set the maximum number of bytes we're allowed to read from buffer */
00933               seek_len = stream->writepos - stream->readpos;
00934               if (seek_len > maxlen) {
00935                      seek_len = maxlen;
00936               }
00937 
00938               if (delim_len == 1) {
00939                      e = memchr(stream->readbuf + stream->readpos, *delim, seek_len);
00940               } else {
00941                      e = php_memnstr(stream->readbuf + stream->readpos, delim, delim_len, (stream->readbuf + stream->readpos + seek_len));
00942               }
00943 
00944               if (!e) {
00945                      /* return with error if the delimiter string was not found, we
00946                       * could not completely fill the read buffer with maxlen bytes
00947                       * and we don't know we've reached end of file. Added with
00948                       * non-blocking streams in mind, where this situation is frequent */
00949                      if (seek_len < maxlen && !stream->eof) {
00950                             return NULL;
00951                      }
00952                      toread = maxlen;
00953               } else {
00954                      toread = e - (char *) stream->readbuf - stream->readpos;
00955                      /* we found the delimiter, so advance the read pointer past it */
00956                      skip = 1;
00957               }
00958        }
00959 
00960        if (toread > maxlen && maxlen > 0) {
00961               toread = maxlen;
00962        }
00963 
00964        buf = emalloc(toread + 1);
00965        *returned_len = php_stream_read(stream, buf, toread);
00966 
00967        if (skip) {
00968               stream->readpos += delim_len;
00969               stream->position += delim_len;
00970        }
00971        buf[*returned_len] = '\0';
00972        return buf;
00973 }
00974 
00975 /* Writes a buffer directly to a stream, using multiple of the chunk size */
00976 static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
00977 {
00978        size_t didwrite = 0, towrite, justwrote;
00979 
00980        /* if we have a seekable stream we need to ensure that data is written at the
00981         * current stream->position. This means invalidating the read buffer and then
00982         * performing a low-level seek */
00983        if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
00984               stream->readpos = stream->writepos = 0;
00985 
00986               stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
00987        }
00988 
00989 
00990        while (count > 0) {
00991               towrite = count;
00992               if (towrite > stream->chunk_size)
00993                      towrite = stream->chunk_size;
00994 
00995               justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC);
00996 
00997               /* convert justwrote to an integer, since normally it is unsigned */
00998               if ((int)justwrote > 0) {
00999                      buf += justwrote;
01000                      count -= justwrote;
01001                      didwrite += justwrote;
01002 
01003                      /* Only screw with the buffer if we can seek, otherwise we lose data
01004                       * buffered from fifos and sockets */
01005                      if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
01006                             stream->position += justwrote;
01007                      }
01008               } else {
01009                      break;
01010               }
01011        }
01012        return didwrite;
01013 
01014 }
01015 
01016 /* push some data through the write filter chain.
01017  * buf may be NULL, if flags are set to indicate a flush.
01018  * This may trigger a real write to the stream.
01019  * Returns the number of bytes consumed from buf by the first filter in the chain.
01020  * */
01021 static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC)
01022 {
01023        size_t consumed = 0;
01024        php_stream_bucket *bucket;
01025        php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
01026        php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
01027        php_stream_filter_status_t status = PSFS_ERR_FATAL;
01028        php_stream_filter *filter;
01029 
01030        if (buf) {
01031               bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC);
01032               php_stream_bucket_append(&brig_in, bucket TSRMLS_CC);
01033        }
01034 
01035        for (filter = stream->writefilters.head; filter; filter = filter->next) {
01036               /* for our return value, we are interested in the number of bytes consumed from
01037                * the first filter in the chain */
01038               status = filter->fops->filter(stream, filter, brig_inp, brig_outp,
01039                             filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC);
01040 
01041               if (status != PSFS_PASS_ON) {
01042                      break;
01043               }
01044               /* brig_out becomes brig_in.
01045                * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
01046                * to its own brigade */
01047               brig_swap = brig_inp;
01048               brig_inp = brig_outp;
01049               brig_outp = brig_swap;
01050               memset(brig_outp, 0, sizeof(*brig_outp));
01051        }
01052 
01053        switch (status) {
01054               case PSFS_PASS_ON:
01055                      /* filter chain generated some output; push it through to the
01056                       * underlying stream */
01057                      while (brig_inp->head) {
01058                             bucket = brig_inp->head;
01059                             _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC);
01060                             /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade
01061                              * hanging around and try to write it later.
01062                              * At the moment, we just drop it on the floor
01063                              * */
01064 
01065                             php_stream_bucket_unlink(bucket TSRMLS_CC);
01066                             php_stream_bucket_delref(bucket TSRMLS_CC);
01067                      }
01068                      break;
01069               case PSFS_FEED_ME:
01070                      /* need more data before we can push data through to the stream */
01071                      break;
01072 
01073               case PSFS_ERR_FATAL:
01074                      /* some fatal error.  Theoretically, the stream is borked, so all
01075                       * further writes should fail. */
01076                      break;
01077        }
01078 
01079        return consumed;
01080 }
01081 
01082 PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC)
01083 {
01084        int ret = 0;
01085 
01086        if (stream->writefilters.head) {
01087               _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC  TSRMLS_CC);
01088        }
01089 
01090        if (stream->ops->flush) {
01091               ret = stream->ops->flush(stream TSRMLS_CC);
01092        }
01093 
01094        return ret;
01095 }
01096 
01097 PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
01098 {
01099        if (buf == NULL || count == 0 || stream->ops->write == NULL) {
01100               return 0;
01101        }
01102 
01103        if (stream->writefilters.head) {
01104               return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC);
01105        } else {
01106               return _php_stream_write_buffer(stream, buf, count TSRMLS_CC);
01107        }
01108 }
01109 
01110 PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...)
01111 {
01112        size_t count;
01113        char *buf;
01114        va_list ap;
01115 
01116        va_start(ap, fmt);
01117        count = vspprintf(&buf, 0, fmt, ap);
01118        va_end(ap);
01119 
01120        if (!buf) {
01121               return 0; /* error condition */
01122        }
01123 
01124        count = php_stream_write(stream, buf, count);
01125        efree(buf);
01126 
01127        return count;
01128 }
01129 
01130 PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
01131 {
01132        return stream->position;
01133 }
01134 
01135 PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
01136 {
01137        if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
01138               /* flush to commit data written to the fopencookie FILE* */
01139               fflush(stream->stdiocast);
01140        }
01141 
01142        /* handle the case where we are in the buffer */
01143        if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
01144               switch(whence) {
01145                      case SEEK_CUR:
01146                             if (offset > 0 && offset <= stream->writepos - stream->readpos) {
01147                                    stream->readpos += offset; /* if offset = ..., then readpos = writepos */
01148                                    stream->position += offset;
01149                                    stream->eof = 0;
01150                                    return 0;
01151                             }
01152                             break;
01153                      case SEEK_SET:
01154                             if (offset > stream->position &&
01155                                           offset <= stream->position + stream->writepos - stream->readpos) {
01156                                    stream->readpos += offset - stream->position;
01157                                    stream->position = offset;
01158                                    stream->eof = 0;
01159                                    return 0;
01160                             }
01161                             break;
01162               }
01163        }
01164 
01165 
01166        if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
01167               int ret;
01168 
01169               if (stream->writefilters.head) {
01170                      _php_stream_flush(stream, 0 TSRMLS_CC);
01171               }
01172 
01173               switch(whence) {
01174                      case SEEK_CUR:
01175                             offset = stream->position + offset;
01176                             whence = SEEK_SET;
01177                             break;
01178               }
01179               ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC);
01180 
01181               if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
01182                      if (ret == 0) {
01183                             stream->eof = 0;
01184                      }
01185 
01186                      /* invalidate the buffer contents */
01187                      stream->readpos = stream->writepos = 0;
01188 
01189                      return ret;
01190               }
01191               /* else the stream has decided that it can't support seeking after all;
01192                * fall through to attempt emulation */
01193        }
01194 
01195        /* emulate forward moving seeks with reads */
01196        if (whence == SEEK_CUR && offset >= 0) {
01197               char tmp[1024];
01198               size_t didread;
01199               while(offset > 0) {
01200                      if ((didread = php_stream_read(stream, tmp, MIN(offset, sizeof(tmp)))) == 0) {
01201                             return -1;
01202                      }
01203                      offset -= didread;
01204               }
01205               stream->eof = 0;
01206               return 0;
01207        }
01208 
01209        php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking");
01210 
01211        return -1;
01212 }
01213 
01214 PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
01215 {
01216        int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
01217 
01218        if (stream->ops->set_option) {
01219               ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);
01220        }
01221 
01222        if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
01223               switch(option) {
01224                      case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
01225                             ret = stream->chunk_size;
01226                             stream->chunk_size = value;
01227                             return ret;
01228 
01229                      case PHP_STREAM_OPTION_READ_BUFFER:
01230                             /* try to match the buffer mode as best we can */
01231                             if (value == PHP_STREAM_BUFFER_NONE) {
01232                                    stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
01233                             } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) {
01234                                    stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
01235                             }
01236                             ret = PHP_STREAM_OPTION_RETURN_OK;
01237                             break;
01238 
01239                      default:
01240                             ;
01241               }
01242        }
01243 
01244        return ret;
01245 }
01246 
01247 PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
01248 {
01249        return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
01250 }
01251 
01252 PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
01253 {
01254        size_t bcount = 0;
01255        char buf[8192];
01256        int b;
01257 
01258        if (php_stream_mmap_possible(stream)) {
01259               char *p;
01260               size_t mapped;
01261 
01262               p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
01263 
01264               if (p) {
01265                      PHPWRITE(p, mapped);
01266 
01267                      php_stream_mmap_unmap_ex(stream, mapped);
01268 
01269                      return mapped;
01270               }
01271        }
01272 
01273        while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
01274               PHPWRITE(buf, b);
01275               bcount += b;
01276        }
01277 
01278        return bcount;
01279 }
01280 
01281 
01282 PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC)
01283 {
01284        size_t ret = 0;
01285        char *ptr;
01286        size_t len = 0, max_len;
01287        int step = CHUNK_SIZE;
01288        int min_room = CHUNK_SIZE / 4;
01289        php_stream_statbuf ssbuf;
01290 
01291        if (maxlen == 0) {
01292               return 0;
01293        }
01294 
01295        if (maxlen == PHP_STREAM_COPY_ALL) {
01296               maxlen = 0;
01297        }
01298 
01299        if (maxlen > 0) {
01300               ptr = *buf = pemalloc_rel_orig(maxlen + 1, persistent);
01301               while ((len < maxlen) && !php_stream_eof(src)) {
01302                      ret = php_stream_read(src, ptr, maxlen - len);
01303                      if (!ret) {
01304                             break;
01305                      }
01306                      len += ret;
01307                      ptr += ret;
01308               }
01309               *ptr = '\0';
01310               return len;
01311        }
01312 
01313        /* avoid many reallocs by allocating a good sized chunk to begin with, if
01314         * we can.  Note that the stream may be filtered, in which case the stat
01315         * result may be inaccurate, as the filter may inflate or deflate the
01316         * number of bytes that we can read.  In order to avoid an upsize followed
01317         * by a downsize of the buffer, overestimate by the step size (which is
01318         * 2K).  */
01319        if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
01320               max_len = ssbuf.sb.st_size + step;
01321        } else {
01322               max_len = step;
01323        }
01324 
01325        ptr = *buf = pemalloc_rel_orig(max_len, persistent);
01326 
01327        while((ret = php_stream_read(src, ptr, max_len - len))) {
01328               len += ret;
01329               if (len + min_room >= max_len) {
01330                      *buf = perealloc_rel_orig(*buf, max_len + step, persistent);
01331                      max_len += step;
01332                      ptr = *buf + len;
01333               } else {
01334                      ptr += ret;
01335               }
01336        }
01337        if (len) {
01338               *buf = perealloc_rel_orig(*buf, len + 1, persistent);
01339               (*buf)[len] = '\0';
01340        } else {
01341               pefree(*buf, persistent);
01342               *buf = NULL;
01343        }
01344        return len;
01345 }
01346 
01347 /* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
01348 PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
01349 {
01350        char buf[CHUNK_SIZE];
01351        size_t readchunk;
01352        size_t haveread = 0;
01353        size_t didread;
01354        size_t dummy;
01355        php_stream_statbuf ssbuf;
01356 
01357        if (!len) {
01358               len = &dummy;
01359        }
01360 
01361        if (maxlen == 0) {
01362               *len = 0;
01363               return SUCCESS;
01364        }
01365 
01366        if (maxlen == PHP_STREAM_COPY_ALL) {
01367               maxlen = 0;
01368        }
01369 
01370        if (php_stream_stat(src, &ssbuf) == 0) {
01371               if (ssbuf.sb.st_size == 0
01372 #ifdef S_ISREG
01373                      && S_ISREG(ssbuf.sb.st_mode)
01374 #endif
01375               ) {
01376                      *len = 0;
01377                      return SUCCESS;
01378               }
01379        }
01380 
01381        if (php_stream_mmap_possible(src)) {
01382               char *p;
01383               size_t mapped;
01384 
01385               p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
01386 
01387               if (p) {
01388                      mapped = php_stream_write(dest, p, mapped);
01389 
01390                      php_stream_mmap_unmap_ex(src, mapped);
01391 
01392                      *len = mapped;
01393 
01394                      /* we've got at least 1 byte to read.
01395                       * less than 1 is an error */
01396 
01397                      if (mapped > 0) {
01398                             return SUCCESS;
01399                      }
01400                      return FAILURE;
01401               }
01402        }
01403 
01404        while(1) {
01405               readchunk = sizeof(buf);
01406 
01407               if (maxlen && (maxlen - haveread) < readchunk) {
01408                      readchunk = maxlen - haveread;
01409               }
01410 
01411               didread = php_stream_read(src, buf, readchunk);
01412 
01413               if (didread) {
01414                      /* extra paranoid */
01415                      size_t didwrite, towrite;
01416                      char *writeptr;
01417 
01418                      towrite = didread;
01419                      writeptr = buf;
01420                      haveread += didread;
01421 
01422                      while(towrite) {
01423                             didwrite = php_stream_write(dest, writeptr, towrite);
01424                             if (didwrite == 0) {
01425                                    *len = haveread - (didread - towrite);
01426                                    return FAILURE;
01427                             }
01428 
01429                             towrite -= didwrite;
01430                             writeptr += didwrite;
01431                      }
01432               } else {
01433                      break;
01434               }
01435 
01436               if (maxlen - haveread == 0) {
01437                      break;
01438               }
01439        }
01440 
01441        *len = haveread;
01442 
01443        /* we've got at least 1 byte to read.
01444         * less than 1 is an error */
01445 
01446        if (haveread > 0 || src->eof) {
01447               return SUCCESS;
01448        }
01449        return FAILURE;
01450 }
01451 
01452 /* Returns the number of bytes moved.
01453  * Returns 1 when source len is 0.
01454  * Deprecated in favor of php_stream_copy_to_stream_ex() */
01455 ZEND_ATTRIBUTE_DEPRECATED
01456 PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
01457 {
01458        size_t len;
01459        int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
01460        if (ret == SUCCESS && len == 0 && maxlen != 0) {
01461               return 1;
01462        }
01463        return len;
01464 }
01465 /* }}} */
01466 
01467 /* {{{ wrapper init and registration */
01468 
01469 static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
01470 {
01471        php_stream *stream = (php_stream*)rsrc->ptr;
01472        /* set the return value for pclose */
01473        FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
01474 }
01475 
01476 static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
01477 {
01478        php_stream *stream = (php_stream*)rsrc->ptr;
01479        FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
01480 }
01481 
01482 void php_shutdown_stream_hashes(TSRMLS_D)
01483 {
01484        if (FG(stream_wrappers)) {
01485               zend_hash_destroy(FG(stream_wrappers));
01486               efree(FG(stream_wrappers));
01487               FG(stream_wrappers) = NULL;
01488        }
01489 
01490        if (FG(stream_filters)) {
01491               zend_hash_destroy(FG(stream_filters));
01492               efree(FG(stream_filters));
01493               FG(stream_filters) = NULL;
01494        }
01495 }
01496 
01497 int php_init_stream_wrappers(int module_number TSRMLS_DC)
01498 {
01499        le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
01500        le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
01501 
01502        /* Filters are cleaned up by the streams they're attached to */
01503        le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
01504 
01505        return (
01506                      zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS
01507                      &&
01508                      zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS
01509                      &&
01510                      zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS
01511                      &&
01512                      php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
01513                      &&
01514                      php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
01515 #if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
01516                      &&
01517                      php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
01518                      &&
01519                      php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
01520 #endif
01521               ) ? SUCCESS : FAILURE;
01522 }
01523 
01524 int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
01525 {
01526        zend_hash_destroy(&url_stream_wrappers_hash);
01527        zend_hash_destroy(php_get_stream_filters_hash_global());
01528        zend_hash_destroy(php_stream_xport_get_hash());
01529        return SUCCESS;
01530 }
01531 
01532 /* Validate protocol scheme names during registration
01533  * Must conform to /^[a-zA-Z0-9+.-]+$/
01534  */
01535 static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len)
01536 {
01537        int i;
01538 
01539        for(i = 0; i < protocol_len; i++) {
01540               if (!isalnum((int)protocol[i]) &&
01541                      protocol[i] != '+' &&
01542                      protocol[i] != '-' &&
01543                      protocol[i] != '.') {
01544                      return FAILURE;
01545               }
01546        }
01547 
01548        return SUCCESS;
01549 }
01550 
01551 /* API for registering GLOBAL wrappers */
01552 PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
01553 {
01554        int protocol_len = strlen(protocol);
01555 
01556        if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
01557               return FAILURE;
01558        }
01559 
01560        return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
01561 }
01562 
01563 PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC)
01564 {
01565        return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1);
01566 }
01567 
01568 static void clone_wrapper_hash(TSRMLS_D)
01569 {
01570        php_stream_wrapper *tmp;
01571 
01572        ALLOC_HASHTABLE(FG(stream_wrappers));
01573        zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
01574        zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp));
01575 }
01576 
01577 /* API for registering VOLATILE wrappers */
01578 PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
01579 {
01580        int protocol_len = strlen(protocol);
01581 
01582        if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
01583               return FAILURE;
01584        }
01585 
01586        if (!FG(stream_wrappers)) {
01587               clone_wrapper_hash(TSRMLS_C);
01588        }
01589 
01590        return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
01591 }
01592 
01593 PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC)
01594 {
01595        if (!FG(stream_wrappers)) {
01596               clone_wrapper_hash(TSRMLS_C);
01597        }
01598 
01599        return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1);
01600 }
01601 /* }}} */
01602 
01603 /* {{{ php_stream_locate_url_wrapper */
01604 PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC)
01605 {
01606        HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
01607        php_stream_wrapper **wrapperpp = NULL;
01608        const char *p, *protocol = NULL;
01609        int n = 0;
01610 
01611        if (path_for_open) {
01612               *path_for_open = (char*)path;
01613        }
01614 
01615        if (options & IGNORE_URL) {
01616               return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
01617        }
01618 
01619        for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
01620               n++;
01621        }
01622 
01623        if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
01624               protocol = path;
01625        } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
01626               /* BC with older php scripts and zlib wrapper */
01627               protocol = "compress.zlib";
01628               n = 13;
01629               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
01630        }
01631 
01632        if (protocol) {
01633               char *tmp = estrndup(protocol, n);
01634               if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
01635                      php_strtolower(tmp, n);
01636                      if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
01637                             char wrapper_name[32];
01638 
01639                             if (n >= sizeof(wrapper_name)) {
01640                                    n = sizeof(wrapper_name) - 1;
01641                             }
01642                             PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
01643 
01644                             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);
01645 
01646                             wrapperpp = NULL;
01647                             protocol = NULL;
01648                      }
01649               }
01650               efree(tmp);
01651        }
01652        /* TODO: curl based streams probably support file:// properly */
01653        if (!protocol || !strncasecmp(protocol, "file", n))     {
01654               /* fall back on regular file access */
01655               php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
01656 
01657               if (protocol) {
01658                      int localhost = 0;
01659 
01660                      if (!strncasecmp(path, "file://localhost/", 17)) {
01661                             localhost = 1;
01662                      }
01663 
01664 #ifdef PHP_WIN32
01665                      if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':')    {
01666 #else
01667                      if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
01668 #endif
01669                             if (options & REPORT_ERRORS) {
01670                                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
01671                             }
01672                             return NULL;
01673                      }
01674 
01675                      if (path_for_open) {
01676                             /* skip past protocol and :/, but handle windows correctly */
01677                             *path_for_open = (char*)path + n + 1;
01678                             if (localhost == 1) {
01679                                    (*path_for_open) += 11;
01680                             }
01681                             while (*(++*path_for_open)=='/');
01682 #ifdef PHP_WIN32
01683                             if (*(*path_for_open + 1) != ':')
01684 #endif
01685                                    (*path_for_open)--;
01686                      }
01687               }
01688 
01689               if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
01690                      return NULL;
01691               }
01692 
01693               if (FG(stream_wrappers)) {
01694               /* The file:// wrapper may have been disabled/overridden */
01695 
01696                      if (wrapperpp) {
01697                             /* It was found so go ahead and provide it */
01698                             return *wrapperpp;
01699                      }
01700 
01701                      /* Check again, the original check might have not known the protocol name */
01702                      if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) {
01703                             return *wrapperpp;
01704                      }
01705 
01706                      if (options & REPORT_ERRORS) {
01707                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
01708                      }
01709                      return NULL;
01710               }
01711 
01712               return plain_files_wrapper;
01713        }
01714 
01715        if (wrapperpp && (*wrapperpp)->is_url &&
01716         (options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
01717            (!PG(allow_url_fopen) ||
01718             (((options & STREAM_OPEN_FOR_INCLUDE) ||
01719               PG(in_user_include)) && !PG(allow_url_include)))) {
01720               if (options & REPORT_ERRORS) {
01721                      /* protocol[n] probably isn't '\0' */
01722                      char *protocol_dup = estrndup(protocol, n);
01723                      if (!PG(allow_url_fopen)) {
01724                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
01725                      } else {
01726                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
01727                      }
01728                      efree(protocol_dup);
01729               }
01730               return NULL;
01731        }
01732 
01733        return *wrapperpp;
01734 }
01735 /* }}} */
01736 
01737 /* {{{ _php_stream_mkdir
01738  */
01739 PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
01740 {
01741        php_stream_wrapper *wrapper = NULL;
01742 
01743        wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC);
01744        if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
01745               return 0;
01746        }
01747 
01748        return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
01749 }
01750 /* }}} */
01751 
01752 /* {{{ _php_stream_rmdir
01753  */
01754 PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC)
01755 {
01756        php_stream_wrapper *wrapper = NULL;
01757 
01758        wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC);
01759        if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
01760               return 0;
01761        }
01762 
01763        return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
01764 }
01765 /* }}} */
01766 
01767 /* {{{ _php_stream_stat_path */
01768 PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
01769 {
01770        php_stream_wrapper *wrapper = NULL;
01771        char *path_to_open = path;
01772        int ret;
01773 
01774        /* Try to hit the cache first */
01775        if (flags & PHP_STREAM_URL_STAT_LINK) {
01776               if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
01777                      memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
01778                      return 0;
01779               }
01780        } else {
01781               if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
01782                      memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
01783                      return 0;
01784               }
01785        }
01786 
01787        wrapper = php_stream_locate_url_wrapper(path, &path_to_open, ENFORCE_SAFE_MODE TSRMLS_CC);
01788        if (wrapper && wrapper->wops->url_stat) {
01789               ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
01790               if (ret == 0) {
01791                      /* Drop into cache */
01792                      if (flags & PHP_STREAM_URL_STAT_LINK) {
01793                             if (BG(CurrentLStatFile)) {
01794                                    efree(BG(CurrentLStatFile));
01795                             }
01796                             BG(CurrentLStatFile) = estrdup(path);
01797                             memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
01798                      } else {
01799                             if (BG(CurrentStatFile)) {
01800                                    efree(BG(CurrentStatFile));
01801                             }
01802                             BG(CurrentStatFile) = estrdup(path);
01803                             memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
01804                      }
01805               }
01806               return ret;
01807        }
01808        return -1;
01809 }
01810 /* }}} */
01811 
01812 /* {{{ php_stream_opendir */
01813 PHPAPI php_stream *_php_stream_opendir(char *path, int options,
01814               php_stream_context *context STREAMS_DC TSRMLS_DC)
01815 {
01816        php_stream *stream = NULL;
01817        php_stream_wrapper *wrapper = NULL;
01818        char *path_to_open;
01819 
01820        if (!path || !*path) {
01821               return NULL;
01822        }
01823 
01824        path_to_open = path;
01825 
01826        wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
01827 
01828        if (wrapper && wrapper->wops->dir_opener) {
01829               stream = wrapper->wops->dir_opener(wrapper,
01830                             path_to_open, "r", options ^ REPORT_ERRORS, NULL,
01831                             context STREAMS_REL_CC TSRMLS_CC);
01832 
01833               if (stream) {
01834                      stream->wrapper = wrapper;
01835                      stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
01836               }
01837        } else if (wrapper) {
01838               php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
01839        }
01840        if (stream == NULL && (options & REPORT_ERRORS)) {
01841               php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
01842        }
01843        php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
01844 
01845        return stream;
01846 }
01847 /* }}} */
01848 
01849 /* {{{ _php_stream_readdir */
01850 PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
01851 {
01852 
01853        if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
01854               return ent;
01855        }
01856 
01857        return NULL;
01858 }
01859 /* }}} */
01860 
01861 /* {{{ php_stream_open_wrapper_ex */
01862 PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options,
01863               char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
01864 {
01865        php_stream *stream = NULL;
01866        php_stream_wrapper *wrapper = NULL;
01867        char *path_to_open;
01868        int persistent = options & STREAM_OPEN_PERSISTENT;
01869        char *resolved_path = NULL;
01870        char *copy_of_path = NULL;
01871 
01872 
01873        if (opened_path) {
01874               *opened_path = NULL;
01875        }
01876 
01877        if (!path || !*path) {
01878               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
01879               return NULL;
01880        }
01881 
01882        if (options & USE_PATH) {
01883               resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC);
01884               if (resolved_path) {
01885                      path = resolved_path;
01886                      /* we've found this file, don't re-check include_path or run realpath */
01887                      options |= STREAM_ASSUME_REALPATH;
01888                      options &= ~USE_PATH;
01889               }
01890        }
01891 
01892        path_to_open = path;
01893 
01894        wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
01895        if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
01896               php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
01897               if (resolved_path) {
01898                      efree(resolved_path);
01899               }
01900               return NULL;
01901        }
01902 
01903        if (wrapper) {
01904               if (!wrapper->wops->stream_opener) {
01905                      php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
01906                                    "wrapper does not support stream open");
01907               } else {
01908                      stream = wrapper->wops->stream_opener(wrapper,
01909                             path_to_open, mode, options ^ REPORT_ERRORS,
01910                             opened_path, context STREAMS_REL_CC TSRMLS_CC);
01911               }
01912 
01913               /* if the caller asked for a persistent stream but the wrapper did not
01914                * return one, force an error here */
01915               if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
01916                      php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
01917                                    "wrapper does not support persistent streams");
01918                      php_stream_close(stream);
01919                      stream = NULL;
01920               }
01921 
01922               if (stream) {
01923                      stream->wrapper = wrapper;
01924               }
01925        }
01926 
01927        if (stream) {
01928               if (opened_path && !*opened_path && resolved_path) {
01929                      *opened_path = resolved_path;
01930                      resolved_path = NULL;
01931               }
01932               if (stream->orig_path) {
01933                      pefree(stream->orig_path, persistent);
01934               }
01935               copy_of_path = pestrdup(path, persistent);
01936               stream->orig_path = copy_of_path;
01937 #if ZEND_DEBUG
01938               stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
01939               stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
01940 #endif
01941        }
01942 
01943        if (stream != NULL && (options & STREAM_MUST_SEEK)) {
01944               php_stream *newstream;
01945 
01946               switch(php_stream_make_seekable_rel(stream, &newstream,
01947                                    (options & STREAM_WILL_CAST)
01948                                           ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
01949                      case PHP_STREAM_UNCHANGED:
01950                             if (resolved_path) {
01951                                    efree(resolved_path);
01952                             }
01953                             return stream;
01954                      case PHP_STREAM_RELEASED:
01955                             if (newstream->orig_path) {
01956                                    pefree(newstream->orig_path, persistent);
01957                             }
01958                             newstream->orig_path = pestrdup(path, persistent);
01959                             if (resolved_path) {
01960                                    efree(resolved_path);
01961                             }
01962                             return newstream;
01963                      default:
01964                             php_stream_close(stream);
01965                             stream = NULL;
01966                             if (options & REPORT_ERRORS) {
01967                                    char *tmp = estrdup(path);
01968                                    php_strip_url_passwd(tmp);
01969                                    php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
01970                                                  tmp);
01971                                    efree(tmp);
01972 
01973                                    options ^= REPORT_ERRORS;
01974                             }
01975               }
01976        }
01977 
01978        if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
01979               off_t newpos = 0;
01980 
01981               /* if opened for append, we need to revise our idea of the initial file position */
01982               if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
01983                      stream->position = newpos;
01984               }
01985        }
01986 
01987        if (stream == NULL && (options & REPORT_ERRORS)) {
01988               php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
01989               if (opened_path && *opened_path) {
01990                      efree(*opened_path);
01991                      *opened_path = NULL;
01992               }
01993        }
01994        php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
01995 #if ZEND_DEBUG
01996        if (stream == NULL && copy_of_path != NULL) {
01997               pefree(copy_of_path, persistent);
01998        }
01999 #endif
02000        if (resolved_path) {
02001               efree(resolved_path);
02002        }
02003        return stream;
02004 }
02005 /* }}} */
02006 
02007 /* {{{ context API */
02008 PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context)
02009 {
02010        php_stream_context *oldcontext = stream->context;
02011        TSRMLS_FETCH();
02012 
02013        stream->context = context;
02014 
02015        if (context) {
02016               zend_list_addref(context->rsrc_id);
02017        }
02018        if (oldcontext) {
02019               zend_list_delete(oldcontext->rsrc_id);
02020        }
02021 
02022        return oldcontext;
02023 }
02024 
02025 PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
02026               char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
02027 {
02028        if (context && context->notifier)
02029               context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
02030 }
02031 
02032 PHPAPI void php_stream_context_free(php_stream_context *context)
02033 {
02034        if (context->options) {
02035               zval_ptr_dtor(&context->options);
02036               context->options = NULL;
02037        }
02038        if (context->notifier) {
02039               php_stream_notification_free(context->notifier);
02040               context->notifier = NULL;
02041        }
02042        if (context->links) {
02043               zval_ptr_dtor(&context->links);
02044               context->links = NULL;
02045        }
02046        efree(context);
02047 }
02048 
02049 PHPAPI php_stream_context *php_stream_context_alloc(void)
02050 {
02051        php_stream_context *context;
02052 
02053        context = ecalloc(1, sizeof(php_stream_context));
02054        context->notifier = NULL;
02055        MAKE_STD_ZVAL(context->options);
02056        array_init(context->options);
02057 
02058        context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context());
02059        return context;
02060 }
02061 
02062 PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
02063 {
02064        return ecalloc(1, sizeof(php_stream_notifier));
02065 }
02066 
02067 PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
02068 {
02069        if (notifier->dtor) {
02070               notifier->dtor(notifier);
02071        }
02072        efree(notifier);
02073 }
02074 
02075 PHPAPI int php_stream_context_get_option(php_stream_context *context,
02076               const char *wrappername, const char *optionname, zval ***optionvalue)
02077 {
02078        zval **wrapperhash;
02079 
02080        if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
02081               return FAILURE;
02082        }
02083        return zend_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue);
02084 }
02085 
02086 PHPAPI int php_stream_context_set_option(php_stream_context *context,
02087               const char *wrappername, const char *optionname, zval *optionvalue)
02088 {
02089        zval **wrapperhash;
02090        zval *category, *copied_val;
02091 
02092        ALLOC_INIT_ZVAL(copied_val);
02093        *copied_val = *optionvalue;
02094        zval_copy_ctor(copied_val);
02095        INIT_PZVAL(copied_val);
02096 
02097        if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
02098               MAKE_STD_ZVAL(category);
02099               array_init(category);
02100               if (FAILURE == zend_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) {
02101                      return FAILURE;
02102               }
02103 
02104               wrapperhash = &category;
02105        }
02106        return zend_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL);
02107 }
02108 
02109 PHPAPI int php_stream_context_get_link(php_stream_context *context,
02110         const char *hostent, php_stream **stream)
02111 {
02112        php_stream **pstream;
02113 
02114        if (!stream || !hostent || !context || !(context->links)) {
02115               return FAILURE;
02116        }
02117        if (SUCCESS == zend_hash_find(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&pstream)) {
02118               *stream = *pstream;
02119               return SUCCESS;
02120        }
02121        return FAILURE;
02122 }
02123 
02124 PHPAPI int php_stream_context_set_link(php_stream_context *context,
02125         const char *hostent, php_stream *stream)
02126 {
02127        if (!context) {
02128               return FAILURE;
02129        }
02130        if (!context->links) {
02131               ALLOC_INIT_ZVAL(context->links);
02132               array_init(context->links);
02133        }
02134        if (!stream) {
02135               /* Delete any entry for <hostent> */
02136               return zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1);
02137        }
02138        return zend_hash_update(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&stream, sizeof(php_stream *), NULL);
02139 }
02140 
02141 PHPAPI int php_stream_context_del_link(php_stream_context *context,
02142         php_stream *stream)
02143 {
02144        php_stream **pstream;
02145        char *hostent;
02146        int ret = SUCCESS;
02147 
02148        if (!context || !context->links || !stream) {
02149               return FAILURE;
02150        }
02151 
02152        for(zend_hash_internal_pointer_reset(Z_ARRVAL_P(context->links));
02153               SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(context->links), (void**)&pstream);
02154               zend_hash_move_forward(Z_ARRVAL_P(context->links))) {
02155               if (*pstream == stream) {
02156                      if (SUCCESS == zend_hash_get_current_key(Z_ARRVAL_P(context->links), &hostent, NULL, 0)) {
02157                             if (FAILURE == zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1)) {
02158                                    ret = FAILURE;
02159                             }
02160                      } else {
02161                             ret = FAILURE;
02162                      }
02163               }
02164        }
02165 
02166        return ret;
02167 }
02168 /* }}} */
02169 
02170 /* {{{ php_stream_dirent_alphasort
02171  */
02172 PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b)
02173 {
02174        return strcoll(*a, *b);
02175 }
02176 /* }}} */
02177 
02178 /* {{{ php_stream_dirent_alphasortr
02179  */
02180 PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b)
02181 {
02182        return strcoll(*b, *a);
02183 }
02184 /* }}} */
02185 
02186 /* {{{ php_stream_scandir
02187  */
02188 PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context,
02189                        int (*compare) (const char **a, const char **b) TSRMLS_DC)
02190 {
02191        php_stream *stream;
02192        php_stream_dirent sdp;
02193        char **vector = NULL;
02194        int vector_size = 0;
02195        int nfiles = 0;
02196 
02197        if (!namelist) {
02198               return FAILURE;
02199        }
02200 
02201        stream = php_stream_opendir(dirname, ENFORCE_SAFE_MODE | REPORT_ERRORS, context);
02202        if (!stream) {
02203               return FAILURE;
02204        }
02205 
02206        while (php_stream_readdir(stream, &sdp)) {
02207               if (nfiles == vector_size) {
02208                      if (vector_size == 0) {
02209                             vector_size = 10;
02210                      } else {
02211                             vector_size *= 2;
02212                      }
02213                      vector = (char **) erealloc(vector, vector_size * sizeof(char *));
02214               }
02215 
02216               vector[nfiles] = estrdup(sdp.d_name);
02217 
02218               nfiles++;
02219        }
02220        php_stream_closedir(stream);
02221 
02222        *namelist = vector;
02223 
02224        if (compare) {
02225               qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare);
02226        }
02227        return nfiles;
02228 }
02229 /* }}} */
02230 
02231 /*
02232  * Local variables:
02233  * tab-width: 4
02234  * c-basic-offset: 4
02235  * End:
02236  * vim600: noet sw=4 ts=4 fdm=marker
02237  * vim<600: noet sw=4 ts=4
02238  */