summaryrefslogtreecommitdiff
path: root/main/streams
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2013-03-14 05:42:27 +0000
committer <>2013-04-03 16:25:08 +0000
commitc4dd7a1a684490673e25aaf4fabec5df138854c4 (patch)
tree4d57c44caae4480efff02b90b9be86f44bf25409 /main/streams
downloadphp2-master.tar.gz
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'main/streams')
-rw-r--r--main/streams/cast.c418
-rw-r--r--main/streams/filter.c544
-rw-r--r--main/streams/glob_wrapper.c291
-rw-r--r--main/streams/memory.c758
-rw-r--r--main/streams/mmap.c75
-rw-r--r--main/streams/php_stream_context.h136
-rw-r--r--main/streams/php_stream_filter_api.h162
-rw-r--r--main/streams/php_stream_glob_wrapper.h44
-rw-r--r--main/streams/php_stream_mmap.h88
-rw-r--r--main/streams/php_stream_plain_wrapper.h66
-rw-r--r--main/streams/php_stream_transport.h211
-rw-r--r--main/streams/php_stream_userspace.h35
-rw-r--r--main/streams/php_streams_int.h71
-rw-r--r--main/streams/plain_wrapper.c1505
-rw-r--r--main/streams/streams.c2397
-rw-r--r--main/streams/transports.c532
-rw-r--r--main/streams/userspace.c1676
-rw-r--r--main/streams/xp_socket.c838
18 files changed, 9847 insertions, 0 deletions
diff --git a/main/streams/cast.c b/main/streams/cast.c
new file mode 100644
index 0000000..878b4b5
--- /dev/null
+++ b/main/streams/cast.c
@@ -0,0 +1,418 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#define _GNU_SOURCE
+#include "php.h"
+#include "php_globals.h"
+#include "php_network.h"
+#include "php_open_temporary_file.h"
+#include "ext/standard/file.h"
+#include <stddef.h>
+#include <fcntl.h>
+
+#include "php_streams_int.h"
+
+/* Under BSD, emulate fopencookie using funopen */
+#if defined(HAVE_FUNOPEN) && !defined(HAVE_FOPENCOOKIE)
+typedef struct {
+ int (*reader)(void *, char *, int);
+ int (*writer)(void *, const char *, int);
+ fpos_t (*seeker)(void *, fpos_t, int);
+ int (*closer)(void *);
+} COOKIE_IO_FUNCTIONS_T;
+
+FILE *fopencookie(void *cookie, const char *mode, COOKIE_IO_FUNCTIONS_T *funcs)
+{
+ return funopen(cookie, funcs->reader, funcs->writer, funcs->seeker, funcs->closer);
+}
+# define HAVE_FOPENCOOKIE 1
+# define PHP_EMULATE_FOPENCOOKIE 1
+# define PHP_STREAM_COOKIE_FUNCTIONS &stream_cookie_functions
+#elif defined(HAVE_FOPENCOOKIE)
+# define PHP_STREAM_COOKIE_FUNCTIONS stream_cookie_functions
+#endif
+
+/* {{{ STDIO with fopencookie */
+#if defined(PHP_EMULATE_FOPENCOOKIE)
+/* use our fopencookie emulation */
+static int stream_cookie_reader(void *cookie, char *buffer, int size)
+{
+ int ret;
+ TSRMLS_FETCH();
+
+ ret = php_stream_read((php_stream*)cookie, buffer, size);
+ return ret;
+}
+
+static int stream_cookie_writer(void *cookie, const char *buffer, int size)
+{
+ TSRMLS_FETCH();
+
+ return php_stream_write((php_stream *)cookie, (char *)buffer, size);
+}
+
+static fpos_t stream_cookie_seeker(void *cookie, off_t position, int whence)
+{
+ TSRMLS_FETCH();
+
+ return (fpos_t)php_stream_seek((php_stream *)cookie, position, whence);
+}
+
+static int stream_cookie_closer(void *cookie)
+{
+ php_stream *stream = (php_stream*)cookie;
+ TSRMLS_FETCH();
+
+ /* prevent recursion */
+ stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
+ return php_stream_close(stream);
+}
+#elif defined(HAVE_FOPENCOOKIE)
+static ssize_t stream_cookie_reader(void *cookie, char *buffer, size_t size)
+{
+ ssize_t ret;
+ TSRMLS_FETCH();
+
+ ret = php_stream_read(((php_stream *)cookie), buffer, size);
+ return ret;
+}
+
+static ssize_t stream_cookie_writer(void *cookie, const char *buffer, size_t size)
+{
+ TSRMLS_FETCH();
+
+ return php_stream_write(((php_stream *)cookie), (char *)buffer, size);
+}
+
+# ifdef COOKIE_SEEKER_USES_OFF64_T
+static int stream_cookie_seeker(void *cookie, __off64_t *position, int whence)
+{
+ TSRMLS_FETCH();
+
+ *position = php_stream_seek((php_stream *)cookie, (off_t)*position, whence);
+
+ if (*position == -1) {
+ return -1;
+ }
+ return 0;
+}
+# else
+static int stream_cookie_seeker(void *cookie, off_t position, int whence)
+{
+ TSRMLS_FETCH();
+
+ return php_stream_seek((php_stream *)cookie, position, whence);
+}
+# endif
+
+static int stream_cookie_closer(void *cookie)
+{
+ php_stream *stream = (php_stream*)cookie;
+ TSRMLS_FETCH();
+
+ /* prevent recursion */
+ stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
+ return php_stream_close(stream);
+}
+#endif /* elif defined(HAVE_FOPENCOOKIE) */
+
+#if HAVE_FOPENCOOKIE
+static COOKIE_IO_FUNCTIONS_T stream_cookie_functions =
+{
+ stream_cookie_reader, stream_cookie_writer,
+ stream_cookie_seeker, stream_cookie_closer
+};
+#else
+/* TODO: use socketpair() to emulate fopencookie, as suggested by Hartmut ? */
+#endif
+/* }}} */
+
+/* {{{ php_stream_mode_sanitize_fdopen_fopencookie
+ * Result should have at least size 5, e.g. to write wbx+\0 */
+void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result)
+{
+ /* replace modes not supported by fdopen and fopencookie, but supported
+ * by PHP's fread(), so that their calls won't fail */
+ const char *cur_mode = stream->mode;
+ int has_plus = 0,
+ has_bin = 0,
+ i,
+ res_curs = 0;
+
+ if (cur_mode[0] == 'r' || cur_mode[0] == 'w' || cur_mode[0] == 'a') {
+ result[res_curs++] = cur_mode[0];
+ } else {
+ /* assume cur_mode[0] is 'c' or 'x'; substitute by 'w', which should not
+ * truncate anything in fdopen/fopencookie */
+ result[res_curs++] = 'w';
+
+ /* x is allowed (at least by glibc & compat), but not as the 1st mode
+ * as in PHP and in any case is (at best) ignored by fdopen and fopencookie */
+ }
+
+ /* assume current mode has at most length 4 (e.g. wbn+) */
+ for (i = 1; i < 4 && cur_mode[i] != '\0'; i++) {
+ if (cur_mode[i] == 'b') {
+ has_bin = 1;
+ } else if (cur_mode[i] == '+') {
+ has_plus = 1;
+ }
+ /* ignore 'n', 't' or other stuff */
+ }
+
+ if (has_bin) {
+ result[res_curs++] = 'b';
+ }
+ if (has_plus) {
+ result[res_curs++] = '+';
+ }
+
+ result[res_curs] = '\0';
+}
+/* }}} */
+
+/* {{{ php_stream_cast */
+PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err TSRMLS_DC)
+{
+ int flags = castas & PHP_STREAM_CAST_MASK;
+ castas &= ~PHP_STREAM_CAST_MASK;
+
+ /* synchronize our buffer (if possible) */
+ if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT) {
+ php_stream_flush(stream);
+ if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
+ off_t dummy;
+
+ stream->ops->seek(stream, stream->position, SEEK_SET, &dummy TSRMLS_CC);
+ stream->readpos = stream->writepos = 0;
+ }
+ }
+
+ /* filtered streams can only be cast as stdio, and only when fopencookie is present */
+
+ if (castas == PHP_STREAM_AS_STDIO) {
+ if (stream->stdiocast) {
+ if (ret) {
+ *(FILE**)ret = stream->stdiocast;
+ }
+ goto exit_success;
+ }
+
+ /* if the stream is a stdio stream let's give it a chance to respond
+ * first, to avoid doubling up the layers of stdio with an fopencookie */
+ if (php_stream_is(stream, PHP_STREAM_IS_STDIO) &&
+ stream->ops->cast &&
+ !php_stream_is_filtered(stream) &&
+ stream->ops->cast(stream, castas, ret TSRMLS_CC) == SUCCESS
+ ) {
+ goto exit_success;
+ }
+
+#if HAVE_FOPENCOOKIE
+ /* if just checking, say yes we can be a FILE*, but don't actually create it yet */
+ if (ret == NULL) {
+ goto exit_success;
+ }
+
+ {
+ char fixed_mode[5];
+ php_stream_mode_sanitize_fdopen_fopencookie(stream, fixed_mode);
+ *(FILE**)ret = fopencookie(stream, fixed_mode, PHP_STREAM_COOKIE_FUNCTIONS);
+ }
+
+ if (*ret != NULL) {
+ off_t pos;
+
+ stream->fclose_stdiocast = PHP_STREAM_FCLOSE_FOPENCOOKIE;
+
+ /* If the stream position is not at the start, we need to force
+ * the stdio layer to believe it's real location. */
+ pos = php_stream_tell(stream);
+ if (pos > 0) {
+ fseek(*ret, pos, SEEK_SET);
+ }
+
+ goto exit_success;
+ }
+
+ /* must be either:
+ a) programmer error
+ b) no memory
+ -> lets bail
+ */
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "fopencookie failed");
+ return FAILURE;
+#endif
+
+ if (!php_stream_is_filtered(stream) && stream->ops->cast && stream->ops->cast(stream, castas, NULL TSRMLS_CC) == SUCCESS) {
+ if (FAILURE == stream->ops->cast(stream, castas, ret TSRMLS_CC)) {
+ return FAILURE;
+ }
+ goto exit_success;
+ } else if (flags & PHP_STREAM_CAST_TRY_HARD) {
+ php_stream *newstream;
+
+ newstream = php_stream_fopen_tmpfile();
+ if (newstream) {
+ int retcopy = php_stream_copy_to_stream_ex(stream, newstream, PHP_STREAM_COPY_ALL, NULL);
+
+ if (retcopy != SUCCESS) {
+ php_stream_close(newstream);
+ } else {
+ int retcast = php_stream_cast(newstream, castas | flags, (void **)ret, show_err);
+
+ if (retcast == SUCCESS) {
+ rewind(*(FILE**)ret);
+ }
+
+ /* do some specialized cleanup */
+ if ((flags & PHP_STREAM_CAST_RELEASE)) {
+ php_stream_free(stream, PHP_STREAM_FREE_CLOSE_CASTED);
+ }
+
+ /* TODO: we probably should be setting .stdiocast and .fclose_stdiocast or
+ * we may be leaking the FILE*. Needs investigation, though. */
+ return retcast;
+ }
+ }
+ }
+ }
+
+ if (php_stream_is_filtered(stream)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot cast a filtered stream on this system");
+ return FAILURE;
+ } else if (stream->ops->cast && stream->ops->cast(stream, castas, ret TSRMLS_CC) == SUCCESS) {
+ goto exit_success;
+ }
+
+ if (show_err) {
+ /* these names depend on the values of the PHP_STREAM_AS_XXX defines in php_streams.h */
+ static const char *cast_names[4] = {
+ "STDIO FILE*",
+ "File Descriptor",
+ "Socket Descriptor",
+ "select()able descriptor"
+ };
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]);
+ }
+
+ return FAILURE;
+
+exit_success:
+
+ if ((stream->writepos - stream->readpos) > 0 &&
+ stream->fclose_stdiocast != PHP_STREAM_FCLOSE_FOPENCOOKIE &&
+ (flags & PHP_STREAM_CAST_INTERNAL) == 0
+ ) {
+ /* the data we have buffered will be lost to the third party library that
+ * will be accessing the stream. Emit a warning so that the end-user will
+ * know that they should try something else */
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%ld bytes of buffered data lost during stream conversion!", (long)(stream->writepos - stream->readpos));
+ }
+
+ if (castas == PHP_STREAM_AS_STDIO && ret) {
+ stream->stdiocast = *(FILE**)ret;
+ }
+
+ if (flags & PHP_STREAM_CAST_RELEASE) {
+ php_stream_free(stream, PHP_STREAM_FREE_CLOSE_CASTED);
+ }
+
+ return SUCCESS;
+
+}
+/* }}} */
+
+/* {{{ php_stream_open_wrapper_as_file */
+PHPAPI FILE * _php_stream_open_wrapper_as_file(char *path, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC)
+{
+ FILE *fp = NULL;
+ php_stream *stream = NULL;
+
+ stream = php_stream_open_wrapper_rel(path, mode, options|STREAM_WILL_CAST, opened_path);
+
+ if (stream == NULL) {
+ return NULL;
+ }
+
+ if (php_stream_cast(stream, PHP_STREAM_AS_STDIO|PHP_STREAM_CAST_TRY_HARD|PHP_STREAM_CAST_RELEASE, (void**)&fp, REPORT_ERRORS) == FAILURE) {
+ php_stream_close(stream);
+ if (opened_path && *opened_path) {
+ efree(*opened_path);
+ }
+ return NULL;
+ }
+ return fp;
+}
+/* }}} */
+
+/* {{{ php_stream_make_seekable */
+PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC TSRMLS_DC)
+{
+ if (newstream == NULL) {
+ return PHP_STREAM_FAILED;
+ }
+ *newstream = NULL;
+
+ if (((flags & PHP_STREAM_FORCE_CONVERSION) == 0) && origstream->ops->seek != NULL) {
+ *newstream = origstream;
+ return PHP_STREAM_UNCHANGED;
+ }
+
+ /* Use a tmpfile and copy the old streams contents into it */
+
+ if (flags & PHP_STREAM_PREFER_STDIO) {
+ *newstream = php_stream_fopen_tmpfile();
+ } else {
+ *newstream = php_stream_temp_new();
+ }
+
+ if (*newstream == NULL) {
+ return PHP_STREAM_FAILED;
+ }
+
+#if ZEND_DEBUG
+ (*newstream)->open_filename = origstream->open_filename;
+ (*newstream)->open_lineno = origstream->open_lineno;
+#endif
+
+ if (php_stream_copy_to_stream_ex(origstream, *newstream, PHP_STREAM_COPY_ALL, NULL) != SUCCESS) {
+ php_stream_close(*newstream);
+ *newstream = NULL;
+ return PHP_STREAM_CRITICAL;
+ }
+
+ php_stream_close(origstream);
+ php_stream_seek(*newstream, 0, SEEK_SET);
+
+ return PHP_STREAM_RELEASED;
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/filter.c b/main/streams/filter.c
new file mode 100644
index 0000000..6de3a92
--- /dev/null
+++ b/main/streams/filter.c
@@ -0,0 +1,544 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_globals.h"
+#include "php_network.h"
+#include "php_open_temporary_file.h"
+#include "ext/standard/file.h"
+#include <stddef.h>
+#include <fcntl.h>
+
+#include "php_streams_int.h"
+
+/* Global filter hash, copied to FG(stream_filters) on registration of volatile filter */
+static HashTable stream_filters_hash;
+
+/* Should only be used during core initialization */
+PHPAPI HashTable *php_get_stream_filters_hash_global(void)
+{
+ return &stream_filters_hash;
+}
+
+/* Normal hash selection/retrieval call */
+PHPAPI HashTable *_php_get_stream_filters_hash(TSRMLS_D)
+{
+ return (FG(stream_filters) ? FG(stream_filters) : &stream_filters_hash);
+}
+
+/* API for registering GLOBAL filters */
+PHPAPI int php_stream_filter_register_factory(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC)
+{
+ return zend_hash_add(&stream_filters_hash, (char*)filterpattern, strlen(filterpattern) + 1, factory, sizeof(*factory), NULL);
+}
+
+PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern TSRMLS_DC)
+{
+ return zend_hash_del(&stream_filters_hash, (char*)filterpattern, strlen(filterpattern) + 1);
+}
+
+/* API for registering VOLATILE wrappers */
+PHPAPI int php_stream_filter_register_factory_volatile(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC)
+{
+ if (!FG(stream_filters)) {
+ php_stream_filter_factory tmpfactory;
+
+ ALLOC_HASHTABLE(FG(stream_filters));
+ zend_hash_init(FG(stream_filters), zend_hash_num_elements(&stream_filters_hash), NULL, NULL, 1);
+ zend_hash_copy(FG(stream_filters), &stream_filters_hash, NULL, &tmpfactory, sizeof(php_stream_filter_factory));
+ }
+
+ return zend_hash_add(FG(stream_filters), (char*)filterpattern, strlen(filterpattern) + 1, factory, sizeof(*factory), NULL);
+}
+
+/* Buckets */
+
+PHPAPI php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, int own_buf, int buf_persistent TSRMLS_DC)
+{
+ int is_persistent = php_stream_is_persistent(stream);
+ php_stream_bucket *bucket;
+
+ bucket = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), is_persistent);
+
+ if (bucket == NULL) {
+ return NULL;
+ }
+
+ bucket->next = bucket->prev = NULL;
+
+ if (is_persistent && !buf_persistent) {
+ /* all data in a persistent bucket must also be persistent */
+ bucket->buf = pemalloc(buflen, 1);
+
+ if (bucket->buf == NULL) {
+ pefree(bucket, 1);
+ return NULL;
+ }
+
+ memcpy(bucket->buf, buf, buflen);
+ bucket->buflen = buflen;
+ bucket->own_buf = 1;
+ } else {
+ bucket->buf = buf;
+ bucket->buflen = buflen;
+ bucket->own_buf = own_buf;
+ }
+ bucket->is_persistent = is_persistent;
+ bucket->refcount = 1;
+ bucket->brigade = NULL;
+
+ return bucket;
+}
+
+/* Given a bucket, returns a version of that bucket with a writeable buffer.
+ * If the original bucket has a refcount of 1 and owns its buffer, then it
+ * is returned unchanged.
+ * Otherwise, a copy of the buffer is made.
+ * In both cases, the original bucket is unlinked from its brigade.
+ * If a copy is made, the original bucket is delref'd.
+ * */
+PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bucket TSRMLS_DC)
+{
+ php_stream_bucket *retval;
+
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+
+ if (bucket->refcount == 1 && bucket->own_buf) {
+ return bucket;
+ }
+
+ retval = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), bucket->is_persistent);
+ memcpy(retval, bucket, sizeof(*retval));
+
+ retval->buf = pemalloc(retval->buflen, retval->is_persistent);
+ memcpy(retval->buf, bucket->buf, retval->buflen);
+
+ retval->refcount = 1;
+ retval->own_buf = 1;
+
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+
+ return retval;
+}
+
+PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length TSRMLS_DC)
+{
+ *left = (php_stream_bucket*)pecalloc(1, sizeof(php_stream_bucket), in->is_persistent);
+ *right = (php_stream_bucket*)pecalloc(1, sizeof(php_stream_bucket), in->is_persistent);
+
+ if (*left == NULL || *right == NULL) {
+ goto exit_fail;
+ }
+
+ (*left)->buf = pemalloc(length, in->is_persistent);
+ (*left)->buflen = length;
+ memcpy((*left)->buf, in->buf, length);
+ (*left)->refcount = 1;
+ (*left)->own_buf = 1;
+ (*left)->is_persistent = in->is_persistent;
+
+ (*right)->buflen = in->buflen - length;
+ (*right)->buf = pemalloc((*right)->buflen, in->is_persistent);
+ memcpy((*right)->buf, in->buf + length, (*right)->buflen);
+ (*right)->refcount = 1;
+ (*right)->own_buf = 1;
+ (*right)->is_persistent = in->is_persistent;
+
+ return SUCCESS;
+
+exit_fail:
+ if (*right) {
+ if ((*right)->buf) {
+ pefree((*right)->buf, in->is_persistent);
+ }
+ pefree(*right, in->is_persistent);
+ }
+ if (*left) {
+ if ((*left)->buf) {
+ pefree((*left)->buf, in->is_persistent);
+ }
+ pefree(*left, in->is_persistent);
+ }
+ return FAILURE;
+}
+
+PHPAPI void php_stream_bucket_delref(php_stream_bucket *bucket TSRMLS_DC)
+{
+ if (--bucket->refcount == 0) {
+ if (bucket->own_buf) {
+ pefree(bucket->buf, bucket->is_persistent);
+ }
+ pefree(bucket, bucket->is_persistent);
+ }
+}
+
+PHPAPI void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC)
+{
+ bucket->next = brigade->head;
+ bucket->prev = NULL;
+
+ if (brigade->head) {
+ brigade->head->prev = bucket;
+ } else {
+ brigade->tail = bucket;
+ }
+ brigade->head = bucket;
+ bucket->brigade = brigade;
+}
+
+PHPAPI void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC)
+{
+ if (brigade->tail == bucket) {
+ return;
+ }
+
+ bucket->prev = brigade->tail;
+ bucket->next = NULL;
+
+ if (brigade->tail) {
+ brigade->tail->next = bucket;
+ } else {
+ brigade->head = bucket;
+ }
+ brigade->tail = bucket;
+ bucket->brigade = brigade;
+}
+
+PHPAPI void php_stream_bucket_unlink(php_stream_bucket *bucket TSRMLS_DC)
+{
+ if (bucket->prev) {
+ bucket->prev->next = bucket->next;
+ } else if (bucket->brigade) {
+ bucket->brigade->head = bucket->next;
+ }
+ if (bucket->next) {
+ bucket->next->prev = bucket->prev;
+ } else if (bucket->brigade) {
+ bucket->brigade->tail = bucket->prev;
+ }
+ bucket->brigade = NULL;
+ bucket->next = bucket->prev = NULL;
+}
+
+
+
+
+
+
+
+
+/* We allow very simple pattern matching for filter factories:
+ * if "convert.charset.utf-8/sjis" is requested, we search first for an exact
+ * match. If that fails, we try "convert.charset.*", then "convert.*"
+ * This means that we don't need to clog up the hashtable with a zillion
+ * charsets (for example) but still be able to provide them all as filters */
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
+{
+ HashTable *filter_hash = (FG(stream_filters) ? FG(stream_filters) : &stream_filters_hash);
+ php_stream_filter_factory *factory = NULL;
+ php_stream_filter *filter = NULL;
+ int n;
+ char *period;
+
+ n = strlen(filtername);
+
+ if (SUCCESS == zend_hash_find(filter_hash, (char*)filtername, n + 1, (void**)&factory)) {
+ filter = factory->create_filter(filtername, filterparams, persistent TSRMLS_CC);
+ } else if ((period = strrchr(filtername, '.'))) {
+ /* try a wildcard */
+ char *wildname;
+
+ wildname = emalloc(n+3);
+ memcpy(wildname, filtername, n+1);
+ period = wildname + (period - filtername);
+ while (period && !filter) {
+ *period = '\0';
+ strncat(wildname, ".*", 2);
+ if (SUCCESS == zend_hash_find(filter_hash, wildname, strlen(wildname) + 1, (void**)&factory)) {
+ filter = factory->create_filter(filtername, filterparams, persistent TSRMLS_CC);
+ }
+
+ *period = '\0';
+ period = strrchr(wildname, '.');
+ }
+ efree(wildname);
+ }
+
+ if (filter == NULL) {
+ /* TODO: these need correct docrefs */
+ if (factory == NULL)
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to locate filter \"%s\"", filtername);
+ else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create or locate filter \"%s\"", filtername);
+ }
+
+ return filter;
+}
+
+PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, int persistent STREAMS_DC TSRMLS_DC)
+{
+ php_stream_filter *filter;
+
+ filter = (php_stream_filter*) pemalloc_rel_orig(sizeof(php_stream_filter), persistent);
+ memset(filter, 0, sizeof(php_stream_filter));
+
+ filter->fops = fops;
+ filter->abstract = abstract;
+ filter->is_persistent = persistent;
+
+ return filter;
+}
+
+PHPAPI void php_stream_filter_free(php_stream_filter *filter TSRMLS_DC)
+{
+ if (filter->fops->dtor)
+ filter->fops->dtor(filter TSRMLS_CC);
+ pefree(filter, filter->is_persistent);
+}
+
+PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC)
+{
+ filter->next = chain->head;
+ filter->prev = NULL;
+
+ if (chain->head) {
+ chain->head->prev = filter;
+ } else {
+ chain->tail = filter;
+ }
+ chain->head = filter;
+ filter->chain = chain;
+
+ return SUCCESS;
+}
+
+PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC)
+{
+ php_stream_filter_prepend_ex(chain, filter TSRMLS_CC);
+}
+
+PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC)
+{
+ php_stream *stream = chain->stream;
+
+ filter->prev = chain->tail;
+ filter->next = NULL;
+ if (chain->tail) {
+ chain->tail->next = filter;
+ } else {
+ chain->head = filter;
+ }
+ chain->tail = filter;
+ filter->chain = chain;
+
+ if (&(stream->readfilters) == chain && (stream->writepos - stream->readpos) > 0) {
+ /* Let's going ahead and wind anything in the buffer through this filter */
+ php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
+ php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out;
+ php_stream_filter_status_t status;
+ php_stream_bucket *bucket;
+ size_t consumed = 0;
+
+ bucket = php_stream_bucket_new(stream, (char*) stream->readbuf + stream->readpos, stream->writepos - stream->readpos, 0, 0 TSRMLS_CC);
+ php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
+ status = filter->fops->filter(stream, filter, brig_inp, brig_outp, &consumed, PSFS_FLAG_NORMAL TSRMLS_CC);
+
+ if (stream->readpos + consumed > (uint)stream->writepos) {
+ /* No behaving filter should cause this. */
+ status = PSFS_ERR_FATAL;
+ }
+
+ switch (status) {
+ case PSFS_ERR_FATAL:
+ while (brig_in.head) {
+ bucket = brig_in.head;
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ while (brig_out.head) {
+ bucket = brig_out.head;
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filter failed to process pre-buffered data");
+ return FAILURE;
+ case PSFS_FEED_ME:
+ /* We don't actually need data yet,
+ leave this filter in a feed me state until data is needed.
+ Reset stream's internal read buffer since the filter is "holding" it. */
+ stream->readpos = 0;
+ stream->writepos = 0;
+ break;
+ case PSFS_PASS_ON:
+ /* If any data is consumed, we cannot rely upon the existing read buffer,
+ as the filtered data must replace the existing data, so invalidate the cache */
+ /* note that changes here should be reflected in
+ main/streams/streams.c::php_stream_fill_read_buffer */
+ stream->writepos = 0;
+ stream->readpos = 0;
+
+ while (brig_outp->head) {
+ bucket = brig_outp->head;
+ /* Grow buffer to hold this bucket if need be.
+ TODO: See warning in main/stream/streams.c::php_stream_fill_read_buffer */
+ if (stream->readbuflen - stream->writepos < bucket->buflen) {
+ stream->readbuflen += bucket->buflen;
+ stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, stream->is_persistent);
+ }
+ memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
+ stream->writepos += bucket->buflen;
+
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC)
+{
+ if (php_stream_filter_append_ex(chain, filter TSRMLS_CC) != SUCCESS) {
+ if (chain->head == filter) {
+ chain->head = NULL;
+ chain->tail = NULL;
+ } else {
+ filter->prev->next = NULL;
+ chain->tail = filter->prev;
+ }
+ }
+}
+
+PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish TSRMLS_DC)
+{
+ php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;
+ php_stream_bucket *bucket;
+ php_stream_filter_chain *chain;
+ php_stream_filter *current;
+ php_stream *stream;
+ size_t flushed_size = 0;
+ long flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC);
+
+ if (!filter->chain || !filter->chain->stream) {
+ /* Filter is not attached to a chain, or chain is somehow not part of a stream */
+ return FAILURE;
+ }
+
+ chain = filter->chain;
+ stream = chain->stream;
+
+ for(current = filter; current; current = current->next) {
+ php_stream_filter_status_t status;
+
+ status = filter->fops->filter(stream, filter, inp, outp, NULL, flags TSRMLS_CC);
+ if (status == PSFS_FEED_ME) {
+ /* We've flushed the data far enough */
+ return SUCCESS;
+ }
+ if (status == PSFS_ERR_FATAL) {
+ return FAILURE;
+ }
+ /* Otherwise we have data available to PASS_ON
+ Swap the brigades and continue */
+ brig_temp = inp;
+ inp = outp;
+ outp = brig_temp;
+ outp->head = NULL;
+ outp->tail = NULL;
+
+ flags = PSFS_FLAG_NORMAL;
+ }
+
+ /* Last filter returned data via PSFS_PASS_ON
+ Do something with it */
+
+ for(bucket = inp->head; bucket; bucket = bucket->next) {
+ flushed_size += bucket->buflen;
+ }
+
+ if (flushed_size == 0) {
+ /* Unlikely, but possible */
+ return SUCCESS;
+ }
+
+ if (chain == &(stream->readfilters)) {
+ /* Dump any newly flushed data to the read buffer */
+ if (stream->readpos > 0) {
+ /* Back the buffer up */
+ memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
+ stream->readpos = 0;
+ stream->writepos -= stream->readpos;
+ }
+ if (flushed_size > (stream->readbuflen - stream->writepos)) {
+ /* Grow the buffer */
+ stream->readbuf = perealloc(stream->readbuf, stream->writepos + flushed_size + stream->chunk_size, stream->is_persistent);
+ }
+ while ((bucket = inp->head)) {
+ memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
+ stream->writepos += bucket->buflen;
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ } else if (chain == &(stream->writefilters)) {
+ /* Send flushed data to the stream */
+ while ((bucket = inp->head)) {
+ stream->ops->write(stream, bucket->buf, bucket->buflen TSRMLS_CC);
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ }
+
+ return SUCCESS;
+}
+
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor TSRMLS_DC)
+{
+ if (filter->prev) {
+ filter->prev->next = filter->next;
+ } else {
+ filter->chain->head = filter->next;
+ }
+ if (filter->next) {
+ filter->next->prev = filter->prev;
+ } else {
+ filter->chain->tail = filter->prev;
+ }
+
+ if (filter->rsrc_id > 0) {
+ zend_list_delete(filter->rsrc_id);
+ }
+
+ if (call_dtor) {
+ php_stream_filter_free(filter TSRMLS_CC);
+ return NULL;
+ }
+ return filter;
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/glob_wrapper.c b/main/streams/glob_wrapper.c
new file mode 100644
index 0000000..9c051a5
--- /dev/null
+++ b/main/streams/glob_wrapper.c
@@ -0,0 +1,291 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Marcus Boerger <helly@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_streams_int.h"
+
+#ifdef HAVE_GLOB
+# ifndef PHP_WIN32
+# include <glob.h>
+# else
+# include "win32/glob.h"
+# endif
+#endif
+
+#ifdef HAVE_GLOB
+#ifndef GLOB_ONLYDIR
+#define GLOB_ONLYDIR (1<<30)
+#define GLOB_FLAGMASK (~GLOB_ONLYDIR)
+#else
+#define GLOB_FLAGMASK (~0)
+#endif
+
+typedef struct {
+ glob_t glob;
+ size_t index;
+ int flags;
+ char *path;
+ size_t path_len;
+ char *pattern;
+ size_t pattern_len;
+} glob_s_t;
+
+PHPAPI char* _php_glob_stream_get_path(php_stream *stream, int copy, int *plen STREAMS_DC TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+
+ if (pglob && pglob->path) {
+ if (plen) {
+ *plen = pglob->path_len;
+ }
+ if (copy) {
+ return estrndup(pglob->path, pglob->path_len);
+ } else {
+ return pglob->path;
+ }
+ } else {
+ if (plen) {
+ *plen = 0;
+ }
+ return NULL;
+ }
+}
+/* }}} */
+
+PHPAPI char* _php_glob_stream_get_pattern(php_stream *stream, int copy, int *plen STREAMS_DC TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+
+ if (pglob && pglob->pattern) {
+ if (plen) {
+ *plen = pglob->pattern_len;
+ }
+ if (copy) {
+ return estrndup(pglob->pattern, pglob->pattern_len);
+ } else {
+ return pglob->pattern;
+ }
+ } else {
+ if (plen) {
+ *plen = 0;
+ }
+ return NULL;
+ }
+}
+/* }}} */
+
+PHPAPI int _php_glob_stream_get_count(php_stream *stream, int *pflags STREAMS_DC TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+
+ if (pglob) {
+ if (pflags) {
+ *pflags = pglob->flags;
+ }
+ return pglob->glob.gl_pathc;
+ } else {
+ if (pflags) {
+ *pflags = 0;
+ }
+ return 0;
+ }
+}
+/* }}} */
+
+static void php_glob_stream_path_split(glob_s_t *pglob, char *path, int get_path, char **p_file TSRMLS_DC) /* {{{ */
+{
+ char *pos, *gpath = path;
+
+ if ((pos = strrchr(path, '/')) != NULL) {
+ path = pos+1;
+ }
+#if defined(PHP_WIN32) || defined(NETWARE)
+ if ((pos = strrchr(path, '\\')) != NULL) {
+ path = pos+1;
+ }
+#endif
+
+ *p_file = path;
+
+ if (get_path) {
+ if (pglob->path) {
+ efree(pglob->path);
+ }
+ if (path != gpath) {
+ path--;
+ }
+ pglob->path_len = path - gpath;
+ pglob->path = estrndup(gpath, pglob->path_len);
+ }
+}
+/* }}} */
+
+static size_t php_glob_stream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+ php_stream_dirent *ent = (php_stream_dirent*)buf;
+ char *path;
+
+ /* avoid problems if someone mis-uses the stream */
+ if (count == sizeof(php_stream_dirent) && pglob) {
+ if (pglob->index < (size_t)pglob->glob.gl_pathc) {
+ php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[pglob->index++], pglob->flags & GLOB_APPEND, &path TSRMLS_CC);
+ PHP_STRLCPY(ent->d_name, path, sizeof(ent->d_name), strlen(path));
+ return sizeof(php_stream_dirent);
+ }
+ pglob->index = pglob->glob.gl_pathc;
+ if (pglob->path) {
+ efree(pglob->path);
+ pglob->path = NULL;
+ }
+ }
+
+ return 0;
+}
+/* }}} */
+
+static int php_glob_stream_close(php_stream *stream, int close_handle TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+
+ if (pglob) {
+ pglob->index = 0;
+ globfree(&pglob->glob);
+ if (pglob->path) {
+ efree(pglob->path);
+ }
+ if (pglob->pattern) {
+ efree(pglob->pattern);
+ }
+ }
+ efree(stream->abstract);
+ return 0;
+}
+/* {{{ */
+
+static int php_glob_stream_rewind(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC) /* {{{ */
+{
+ glob_s_t *pglob = (glob_s_t *)stream->abstract;
+
+ if (pglob) {
+ pglob->index = 0;
+ if (pglob->path) {
+ efree(pglob->path);
+ pglob->path = NULL;
+ }
+ }
+ return 0;
+}
+/* }}} */
+
+php_stream_ops php_glob_stream_ops = {
+ NULL, php_glob_stream_read,
+ php_glob_stream_close, NULL,
+ "glob",
+ php_glob_stream_rewind,
+ NULL, /* cast */
+ NULL, /* stat */
+ NULL /* set_option */
+};
+
+ /* {{{ php_glob_stream_opener */
+static php_stream *php_glob_stream_opener(php_stream_wrapper *wrapper, char *path, char *mode,
+ int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ glob_s_t *pglob;
+ int ret;
+ char *tmp, *pos;
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path TSRMLS_CC)) {
+ return NULL;
+ }
+
+ if (!strncmp(path, "glob://", sizeof("glob://")-1)) {
+ path += sizeof("glob://")-1;
+ if (opened_path) {
+ *opened_path = estrdup(path);
+ }
+ }
+
+ pglob = ecalloc(sizeof(*pglob), 1);
+
+ if (0 != (ret = glob(path, pglob->flags & GLOB_FLAGMASK, NULL, &pglob->glob))) {
+#ifdef GLOB_NOMATCH
+ if (GLOB_NOMATCH != ret)
+#endif
+ {
+ efree(pglob);
+ return NULL;
+ }
+ }
+
+ pos = path;
+ if ((tmp = strrchr(pos, '/')) != NULL) {
+ pos = tmp+1;
+ }
+#if defined(PHP_WIN32) || defined(NETWARE)
+ if ((tmp = strrchr(pos, '\\')) != NULL) {
+ pos = tmp+1;
+ }
+#endif
+
+ pglob->pattern_len = strlen(pos);
+ pglob->pattern = estrndup(pos, pglob->pattern_len);
+
+ pglob->flags |= GLOB_APPEND;
+
+ if (pglob->glob.gl_pathc) {
+ php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[0], 1, &tmp TSRMLS_CC);
+ } else {
+ php_glob_stream_path_split(pglob, path, 1, &tmp TSRMLS_CC);
+ }
+
+ return php_stream_alloc(&php_glob_stream_ops, pglob, 0, mode);
+}
+/* }}} */
+
+static php_stream_wrapper_ops php_glob_stream_wrapper_ops = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ php_glob_stream_opener,
+ "glob",
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+php_stream_wrapper php_glob_stream_wrapper = {
+ &php_glob_stream_wrapper_ops,
+ NULL,
+ 0
+};
+#endif /* HAVE_GLOB */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/memory.c b/main/streams/memory.c
new file mode 100644
index 0000000..328d3be
--- /dev/null
+++ b/main/streams/memory.c
@@ -0,0 +1,758 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Marcus Boerger <helly@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#define _GNU_SOURCE
+#include "php.h"
+
+PHPAPI int php_url_decode(char *str, int len);
+PHPAPI unsigned char *php_base64_decode(const unsigned char *str, int length, int *ret_length);
+
+/* Memory streams use a dynamic memory buffer to emulate a stream.
+ * You can use php_stream_memory_open to create a readonly stream
+ * from an existing memory buffer.
+ */
+
+/* Temp streams are streams that uses memory streams as long their
+ * size is less than a given memory amount. When a write operation
+ * exceeds that limit the content is written to a temporary file.
+ */
+
+/* {{{ ------- MEMORY stream implementation -------*/
+
+typedef struct {
+ char *data;
+ size_t fpos;
+ size_t fsize;
+ size_t smax;
+ int mode;
+} php_stream_memory_data;
+
+
+/* {{{ */
+static size_t php_stream_memory_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ assert(ms != NULL);
+
+ if (ms->mode & TEMP_STREAM_READONLY) {
+ return 0;
+ }
+ if (ms->fpos + count > ms->fsize) {
+ char *tmp;
+
+ if (!ms->data) {
+ tmp = emalloc(ms->fpos + count);
+ } else {
+ tmp = erealloc(ms->data, ms->fpos + count);
+ }
+ if (!tmp) {
+ count = ms->fsize - ms->fpos + 1;
+ } else {
+ ms->data = tmp;
+ ms->fsize = ms->fpos + count;
+ }
+ }
+ if (!ms->data)
+ count = 0;
+ if (count) {
+ assert(buf!= NULL);
+ memcpy(ms->data+ms->fpos, (char*)buf, count);
+ ms->fpos += count;
+ }
+ return count;
+}
+/* }}} */
+
+
+/* {{{ */
+static size_t php_stream_memory_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ assert(ms != NULL);
+
+ if (ms->fpos + count >= ms->fsize) {
+ count = ms->fsize - ms->fpos;
+ stream->eof = 1;
+ }
+ if (count) {
+ assert(ms->data!= NULL);
+ assert(buf!= NULL);
+ memcpy(buf, ms->data+ms->fpos, count);
+ ms->fpos += count;
+ }
+ return count;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_memory_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ assert(ms != NULL);
+
+ if (ms->data && close_handle && ms->mode != TEMP_STREAM_READONLY) {
+ efree(ms->data);
+ }
+ efree(ms);
+ return 0;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_memory_flush(php_stream *stream TSRMLS_DC)
+{
+ /* nothing to do here */
+ return 0;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_memory_seek(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ assert(ms != NULL);
+
+ switch(whence) {
+ case SEEK_CUR:
+ if (offset < 0) {
+ if (ms->fpos < (size_t)(-offset)) {
+ ms->fpos = 0;
+ *newoffs = -1;
+ return -1;
+ } else {
+ ms->fpos = ms->fpos + offset;
+ *newoffs = ms->fpos;
+ stream->eof = 0;
+ return 0;
+ }
+ } else {
+ if (ms->fpos + (size_t)(offset) > ms->fsize) {
+ ms->fpos = ms->fsize;
+ *newoffs = -1;
+ return -1;
+ } else {
+ ms->fpos = ms->fpos + offset;
+ *newoffs = ms->fpos;
+ stream->eof = 0;
+ return 0;
+ }
+ }
+ case SEEK_SET:
+ if (ms->fsize < (size_t)(offset)) {
+ ms->fpos = ms->fsize;
+ *newoffs = -1;
+ return -1;
+ } else {
+ ms->fpos = offset;
+ *newoffs = ms->fpos;
+ stream->eof = 0;
+ return 0;
+ }
+ case SEEK_END:
+ if (offset > 0) {
+ ms->fpos = ms->fsize;
+ *newoffs = -1;
+ return -1;
+ } else if (ms->fsize < (size_t)(-offset)) {
+ ms->fpos = 0;
+ *newoffs = -1;
+ return -1;
+ } else {
+ ms->fpos = ms->fsize + offset;
+ *newoffs = ms->fpos;
+ stream->eof = 0;
+ return 0;
+ }
+ default:
+ *newoffs = ms->fpos;
+ return -1;
+ }
+}
+/* }}} */
+
+/* {{{ */
+static int php_stream_memory_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
+{
+ return FAILURE;
+}
+/* }}} */
+
+static int php_stream_memory_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
+{
+ time_t timestamp = 0;
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ assert(ms != NULL);
+
+ memset(ssb, 0, sizeof(php_stream_statbuf));
+ /* read-only across the board */
+
+ ssb->sb.st_mode = ms->mode & TEMP_STREAM_READONLY ? 0444 : 0666;
+
+ ssb->sb.st_size = ms->fsize;
+ ssb->sb.st_mode |= S_IFREG; /* regular file */
+
+#ifdef NETWARE
+ ssb->sb.st_mtime.tv_sec = timestamp;
+ ssb->sb.st_atime.tv_sec = timestamp;
+ ssb->sb.st_ctime.tv_sec = timestamp;
+#else
+ ssb->sb.st_mtime = timestamp;
+ ssb->sb.st_atime = timestamp;
+ ssb->sb.st_ctime = timestamp;
+#endif
+
+ ssb->sb.st_nlink = 1;
+ ssb->sb.st_rdev = -1;
+ /* this is only for APC, so use /dev/null device - no chance of conflict there! */
+ ssb->sb.st_dev = 0xC;
+ /* generate unique inode number for alias/filename, so no phars will conflict */
+ ssb->sb.st_ino = 0;
+
+#ifndef PHP_WIN32
+ ssb->sb.st_blksize = -1;
+#endif
+
+#if !defined(PHP_WIN32) && !defined(__BEOS__)
+ ssb->sb.st_blocks = -1;
+#endif
+
+ return 0;
+}
+/* }}} */
+
+static int php_stream_memory_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) /* {{{ */
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+ size_t newsize;
+
+ switch(option) {
+ case PHP_STREAM_OPTION_TRUNCATE_API:
+ switch (value) {
+ case PHP_STREAM_TRUNCATE_SUPPORTED:
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_TRUNCATE_SET_SIZE:
+ if (ms->mode & TEMP_STREAM_READONLY) {
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ newsize = *(size_t*)ptrparam;
+ if (newsize <= ms->fsize) {
+ if (newsize < ms->fpos) {
+ ms->fpos = newsize;
+ }
+ } else {
+ ms->data = erealloc(ms->data, newsize);
+ memset(ms->data+ms->fsize, 0, newsize - ms->fsize);
+ ms->fsize = newsize;
+ }
+ ms->fsize = newsize;
+ return PHP_STREAM_OPTION_RETURN_OK;
+ }
+ default:
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ }
+}
+/* }}} */
+
+PHPAPI php_stream_ops php_stream_memory_ops = {
+ php_stream_memory_write, php_stream_memory_read,
+ php_stream_memory_close, php_stream_memory_flush,
+ "MEMORY",
+ php_stream_memory_seek,
+ php_stream_memory_cast,
+ php_stream_memory_stat,
+ php_stream_memory_set_option
+};
+
+
+/* {{{ */
+PHPAPI php_stream *_php_stream_memory_create(int mode STREAMS_DC TSRMLS_DC)
+{
+ php_stream_memory_data *self;
+ php_stream *stream;
+
+ self = emalloc(sizeof(*self));
+ self->data = NULL;
+ self->fpos = 0;
+ self->fsize = 0;
+ self->smax = ~0u;
+ self->mode = mode;
+
+ stream = php_stream_alloc_rel(&php_stream_memory_ops, self, 0, mode & TEMP_STREAM_READONLY ? "rb" : "w+b");
+ stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
+ return stream;
+}
+/* }}} */
+
+
+/* {{{ */
+PHPAPI php_stream *_php_stream_memory_open(int mode, char *buf, size_t length STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream;
+ php_stream_memory_data *ms;
+
+ if ((stream = php_stream_memory_create_rel(mode)) != NULL) {
+ ms = (php_stream_memory_data*)stream->abstract;
+
+ if (mode == TEMP_STREAM_READONLY || mode == TEMP_STREAM_TAKE_BUFFER) {
+ /* use the buffer directly */
+ ms->data = buf;
+ ms->fsize = length;
+ } else {
+ if (length) {
+ assert(buf != NULL);
+ php_stream_write(stream, buf, length);
+ }
+ }
+ }
+ return stream;
+}
+/* }}} */
+
+
+/* {{{ */
+PHPAPI char *_php_stream_memory_get_buffer(php_stream *stream, size_t *length STREAMS_DC TSRMLS_DC)
+{
+ php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
+
+ assert(ms != NULL);
+ assert(length != 0);
+
+ *length = ms->fsize;
+ return ms->data;
+}
+/* }}} */
+
+/* }}} */
+
+/* {{{ ------- TEMP stream implementation -------*/
+
+typedef struct {
+ php_stream *innerstream;
+ size_t smax;
+ int mode;
+ zval* meta;
+} php_stream_temp_data;
+
+
+/* {{{ */
+static size_t php_stream_temp_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ assert(ts != NULL);
+
+ if (!ts->innerstream) {
+ return -1;
+ }
+ if (php_stream_is(ts->innerstream, PHP_STREAM_IS_MEMORY)) {
+ size_t memsize;
+ char *membuf = php_stream_memory_get_buffer(ts->innerstream, &memsize);
+
+ if (memsize + count >= ts->smax) {
+ php_stream *file = php_stream_fopen_tmpfile();
+ php_stream_write(file, membuf, memsize);
+ php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE);
+ ts->innerstream = file;
+ php_stream_encloses(stream, ts->innerstream);
+ }
+ }
+ return php_stream_write(ts->innerstream, buf, count);
+}
+/* }}} */
+
+
+/* {{{ */
+static size_t php_stream_temp_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ size_t got;
+
+ assert(ts != NULL);
+
+ if (!ts->innerstream) {
+ return -1;
+ }
+
+ got = php_stream_read(ts->innerstream, buf, count);
+
+ stream->eof = ts->innerstream->eof;
+
+ return got;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_temp_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ int ret;
+
+ assert(ts != NULL);
+
+ if (ts->innerstream) {
+ ret = php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE | (close_handle ? 0 : PHP_STREAM_FREE_PRESERVE_HANDLE));
+ } else {
+ ret = 0;
+ }
+
+ if (ts->meta) {
+ zval_ptr_dtor(&ts->meta);
+ }
+
+ efree(ts);
+
+ return ret;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_temp_flush(php_stream *stream TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ assert(ts != NULL);
+
+ return ts->innerstream ? php_stream_flush(ts->innerstream) : -1;
+}
+/* }}} */
+
+
+/* {{{ */
+static int php_stream_temp_seek(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ int ret;
+
+ assert(ts != NULL);
+
+ if (!ts->innerstream) {
+ *newoffs = -1;
+ return -1;
+ }
+ ret = php_stream_seek(ts->innerstream, offset, whence);
+ *newoffs = php_stream_tell(ts->innerstream);
+ stream->eof = ts->innerstream->eof;
+
+ return ret;
+}
+/* }}} */
+
+/* {{{ */
+static int php_stream_temp_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+ php_stream *file;
+ size_t memsize;
+ char *membuf;
+ off_t pos;
+
+ assert(ts != NULL);
+
+ if (!ts->innerstream) {
+ return FAILURE;
+ }
+ if (php_stream_is(ts->innerstream, PHP_STREAM_IS_STDIO)) {
+ return php_stream_cast(ts->innerstream, castas, ret, 0);
+ }
+
+ /* we are still using a memory based backing. If they are if we can be
+ * a FILE*, say yes because we can perform the conversion.
+ * If they actually want to perform the conversion, we need to switch
+ * the memory stream to a tmpfile stream */
+
+ if (ret == NULL && castas == PHP_STREAM_AS_STDIO) {
+ return SUCCESS;
+ }
+
+ /* say "no" to other stream forms */
+ if (ret == NULL) {
+ return FAILURE;
+ }
+
+ /* perform the conversion and then pass the request on to the innerstream */
+ membuf = php_stream_memory_get_buffer(ts->innerstream, &memsize);
+ file = php_stream_fopen_tmpfile();
+ php_stream_write(file, membuf, memsize);
+ pos = php_stream_tell(ts->innerstream);
+
+ php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE);
+ ts->innerstream = file;
+ php_stream_encloses(stream, ts->innerstream);
+ php_stream_seek(ts->innerstream, pos, SEEK_SET);
+
+ return php_stream_cast(ts->innerstream, castas, ret, 1);
+}
+/* }}} */
+
+static int php_stream_temp_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+
+ if (!ts || !ts->innerstream) {
+ return -1;
+ }
+ return php_stream_stat(ts->innerstream, ssb);
+}
+/* }}} */
+
+static int php_stream_temp_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) /* {{{ */
+{
+ php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
+
+ switch(option) {
+ case PHP_STREAM_OPTION_META_DATA_API:
+ if (ts->meta) {
+ zend_hash_copy(Z_ARRVAL_P((zval*)ptrparam), Z_ARRVAL_P(ts->meta), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));
+ }
+ return PHP_STREAM_OPTION_RETURN_OK;
+ default:
+ if (ts->innerstream) {
+ return php_stream_set_option(ts->innerstream, option, value, ptrparam);
+ }
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ }
+}
+/* }}} */
+
+PHPAPI php_stream_ops php_stream_temp_ops = {
+ php_stream_temp_write, php_stream_temp_read,
+ php_stream_temp_close, php_stream_temp_flush,
+ "TEMP",
+ php_stream_temp_seek,
+ php_stream_temp_cast,
+ php_stream_temp_stat,
+ php_stream_temp_set_option
+};
+
+/* }}} */
+
+/* {{{ _php_stream_temp_create */
+PHPAPI php_stream *_php_stream_temp_create(int mode, size_t max_memory_usage STREAMS_DC TSRMLS_DC)
+{
+ php_stream_temp_data *self;
+ php_stream *stream;
+
+ self = ecalloc(1, sizeof(*self));
+ self->smax = max_memory_usage;
+ self->mode = mode;
+ self->meta = NULL;
+ stream = php_stream_alloc_rel(&php_stream_temp_ops, self, 0, mode & TEMP_STREAM_READONLY ? "rb" : "w+b");
+ stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
+ self->innerstream = php_stream_memory_create_rel(mode);
+ php_stream_encloses(stream, self->innerstream);
+
+ return stream;
+}
+/* }}} */
+
+
+/* {{{ _php_stream_temp_open */
+PHPAPI php_stream *_php_stream_temp_open(int mode, size_t max_memory_usage, char *buf, size_t length STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream;
+ php_stream_temp_data *ts;
+ off_t newoffs;
+
+ if ((stream = php_stream_temp_create_rel(mode, max_memory_usage)) != NULL) {
+ if (length) {
+ assert(buf != NULL);
+ php_stream_temp_write(stream, buf, length TSRMLS_CC);
+ php_stream_temp_seek(stream, 0, SEEK_SET, &newoffs TSRMLS_CC);
+ }
+ ts = (php_stream_temp_data*)stream->abstract;
+ assert(ts != NULL);
+ ts->mode = mode;
+ }
+ return stream;
+}
+/* }}} */
+
+PHPAPI php_stream_ops php_stream_rfc2397_ops = {
+ php_stream_temp_write, php_stream_temp_read,
+ php_stream_temp_close, php_stream_temp_flush,
+ "RFC2397",
+ php_stream_temp_seek,
+ php_stream_temp_cast,
+ php_stream_temp_stat,
+ php_stream_temp_set_option
+};
+
+static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) /* {{{ */
+{
+ php_stream *stream;
+ php_stream_temp_data *ts;
+ char *comma, *semi, *sep, *key;
+ size_t mlen, dlen, plen, vlen;
+ off_t newoffs;
+ zval *meta = NULL;
+ int base64 = 0, ilen;
+
+ if (memcmp(path, "data:", 5)) {
+ return NULL;
+ }
+
+ path += 5;
+ dlen = strlen(path);
+
+ if (dlen >= 2 && path[0] == '/' && path[1] == '/') {
+ dlen -= 2;
+ path += 2;
+ }
+
+ if ((comma = memchr(path, ',', dlen)) == NULL) {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: no comma in URL");
+ return NULL;
+ }
+
+ if (comma != path) {
+ /* meta info */
+ mlen = comma - path;
+ dlen -= mlen;
+ semi = memchr(path, ';', mlen);
+ sep = memchr(path, '/', mlen);
+
+ if (!semi && !sep) {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: illegal media type");
+ return NULL;
+ }
+
+ MAKE_STD_ZVAL(meta);
+ array_init(meta);
+ if (!semi) { /* there is only a mime type */
+ add_assoc_stringl(meta, "mediatype", path, mlen, 1);
+ mlen = 0;
+ } else if (sep && sep < semi) { /* there is a mime type */
+ plen = semi - path;
+ add_assoc_stringl(meta, "mediatype", path, plen, 1);
+ mlen -= plen;
+ path += plen;
+ } else if (semi != path || mlen != sizeof(";base64")-1 || memcmp(path, ";base64", sizeof(";base64")-1)) { /* must be error since parameters are only allowed after mediatype */
+ zval_ptr_dtor(&meta);
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: illegal media type");
+ return NULL;
+ }
+ /* get parameters and potentially ';base64' */
+ while(semi && (semi == path)) {
+ path++;
+ mlen--;
+ sep = memchr(path, '=', mlen);
+ semi = memchr(path, ';', mlen);
+ if (!sep || (semi && semi < sep)) { /* must be ';base64' or failure */
+ if (mlen != sizeof("base64")-1 || memcmp(path, "base64", sizeof("base64")-1)) {
+ /* must be error since parameters are only allowed after mediatype and we have no '=' sign */
+ zval_ptr_dtor(&meta);
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: illegal parameter");
+ return NULL;
+ }
+ base64 = 1;
+ mlen -= sizeof("base64") - 1;
+ path += sizeof("base64") - 1;
+ break;
+ }
+ /* found parameter ... the heart of cs ppl lies in +1/-1 or was it +2 this time? */
+ plen = sep - path;
+ vlen = (semi ? semi - sep : mlen - plen) - 1 /* '=' */;
+ key = estrndup(path, plen);
+ add_assoc_stringl_ex(meta, key, plen + 1, sep + 1, vlen, 1);
+ efree(key);
+ plen += vlen + 1;
+ mlen -= plen;
+ path += plen;
+ }
+ if (mlen) {
+ zval_ptr_dtor(&meta);
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: illegal URL");
+ return NULL;
+ }
+ } else {
+ MAKE_STD_ZVAL(meta);
+ array_init(meta);
+ }
+ add_assoc_bool(meta, "base64", base64);
+
+ /* skip ',' */
+ comma++;
+ dlen--;
+
+ if (base64) {
+ comma = (char*)php_base64_decode((const unsigned char *)comma, dlen, &ilen);
+ if (!comma) {
+ zval_ptr_dtor(&meta);
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "rfc2397: unable to decode");
+ return NULL;
+ }
+ } else {
+ comma = estrndup(comma, dlen);
+ ilen = dlen = php_url_decode(comma, dlen);
+ }
+
+ if ((stream = php_stream_temp_create_rel(0, ~0u)) != NULL) {
+ /* store data */
+ php_stream_temp_write(stream, comma, ilen TSRMLS_CC);
+ php_stream_temp_seek(stream, 0, SEEK_SET, &newoffs TSRMLS_CC);
+ /* set special stream stuff (enforce exact mode) */
+ vlen = strlen(mode);
+ if (vlen >= sizeof(stream->mode)) {
+ vlen = sizeof(stream->mode) - 1;
+ }
+ memcpy(stream->mode, mode, vlen);
+ stream->mode[vlen] = '\0';
+ stream->ops = &php_stream_rfc2397_ops;
+ ts = (php_stream_temp_data*)stream->abstract;
+ assert(ts != NULL);
+ ts->mode = mode && mode[0] == 'r' && mode[1] != '+' ? TEMP_STREAM_READONLY : 0;
+ ts->meta = meta;
+ }
+ efree(comma);
+
+ return stream;
+}
+
+PHPAPI php_stream_wrapper_ops php_stream_rfc2397_wops = {
+ php_stream_url_wrap_rfc2397,
+ NULL, /* close */
+ NULL, /* fstat */
+ NULL, /* stat */
+ NULL, /* opendir */
+ "RFC2397",
+ NULL, /* unlink */
+ NULL, /* rename */
+ NULL, /* mkdir */
+ NULL /* rmdir */
+};
+
+PHPAPI php_stream_wrapper php_stream_rfc2397_wrapper = {
+ &php_stream_rfc2397_wops,
+ NULL,
+ 1, /* is_url */
+};
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/mmap.c b/main/streams/mmap.c
new file mode 100644
index 0000000..4f9388c
--- /dev/null
+++ b/main/streams/mmap.c
@@ -0,0 +1,75 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* Memory Mapping interface for streams */
+#include "php.h"
+#include "php_streams_int.h"
+
+PHPAPI char *_php_stream_mmap_range(php_stream *stream, size_t offset, size_t length, php_stream_mmap_operation_t mode, size_t *mapped_len TSRMLS_DC)
+{
+ php_stream_mmap_range range;
+
+ range.offset = offset;
+ range.length = length;
+ range.mode = mode;
+ range.mapped = NULL;
+
+ /* For now, we impose an arbitrary limit to avoid
+ * runaway swapping when large files are passed thru. */
+ if (length > 4 * 1024 * 1024) {
+ return NULL;
+ }
+
+ if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_MAP_RANGE, &range)) {
+ if (mapped_len) {
+ *mapped_len = range.length;
+ }
+ return range.mapped;
+ }
+ return NULL;
+}
+
+PHPAPI int _php_stream_mmap_unmap(php_stream *stream TSRMLS_DC)
+{
+ return php_stream_set_option(stream, PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_UNMAP, NULL) == PHP_STREAM_OPTION_RETURN_OK ? 1 : 0;
+}
+
+PHPAPI int _php_stream_mmap_unmap_ex(php_stream *stream, off_t readden TSRMLS_DC)
+{
+ int ret = 1;
+
+ if (php_stream_seek(stream, readden, SEEK_CUR) != 0) {
+ ret = 0;
+ }
+ if (php_stream_mmap_unmap(stream) == 0) {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h
new file mode 100644
index 0000000..240f6e0
--- /dev/null
+++ b/main/streams/php_stream_context.h
@@ -0,0 +1,136 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+/* Stream context and status notification related definitions */
+
+/* callback for status notifications */
+typedef void (*php_stream_notification_func)(php_stream_context *context,
+ int notifycode, int severity,
+ char *xmsg, int xcode,
+ size_t bytes_sofar, size_t bytes_max,
+ void * ptr TSRMLS_DC);
+
+#define PHP_STREAM_NOTIFIER_PROGRESS 1
+
+/* Attempt to fetch context from the zval passed,
+ If no context was passed, use the default context
+ The default context has not yet been created, do it now. */
+#define php_stream_context_from_zval(zcontext, nocontext) ( \
+ (zcontext) ? zend_fetch_resource(&(zcontext) TSRMLS_CC, -1, "Stream-Context", NULL, 1, php_le_stream_context(TSRMLS_C)) : \
+ (nocontext) ? NULL : \
+ FG(default_context) ? FG(default_context) : \
+ (FG(default_context) = php_stream_context_alloc(TSRMLS_C)) )
+
+#define php_stream_context_to_zval(context, zval) { ZVAL_RESOURCE(zval, (context)->rsrc_id); zend_list_addref((context)->rsrc_id); }
+
+typedef struct _php_stream_notifier php_stream_notifier;
+
+struct _php_stream_notifier {
+ php_stream_notification_func func;
+ void (*dtor)(php_stream_notifier *notifier);
+ void *ptr;
+ int mask;
+ size_t progress, progress_max; /* position for progress notification */
+};
+
+struct _php_stream_context {
+ php_stream_notifier *notifier;
+ zval *options; /* hash keyed by wrapper family or specific wrapper */
+ zval *links; /* hash keyed by hostent for connection pooling */
+ int rsrc_id; /* used for auto-cleanup */
+};
+
+BEGIN_EXTERN_C()
+PHPAPI void php_stream_context_free(php_stream_context *context);
+PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D);
+PHPAPI int php_stream_context_get_option(php_stream_context *context,
+ const char *wrappername, const char *optionname, zval ***optionvalue);
+PHPAPI int php_stream_context_set_option(php_stream_context *context,
+ const char *wrappername, const char *optionname, zval *optionvalue);
+
+PHPAPI int php_stream_context_get_link(php_stream_context *context,
+ const char *hostent, php_stream **stream);
+PHPAPI int php_stream_context_set_link(php_stream_context *context,
+ const char *hostent, php_stream *stream);
+PHPAPI int php_stream_context_del_link(php_stream_context *context,
+ php_stream *stream);
+
+PHPAPI php_stream_notifier *php_stream_notification_alloc(void);
+PHPAPI void php_stream_notification_free(php_stream_notifier *notifier);
+END_EXTERN_C()
+
+/* not all notification codes are implemented */
+#define PHP_STREAM_NOTIFY_RESOLVE 1
+#define PHP_STREAM_NOTIFY_CONNECT 2
+#define PHP_STREAM_NOTIFY_AUTH_REQUIRED 3
+#define PHP_STREAM_NOTIFY_MIME_TYPE_IS 4
+#define PHP_STREAM_NOTIFY_FILE_SIZE_IS 5
+#define PHP_STREAM_NOTIFY_REDIRECTED 6
+#define PHP_STREAM_NOTIFY_PROGRESS 7
+#define PHP_STREAM_NOTIFY_COMPLETED 8
+#define PHP_STREAM_NOTIFY_FAILURE 9
+#define PHP_STREAM_NOTIFY_AUTH_RESULT 10
+
+#define PHP_STREAM_NOTIFY_SEVERITY_INFO 0
+#define PHP_STREAM_NOTIFY_SEVERITY_WARN 1
+#define PHP_STREAM_NOTIFY_SEVERITY_ERR 2
+
+BEGIN_EXTERN_C()
+PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
+ char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC);
+PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context);
+END_EXTERN_C()
+
+#define php_stream_notify_info(context, code, xmsg, xcode) do { if ((context) && (context)->notifier) { \
+ php_stream_notification_notify((context), (code), PHP_STREAM_NOTIFY_SEVERITY_INFO, \
+ (xmsg), (xcode), 0, 0, NULL TSRMLS_CC); } } while (0)
+
+#define php_stream_notify_progress(context, bsofar, bmax) do { if ((context) && (context)->notifier) { \
+ php_stream_notification_notify((context), PHP_STREAM_NOTIFY_PROGRESS, PHP_STREAM_NOTIFY_SEVERITY_INFO, \
+ NULL, 0, (bsofar), (bmax), NULL TSRMLS_CC); } } while(0)
+
+#define php_stream_notify_progress_init(context, sofar, bmax) do { if ((context) && (context)->notifier) { \
+ (context)->notifier->progress = (sofar); \
+ (context)->notifier->progress_max = (bmax); \
+ (context)->notifier->mask |= PHP_STREAM_NOTIFIER_PROGRESS; \
+ php_stream_notify_progress((context), (sofar), (bmax)); } } while (0)
+
+#define php_stream_notify_progress_increment(context, dsofar, dmax) do { if ((context) && (context)->notifier && (context)->notifier->mask & PHP_STREAM_NOTIFIER_PROGRESS) { \
+ (context)->notifier->progress += (dsofar); \
+ (context)->notifier->progress_max += (dmax); \
+ php_stream_notify_progress((context), (context)->notifier->progress, (context)->notifier->progress_max); } } while (0)
+
+#define php_stream_notify_file_size(context, file_size, xmsg, xcode) do { if ((context) && (context)->notifier) { \
+ php_stream_notification_notify((context), PHP_STREAM_NOTIFY_FILE_SIZE_IS, PHP_STREAM_NOTIFY_SEVERITY_INFO, \
+ (xmsg), (xcode), 0, (file_size), NULL TSRMLS_CC); } } while(0)
+
+#define php_stream_notify_error(context, code, xmsg, xcode) do { if ((context) && (context)->notifier) {\
+ php_stream_notification_notify((context), (code), PHP_STREAM_NOTIFY_SEVERITY_ERR, \
+ (xmsg), (xcode), 0, 0, NULL TSRMLS_CC); } } while(0)
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
new file mode 100644
index 0000000..abb5906
--- /dev/null
+++ b/main/streams/php_stream_filter_api.h
@@ -0,0 +1,162 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ | With suggestions from: |
+ | Moriyoshi Koizumi <moriyoshi@at.wakwak.com> |
+ | Sara Golemon <pollita@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+/* The filter API works on the principle of "Bucket-Brigades". This is
+ * partially inspired by the Apache 2 method of doing things, although
+ * it is intentially a light-weight implementation.
+ *
+ * Each stream can have a chain of filters for reading and another for writing.
+ *
+ * When data is written to the stream, it is placed into a bucket and placed at
+ * the start of the input brigade.
+ *
+ * The first filter in the chain is invoked on the brigade and (depending on
+ * it's return value), the next filter is invoked and so on.
+ * */
+
+#define PHP_STREAM_FILTER_READ 0x0001
+#define PHP_STREAM_FILTER_WRITE 0x0002
+#define PHP_STREAM_FILTER_ALL (PHP_STREAM_FILTER_READ | PHP_STREAM_FILTER_WRITE)
+
+typedef struct _php_stream_bucket php_stream_bucket;
+typedef struct _php_stream_bucket_brigade php_stream_bucket_brigade;
+
+struct _php_stream_bucket {
+ php_stream_bucket *next, *prev;
+ php_stream_bucket_brigade *brigade;
+
+ char *buf;
+ size_t buflen;
+ /* if non-zero, buf should be pefreed when the bucket is destroyed */
+ int own_buf;
+ int is_persistent;
+
+ /* destroy this struct when refcount falls to zero */
+ int refcount;
+};
+
+struct _php_stream_bucket_brigade {
+ php_stream_bucket *head, *tail;
+};
+
+typedef enum {
+ PSFS_ERR_FATAL, /* error in data stream */
+ PSFS_FEED_ME, /* filter needs more data; stop processing chain until more is available */
+ PSFS_PASS_ON /* filter generated output buckets; pass them on to next in chain */
+} php_stream_filter_status_t;
+
+/* Buckets API. */
+BEGIN_EXTERN_C()
+PHPAPI php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, int own_buf, int buf_persistent TSRMLS_DC);
+PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length TSRMLS_DC);
+PHPAPI void php_stream_bucket_delref(php_stream_bucket *bucket TSRMLS_DC);
+#define php_stream_bucket_addref(bucket) (bucket)->refcount++
+PHPAPI void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC);
+PHPAPI void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC);
+PHPAPI void php_stream_bucket_unlink(php_stream_bucket *bucket TSRMLS_DC);
+PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bucket TSRMLS_DC);
+END_EXTERN_C()
+
+#define PSFS_FLAG_NORMAL 0 /* regular read/write */
+#define PSFS_FLAG_FLUSH_INC 1 /* an incremental flush */
+#define PSFS_FLAG_FLUSH_CLOSE 2 /* final flush prior to closing */
+
+typedef struct _php_stream_filter_ops {
+
+ php_stream_filter_status_t (*filter)(
+ php_stream *stream,
+ php_stream_filter *thisfilter,
+ php_stream_bucket_brigade *buckets_in,
+ php_stream_bucket_brigade *buckets_out,
+ size_t *bytes_consumed,
+ int flags
+ TSRMLS_DC);
+
+ void (*dtor)(php_stream_filter *thisfilter TSRMLS_DC);
+
+ const char *label;
+
+} php_stream_filter_ops;
+
+typedef struct _php_stream_filter_chain {
+ php_stream_filter *head, *tail;
+
+ /* Owning stream */
+ php_stream *stream;
+} php_stream_filter_chain;
+
+struct _php_stream_filter {
+ php_stream_filter_ops *fops;
+ void *abstract; /* for use by filter implementation */
+ php_stream_filter *next;
+ php_stream_filter *prev;
+ int is_persistent;
+
+ /* link into stream and chain */
+ php_stream_filter_chain *chain;
+
+ /* buffered buckets */
+ php_stream_bucket_brigade buffer;
+
+ /* filters are auto_registered when they're applied */
+ int rsrc_id;
+};
+
+/* stack filter onto a stream */
+BEGIN_EXTERN_C()
+PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC);
+PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC);
+PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC);
+PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC);
+PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish TSRMLS_DC);
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor TSRMLS_DC);
+PHPAPI void php_stream_filter_free(php_stream_filter *filter TSRMLS_DC);
+PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, int persistent STREAMS_DC TSRMLS_DC);
+END_EXTERN_C()
+#define php_stream_filter_alloc(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_CC TSRMLS_CC)
+#define php_stream_filter_alloc_rel(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_REL_CC TSRMLS_CC)
+#define php_stream_filter_prepend(chain, filter) _php_stream_filter_prepend((chain), (filter) TSRMLS_CC)
+#define php_stream_filter_append(chain, filter) _php_stream_filter_append((chain), (filter) TSRMLS_CC)
+#define php_stream_filter_flush(filter, finish) _php_stream_filter_flush((filter), (finish) TSRMLS_CC)
+
+#define php_stream_is_filtered(stream) ((stream)->readfilters.head || (stream)->writefilters.head)
+
+typedef struct _php_stream_filter_factory {
+ php_stream_filter *(*create_filter)(const char *filtername, zval *filterparams, int persistent TSRMLS_DC);
+} php_stream_filter_factory;
+
+BEGIN_EXTERN_C()
+PHPAPI int php_stream_filter_register_factory(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC);
+PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern TSRMLS_DC);
+PHPAPI int php_stream_filter_register_factory_volatile(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC);
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC);
+END_EXTERN_C()
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_glob_wrapper.h b/main/streams/php_stream_glob_wrapper.h
new file mode 100644
index 0000000..330e917
--- /dev/null
+++ b/main/streams/php_stream_glob_wrapper.h
@@ -0,0 +1,44 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Marcus Boerger <helly@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+PHPAPI extern php_stream_wrapper php_glob_stream_wrapper;
+PHPAPI extern php_stream_ops php_glob_stream_ops;
+
+BEGIN_EXTERN_C()
+
+PHPAPI char* _php_glob_stream_get_path(php_stream *stream, int copy, int *plen STREAMS_DC TSRMLS_DC);
+#define php_glob_stream_get_path(stream, copy, plen) _php_glob_stream_get_path((stream), (copy), (plen) STREAMS_CC TSRMLS_CC)
+
+PHPAPI char* _php_glob_stream_get_pattern(php_stream *stream, int copy, int *plen STREAMS_DC TSRMLS_DC);
+#define php_glob_stream_get_pattern(stream, copy, plen) _php_glob_stream_get_pattern((stream), (copy), (plen) STREAMS_CC TSRMLS_CC)
+
+PHPAPI int _php_glob_stream_get_count(php_stream *stream, int *pflags STREAMS_DC TSRMLS_DC);
+#define php_glob_stream_get_count(stream, pflags) _php_glob_stream_get_count((stream), (pflags) STREAMS_CC TSRMLS_CC)
+
+END_EXTERN_C()
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_mmap.h b/main/streams/php_stream_mmap.h
new file mode 100644
index 0000000..7895ac6
--- /dev/null
+++ b/main/streams/php_stream_mmap.h
@@ -0,0 +1,88 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* Memory Mapping interface for streams.
+ * The intention is to provide a uniform interface over the most common
+ * operations that are used within PHP itself, rather than a complete
+ * API for all memory mapping needs.
+ *
+ * ATM, we support only mmap(), but win32 memory mapping support will
+ * follow soon.
+ * */
+
+typedef enum {
+ /* Does the stream support mmap ? */
+ PHP_STREAM_MMAP_SUPPORTED,
+ /* Request a range and offset to be mapped;
+ * while mapped, you MUST NOT use any read/write functions
+ * on the stream (win9x compatibility) */
+ PHP_STREAM_MMAP_MAP_RANGE,
+ /* Unmap the last range that was mapped for the stream */
+ PHP_STREAM_MMAP_UNMAP
+} php_stream_mmap_operation_t;
+
+typedef enum {
+ PHP_STREAM_MAP_MODE_READONLY,
+ PHP_STREAM_MAP_MODE_READWRITE,
+ PHP_STREAM_MAP_MODE_SHARED_READONLY,
+ PHP_STREAM_MAP_MODE_SHARED_READWRITE
+} php_stream_mmap_access_t;
+
+typedef struct {
+ /* requested offset and length.
+ * If length is 0, the whole file is mapped */
+ size_t offset;
+ size_t length;
+
+ php_stream_mmap_access_t mode;
+
+ /* returned mapped address */
+ char *mapped;
+
+} php_stream_mmap_range;
+
+#define PHP_STREAM_MMAP_ALL 0
+
+#define php_stream_mmap_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_SUPPORTED, NULL TSRMLS_CC) == 0 ? 1 : 0)
+
+/* Returns 1 if the stream in its current state can be memory mapped,
+ * 0 otherwise */
+#define php_stream_mmap_possible(stream) (!php_stream_is_filtered((stream)) && php_stream_mmap_supported((stream)))
+
+BEGIN_EXTERN_C()
+PHPAPI char *_php_stream_mmap_range(php_stream *stream, size_t offset, size_t length, php_stream_mmap_operation_t mode, size_t *mapped_len TSRMLS_DC);
+#define php_stream_mmap_range(stream, offset, length, mode, mapped_len) _php_stream_mmap_range((stream), (offset), (length), (mode), (mapped_len) TSRMLS_CC)
+
+/* un-maps the last mapped range */
+PHPAPI int _php_stream_mmap_unmap(php_stream *stream TSRMLS_DC);
+#define php_stream_mmap_unmap(stream) _php_stream_mmap_unmap((stream) TSRMLS_CC)
+
+PHPAPI int _php_stream_mmap_unmap_ex(php_stream *stream, off_t readden TSRMLS_DC);
+#define php_stream_mmap_unmap_ex(stream, readden) _php_stream_mmap_unmap_ex((stream), (readden) TSRMLS_CC)
+END_EXTERN_C()
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_plain_wrapper.h b/main/streams/php_stream_plain_wrapper.h
new file mode 100644
index 0000000..d88b30c
--- /dev/null
+++ b/main/streams/php_stream_plain_wrapper.h
@@ -0,0 +1,66 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+/* definitions for the plain files wrapper */
+
+/* operations for a plain file; use the php_stream_fopen_XXX funcs below */
+PHPAPI extern php_stream_ops php_stream_stdio_ops;
+PHPAPI extern php_stream_wrapper php_plain_files_wrapper;
+
+BEGIN_EXTERN_C()
+
+/* like fopen, but returns a stream */
+PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, char **opened_path, int options STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen(filename, mode, opened) _php_stream_fopen((filename), (mode), (opened), 0 STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_with_path(char *filename, char *mode, char *path, char **opened_path, int options STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_with_path(filename, mode, path, opened) _php_stream_fopen_with_path((filename), (mode), (path), (opened), 0 STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_from_file(FILE *file, const char *mode STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_from_file(file, mode) _php_stream_fopen_from_file((file), (mode) STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_from_fd(fd, mode, persistent_id) _php_stream_fopen_from_fd((fd), (mode), (persistent_id) STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_from_pipe(file, mode) _php_stream_fopen_from_pipe((file), (mode) STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_tmpfile(int dummy STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_tmpfile() _php_stream_fopen_tmpfile(0 STREAMS_CC TSRMLS_CC)
+
+PHPAPI php_stream *_php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path STREAMS_DC TSRMLS_DC);
+#define php_stream_fopen_temporary_file(dir, pfx, opened_path) _php_stream_fopen_temporary_file((dir), (pfx), (opened_path) STREAMS_CC TSRMLS_CC)
+
+/* This is a utility API for extensions that are opening a stream, converting it
+ * to a FILE* and then closing it again. Be warned that fileno() on the result
+ * will most likely fail on systems with fopencookie. */
+PHPAPI FILE * _php_stream_open_wrapper_as_file(char * path, char * mode, int options, char **opened_path STREAMS_DC TSRMLS_DC);
+#define php_stream_open_wrapper_as_file(path, mode, options, opened_path) _php_stream_open_wrapper_as_file((path), (mode), (options), (opened_path) STREAMS_CC TSRMLS_CC)
+
+END_EXTERN_C()
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_transport.h b/main/streams/php_stream_transport.h
new file mode 100644
index 0000000..c2d9110
--- /dev/null
+++ b/main/streams/php_stream_transport.h
@@ -0,0 +1,211 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+#ifdef PHP_WIN32
+#include "config.w32.h"
+#include <Ws2tcpip.h>
+#endif
+
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+
+typedef php_stream *(php_stream_transport_factory_func)(const char *proto, long protolen,
+ char *resourcename, long resourcenamelen,
+ const char *persistent_id, int options, int flags,
+ struct timeval *timeout,
+ php_stream_context *context STREAMS_DC TSRMLS_DC);
+typedef php_stream_transport_factory_func *php_stream_transport_factory;
+
+BEGIN_EXTERN_C()
+PHPAPI int php_stream_xport_register(char *protocol, php_stream_transport_factory factory TSRMLS_DC);
+PHPAPI int php_stream_xport_unregister(char *protocol TSRMLS_DC);
+
+#define STREAM_XPORT_CLIENT 0
+#define STREAM_XPORT_SERVER 1
+
+#define STREAM_XPORT_CONNECT 2
+#define STREAM_XPORT_BIND 4
+#define STREAM_XPORT_LISTEN 8
+#define STREAM_XPORT_CONNECT_ASYNC 16
+
+/* Open a client or server socket connection */
+PHPAPI php_stream *_php_stream_xport_create(const char *name, long namelen, int options,
+ int flags, const char *persistent_id,
+ struct timeval *timeout,
+ php_stream_context *context,
+ char **error_string,
+ int *error_code
+ STREAMS_DC TSRMLS_DC);
+
+#define php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode) \
+ _php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode STREAMS_CC TSRMLS_CC)
+
+/* Bind the stream to a local address */
+PHPAPI int php_stream_xport_bind(php_stream *stream,
+ const char *name, long namelen,
+ char **error_text
+ TSRMLS_DC);
+
+/* Connect to a remote address */
+PHPAPI int php_stream_xport_connect(php_stream *stream,
+ const char *name, long namelen,
+ int asynchronous,
+ struct timeval *timeout,
+ char **error_text,
+ int *error_code
+ TSRMLS_DC);
+
+/* Prepare to listen */
+PHPAPI int php_stream_xport_listen(php_stream *stream,
+ int backlog,
+ char **error_text
+ TSRMLS_DC);
+
+/* Get the next client and their address as a string, or the underlying address
+ * structure. You must efree either of these if you request them */
+PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
+ char **textaddr, int *textaddrlen,
+ void **addr, socklen_t *addrlen,
+ struct timeval *timeout,
+ char **error_text
+ TSRMLS_DC);
+
+/* Get the name of either the socket or it's peer */
+PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
+ char **textaddr, int *textaddrlen,
+ void **addr, socklen_t *addrlen
+ TSRMLS_DC);
+
+enum php_stream_xport_send_recv_flags {
+ STREAM_OOB = 1,
+ STREAM_PEEK = 2
+};
+
+/* Similar to recv() system call; read data from the stream, optionally
+ * peeking, optionally retrieving OOB data */
+PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
+ long flags, void **addr, socklen_t *addrlen,
+ char **textaddr, int *textaddrlen TSRMLS_DC);
+
+/* Similar to send() system call; send data to the stream, optionally
+ * sending it as OOB data */
+PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
+ long flags, void *addr, socklen_t addrlen TSRMLS_DC);
+
+typedef enum {
+ STREAM_SHUT_RD,
+ STREAM_SHUT_WR,
+ STREAM_SHUT_RDWR
+} stream_shutdown_t;
+
+/* Similar to shutdown() system call; shut down part of a full-duplex
+ * connection */
+PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how TSRMLS_DC);
+END_EXTERN_C()
+
+
+/* Structure definition for the set_option interface that the above functions wrap */
+
+typedef struct _php_stream_xport_param {
+ enum {
+ STREAM_XPORT_OP_BIND, STREAM_XPORT_OP_CONNECT,
+ STREAM_XPORT_OP_LISTEN, STREAM_XPORT_OP_ACCEPT,
+ STREAM_XPORT_OP_CONNECT_ASYNC,
+ STREAM_XPORT_OP_GET_NAME,
+ STREAM_XPORT_OP_GET_PEER_NAME,
+ STREAM_XPORT_OP_RECV,
+ STREAM_XPORT_OP_SEND,
+ STREAM_XPORT_OP_SHUTDOWN
+ } op;
+ unsigned int want_addr:1;
+ unsigned int want_textaddr:1;
+ unsigned int want_errortext:1;
+ unsigned int how:2;
+
+ struct {
+ char *name;
+ long namelen;
+ int backlog;
+ struct timeval *timeout;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ char *buf;
+ size_t buflen;
+ long flags;
+ } inputs;
+ struct {
+ php_stream *client;
+ int returncode;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ char *textaddr;
+ long textaddrlen;
+
+ char *error_text;
+ int error_code;
+ } outputs;
+} php_stream_xport_param;
+
+
+/* These functions provide crypto support on the underlying transport */
+typedef enum {
+ STREAM_CRYPTO_METHOD_SSLv2_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
+ STREAM_CRYPTO_METHOD_TLS_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv2_SERVER,
+ STREAM_CRYPTO_METHOD_SSLv3_SERVER,
+ STREAM_CRYPTO_METHOD_SSLv23_SERVER,
+ STREAM_CRYPTO_METHOD_TLS_SERVER
+} php_stream_xport_crypt_method_t;
+
+BEGIN_EXTERN_C()
+PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream TSRMLS_DC);
+PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate TSRMLS_DC);
+END_EXTERN_C()
+
+typedef struct _php_stream_xport_crypto_param {
+ enum {
+ STREAM_XPORT_CRYPTO_OP_SETUP,
+ STREAM_XPORT_CRYPTO_OP_ENABLE
+ } op;
+ struct {
+ int activate;
+ php_stream_xport_crypt_method_t method;
+ php_stream *session;
+ } inputs;
+ struct {
+ int returncode;
+ } outputs;
+} php_stream_xport_crypto_param;
+
+BEGIN_EXTERN_C()
+PHPAPI HashTable *php_stream_xport_get_hash(void);
+PHPAPI php_stream_transport_factory_func php_stream_generic_socket_factory;
+END_EXTERN_C()
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/php_stream_userspace.h b/main/streams/php_stream_userspace.h
new file mode 100644
index 0000000..2830dd0
--- /dev/null
+++ b/main/streams/php_stream_userspace.h
@@ -0,0 +1,35 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+
+/* for user-space streams */
+PHPAPI extern php_stream_ops php_stream_userspace_ops;
+PHPAPI extern php_stream_ops php_stream_userspace_dir_ops;
+#define PHP_STREAM_IS_USERSPACE &php_stream_userspace_ops
+#define PHP_STREAM_IS_USERSPACE_DIR &php_stream_userspace_dir_ops
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/streams/php_streams_int.h b/main/streams/php_streams_int.h
new file mode 100644
index 0000000..daae2b8
--- /dev/null
+++ b/main/streams/php_streams_int.h
@@ -0,0 +1,71 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+
+#if ZEND_DEBUG
+
+#define emalloc_rel_orig(size) \
+ ( __php_stream_call_depth == 0 \
+ ? _emalloc((size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_RELAY_CC) \
+ : _emalloc((size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_ORIG_RELAY_CC) )
+
+#define erealloc_rel_orig(ptr, size) \
+ ( __php_stream_call_depth == 0 \
+ ? _erealloc((ptr), (size), 0 ZEND_FILE_LINE_CC ZEND_FILE_LINE_RELAY_CC) \
+ : _erealloc((ptr), (size), 0 ZEND_FILE_LINE_CC ZEND_FILE_LINE_ORIG_RELAY_CC) )
+
+#define pemalloc_rel_orig(size, persistent) ((persistent) ? malloc((size)) : emalloc_rel_orig((size)))
+#define perealloc_rel_orig(ptr, size, persistent) ((persistent) ? realloc((ptr), (size)) : erealloc_rel_orig((ptr), (size)))
+#else
+# define pemalloc_rel_orig(size, persistent) pemalloc((size), (persistent))
+# define perealloc_rel_orig(ptr, size, persistent) perealloc((ptr), (size), (persistent))
+# define emalloc_rel_orig(size) emalloc((size))
+#endif
+
+#define STREAM_DEBUG 0
+#define STREAM_WRAPPER_PLAIN_FILES ((php_stream_wrapper*)-1)
+
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *) -1)
+#endif
+
+#define CHUNK_SIZE 8192
+
+#ifdef PHP_WIN32
+# ifdef EWOULDBLOCK
+# undef EWOULDBLOCK
+# endif
+# define EWOULDBLOCK WSAEWOULDBLOCK
+#endif
+
+#ifndef S_ISREG
+#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG)
+#endif
+
+/* This functions transforms the first char to 'w' if it's not 'r', 'a' or 'w'
+ * and strips any subsequent chars except '+' and 'b'.
+ * Use this to sanitize stream->mode if you call e.g. fdopen, fopencookie or
+ * any other function that expects standard modes and you allow non-standard
+ * ones. result should be a char[5]. */
+void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result);
+
+void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC);
+void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC);
+
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
new file mode 100644
index 0000000..4ee8150
--- /dev/null
+++ b/main/streams/plain_wrapper.c
@@ -0,0 +1,1505 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_globals.h"
+#include "php_network.h"
+#include "php_open_temporary_file.h"
+#include "ext/standard/file.h"
+#include "ext/standard/flock_compat.h"
+#include "ext/standard/php_filestat.h"
+#include <stddef.h>
+#include <fcntl.h>
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#if HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#include "SAPI.h"
+
+#include "php_streams_int.h"
+#ifdef PHP_WIN32
+# include "win32/winutil.h"
+#endif
+
+#define php_stream_fopen_from_fd_int(fd, mode, persistent_id) _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_CC TSRMLS_CC)
+#define php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id) _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC TSRMLS_CC)
+#define php_stream_fopen_from_file_int(file, mode) _php_stream_fopen_from_file_int((file), (mode) STREAMS_CC TSRMLS_CC)
+#define php_stream_fopen_from_file_int_rel(file, mode) _php_stream_fopen_from_file_int((file), (mode) STREAMS_REL_CC TSRMLS_CC)
+
+#if !defined(WINDOWS) && !defined(NETWARE)
+extern int php_get_uid_by_name(const char *name, uid_t *uid TSRMLS_DC);
+extern int php_get_gid_by_name(const char *name, gid_t *gid TSRMLS_DC);
+#endif
+
+/* parse standard "fopen" modes into open() flags */
+PHPAPI int php_stream_parse_fopen_modes(const char *mode, int *open_flags)
+{
+ int flags;
+
+ switch (mode[0]) {
+ case 'r':
+ flags = 0;
+ break;
+ case 'w':
+ flags = O_TRUNC|O_CREAT;
+ break;
+ case 'a':
+ flags = O_CREAT|O_APPEND;
+ break;
+ case 'x':
+ flags = O_CREAT|O_EXCL;
+ break;
+ case 'c':
+ flags = O_CREAT;
+ break;
+ default:
+ /* unknown mode */
+ return FAILURE;
+ }
+#if defined(O_NONBLOCK)
+ if (strchr(mode, 'n')) {
+ flags |= O_NONBLOCK;
+ }
+#endif
+ if (strchr(mode, '+')) {
+ flags |= O_RDWR;
+ } else if (flags) {
+ flags |= O_WRONLY;
+ } else {
+ flags |= O_RDONLY;
+ }
+
+#if defined(_O_TEXT) && defined(O_BINARY)
+ if (strchr(mode, 't')) {
+ flags |= _O_TEXT;
+ } else {
+ flags |= O_BINARY;
+ }
+#endif
+
+ *open_flags = flags;
+ return SUCCESS;
+}
+
+
+/* {{{ ------- STDIO stream implementation -------*/
+
+typedef struct {
+ FILE *file;
+ int fd; /* underlying file descriptor */
+ unsigned is_process_pipe:1; /* use pclose instead of fclose */
+ unsigned is_pipe:1; /* don't try and seek */
+ unsigned cached_fstat:1; /* sb is valid */
+ unsigned _reserved:29;
+
+ int lock_flag; /* stores the lock state */
+ char *temp_file_name; /* if non-null, this is the path to a temporary file that
+ * is to be deleted when the stream is closed */
+#if HAVE_FLUSHIO
+ char last_op;
+#endif
+
+#if HAVE_MMAP
+ char *last_mapped_addr;
+ size_t last_mapped_len;
+#endif
+#ifdef PHP_WIN32
+ char *last_mapped_addr;
+ HANDLE file_mapping;
+#endif
+
+ struct stat sb;
+} php_stdio_stream_data;
+#define PHP_STDIOP_GET_FD(anfd, data) anfd = (data)->file ? fileno((data)->file) : (data)->fd
+
+static int do_fstat(php_stdio_stream_data *d, int force)
+{
+ if (!d->cached_fstat || force) {
+ int fd;
+ int r;
+
+ PHP_STDIOP_GET_FD(fd, d);
+ r = fstat(fd, &d->sb);
+ d->cached_fstat = r == 0;
+
+ return r;
+ }
+ return 0;
+}
+
+static php_stream *_php_stream_fopen_from_fd_int(int fd, const char *mode, const char *persistent_id STREAMS_DC TSRMLS_DC)
+{
+ php_stdio_stream_data *self;
+
+ self = pemalloc_rel_orig(sizeof(*self), persistent_id);
+ memset(self, 0, sizeof(*self));
+ self->file = NULL;
+ self->is_pipe = 0;
+ self->lock_flag = LOCK_UN;
+ self->is_process_pipe = 0;
+ self->temp_file_name = NULL;
+ self->fd = fd;
+
+ return php_stream_alloc_rel(&php_stream_stdio_ops, self, persistent_id, mode);
+}
+
+static php_stream *_php_stream_fopen_from_file_int(FILE *file, const char *mode STREAMS_DC TSRMLS_DC)
+{
+ php_stdio_stream_data *self;
+
+ self = emalloc_rel_orig(sizeof(*self));
+ memset(self, 0, sizeof(*self));
+ self->file = file;
+ self->is_pipe = 0;
+ self->lock_flag = LOCK_UN;
+ self->is_process_pipe = 0;
+ self->temp_file_name = NULL;
+ self->fd = fileno(file);
+
+ return php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
+}
+
+PHPAPI php_stream *_php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path STREAMS_DC TSRMLS_DC)
+{
+ int fd = php_open_temporary_fd(dir, pfx, opened_path TSRMLS_CC);
+
+ if (fd != -1) {
+ php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, "r+b", NULL);
+ if (stream) {
+ return stream;
+ }
+ close(fd);
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to allocate stream");
+
+ return NULL;
+ }
+ return NULL;
+}
+
+PHPAPI php_stream *_php_stream_fopen_tmpfile(int dummy STREAMS_DC TSRMLS_DC)
+{
+ char *opened_path = NULL;
+ int fd = php_open_temporary_fd(NULL, "php", &opened_path TSRMLS_CC);
+
+ if (fd != -1) {
+ php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, "r+b", NULL);
+ if (stream) {
+ php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
+ stream->wrapper = &php_plain_files_wrapper;
+ stream->orig_path = estrdup(opened_path);
+
+ self->temp_file_name = opened_path;
+ self->lock_flag = LOCK_UN;
+
+ return stream;
+ }
+ close(fd);
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to allocate stream");
+
+ return NULL;
+ }
+ return NULL;
+}
+
+PHPAPI php_stream *_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
+
+ if (stream) {
+ php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
+
+#ifdef S_ISFIFO
+ /* detect if this is a pipe */
+ if (self->fd >= 0) {
+ self->is_pipe = (do_fstat(self, 0) == 0 && S_ISFIFO(self->sb.st_mode)) ? 1 : 0;
+ }
+#elif defined(PHP_WIN32)
+ {
+ zend_uintptr_t handle = _get_osfhandle(self->fd);
+
+ if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) {
+ self->is_pipe = GetFileType((HANDLE)handle) == FILE_TYPE_PIPE;
+ }
+ }
+#endif
+
+ if (self->is_pipe) {
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ } else {
+ stream->position = lseek(self->fd, 0, SEEK_CUR);
+#ifdef ESPIPE
+ if (stream->position == (off_t)-1 && errno == ESPIPE) {
+ stream->position = 0;
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ self->is_pipe = 1;
+ }
+#endif
+ }
+ }
+
+ return stream;
+}
+
+PHPAPI php_stream *_php_stream_fopen_from_file(FILE *file, const char *mode STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = php_stream_fopen_from_file_int_rel(file, mode);
+
+ if (stream) {
+ php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
+
+#ifdef S_ISFIFO
+ /* detect if this is a pipe */
+ if (self->fd >= 0) {
+ self->is_pipe = (do_fstat(self, 0) == 0 && S_ISFIFO(self->sb.st_mode)) ? 1 : 0;
+ }
+#elif defined(PHP_WIN32)
+ {
+ zend_uintptr_t handle = _get_osfhandle(self->fd);
+
+ if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) {
+ self->is_pipe = GetFileType((HANDLE)handle) == FILE_TYPE_PIPE;
+ }
+ }
+#endif
+
+ if (self->is_pipe) {
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ } else {
+ stream->position = ftell(file);
+ }
+ }
+
+ return stream;
+}
+
+PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STREAMS_DC TSRMLS_DC)
+{
+ php_stdio_stream_data *self;
+ php_stream *stream;
+
+ self = emalloc_rel_orig(sizeof(*self));
+ memset(self, 0, sizeof(*self));
+ self->file = file;
+ self->is_pipe = 1;
+ self->lock_flag = LOCK_UN;
+ self->is_process_pipe = 1;
+ self->fd = fileno(file);
+ self->temp_file_name = NULL;
+
+ stream = php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ return stream;
+}
+
+static size_t php_stdiop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
+
+ assert(data != NULL);
+
+ if (data->fd >= 0) {
+ int bytes_written = write(data->fd, buf, count);
+ if (bytes_written < 0) return 0;
+ return (size_t) bytes_written;
+ } else {
+
+#if HAVE_FLUSHIO
+ if (!data->is_pipe && data->last_op == 'r') {
+ fseek(data->file, 0, SEEK_CUR);
+ }
+ data->last_op = 'w';
+#endif
+
+ return fwrite(buf, 1, count, data->file);
+ }
+}
+
+static size_t php_stdiop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
+ size_t ret;
+
+ assert(data != NULL);
+
+ if (data->fd >= 0) {
+ ret = read(data->fd, buf, count);
+
+ if (ret == (size_t)-1 && errno == EINTR) {
+ /* Read was interrupted, retry once,
+ If read still fails, giveup with feof==0
+ so script can retry if desired */
+ ret = read(data->fd, buf, count);
+ }
+
+ stream->eof = (ret == 0 || (ret == (size_t)-1 && errno != EWOULDBLOCK && errno != EINTR && errno != EBADF));
+
+ } else {
+#if HAVE_FLUSHIO
+ if (!data->is_pipe && data->last_op == 'w')
+ fseek(data->file, 0, SEEK_CUR);
+ data->last_op = 'r';
+#endif
+
+ ret = fread(buf, 1, count, data->file);
+
+ stream->eof = feof(data->file);
+ }
+ return ret;
+}
+
+static int php_stdiop_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ int ret;
+ php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
+
+ assert(data != NULL);
+
+#if HAVE_MMAP
+ if (data->last_mapped_addr) {
+ munmap(data->last_mapped_addr, data->last_mapped_len);
+ data->last_mapped_addr = NULL;
+ }
+#elif defined(PHP_WIN32)
+ if (data->last_mapped_addr) {
+ UnmapViewOfFile(data->last_mapped_addr);
+ data->last_mapped_addr = NULL;
+ }
+ if (data->file_mapping) {
+ CloseHandle(data->file_mapping);
+ data->file_mapping = NULL;
+ }
+#endif
+
+ if (close_handle) {
+ if (data->file) {
+ if (data->is_process_pipe) {
+ errno = 0;
+ ret = pclose(data->file);
+
+#if HAVE_SYS_WAIT_H
+ if (WIFEXITED(ret)) {
+ ret = WEXITSTATUS(ret);
+ }
+#endif
+ } else {
+ ret = fclose(data->file);
+ data->file = NULL;
+ }
+ } else if (data->fd != -1) {
+ ret = close(data->fd);
+ data->fd = -1;
+ } else {
+ return 0; /* everything should be closed already -> success */
+ }
+ if (data->temp_file_name) {
+ unlink(data->temp_file_name);
+ /* temporary streams are never persistent */
+ efree(data->temp_file_name);
+ data->temp_file_name = NULL;
+ }
+ } else {
+ ret = 0;
+ data->file = NULL;
+ data->fd = -1;
+ }
+
+ pefree(data, stream->is_persistent);
+
+ return ret;
+}
+
+static int php_stdiop_flush(php_stream *stream TSRMLS_DC)
+{
+ php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
+
+ assert(data != NULL);
+
+ /*
+ * stdio buffers data in user land. By calling fflush(3), this
+ * data is send to the kernel using write(2). fsync'ing is
+ * something completely different.
+ */
+ if (data->file) {
+ return fflush(data->file);
+ }
+ return 0;
+}
+
+static int php_stdiop_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC)
+{
+ php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
+ int ret;
+
+ assert(data != NULL);
+
+ if (data->is_pipe) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot seek on a pipe");
+ return -1;
+ }
+
+ if (data->fd >= 0) {
+ off_t result;
+
+ result = lseek(data->fd, offset, whence);
+ if (result == (off_t)-1)
+ return -1;
+
+ *newoffset = result;
+ return 0;
+
+ } else {
+ ret = fseek(data->file, offset, whence);
+ *newoffset = ftell(data->file);
+ return ret;
+ }
+}
+
+static int php_stdiop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
+{
+ int fd;
+ php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
+
+ assert(data != NULL);
+
+ /* as soon as someone touches the stdio layer, buffering may ensue,
+ * so we need to stop using the fd directly in that case */
+
+ switch (castas) {
+ case PHP_STREAM_AS_STDIO:
+ if (ret) {
+
+ if (data->file == NULL) {
+ /* we were opened as a plain file descriptor, so we
+ * need fdopen now */
+ char fixed_mode[5];
+ php_stream_mode_sanitize_fdopen_fopencookie(stream, fixed_mode);
+ data->file = fdopen(data->fd, fixed_mode);
+ if (data->file == NULL) {
+ return FAILURE;
+ }
+ }
+
+ *(FILE**)ret = data->file;
+ data->fd = -1;
+ }
+ return SUCCESS;
+
+ case PHP_STREAM_AS_FD_FOR_SELECT:
+ PHP_STDIOP_GET_FD(fd, data);
+ if (fd < 0) {
+ return FAILURE;
+ }
+ if (ret) {
+ *(int*)ret = fd;
+ }
+ return SUCCESS;
+
+ case PHP_STREAM_AS_FD:
+ PHP_STDIOP_GET_FD(fd, data);
+
+ if (fd < 0) {
+ return FAILURE;
+ }
+ if (data->file) {
+ fflush(data->file);
+ }
+ if (ret) {
+ *(int*)ret = fd;
+ }
+ return SUCCESS;
+ default:
+ return FAILURE;
+ }
+}
+
+static int php_stdiop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
+{
+ int ret;
+ php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
+
+ assert(data != NULL);
+
+ ret = do_fstat(data, 1);
+ memcpy(&ssb->sb, &data->sb, sizeof(ssb->sb));
+ return ret;
+}
+
+static int php_stdiop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
+{
+ php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
+ size_t size;
+ int fd;
+#ifdef O_NONBLOCK
+ /* FIXME: make this work for win32 */
+ int flags;
+ int oldval;
+#endif
+
+ PHP_STDIOP_GET_FD(fd, data);
+
+ switch(option) {
+ case PHP_STREAM_OPTION_BLOCKING:
+ if (fd == -1)
+ return -1;
+#ifdef O_NONBLOCK
+ flags = fcntl(fd, F_GETFL, 0);
+ oldval = (flags & O_NONBLOCK) ? 0 : 1;
+ if (value)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (-1 == fcntl(fd, F_SETFL, flags))
+ return -1;
+ return oldval;
+#else
+ return -1; /* not yet implemented */
+#endif
+
+ case PHP_STREAM_OPTION_WRITE_BUFFER:
+
+ if (data->file == NULL) {
+ return -1;
+ }
+
+ if (ptrparam)
+ size = *(size_t *)ptrparam;
+ else
+ size = BUFSIZ;
+
+ switch(value) {
+ case PHP_STREAM_BUFFER_NONE:
+ return setvbuf(data->file, NULL, _IONBF, 0);
+
+ case PHP_STREAM_BUFFER_LINE:
+ return setvbuf(data->file, NULL, _IOLBF, size);
+
+ case PHP_STREAM_BUFFER_FULL:
+ return setvbuf(data->file, NULL, _IOFBF, size);
+
+ default:
+ return -1;
+ }
+ break;
+
+ case PHP_STREAM_OPTION_LOCKING:
+ if (fd == -1) {
+ return -1;
+ }
+
+ if ((zend_uintptr_t) ptrparam == PHP_STREAM_LOCK_SUPPORTED) {
+ return 0;
+ }
+
+ if (!flock(fd, value)) {
+ data->lock_flag = value;
+ return 0;
+ } else {
+ return -1;
+ }
+ break;
+
+ case PHP_STREAM_OPTION_MMAP_API:
+#if HAVE_MMAP
+ {
+ php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
+ int prot, flags;
+
+ switch (value) {
+ case PHP_STREAM_MMAP_SUPPORTED:
+ return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_MMAP_MAP_RANGE:
+ do_fstat(data, 1);
+ if (range->length == 0 && range->offset > 0 && range->offset < data->sb.st_size) {
+ range->length = data->sb.st_size - range->offset;
+ }
+ if (range->length == 0 || range->length > data->sb.st_size) {
+ range->length = data->sb.st_size;
+ }
+ if (range->offset >= data->sb.st_size) {
+ range->offset = data->sb.st_size;
+ range->length = 0;
+ }
+ switch (range->mode) {
+ case PHP_STREAM_MAP_MODE_READONLY:
+ prot = PROT_READ;
+ flags = MAP_PRIVATE;
+ break;
+ case PHP_STREAM_MAP_MODE_READWRITE:
+ prot = PROT_READ | PROT_WRITE;
+ flags = MAP_PRIVATE;
+ break;
+ case PHP_STREAM_MAP_MODE_SHARED_READONLY:
+ prot = PROT_READ;
+ flags = MAP_SHARED;
+ break;
+ case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
+ prot = PROT_READ | PROT_WRITE;
+ flags = MAP_SHARED;
+ break;
+ default:
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset);
+ if (range->mapped == (char*)MAP_FAILED) {
+ range->mapped = NULL;
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ /* remember the mapping */
+ data->last_mapped_addr = range->mapped;
+ data->last_mapped_len = range->length;
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_MMAP_UNMAP:
+ if (data->last_mapped_addr) {
+ munmap(data->last_mapped_addr, data->last_mapped_len);
+ data->last_mapped_addr = NULL;
+
+ return PHP_STREAM_OPTION_RETURN_OK;
+ }
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ }
+#elif defined(PHP_WIN32)
+ {
+ php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
+ HANDLE hfile = (HANDLE)_get_osfhandle(fd);
+ DWORD prot, acc, loffs = 0, delta = 0;
+
+ switch (value) {
+ case PHP_STREAM_MMAP_SUPPORTED:
+ return hfile == INVALID_HANDLE_VALUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_MMAP_MAP_RANGE:
+ switch (range->mode) {
+ case PHP_STREAM_MAP_MODE_READONLY:
+ prot = PAGE_READONLY;
+ acc = FILE_MAP_READ;
+ break;
+ case PHP_STREAM_MAP_MODE_READWRITE:
+ prot = PAGE_READWRITE;
+ acc = FILE_MAP_READ | FILE_MAP_WRITE;
+ break;
+ case PHP_STREAM_MAP_MODE_SHARED_READONLY:
+ prot = PAGE_READONLY;
+ acc = FILE_MAP_READ;
+ /* TODO: we should assign a name for the mapping */
+ break;
+ case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
+ prot = PAGE_READWRITE;
+ acc = FILE_MAP_READ | FILE_MAP_WRITE;
+ /* TODO: we should assign a name for the mapping */
+ break;
+ default:
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+
+ /* create a mapping capable of viewing the whole file (this costs no real resources) */
+ data->file_mapping = CreateFileMapping(hfile, NULL, prot, 0, 0, NULL);
+
+ if (data->file_mapping == NULL) {
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+
+ size = GetFileSize(hfile, NULL);
+ if (range->length == 0 && range->offset > 0 && range->offset < size) {
+ range->length = size - range->offset;
+ }
+ if (range->length == 0 || range->length > size) {
+ range->length = size;
+ }
+ if (range->offset >= size) {
+ range->offset = size;
+ range->length = 0;
+ }
+
+ /* figure out how big a chunk to map to be able to view the part that we need */
+ if (range->offset != 0) {
+ SYSTEM_INFO info;
+ DWORD gran;
+
+ GetSystemInfo(&info);
+ gran = info.dwAllocationGranularity;
+ loffs = (range->offset / gran) * gran;
+ delta = range->offset - loffs;
+ }
+
+ data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta);
+
+ if (data->last_mapped_addr) {
+ /* give them back the address of the start offset they requested */
+ range->mapped = data->last_mapped_addr + delta;
+ return PHP_STREAM_OPTION_RETURN_OK;
+ }
+
+ CloseHandle(data->file_mapping);
+ data->file_mapping = NULL;
+
+ return PHP_STREAM_OPTION_RETURN_ERR;
+
+ case PHP_STREAM_MMAP_UNMAP:
+ if (data->last_mapped_addr) {
+ UnmapViewOfFile(data->last_mapped_addr);
+ data->last_mapped_addr = NULL;
+ CloseHandle(data->file_mapping);
+ data->file_mapping = NULL;
+ return PHP_STREAM_OPTION_RETURN_OK;
+ }
+ return PHP_STREAM_OPTION_RETURN_ERR;
+
+ default:
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ }
+
+#endif
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+
+ case PHP_STREAM_OPTION_TRUNCATE_API:
+ switch (value) {
+ case PHP_STREAM_TRUNCATE_SUPPORTED:
+ return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_TRUNCATE_SET_SIZE: {
+ ptrdiff_t new_size = *(ptrdiff_t*)ptrparam;
+ if (new_size < 0) {
+ return PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ }
+
+ default:
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ }
+}
+
+PHPAPI php_stream_ops php_stream_stdio_ops = {
+ php_stdiop_write, php_stdiop_read,
+ php_stdiop_close, php_stdiop_flush,
+ "STDIO",
+ php_stdiop_seek,
+ php_stdiop_cast,
+ php_stdiop_stat,
+ php_stdiop_set_option
+};
+/* }}} */
+
+/* {{{ plain files opendir/readdir implementation */
+static size_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ DIR *dir = (DIR*)stream->abstract;
+ /* avoid libc5 readdir problems */
+ char entry[sizeof(struct dirent)+MAXPATHLEN];
+ struct dirent *result = (struct dirent *)&entry;
+ php_stream_dirent *ent = (php_stream_dirent*)buf;
+
+ /* avoid problems if someone mis-uses the stream */
+ if (count != sizeof(php_stream_dirent))
+ return 0;
+
+ if (php_readdir_r(dir, (struct dirent *)entry, &result) == 0 && result) {
+ PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name));
+ return sizeof(php_stream_dirent);
+ }
+ return 0;
+}
+
+static int php_plain_files_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ return closedir((DIR *)stream->abstract);
+}
+
+static int php_plain_files_dirstream_rewind(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
+{
+ rewinddir((DIR *)stream->abstract);
+ return 0;
+}
+
+static php_stream_ops php_plain_files_dirstream_ops = {
+ NULL, php_plain_files_dirstream_read,
+ php_plain_files_dirstream_close, NULL,
+ "dir",
+ php_plain_files_dirstream_rewind,
+ NULL, /* cast */
+ NULL, /* stat */
+ NULL /* set_option */
+};
+
+static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, char *path, char *mode,
+ int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ DIR *dir = NULL;
+ php_stream *stream = NULL;
+
+#ifdef HAVE_GLOB
+ if (options & STREAM_USE_GLOB_DIR_OPEN) {
+ return php_glob_stream_wrapper.wops->dir_opener(&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC TSRMLS_CC);
+ }
+#endif
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path TSRMLS_CC)) {
+ return NULL;
+ }
+
+ dir = VCWD_OPENDIR(path);
+
+#ifdef PHP_WIN32
+ if (!dir) {
+ php_win32_docref2_from_error(GetLastError(), path, path TSRMLS_CC);
+ }
+
+ if (dir && dir->finished) {
+ closedir(dir);
+ dir = NULL;
+ }
+#endif
+ if (dir) {
+ stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
+ if (stream == NULL)
+ closedir(dir);
+ }
+
+ return stream;
+}
+/* }}} */
+
+/* {{{ php_stream_fopen */
+PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, char **opened_path, int options STREAMS_DC TSRMLS_DC)
+{
+ char *realpath = NULL;
+ int open_flags;
+ int fd;
+ php_stream *ret;
+ int persistent = options & STREAM_OPEN_PERSISTENT;
+ char *persistent_id = NULL;
+
+ if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
+ if (options & REPORT_ERRORS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "`%s' is not a valid mode for fopen", mode);
+ }
+ return NULL;
+ }
+
+ if (options & STREAM_ASSUME_REALPATH) {
+ realpath = estrdup(filename);
+ } else {
+ if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) {
+ return NULL;
+ }
+ }
+
+ if (persistent) {
+ spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
+ switch (php_stream_from_persistent_id(persistent_id, &ret TSRMLS_CC)) {
+ case PHP_STREAM_PERSISTENT_SUCCESS:
+ if (opened_path) {
+ *opened_path = realpath;
+ realpath = NULL;
+ }
+ /* fall through */
+
+ case PHP_STREAM_PERSISTENT_FAILURE:
+ if (realpath) {
+ efree(realpath);
+ }
+ efree(persistent_id);;
+ return ret;
+ }
+ }
+
+ fd = open(realpath, open_flags, 0666);
+
+ if (fd != -1) {
+
+ if (options & STREAM_OPEN_FOR_INCLUDE) {
+ ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
+ } else {
+ ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id);
+ }
+
+ if (ret) {
+ if (opened_path) {
+ *opened_path = realpath;
+ realpath = NULL;
+ }
+ if (realpath) {
+ efree(realpath);
+ }
+ if (persistent_id) {
+ efree(persistent_id);
+ }
+
+ /* WIN32 always set ISREG flag */
+#ifndef PHP_WIN32
+ /* sanity checks for include/require.
+ * We check these after opening the stream, so that we save
+ * on fstat() syscalls */
+ if (options & STREAM_OPEN_FOR_INCLUDE) {
+ php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
+ int r;
+
+ r = do_fstat(self, 0);
+ if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
+ if (opened_path) {
+ efree(*opened_path);
+ *opened_path = NULL;
+ }
+ php_stream_close(ret);
+ return NULL;
+ }
+ }
+#endif
+
+ return ret;
+ }
+ close(fd);
+ }
+ efree(realpath);
+ if (persistent_id) {
+ efree(persistent_id);
+ }
+ return NULL;
+}
+/* }}} */
+
+
+static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, char *path, char *mode,
+ int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path TSRMLS_CC)) {
+ return NULL;
+ }
+
+ return php_stream_fopen_rel(path, mode, opened_path, options);
+}
+
+static int php_plain_files_url_stater(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
+{
+
+ if (strncmp(url, "file://", sizeof("file://") - 1) == 0) {
+ url += sizeof("file://") - 1;
+ }
+
+ if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1 TSRMLS_CC)) {
+ return -1;
+ }
+
+#ifdef PHP_WIN32
+ if (EG(windows_version_info).dwMajorVersion >= 5) {
+ if (flags & PHP_STREAM_URL_STAT_LINK) {
+ return VCWD_LSTAT(url, &ssb->sb);
+ }
+ }
+#else
+# ifdef HAVE_SYMLINK
+ if (flags & PHP_STREAM_URL_STAT_LINK) {
+ return VCWD_LSTAT(url, &ssb->sb);
+ } else
+# endif
+#endif
+ return VCWD_STAT(url, &ssb->sb);
+}
+
+static int php_plain_files_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
+{
+ char *p;
+ int ret;
+
+ if ((p = strstr(url, "://")) != NULL) {
+ url = p + 3;
+ }
+
+ if (php_check_open_basedir(url TSRMLS_CC)) {
+ return 0;
+ }
+
+ ret = VCWD_UNLINK(url);
+ if (ret == -1) {
+ if (options & REPORT_ERRORS) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "%s", strerror(errno));
+ }
+ return 0;
+ }
+
+ /* Clear stat cache (and realpath cache) */
+ php_clear_stat_cache(1, NULL, 0 TSRMLS_CC);
+
+ return 1;
+}
+
+static int php_plain_files_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
+{
+ char *p;
+ int ret;
+
+ if (!url_from || !url_to) {
+ return 0;
+ }
+
+#ifdef PHP_WIN32
+ if (!php_win32_check_trailing_space(url_from, strlen(url_from))) {
+ php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to TSRMLS_CC);
+ return 0;
+ }
+ if (!php_win32_check_trailing_space(url_to, strlen(url_to))) {
+ php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to TSRMLS_CC);
+ return 0;
+ }
+#endif
+
+ if ((p = strstr(url_from, "://")) != NULL) {
+ url_from = p + 3;
+ }
+
+ if ((p = strstr(url_to, "://")) != NULL) {
+ url_to = p + 3;
+ }
+
+ if (php_check_open_basedir(url_from TSRMLS_CC) || php_check_open_basedir(url_to TSRMLS_CC)) {
+ return 0;
+ }
+
+ ret = VCWD_RENAME(url_from, url_to);
+
+ if (ret == -1) {
+#ifndef PHP_WIN32
+# ifdef EXDEV
+ if (errno == EXDEV) {
+ struct stat sb;
+ if (php_copy_file(url_from, url_to TSRMLS_CC) == SUCCESS) {
+ if (VCWD_STAT(url_from, &sb) == 0) {
+# if !defined(TSRM_WIN32) && !defined(NETWARE)
+ if (VCWD_CHMOD(url_to, sb.st_mode)) {
+ if (errno == EPERM) {
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+ VCWD_UNLINK(url_from);
+ return 1;
+ }
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+ return 0;
+ }
+ if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
+ if (errno == EPERM) {
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+ VCWD_UNLINK(url_from);
+ return 1;
+ }
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+ return 0;
+ }
+# endif
+ VCWD_UNLINK(url_from);
+ return 1;
+ }
+ }
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+ return 0;
+ }
+# endif
+#endif
+
+#ifdef PHP_WIN32
+ php_win32_docref2_from_error(GetLastError(), url_from, url_to TSRMLS_CC);
+#else
+ php_error_docref2(NULL TSRMLS_CC, url_from, url_to, E_WARNING, "%s", strerror(errno));
+#endif
+ return 0;
+ }
+
+ /* Clear stat cache (and realpath cache) */
+ php_clear_stat_cache(1, NULL, 0 TSRMLS_CC);
+
+ return 1;
+}
+
+static int php_plain_files_mkdir(php_stream_wrapper *wrapper, char *dir, int mode, int options, php_stream_context *context TSRMLS_DC)
+{
+ int ret, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
+ char *p;
+
+ if ((p = strstr(dir, "://")) != NULL) {
+ dir = p + 3;
+ }
+
+ if (!recursive) {
+ ret = php_mkdir(dir, mode TSRMLS_CC);
+ } else {
+ /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
+ char *e;
+ struct stat sb;
+ int dir_len = strlen(dir);
+ int offset = 0;
+ char buf[MAXPATHLEN];
+
+ if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND TSRMLS_CC)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path");
+ return 0;
+ }
+
+ e = buf + strlen(buf);
+
+ if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
+ offset = p - buf + 1;
+ }
+
+ if (p && dir_len == 1) {
+ /* buf == "DEFAULT_SLASH" */
+ }
+ else {
+ /* find a top level directory we need to create */
+ while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
+ int n = 0;
+
+ *p = '\0';
+ while (p > buf && *(p-1) == DEFAULT_SLASH) {
+ ++n;
+ --p;
+ *p = '\0';
+ }
+ if (VCWD_STAT(buf, &sb) == 0) {
+ while (1) {
+ *p = DEFAULT_SLASH;
+ if (!n) break;
+ --n;
+ ++p;
+ }
+ break;
+ }
+ }
+ }
+
+ if (p == buf) {
+ ret = php_mkdir(dir, mode TSRMLS_CC);
+ } else if (!(ret = php_mkdir(buf, mode TSRMLS_CC))) {
+ if (!p) {
+ p = buf;
+ }
+ /* create any needed directories if the creation of the 1st directory worked */
+ while (++p != e) {
+ if (*p == '\0') {
+ *p = DEFAULT_SLASH;
+ if ((*(p+1) != '\0') &&
+ (ret = VCWD_MKDIR(buf, (mode_t)mode)) < 0) {
+ if (options & REPORT_ERRORS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", strerror(errno));
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (ret < 0) {
+ /* Failure */
+ return 0;
+ } else {
+ /* Success */
+ return 1;
+ }
+}
+
+static int php_plain_files_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
+{
+#if PHP_WIN32
+ int url_len = strlen(url);
+#endif
+ if (php_check_open_basedir(url TSRMLS_CC)) {
+ return 0;
+ }
+
+#if PHP_WIN32
+ if (!php_win32_check_trailing_space(url, url_len)) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "%s", strerror(ENOENT));
+ return 0;
+ }
+#endif
+
+ if (VCWD_RMDIR(url) < 0) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "%s", strerror(errno));
+ return 0;
+ }
+
+ /* Clear stat cache (and realpath cache) */
+ php_clear_stat_cache(1, NULL, 0 TSRMLS_CC);
+
+ return 1;
+}
+
+static int php_plain_files_metadata(php_stream_wrapper *wrapper, char *url, int option, void *value, php_stream_context *context TSRMLS_DC)
+{
+ struct utimbuf *newtime;
+ char *p;
+#if !defined(WINDOWS) && !defined(NETWARE)
+ uid_t uid;
+ gid_t gid;
+#endif
+ mode_t mode;
+ int ret = 0;
+#if PHP_WIN32
+ int url_len = strlen(url);
+#endif
+
+#if PHP_WIN32
+ if (!php_win32_check_trailing_space(url, url_len)) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "%s", strerror(ENOENT));
+ return 0;
+ }
+#endif
+
+ if ((p = strstr(url, "://")) != NULL) {
+ url = p + 3;
+ }
+
+ if (php_check_open_basedir(url TSRMLS_CC)) {
+ return 0;
+ }
+
+ switch(option) {
+ case PHP_STREAM_META_TOUCH:
+ newtime = (struct utimbuf *)value;
+ if (VCWD_ACCESS(url, F_OK) != 0) {
+ FILE *file = VCWD_FOPEN(url, "w");
+ if (file == NULL) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
+ return 0;
+ }
+ fclose(file);
+ }
+
+ ret = VCWD_UTIME(url, newtime);
+ break;
+#if !defined(WINDOWS) && !defined(NETWARE)
+ case PHP_STREAM_META_OWNER_NAME:
+ case PHP_STREAM_META_OWNER:
+ if(option == PHP_STREAM_META_OWNER_NAME) {
+ if(php_get_uid_by_name((char *)value, &uid TSRMLS_CC) != SUCCESS) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "Unable to find uid for %s", (char *)value);
+ return 0;
+ }
+ } else {
+ uid = (uid_t)*(long *)value;
+ }
+ ret = VCWD_CHOWN(url, uid, -1);
+ break;
+ case PHP_STREAM_META_GROUP:
+ case PHP_STREAM_META_GROUP_NAME:
+ if(option == PHP_STREAM_META_OWNER_NAME) {
+ if(php_get_gid_by_name((char *)value, &gid TSRMLS_CC) != SUCCESS) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "Unable to find gid for %s", (char *)value);
+ return 0;
+ }
+ } else {
+ gid = (gid_t)*(long *)value;
+ }
+ ret = VCWD_CHOWN(url, -1, gid);
+ break;
+#endif
+ case PHP_STREAM_META_ACCESS:
+ mode = (mode_t)*(long *)value;
+ ret = VCWD_CHMOD(url, mode);
+ break;
+ default:
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "Unknown option %d for stream_metadata", option);
+ return 0;
+ }
+ if (ret == -1) {
+ php_error_docref1(NULL TSRMLS_CC, url, E_WARNING, "Operation failed: %s", strerror(errno));
+ return 0;
+ }
+ php_clear_stat_cache(0, NULL, 0 TSRMLS_CC);
+ return 1;
+}
+
+
+static php_stream_wrapper_ops php_plain_files_wrapper_ops = {
+ php_plain_files_stream_opener,
+ NULL,
+ NULL,
+ php_plain_files_url_stater,
+ php_plain_files_dir_opener,
+ "plainfile",
+ php_plain_files_unlink,
+ php_plain_files_rename,
+ php_plain_files_mkdir,
+ php_plain_files_rmdir,
+ php_plain_files_metadata
+};
+
+php_stream_wrapper php_plain_files_wrapper = {
+ &php_plain_files_wrapper_ops,
+ NULL,
+ 0
+};
+
+/* {{{ php_stream_fopen_with_path */
+PHPAPI php_stream *_php_stream_fopen_with_path(char *filename, char *mode, char *path, char **opened_path, int options STREAMS_DC TSRMLS_DC)
+{
+ /* code ripped off from fopen_wrappers.c */
+ char *pathbuf, *ptr, *end;
+ const char *exec_fname;
+ char trypath[MAXPATHLEN];
+ php_stream *stream;
+ int path_length;
+ int filename_length;
+ int exec_fname_length;
+
+ if (opened_path) {
+ *opened_path = NULL;
+ }
+
+ if(!filename) {
+ return NULL;
+ }
+
+ filename_length = strlen(filename);
+
+ /* Relative path open */
+ if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
+ /* further checks, we could have ....... filenames */
+ ptr = filename + 1;
+ if (*ptr == '.') {
+ while (*(++ptr) == '.');
+ if (!IS_SLASH(*ptr)) { /* not a relative path after all */
+ goto not_relative_path;
+ }
+ }
+
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename TSRMLS_CC)) {
+ return NULL;
+ }
+
+ return php_stream_fopen_rel(filename, mode, opened_path, options);
+ }
+
+not_relative_path:
+
+ /* Absolute path open */
+ if (IS_ABSOLUTE_PATH(filename, filename_length)) {
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename TSRMLS_CC)) {
+ return NULL;
+ }
+
+ return php_stream_fopen_rel(filename, mode, opened_path, options);
+ }
+
+#ifdef PHP_WIN32
+ if (IS_SLASH(filename[0])) {
+ size_t cwd_len;
+ char *cwd;
+ cwd = virtual_getcwd_ex(&cwd_len TSRMLS_CC);
+ /* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
+ *(cwd+3) = '\0';
+
+ if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
+ php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
+ }
+
+ free(cwd);
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath TSRMLS_CC)) {
+ return NULL;
+ }
+
+ return php_stream_fopen_rel(trypath, mode, opened_path, options);
+ }
+#endif
+
+ if (!path || (path && !*path)) {
+ return php_stream_fopen_rel(filename, mode, opened_path, options);
+ }
+
+ /* check in provided path */
+ /* append the calling scripts' current working directory
+ * as a fall back case
+ */
+ if (zend_is_executing(TSRMLS_C)) {
+ exec_fname = zend_get_executed_filename(TSRMLS_C);
+ exec_fname_length = strlen(exec_fname);
+ path_length = strlen(path);
+
+ while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length]));
+ if ((exec_fname && exec_fname[0] == '[')
+ || exec_fname_length<=0) {
+ /* [no active file] or no path */
+ pathbuf = estrdup(path);
+ } else {
+ pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
+ memcpy(pathbuf, path, path_length);
+ pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
+ memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
+ pathbuf[path_length + exec_fname_length +1] = '\0';
+ }
+ } else {
+ pathbuf = estrdup(path);
+ }
+
+ ptr = pathbuf;
+
+ while (ptr && *ptr) {
+ end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
+ if (end != NULL) {
+ *end = '\0';
+ end++;
+ }
+ if (*ptr == '\0') {
+ goto stream_skip;
+ }
+ if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
+ php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
+ }
+
+ if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0 TSRMLS_CC)) {
+ goto stream_skip;
+ }
+
+ stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
+ if (stream) {
+ efree(pathbuf);
+ return stream;
+ }
+stream_skip:
+ ptr = end;
+ } /* end provided path */
+
+ efree(pathbuf);
+ return NULL;
+
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/streams.c b/main/streams/streams.c
new file mode 100644
index 0000000..47d86b5
--- /dev/null
+++ b/main/streams/streams.c
@@ -0,0 +1,2397 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ | Borrowed code from: |
+ | Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
+ | Jim Winstead <jimw@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#define _GNU_SOURCE
+#include "php.h"
+#include "php_globals.h"
+#include "php_network.h"
+#include "php_open_temporary_file.h"
+#include "ext/standard/file.h"
+#include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */
+#include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */
+#include <stddef.h>
+#include <fcntl.h>
+#include "php_streams_int.h"
+
+/* {{{ resource and registration code */
+/* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */
+static HashTable url_stream_wrappers_hash;
+static int le_stream = FAILURE; /* true global */
+static int le_pstream = FAILURE; /* true global */
+static int le_stream_filter = FAILURE; /* true global */
+
+PHPAPI int php_file_le_stream(void)
+{
+ return le_stream;
+}
+
+PHPAPI int php_file_le_pstream(void)
+{
+ return le_pstream;
+}
+
+PHPAPI int php_file_le_stream_filter(void)
+{
+ return le_stream_filter;
+}
+
+PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)
+{
+ return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
+}
+
+PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void)
+{
+ return &url_stream_wrappers_hash;
+}
+
+static int _php_stream_release_context(zend_rsrc_list_entry *le, void *pContext TSRMLS_DC)
+{
+ if (le->ptr == pContext) {
+ return --le->refcount == 0;
+ }
+ return 0;
+}
+
+static int forget_persistent_resource_id_numbers(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ php_stream *stream;
+
+ if (Z_TYPE_P(rsrc) != le_pstream) {
+ return 0;
+ }
+
+ stream = (php_stream*)rsrc->ptr;
+
+#if STREAM_DEBUG
+fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
+#endif
+
+ stream->rsrc_id = FAILURE;
+
+ if (stream->context) {
+ zend_hash_apply_with_argument(&EG(regular_list),
+ (apply_func_arg_t) _php_stream_release_context,
+ stream->context TSRMLS_CC);
+ stream->context = NULL;
+ }
+
+ return 0;
+}
+
+PHP_RSHUTDOWN_FUNCTION(streams)
+{
+ zend_hash_apply(&EG(persistent_list), (apply_func_t)forget_persistent_resource_id_numbers TSRMLS_CC);
+ return SUCCESS;
+}
+
+PHPAPI php_stream *php_stream_encloses(php_stream *enclosing, php_stream *enclosed)
+{
+ php_stream *orig = enclosed->enclosing_stream;
+
+ php_stream_auto_cleanup(enclosed);
+ enclosed->enclosing_stream = enclosing;
+ return orig;
+}
+
+PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC)
+{
+ zend_rsrc_list_entry *le;
+
+ if (zend_hash_find(&EG(persistent_list), (char*)persistent_id, strlen(persistent_id)+1, (void*) &le) == SUCCESS) {
+ if (Z_TYPE_P(le) == le_pstream) {
+ if (stream) {
+ HashPosition pos;
+ zend_rsrc_list_entry *regentry;
+ ulong index = -1; /* intentional */
+
+ /* see if this persistent resource already has been loaded to the
+ * regular list; allowing the same resource in several entries in the
+ * regular list causes trouble (see bug #54623) */
+ zend_hash_internal_pointer_reset_ex(&EG(regular_list), &pos);
+ while (zend_hash_get_current_data_ex(&EG(regular_list),
+ (void **)&regentry, &pos) == SUCCESS) {
+ if (regentry->ptr == le->ptr) {
+ zend_hash_get_current_key_ex(&EG(regular_list), NULL, NULL,
+ &index, 0, &pos);
+ break;
+ }
+ zend_hash_move_forward_ex(&EG(regular_list), &pos);
+ }
+
+ *stream = (php_stream*)le->ptr;
+ if (index == -1) { /* not found in regular list */
+ le->refcount++;
+ (*stream)->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream);
+ } else {
+ regentry->refcount++;
+ (*stream)->rsrc_id = index;
+ }
+ }
+ return PHP_STREAM_PERSISTENT_SUCCESS;
+ }
+ return PHP_STREAM_PERSISTENT_FAILURE;
+ }
+ return PHP_STREAM_PERSISTENT_NOT_EXIST;
+}
+
+/* }}} */
+
+static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper TSRMLS_DC)
+{
+ zend_llist *list = NULL;
+ if (!FG(wrapper_errors)) {
+ return NULL;
+ } else {
+ zend_hash_find(FG(wrapper_errors), (const char*)&wrapper,
+ sizeof wrapper, (void**)&list);
+ return list;
+ }
+}
+
+/* {{{ wrapper error reporting */
+void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC)
+{
+ char *tmp = estrdup(path);
+ char *msg;
+ int free_msg = 0;
+
+ if (wrapper) {
+ zend_llist *err_list = php_get_wrapper_errors_list(wrapper TSRMLS_CC);
+ if (err_list) {
+ size_t l = 0;
+ int brlen;
+ int i;
+ int count = zend_llist_count(err_list);
+ const char *br;
+ const char **err_buf_p;
+ zend_llist_position pos;
+
+ if (PG(html_errors)) {
+ brlen = 7;
+ br = "<br />\n";
+ } else {
+ brlen = 1;
+ br = "\n";
+ }
+
+ for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
+ err_buf_p;
+ err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
+ l += strlen(*err_buf_p);
+ if (i < count - 1) {
+ l += brlen;
+ }
+ }
+ msg = emalloc(l + 1);
+ msg[0] = '\0';
+ for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
+ err_buf_p;
+ err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
+ strcat(msg, *err_buf_p);
+ if (i < count - 1) {
+ strcat(msg, br);
+ }
+ }
+
+ free_msg = 1;
+ } else {
+ if (wrapper == &php_plain_files_wrapper) {
+ msg = strerror(errno); /* TODO: not ts on linux */
+ } else {
+ msg = "operation failed";
+ }
+ }
+ } else {
+ msg = "no suitable wrapper could be found";
+ }
+
+ php_strip_url_passwd(tmp);
+ php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg);
+ efree(tmp);
+ if (free_msg) {
+ efree(msg);
+ }
+}
+
+void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC)
+{
+ if (wrapper && FG(wrapper_errors)) {
+ zend_hash_del(FG(wrapper_errors), (const char*)&wrapper, sizeof wrapper);
+ }
+}
+
+static void wrapper_error_dtor(void *error)
+{
+ efree(*(char**)error);
+}
+
+PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...)
+{
+ va_list args;
+ char *buffer = NULL;
+
+ va_start(args, fmt);
+ vspprintf(&buffer, 0, fmt, args);
+ va_end(args);
+
+ if (options & REPORT_ERRORS || wrapper == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer);
+ efree(buffer);
+ } else {
+ zend_llist *list = NULL;
+ if (!FG(wrapper_errors)) {
+ ALLOC_HASHTABLE(FG(wrapper_errors));
+ zend_hash_init(FG(wrapper_errors), 8, NULL,
+ (dtor_func_t)zend_llist_destroy, 0);
+ } else {
+ zend_hash_find(FG(wrapper_errors), (const char*)&wrapper,
+ sizeof wrapper, (void**)&list);
+ }
+
+ if (!list) {
+ zend_llist new_list;
+ zend_llist_init(&new_list, sizeof buffer, wrapper_error_dtor, 0);
+ zend_hash_update(FG(wrapper_errors), (const char*)&wrapper,
+ sizeof wrapper, &new_list, sizeof new_list, (void**)&list);
+ }
+
+ /* append to linked list */
+ zend_llist_add_element(list, &buffer);
+ }
+}
+
+
+/* }}} */
+
+/* allocate a new stream for a particular ops */
+PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */
+{
+ php_stream *ret;
+
+ ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0);
+
+ memset(ret, 0, sizeof(php_stream));
+
+ ret->readfilters.stream = ret;
+ ret->writefilters.stream = ret;
+
+#if STREAM_DEBUG
+fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id);
+#endif
+
+ ret->ops = ops;
+ ret->abstract = abstract;
+ ret->is_persistent = persistent_id ? 1 : 0;
+ ret->chunk_size = FG(def_chunk_size);
+
+#if ZEND_DEBUG
+ ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
+ ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
+#endif
+
+ if (FG(auto_detect_line_endings)) {
+ ret->flags |= PHP_STREAM_FLAG_DETECT_EOL;
+ }
+
+ if (persistent_id) {
+ zend_rsrc_list_entry le;
+
+ Z_TYPE(le) = le_pstream;
+ le.ptr = ret;
+ le.refcount = 0;
+
+ if (FAILURE == zend_hash_update(&EG(persistent_list), (char *)persistent_id,
+ strlen(persistent_id) + 1,
+ (void *)&le, sizeof(le), NULL)) {
+
+ pefree(ret, 1);
+ return NULL;
+ }
+ }
+
+ ret->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream);
+ strlcpy(ret->mode, mode, sizeof(ret->mode));
+
+ ret->wrapper = NULL;
+ ret->wrapperthis = NULL;
+ ret->wrapperdata = NULL;
+ ret->stdiocast = NULL;
+ ret->orig_path = NULL;
+ ret->context = NULL;
+ ret->readbuf = NULL;
+ ret->enclosing_stream = NULL;
+
+ return ret;
+}
+/* }}} */
+
+PHPAPI int _php_stream_free_enclosed(php_stream *stream_enclosed, int close_options TSRMLS_DC) /* {{{ */
+{
+ return _php_stream_free(stream_enclosed,
+ close_options | PHP_STREAM_FREE_IGNORE_ENCLOSING TSRMLS_CC);
+}
+/* }}} */
+
+#if STREAM_DEBUG
+static const char *_php_stream_pretty_free_options(int close_options, char *out)
+{
+ if (close_options & PHP_STREAM_FREE_CALL_DTOR)
+ strcat(out, "CALL_DTOR, ");
+ if (close_options & PHP_STREAM_FREE_RELEASE_STREAM)
+ strcat(out, "RELEASE_STREAM, ");
+ if (close_options & PHP_STREAM_FREE_PRESERVE_HANDLE)
+ strcat(out, "PREVERSE_HANDLE, ");
+ if (close_options & PHP_STREAM_FREE_RSRC_DTOR)
+ strcat(out, "RSRC_DTOR, ");
+ if (close_options & PHP_STREAM_FREE_PERSISTENT)
+ strcat(out, "PERSISTENT, ");
+ if (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING)
+ strcat(out, "IGNORE_ENCLOSING, ");
+ if (out[0] != '\0')
+ out[strlen(out) - 2] = '\0';
+ return out;
+}
+#endif
+
+static int _php_stream_free_persistent(zend_rsrc_list_entry *le, void *pStream TSRMLS_DC)
+{
+ return le->ptr == pStream;
+}
+
+
+PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */
+{
+ int ret = 1;
+ int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0;
+ int release_cast = 1;
+ php_stream_context *context = NULL;
+
+ /* on an resource list destruction, the context, another resource, may have
+ * already been freed (if it was created after the stream resource), so
+ * don't reference it */
+ if (EG(active)) {
+ context = stream->context;
+ }
+
+ if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) {
+ preserve_handle = 1;
+ }
+
+#if STREAM_DEBUG
+ {
+ char out[200] = "";
+ fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%s\n",
+ stream->ops->label, stream, stream->orig_path, stream->in_free, _php_stream_pretty_free_options(close_options, out));
+ }
+
+#endif
+
+ if (stream->in_free) {
+ /* hopefully called recursively from the enclosing stream; the pointer was NULLed below */
+ if ((stream->in_free == 1) && (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) && (stream->enclosing_stream == NULL)) {
+ close_options |= PHP_STREAM_FREE_RSRC_DTOR; /* restore flag */
+ } else {
+ return 1; /* recursion protection */
+ }
+ }
+
+ stream->in_free++;
+
+ /* force correct order on enclosing/enclosed stream destruction (only from resource
+ * destructor as in when reverse destroying the resource list) */
+ if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) &&
+ !(close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) &&
+ (close_options & (PHP_STREAM_FREE_CALL_DTOR | PHP_STREAM_FREE_RELEASE_STREAM)) && /* always? */
+ (stream->enclosing_stream != NULL)) {
+ php_stream *enclosing_stream = stream->enclosing_stream;
+ stream->enclosing_stream = NULL;
+ /* we force PHP_STREAM_CALL_DTOR because that's from where the
+ * enclosing stream can free this stream. We remove rsrc_dtor because
+ * we want the enclosing stream to be deleted from the resource list */
+ return _php_stream_free(enclosing_stream,
+ (close_options | PHP_STREAM_FREE_CALL_DTOR) & ~PHP_STREAM_FREE_RSRC_DTOR TSRMLS_CC);
+ }
+
+ /* if we are releasing the stream only (and preserving the underlying handle),
+ * we need to do things a little differently.
+ * We are only ever called like this when the stream is cast to a FILE*
+ * for include (or other similar) purposes.
+ * */
+ if (preserve_handle) {
+ if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
+ /* If the stream was fopencookied, we must NOT touch anything
+ * here, as the cookied stream relies on it all.
+ * Instead, mark the stream as OK to auto-clean */
+ php_stream_auto_cleanup(stream);
+ stream->in_free--;
+ return 0;
+ }
+ /* otherwise, make sure that we don't close the FILE* from a cast */
+ release_cast = 0;
+ }
+
+#if STREAM_DEBUG
+fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n",
+ stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast,
+ (close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0);
+#endif
+
+ /* make sure everything is saved */
+ _php_stream_flush(stream, 1 TSRMLS_CC);
+
+ /* If not called from the resource dtor, remove the stream from the resource list. */
+ if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0) {
+ /* zend_list_delete actually only decreases the refcount; if we're
+ * releasing the stream, we want to actually delete the resource from
+ * the resource list, otherwise the resource will point to invalid memory.
+ * In any case, let's always completely delete it from the resource list,
+ * not only when PHP_STREAM_FREE_RELEASE_STREAM is set */
+ while (zend_list_delete(stream->rsrc_id) == SUCCESS) {}
+ }
+
+ /* Remove stream from any context link list */
+ if (context && context->links) {
+ php_stream_context_del_link(context, stream);
+ }
+
+ if (close_options & PHP_STREAM_FREE_CALL_DTOR) {
+ if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
+ /* calling fclose on an fopencookied stream will ultimately
+ call this very same function. If we were called via fclose,
+ the cookie_closer unsets the fclose_stdiocast flags, so
+ we can be sure that we only reach here when PHP code calls
+ php_stream_free.
+ Lets let the cookie code clean it all up.
+ */
+ stream->in_free = 0;
+ return fclose(stream->stdiocast);
+ }
+
+ ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
+ stream->abstract = NULL;
+
+ /* tidy up any FILE* that might have been fdopened */
+ if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) {
+ fclose(stream->stdiocast);
+ stream->stdiocast = NULL;
+ stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
+ }
+ }
+
+ if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
+ while (stream->readfilters.head) {
+ php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC);
+ }
+ while (stream->writefilters.head) {
+ php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC);
+ }
+
+ if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
+ stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
+ stream->wrapper = NULL;
+ }
+
+ if (stream->wrapperdata) {
+ zval_ptr_dtor(&stream->wrapperdata);
+ stream->wrapperdata = NULL;
+ }
+
+ if (stream->readbuf) {
+ pefree(stream->readbuf, stream->is_persistent);
+ stream->readbuf = NULL;
+ }
+
+ if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) {
+ /* we don't work with *stream but need its value for comparison */
+ zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC);
+ }
+#if ZEND_DEBUG
+ if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) {
+ /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it
+ * as leaked; it will log a warning, but lets help it out and display what kind
+ * of stream it was. */
+ char *leakinfo;
+ spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path);
+
+ if (stream->orig_path) {
+ pefree(stream->orig_path, stream->is_persistent);
+ stream->orig_path = NULL;
+ }
+
+# if defined(PHP_WIN32)
+ OutputDebugString(leakinfo);
+# else
+ fprintf(stderr, "%s", leakinfo);
+# endif
+ efree(leakinfo);
+ } else {
+ if (stream->orig_path) {
+ pefree(stream->orig_path, stream->is_persistent);
+ stream->orig_path = NULL;
+ }
+
+ pefree(stream, stream->is_persistent);
+ }
+#else
+ if (stream->orig_path) {
+ pefree(stream->orig_path, stream->is_persistent);
+ stream->orig_path = NULL;
+ }
+
+ pefree(stream, stream->is_persistent);
+#endif
+ }
+
+ if (context) {
+ zend_list_delete(context->rsrc_id);
+ }
+
+ return ret;
+}
+/* }}} */
+
+/* {{{ generic stream operations */
+
+static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC)
+{
+ /* allocate/fill the buffer */
+
+ if (stream->readfilters.head) {
+ char *chunk_buf;
+ int err_flag = 0;
+ php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
+ php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
+
+ /* Invalidate the existing cache, otherwise reads can fail, see note in
+ main/streams/filter.c::_php_stream_filter_append */
+ stream->writepos = stream->readpos = 0;
+
+ /* allocate a buffer for reading chunks */
+ chunk_buf = emalloc(stream->chunk_size);
+
+ while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) {
+ size_t justread = 0;
+ int flags;
+ php_stream_bucket *bucket;
+ php_stream_filter_status_t status = PSFS_ERR_FATAL;
+ php_stream_filter *filter;
+
+ /* read a chunk into a bucket */
+ justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);
+ if (justread && justread != (size_t)-1) {
+ bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC);
+
+ /* after this call, bucket is owned by the brigade */
+ php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
+
+ flags = PSFS_FLAG_NORMAL;
+ } else {
+ flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
+ }
+
+ /* wind the handle... */
+ for (filter = stream->readfilters.head; filter; filter = filter->next) {
+ status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC);
+
+ if (status != PSFS_PASS_ON) {
+ break;
+ }
+
+ /* brig_out becomes brig_in.
+ * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
+ * to its own brigade */
+ brig_swap = brig_inp;
+ brig_inp = brig_outp;
+ brig_outp = brig_swap;
+ memset(brig_outp, 0, sizeof(*brig_outp));
+ }
+
+ switch (status) {
+ case PSFS_PASS_ON:
+ /* we get here when the last filter in the chain has data to pass on.
+ * in this situation, we are passing the brig_in brigade into the
+ * stream read buffer */
+ while (brig_inp->head) {
+ bucket = brig_inp->head;
+ /* grow buffer to hold this bucket
+ * TODO: this can fail for persistent streams */
+ if (stream->readbuflen - stream->writepos < bucket->buflen) {
+ stream->readbuflen += bucket->buflen;
+ stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
+ stream->is_persistent);
+ }
+ memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
+ stream->writepos += bucket->buflen;
+
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ break;
+
+ case PSFS_FEED_ME:
+ /* when a filter needs feeding, there is no brig_out to deal with.
+ * we simply continue the loop; if the caller needs more data,
+ * we will read again, otherwise out job is done here */
+ if (justread == 0) {
+ /* there is no data */
+ err_flag = 1;
+ break;
+ }
+ continue;
+
+ case PSFS_ERR_FATAL:
+ /* some fatal error. Theoretically, the stream is borked, so all
+ * further reads should fail. */
+ err_flag = 1;
+ break;
+ }
+
+ if (justread == 0 || justread == (size_t)-1) {
+ break;
+ }
+ }
+
+ efree(chunk_buf);
+
+ } else {
+ /* is there enough data in the buffer ? */
+ if (stream->writepos - stream->readpos < (off_t)size) {
+ size_t justread = 0;
+
+ /* reduce buffer memory consumption if possible, to avoid a realloc */
+ if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) {
+ memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos);
+ stream->writepos -= stream->readpos;
+ stream->readpos = 0;
+ }
+
+ /* grow the buffer if required
+ * TODO: this can fail for persistent streams */
+ if (stream->readbuflen - stream->writepos < stream->chunk_size) {
+ stream->readbuflen += stream->chunk_size;
+ stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
+ stream->is_persistent);
+ }
+
+ justread = stream->ops->read(stream, stream->readbuf + stream->writepos,
+ stream->readbuflen - stream->writepos
+ TSRMLS_CC);
+
+ if (justread != (size_t)-1) {
+ stream->writepos += justread;
+ }
+ }
+ }
+}
+
+PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
+{
+ size_t toread = 0, didread = 0;
+
+ while (size > 0) {
+
+ /* take from the read buffer first.
+ * It is possible that a buffered stream was switched to non-buffered, so we
+ * drain the remainder of the buffer before using the "raw" read mode for
+ * the excess */
+ if (stream->writepos > stream->readpos) {
+
+ toread = stream->writepos - stream->readpos;
+ if (toread > size) {
+ toread = size;
+ }
+
+ memcpy(buf, stream->readbuf + stream->readpos, toread);
+ stream->readpos += toread;
+ size -= toread;
+ buf += toread;
+ didread += toread;
+ }
+
+ /* ignore eof here; the underlying state might have changed */
+ if (size == 0) {
+ break;
+ }
+
+ if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
+ toread = stream->ops->read(stream, buf, size TSRMLS_CC);
+ } else {
+ php_stream_fill_read_buffer(stream, size TSRMLS_CC);
+
+ toread = stream->writepos - stream->readpos;
+ if (toread > size) {
+ toread = size;
+ }
+
+ if (toread > 0) {
+ memcpy(buf, stream->readbuf + stream->readpos, toread);
+ stream->readpos += toread;
+ }
+ }
+ if (toread > 0) {
+ didread += toread;
+ buf += toread;
+ size -= toread;
+ } else {
+ /* EOF, or temporary end of data (for non-blocking mode). */
+ break;
+ }
+
+ /* just break anyway, to avoid greedy read */
+ if (stream->wrapper != &php_plain_files_wrapper) {
+ break;
+ }
+ }
+
+ if (didread > 0) {
+ stream->position += didread;
+ }
+
+ return didread;
+}
+
+PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
+{
+ /* if there is data in the buffer, it's not EOF */
+ if (stream->writepos - stream->readpos > 0) {
+ return 0;
+ }
+
+ /* use the configured timeout when checking eof */
+ if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR ==
+ php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
+ 0, NULL)) {
+ stream->eof = 1;
+ }
+
+ return stream->eof;
+}
+
+PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
+{
+ unsigned char buf = c;
+
+ if (php_stream_write(stream, &buf, 1) > 0) {
+ return 1;
+ }
+ return EOF;
+}
+
+PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC)
+{
+ char buf;
+
+ if (php_stream_read(stream, &buf, 1) > 0) {
+ return buf & 0xff;
+ }
+ return EOF;
+}
+
+PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC)
+{
+ int len;
+ char newline[2] = "\n"; /* is this OK for Win? */
+ len = strlen(buf);
+
+ if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) {
+ return 1;
+ }
+ return 0;
+}
+
+PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
+{
+ memset(ssb, 0, sizeof(*ssb));
+
+ /* if the stream was wrapped, allow the wrapper to stat it */
+ if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) {
+ return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC);
+ }
+
+ /* if the stream doesn't directly support stat-ing, return with failure.
+ * We could try and emulate this by casting to a FD and fstat-ing it,
+ * but since the fd might not represent the actual underlying content
+ * this would give bogus results. */
+ if (stream->ops->stat == NULL) {
+ return -1;
+ }
+
+ return (stream->ops->stat)(stream, ssb TSRMLS_CC);
+}
+
+PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC)
+{
+ size_t avail;
+ char *cr, *lf, *eol = NULL;
+ char *readptr;
+
+ if (!buf) {
+ readptr = stream->readbuf + stream->readpos;
+ avail = stream->writepos - stream->readpos;
+ } else {
+ readptr = buf;
+ avail = buf_len;
+ }
+
+ /* Look for EOL */
+ if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
+ cr = memchr(readptr, '\r', avail);
+ lf = memchr(readptr, '\n', avail);
+
+ if (cr && lf != cr + 1 && !(lf && lf < cr)) {
+ /* mac */
+ stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
+ stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
+ eol = cr;
+ } else if ((cr && lf && cr == lf - 1) || (lf)) {
+ /* dos or unix endings */
+ stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
+ eol = lf;
+ }
+ } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
+ eol = memchr(readptr, '\r', avail);
+ } else {
+ /* unix (and dos) line endings */
+ eol = memchr(readptr, '\n', avail);
+ }
+
+ return eol;
+}
+
+/* If buf == NULL, the buffer will be allocated automatically and will be of an
+ * appropriate length to hold the line, regardless of the line length, memory
+ * permitting */
+PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
+ size_t *returned_len TSRMLS_DC)
+{
+ size_t avail = 0;
+ size_t current_buf_size = 0;
+ size_t total_copied = 0;
+ int grow_mode = 0;
+ char *bufstart = buf;
+
+ if (buf == NULL) {
+ grow_mode = 1;
+ } else if (maxlen == 0) {
+ return NULL;
+ }
+
+ /*
+ * If the underlying stream operations block when no new data is readable,
+ * we need to take extra precautions.
+ *
+ * If there is buffered data available, we check for a EOL. If it exists,
+ * we pass the data immediately back to the caller. This saves a call
+ * to the read implementation and will not block where blocking
+ * is not necessary at all.
+ *
+ * If the stream buffer contains more data than the caller requested,
+ * we can also avoid that costly step and simply return that data.
+ */
+
+ for (;;) {
+ avail = stream->writepos - stream->readpos;
+
+ if (avail > 0) {
+ size_t cpysz = 0;
+ char *readptr;
+ char *eol;
+ int done = 0;
+
+ readptr = stream->readbuf + stream->readpos;
+ eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC);
+
+ if (eol) {
+ cpysz = eol - readptr + 1;
+ done = 1;
+ } else {
+ cpysz = avail;
+ }
+
+ if (grow_mode) {
+ /* allow room for a NUL. If this realloc is really a realloc
+ * (ie: second time around), we get an extra byte. In most
+ * cases, with the default chunk size of 8K, we will only
+ * incur that overhead once. When people have lines longer
+ * than 8K, we waste 1 byte per additional 8K or so.
+ * That seems acceptable to me, to avoid making this code
+ * hard to follow */
+ bufstart = erealloc(bufstart, current_buf_size + cpysz + 1);
+ current_buf_size += cpysz + 1;
+ buf = bufstart + total_copied;
+ } else {
+ if (cpysz >= maxlen - 1) {
+ cpysz = maxlen - 1;
+ done = 1;
+ }
+ }
+
+ memcpy(buf, readptr, cpysz);
+
+ stream->position += cpysz;
+ stream->readpos += cpysz;
+ buf += cpysz;
+ maxlen -= cpysz;
+ total_copied += cpysz;
+
+ if (done) {
+ break;
+ }
+ } else if (stream->eof) {
+ break;
+ } else {
+ /* XXX: Should be fine to always read chunk_size */
+ size_t toread;
+
+ if (grow_mode) {
+ toread = stream->chunk_size;
+ } else {
+ toread = maxlen - 1;
+ if (toread > stream->chunk_size) {
+ toread = stream->chunk_size;
+ }
+ }
+
+ php_stream_fill_read_buffer(stream, toread TSRMLS_CC);
+
+ if (stream->writepos - stream->readpos == 0) {
+ break;
+ }
+ }
+ }
+
+ if (total_copied == 0) {
+ if (grow_mode) {
+ assert(bufstart == NULL);
+ }
+ return NULL;
+ }
+
+ buf[0] = '\0';
+ if (returned_len) {
+ *returned_len = total_copied;
+ }
+
+ return bufstart;
+}
+
+#define STREAM_BUFFERED_AMOUNT(stream) \
+ ((size_t)(((stream)->writepos) - (stream)->readpos))
+
+static char *_php_stream_search_delim(php_stream *stream,
+ size_t maxlen,
+ size_t skiplen,
+ char *delim, /* non-empty! */
+ size_t delim_len TSRMLS_DC)
+{
+ size_t seek_len;
+
+ /* set the maximum number of bytes we're allowed to read from buffer */
+ seek_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen);
+ if (seek_len <= skiplen) {
+ return NULL;
+ }
+
+ if (delim_len == 1) {
+ return memchr(&stream->readbuf[stream->readpos + skiplen],
+ delim[0], seek_len - skiplen);
+ } else {
+ return php_memnstr((char*)&stream->readbuf[stream->readpos + skiplen],
+ delim, delim_len,
+ (char*)&stream->readbuf[stream->readpos + seek_len]);
+ }
+}
+
+PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC)
+{
+ char *ret_buf, /* returned buffer */
+ *found_delim = NULL;
+ size_t buffered_len,
+ tent_ret_len; /* tentative returned length */
+ int has_delim = delim_len > 0;
+
+ if (maxlen == 0) {
+ return NULL;
+ }
+
+ if (has_delim) {
+ found_delim = _php_stream_search_delim(
+ stream, maxlen, 0, delim, delim_len TSRMLS_CC);
+ }
+
+ buffered_len = STREAM_BUFFERED_AMOUNT(stream);
+ /* try to read up to maxlen length bytes while we don't find the delim */
+ while (!found_delim && buffered_len < maxlen) {
+ size_t just_read,
+ to_read_now;
+
+ to_read_now = MIN(maxlen - buffered_len, stream->chunk_size);
+
+ php_stream_fill_read_buffer(stream, buffered_len + to_read_now TSRMLS_CC);
+
+ just_read = STREAM_BUFFERED_AMOUNT(stream) - buffered_len;
+
+ /* Assume the stream is temporarily or permanently out of data */
+ if (just_read == 0) {
+ break;
+ }
+
+ if (has_delim) {
+ /* search for delimiter, but skip buffered_len (the number of bytes
+ * buffered before this loop iteration), as they have already been
+ * searched for the delimiter.
+ * The left part of the delimiter may still remain in the buffer,
+ * so subtract up to <delim_len - 1> from buffered_len, which is
+ * the ammount of data we skip on this search as an optimization
+ */
+ found_delim = _php_stream_search_delim(
+ stream, maxlen,
+ buffered_len >= (delim_len - 1)
+ ? buffered_len - (delim_len - 1)
+ : 0,
+ delim, delim_len TSRMLS_CC);
+ if (found_delim) {
+ break;
+ }
+ }
+ buffered_len += just_read;
+ }
+
+ if (has_delim && found_delim) {
+ tent_ret_len = found_delim - (char*)&stream->readbuf[stream->readpos];
+ } else if (!has_delim && STREAM_BUFFERED_AMOUNT(stream) >= maxlen) {
+ tent_ret_len = maxlen;
+ } else {
+ /* return with error if the delimiter string (if any) was not found, we
+ * could not completely fill the read buffer with maxlen bytes and we
+ * don't know we've reached end of file. Added with non-blocking streams
+ * in mind, where this situation is frequent */
+ if (STREAM_BUFFERED_AMOUNT(stream) < maxlen && !stream->eof) {
+ return NULL;
+ } else if (STREAM_BUFFERED_AMOUNT(stream) == 0 && stream->eof) {
+ /* refuse to return an empty string just because by accident
+ * we knew of EOF in a read that returned no data */
+ return NULL;
+ } else {
+ tent_ret_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen);
+ }
+ }
+
+ ret_buf = emalloc(tent_ret_len + 1);
+ /* php_stream_read will not call ops->read here because the necessary
+ * data is guaranteedly buffered */
+ *returned_len = php_stream_read(stream, ret_buf, tent_ret_len);
+
+ if (found_delim) {
+ stream->readpos += delim_len;
+ stream->position += delim_len;
+ }
+ ret_buf[*returned_len] = '\0';
+ return ret_buf;
+}
+
+/* Writes a buffer directly to a stream, using multiple of the chunk size */
+static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ size_t didwrite = 0, towrite, justwrote;
+
+ /* if we have a seekable stream we need to ensure that data is written at the
+ * current stream->position. This means invalidating the read buffer and then
+ * performing a low-level seek */
+ if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
+ stream->readpos = stream->writepos = 0;
+
+ stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
+ }
+
+
+ while (count > 0) {
+ towrite = count;
+ if (towrite > stream->chunk_size)
+ towrite = stream->chunk_size;
+
+ justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC);
+
+ /* convert justwrote to an integer, since normally it is unsigned */
+ if ((int)justwrote > 0) {
+ buf += justwrote;
+ count -= justwrote;
+ didwrite += justwrote;
+
+ /* Only screw with the buffer if we can seek, otherwise we lose data
+ * buffered from fifos and sockets */
+ if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
+ stream->position += justwrote;
+ }
+ } else {
+ break;
+ }
+ }
+ return didwrite;
+
+}
+
+/* push some data through the write filter chain.
+ * buf may be NULL, if flags are set to indicate a flush.
+ * This may trigger a real write to the stream.
+ * Returns the number of bytes consumed from buf by the first filter in the chain.
+ * */
+static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC)
+{
+ size_t consumed = 0;
+ php_stream_bucket *bucket;
+ php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
+ php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
+ php_stream_filter_status_t status = PSFS_ERR_FATAL;
+ php_stream_filter *filter;
+
+ if (buf) {
+ bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC);
+ php_stream_bucket_append(&brig_in, bucket TSRMLS_CC);
+ }
+
+ for (filter = stream->writefilters.head; filter; filter = filter->next) {
+ /* for our return value, we are interested in the number of bytes consumed from
+ * the first filter in the chain */
+ status = filter->fops->filter(stream, filter, brig_inp, brig_outp,
+ filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC);
+
+ if (status != PSFS_PASS_ON) {
+ break;
+ }
+ /* brig_out becomes brig_in.
+ * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
+ * to its own brigade */
+ brig_swap = brig_inp;
+ brig_inp = brig_outp;
+ brig_outp = brig_swap;
+ memset(brig_outp, 0, sizeof(*brig_outp));
+ }
+
+ switch (status) {
+ case PSFS_PASS_ON:
+ /* filter chain generated some output; push it through to the
+ * underlying stream */
+ while (brig_inp->head) {
+ bucket = brig_inp->head;
+ _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC);
+ /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade
+ * hanging around and try to write it later.
+ * At the moment, we just drop it on the floor
+ * */
+
+ php_stream_bucket_unlink(bucket TSRMLS_CC);
+ php_stream_bucket_delref(bucket TSRMLS_CC);
+ }
+ break;
+ case PSFS_FEED_ME:
+ /* need more data before we can push data through to the stream */
+ break;
+
+ case PSFS_ERR_FATAL:
+ /* some fatal error. Theoretically, the stream is borked, so all
+ * further writes should fail. */
+ break;
+ }
+
+ return consumed;
+}
+
+PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC)
+{
+ int ret = 0;
+
+ if (stream->writefilters.head) {
+ _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC TSRMLS_CC);
+ }
+
+ if (stream->ops->flush) {
+ ret = stream->ops->flush(stream TSRMLS_CC);
+ }
+
+ return ret;
+}
+
+PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ if (buf == NULL || count == 0 || stream->ops->write == NULL) {
+ return 0;
+ }
+
+ if (stream->writefilters.head) {
+ return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC);
+ } else {
+ return _php_stream_write_buffer(stream, buf, count TSRMLS_CC);
+ }
+}
+
+PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...)
+{
+ size_t count;
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ count = vspprintf(&buf, 0, fmt, ap);
+ va_end(ap);
+
+ if (!buf) {
+ return 0; /* error condition */
+ }
+
+ count = php_stream_write(stream, buf, count);
+ efree(buf);
+
+ return count;
+}
+
+PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
+{
+ return stream->position;
+}
+
+PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
+{
+ if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
+ /* flush to commit data written to the fopencookie FILE* */
+ fflush(stream->stdiocast);
+ }
+
+ /* handle the case where we are in the buffer */
+ if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
+ switch(whence) {
+ case SEEK_CUR:
+ if (offset > 0 && offset <= stream->writepos - stream->readpos) {
+ stream->readpos += offset; /* if offset = ..., then readpos = writepos */
+ stream->position += offset;
+ stream->eof = 0;
+ return 0;
+ }
+ break;
+ case SEEK_SET:
+ if (offset > stream->position &&
+ offset <= stream->position + stream->writepos - stream->readpos) {
+ stream->readpos += offset - stream->position;
+ stream->position = offset;
+ stream->eof = 0;
+ return 0;
+ }
+ break;
+ }
+ }
+
+
+ if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
+ int ret;
+
+ if (stream->writefilters.head) {
+ _php_stream_flush(stream, 0 TSRMLS_CC);
+ }
+
+ switch(whence) {
+ case SEEK_CUR:
+ offset = stream->position + offset;
+ whence = SEEK_SET;
+ break;
+ }
+ ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC);
+
+ if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
+ if (ret == 0) {
+ stream->eof = 0;
+ }
+
+ /* invalidate the buffer contents */
+ stream->readpos = stream->writepos = 0;
+
+ return ret;
+ }
+ /* else the stream has decided that it can't support seeking after all;
+ * fall through to attempt emulation */
+ }
+
+ /* emulate forward moving seeks with reads */
+ if (whence == SEEK_CUR && offset >= 0) {
+ char tmp[1024];
+ size_t didread;
+ while(offset > 0) {
+ if ((didread = php_stream_read(stream, tmp, MIN(offset, sizeof(tmp)))) == 0) {
+ return -1;
+ }
+ offset -= didread;
+ }
+ stream->eof = 0;
+ return 0;
+ }
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking");
+
+ return -1;
+}
+
+PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
+{
+ int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
+
+ if (stream->ops->set_option) {
+ ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);
+ }
+
+ if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
+ switch(option) {
+ case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
+ ret = stream->chunk_size;
+ stream->chunk_size = value;
+ return ret;
+
+ case PHP_STREAM_OPTION_READ_BUFFER:
+ /* try to match the buffer mode as best we can */
+ if (value == PHP_STREAM_BUFFER_NONE) {
+ stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
+ } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) {
+ stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
+ }
+ ret = PHP_STREAM_OPTION_RETURN_OK;
+ break;
+
+ default:
+ ;
+ }
+ }
+
+ return ret;
+}
+
+PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
+{
+ return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
+}
+
+PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
+{
+ size_t bcount = 0;
+ char buf[8192];
+ int b;
+
+ if (php_stream_mmap_possible(stream)) {
+ char *p;
+ size_t mapped;
+
+ p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
+
+ if (p) {
+ PHPWRITE(p, mapped);
+
+ php_stream_mmap_unmap_ex(stream, mapped);
+
+ return mapped;
+ }
+ }
+
+ while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
+ PHPWRITE(buf, b);
+ bcount += b;
+ }
+
+ return bcount;
+}
+
+
+PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC)
+{
+ size_t ret = 0;
+ char *ptr;
+ size_t len = 0, max_len;
+ int step = CHUNK_SIZE;
+ int min_room = CHUNK_SIZE / 4;
+ php_stream_statbuf ssbuf;
+
+ if (maxlen == 0) {
+ return 0;
+ }
+
+ if (maxlen == PHP_STREAM_COPY_ALL) {
+ maxlen = 0;
+ }
+
+ if (maxlen > 0) {
+ ptr = *buf = pemalloc_rel_orig(maxlen + 1, persistent);
+ while ((len < maxlen) && !php_stream_eof(src)) {
+ ret = php_stream_read(src, ptr, maxlen - len);
+ if (!ret) {
+ break;
+ }
+ len += ret;
+ ptr += ret;
+ }
+ if (len) {
+ *ptr = '\0';
+ } else {
+ pefree(*buf, persistent);
+ *buf = NULL;
+ }
+ return len;
+ }
+
+ /* avoid many reallocs by allocating a good sized chunk to begin with, if
+ * we can. Note that the stream may be filtered, in which case the stat
+ * result may be inaccurate, as the filter may inflate or deflate the
+ * number of bytes that we can read. In order to avoid an upsize followed
+ * by a downsize of the buffer, overestimate by the step size (which is
+ * 2K). */
+ if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
+ max_len = ssbuf.sb.st_size + step;
+ } else {
+ max_len = step;
+ }
+
+ ptr = *buf = pemalloc_rel_orig(max_len, persistent);
+
+ while((ret = php_stream_read(src, ptr, max_len - len))) {
+ len += ret;
+ if (len + min_room >= max_len) {
+ *buf = perealloc_rel_orig(*buf, max_len + step, persistent);
+ max_len += step;
+ ptr = *buf + len;
+ } else {
+ ptr += ret;
+ }
+ }
+ if (len) {
+ *buf = perealloc_rel_orig(*buf, len + 1, persistent);
+ (*buf)[len] = '\0';
+ } else {
+ pefree(*buf, persistent);
+ *buf = NULL;
+ }
+ return len;
+}
+
+/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
+PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
+{
+ char buf[CHUNK_SIZE];
+ size_t readchunk;
+ size_t haveread = 0;
+ size_t didread;
+ size_t dummy;
+ php_stream_statbuf ssbuf;
+
+ if (!len) {
+ len = &dummy;
+ }
+
+ if (maxlen == 0) {
+ *len = 0;
+ return SUCCESS;
+ }
+
+ if (maxlen == PHP_STREAM_COPY_ALL) {
+ maxlen = 0;
+ }
+
+ if (php_stream_stat(src, &ssbuf) == 0) {
+ if (ssbuf.sb.st_size == 0
+#ifdef S_ISREG
+ && S_ISREG(ssbuf.sb.st_mode)
+#endif
+ ) {
+ *len = 0;
+ return SUCCESS;
+ }
+ }
+
+ if (php_stream_mmap_possible(src)) {
+ char *p;
+ size_t mapped;
+
+ p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
+
+ if (p) {
+ mapped = php_stream_write(dest, p, mapped);
+
+ php_stream_mmap_unmap_ex(src, mapped);
+
+ *len = mapped;
+
+ /* we've got at least 1 byte to read.
+ * less than 1 is an error */
+
+ if (mapped > 0) {
+ return SUCCESS;
+ }
+ return FAILURE;
+ }
+ }
+
+ while(1) {
+ readchunk = sizeof(buf);
+
+ if (maxlen && (maxlen - haveread) < readchunk) {
+ readchunk = maxlen - haveread;
+ }
+
+ didread = php_stream_read(src, buf, readchunk);
+
+ if (didread) {
+ /* extra paranoid */
+ size_t didwrite, towrite;
+ char *writeptr;
+
+ towrite = didread;
+ writeptr = buf;
+ haveread += didread;
+
+ while(towrite) {
+ didwrite = php_stream_write(dest, writeptr, towrite);
+ if (didwrite == 0) {
+ *len = haveread - (didread - towrite);
+ return FAILURE;
+ }
+
+ towrite -= didwrite;
+ writeptr += didwrite;
+ }
+ } else {
+ break;
+ }
+
+ if (maxlen - haveread == 0) {
+ break;
+ }
+ }
+
+ *len = haveread;
+
+ /* we've got at least 1 byte to read.
+ * less than 1 is an error */
+
+ if (haveread > 0 || src->eof) {
+ return SUCCESS;
+ }
+ return FAILURE;
+}
+
+/* Returns the number of bytes moved.
+ * Returns 1 when source len is 0.
+ * Deprecated in favor of php_stream_copy_to_stream_ex() */
+ZEND_ATTRIBUTE_DEPRECATED
+PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
+{
+ size_t len;
+ int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
+ if (ret == SUCCESS && len == 0 && maxlen != 0) {
+ return 1;
+ }
+ return len;
+}
+/* }}} */
+
+/* {{{ wrapper init and registration */
+
+static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ php_stream *stream = (php_stream*)rsrc->ptr;
+ /* set the return value for pclose */
+ FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
+}
+
+static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ php_stream *stream = (php_stream*)rsrc->ptr;
+ FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
+}
+
+void php_shutdown_stream_hashes(TSRMLS_D)
+{
+ if (FG(stream_wrappers)) {
+ zend_hash_destroy(FG(stream_wrappers));
+ efree(FG(stream_wrappers));
+ FG(stream_wrappers) = NULL;
+ }
+
+ if (FG(stream_filters)) {
+ zend_hash_destroy(FG(stream_filters));
+ efree(FG(stream_filters));
+ FG(stream_filters) = NULL;
+ }
+
+ if (FG(wrapper_errors)) {
+ zend_hash_destroy(FG(wrapper_errors));
+ efree(FG(wrapper_errors));
+ FG(wrapper_errors) = NULL;
+ }
+}
+
+int php_init_stream_wrappers(int module_number TSRMLS_DC)
+{
+ le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
+ le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
+
+ /* Filters are cleaned up by the streams they're attached to */
+ le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
+
+ return (
+ zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS
+ &&
+ zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS
+ &&
+ zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS
+ &&
+ php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
+ &&
+ php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
+#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
+ &&
+ php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
+ &&
+ php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
+#endif
+ ) ? SUCCESS : FAILURE;
+}
+
+int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
+{
+ zend_hash_destroy(&url_stream_wrappers_hash);
+ zend_hash_destroy(php_get_stream_filters_hash_global());
+ zend_hash_destroy(php_stream_xport_get_hash());
+ return SUCCESS;
+}
+
+/* Validate protocol scheme names during registration
+ * Must conform to /^[a-zA-Z0-9+.-]+$/
+ */
+static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len)
+{
+ int i;
+
+ for(i = 0; i < protocol_len; i++) {
+ if (!isalnum((int)protocol[i]) &&
+ protocol[i] != '+' &&
+ protocol[i] != '-' &&
+ protocol[i] != '.') {
+ return FAILURE;
+ }
+ }
+
+ return SUCCESS;
+}
+
+/* API for registering GLOBAL wrappers */
+PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
+{
+ int protocol_len = strlen(protocol);
+
+ if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
+ return FAILURE;
+ }
+
+ return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
+}
+
+PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC)
+{
+ return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1);
+}
+
+static void clone_wrapper_hash(TSRMLS_D)
+{
+ php_stream_wrapper *tmp;
+
+ ALLOC_HASHTABLE(FG(stream_wrappers));
+ zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
+ zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp));
+}
+
+/* API for registering VOLATILE wrappers */
+PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
+{
+ int protocol_len = strlen(protocol);
+
+ if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
+ return FAILURE;
+ }
+
+ if (!FG(stream_wrappers)) {
+ clone_wrapper_hash(TSRMLS_C);
+ }
+
+ return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
+}
+
+PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC)
+{
+ if (!FG(stream_wrappers)) {
+ clone_wrapper_hash(TSRMLS_C);
+ }
+
+ return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1);
+}
+/* }}} */
+
+/* {{{ php_stream_locate_url_wrapper */
+PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC)
+{
+ HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
+ php_stream_wrapper **wrapperpp = NULL;
+ const char *p, *protocol = NULL;
+ int n = 0;
+
+ if (path_for_open) {
+ *path_for_open = (char*)path;
+ }
+
+ if (options & IGNORE_URL) {
+ return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
+ }
+
+ for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
+ n++;
+ }
+
+ if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
+ protocol = path;
+ } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
+ /* BC with older php scripts and zlib wrapper */
+ protocol = "compress.zlib";
+ n = 13;
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
+ }
+
+ if (protocol) {
+ char *tmp = estrndup(protocol, n);
+ if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
+ php_strtolower(tmp, n);
+ if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
+ char wrapper_name[32];
+
+ if (n >= sizeof(wrapper_name)) {
+ n = sizeof(wrapper_name) - 1;
+ }
+ PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
+
+ 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);
+
+ wrapperpp = NULL;
+ protocol = NULL;
+ }
+ }
+ efree(tmp);
+ }
+ /* TODO: curl based streams probably support file:// properly */
+ if (!protocol || !strncasecmp(protocol, "file", n)) {
+ /* fall back on regular file access */
+ php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
+
+ if (protocol) {
+ int localhost = 0;
+
+ if (!strncasecmp(path, "file://localhost/", 17)) {
+ localhost = 1;
+ }
+
+#ifdef PHP_WIN32
+ if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':') {
+#else
+ if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
+#endif
+ if (options & REPORT_ERRORS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
+ }
+ return NULL;
+ }
+
+ if (path_for_open) {
+ /* skip past protocol and :/, but handle windows correctly */
+ *path_for_open = (char*)path + n + 1;
+ if (localhost == 1) {
+ (*path_for_open) += 11;
+ }
+ while (*(++*path_for_open)=='/');
+#ifdef PHP_WIN32
+ if (*(*path_for_open + 1) != ':')
+#endif
+ (*path_for_open)--;
+ }
+ }
+
+ if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
+ return NULL;
+ }
+
+ if (FG(stream_wrappers)) {
+ /* The file:// wrapper may have been disabled/overridden */
+
+ if (wrapperpp) {
+ /* It was found so go ahead and provide it */
+ return *wrapperpp;
+ }
+
+ /* Check again, the original check might have not known the protocol name */
+ if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) {
+ return *wrapperpp;
+ }
+
+ if (options & REPORT_ERRORS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
+ }
+ return NULL;
+ }
+
+ return plain_files_wrapper;
+ }
+
+ if (wrapperpp && (*wrapperpp)->is_url &&
+ (options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
+ (!PG(allow_url_fopen) ||
+ (((options & STREAM_OPEN_FOR_INCLUDE) ||
+ PG(in_user_include)) && !PG(allow_url_include)))) {
+ if (options & REPORT_ERRORS) {
+ /* protocol[n] probably isn't '\0' */
+ char *protocol_dup = estrndup(protocol, n);
+ if (!PG(allow_url_fopen)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
+ }
+ efree(protocol_dup);
+ }
+ return NULL;
+ }
+
+ return *wrapperpp;
+}
+/* }}} */
+
+/* {{{ _php_stream_mkdir
+ */
+PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
+{
+ php_stream_wrapper *wrapper = NULL;
+
+ wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
+ if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
+ return 0;
+ }
+
+ return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
+}
+/* }}} */
+
+/* {{{ _php_stream_rmdir
+ */
+PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC)
+{
+ php_stream_wrapper *wrapper = NULL;
+
+ wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
+ if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
+ return 0;
+ }
+
+ return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
+}
+/* }}} */
+
+/* {{{ _php_stream_stat_path */
+PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
+{
+ php_stream_wrapper *wrapper = NULL;
+ char *path_to_open = path;
+ int ret;
+
+ /* Try to hit the cache first */
+ if (flags & PHP_STREAM_URL_STAT_LINK) {
+ if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
+ memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
+ return 0;
+ }
+ } else {
+ if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
+ memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
+ return 0;
+ }
+ }
+
+ wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC);
+ if (wrapper && wrapper->wops->url_stat) {
+ ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
+ if (ret == 0) {
+ /* Drop into cache */
+ if (flags & PHP_STREAM_URL_STAT_LINK) {
+ if (BG(CurrentLStatFile)) {
+ efree(BG(CurrentLStatFile));
+ }
+ BG(CurrentLStatFile) = estrdup(path);
+ memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
+ } else {
+ if (BG(CurrentStatFile)) {
+ efree(BG(CurrentStatFile));
+ }
+ BG(CurrentStatFile) = estrdup(path);
+ memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
+ }
+ }
+ return ret;
+ }
+ return -1;
+}
+/* }}} */
+
+/* {{{ php_stream_opendir */
+PHPAPI php_stream *_php_stream_opendir(char *path, int options,
+ php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = NULL;
+ php_stream_wrapper *wrapper = NULL;
+ char *path_to_open;
+
+ if (!path || !*path) {
+ return NULL;
+ }
+
+ path_to_open = path;
+
+ wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
+
+ if (wrapper && wrapper->wops->dir_opener) {
+ stream = wrapper->wops->dir_opener(wrapper,
+ path_to_open, "r", options ^ REPORT_ERRORS, NULL,
+ context STREAMS_REL_CC TSRMLS_CC);
+
+ if (stream) {
+ stream->wrapper = wrapper;
+ stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
+ }
+ } else if (wrapper) {
+ php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
+ }
+ if (stream == NULL && (options & REPORT_ERRORS)) {
+ php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
+ }
+ php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
+
+ return stream;
+}
+/* }}} */
+
+/* {{{ _php_stream_readdir */
+PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
+{
+
+ if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
+ return ent;
+ }
+
+ return NULL;
+}
+/* }}} */
+
+/* {{{ php_stream_open_wrapper_ex */
+PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options,
+ char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = NULL;
+ php_stream_wrapper *wrapper = NULL;
+ char *path_to_open;
+ int persistent = options & STREAM_OPEN_PERSISTENT;
+ char *resolved_path = NULL;
+ char *copy_of_path = NULL;
+
+ if (opened_path) {
+ *opened_path = NULL;
+ }
+
+ if (!path || !*path) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
+ return NULL;
+ }
+
+ if (options & USE_PATH) {
+ resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC);
+ if (resolved_path) {
+ path = resolved_path;
+ /* we've found this file, don't re-check include_path or run realpath */
+ options |= STREAM_ASSUME_REALPATH;
+ options &= ~USE_PATH;
+ }
+ }
+
+ path_to_open = path;
+
+ wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
+ if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
+ if (resolved_path) {
+ efree(resolved_path);
+ }
+ return NULL;
+ }
+
+ if (wrapper) {
+ if (!wrapper->wops->stream_opener) {
+ php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
+ "wrapper does not support stream open");
+ } else {
+ stream = wrapper->wops->stream_opener(wrapper,
+ path_to_open, mode, options ^ REPORT_ERRORS,
+ opened_path, context STREAMS_REL_CC TSRMLS_CC);
+ }
+
+ /* if the caller asked for a persistent stream but the wrapper did not
+ * return one, force an error here */
+ if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
+ php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
+ "wrapper does not support persistent streams");
+ php_stream_close(stream);
+ stream = NULL;
+ }
+
+ if (stream) {
+ stream->wrapper = wrapper;
+ }
+ }
+
+ if (stream) {
+ if (opened_path && !*opened_path && resolved_path) {
+ *opened_path = resolved_path;
+ resolved_path = NULL;
+ }
+ if (stream->orig_path) {
+ pefree(stream->orig_path, persistent);
+ }
+ copy_of_path = pestrdup(path, persistent);
+ stream->orig_path = copy_of_path;
+#if ZEND_DEBUG
+ stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
+ stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
+#endif
+ }
+
+ if (stream != NULL && (options & STREAM_MUST_SEEK)) {
+ php_stream *newstream;
+
+ switch(php_stream_make_seekable_rel(stream, &newstream,
+ (options & STREAM_WILL_CAST)
+ ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
+ case PHP_STREAM_UNCHANGED:
+ if (resolved_path) {
+ efree(resolved_path);
+ }
+ return stream;
+ case PHP_STREAM_RELEASED:
+ if (newstream->orig_path) {
+ pefree(newstream->orig_path, persistent);
+ }
+ newstream->orig_path = pestrdup(path, persistent);
+ if (resolved_path) {
+ efree(resolved_path);
+ }
+ return newstream;
+ default:
+ php_stream_close(stream);
+ stream = NULL;
+ if (options & REPORT_ERRORS) {
+ char *tmp = estrdup(path);
+ php_strip_url_passwd(tmp);
+ php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
+ tmp);
+ efree(tmp);
+
+ options ^= REPORT_ERRORS;
+ }
+ }
+ }
+
+ if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
+ off_t newpos = 0;
+
+ /* if opened for append, we need to revise our idea of the initial file position */
+ if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
+ stream->position = newpos;
+ }
+ }
+
+ if (stream == NULL && (options & REPORT_ERRORS)) {
+ php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
+ if (opened_path && *opened_path) {
+ efree(*opened_path);
+ *opened_path = NULL;
+ }
+ }
+ php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
+#if ZEND_DEBUG
+ if (stream == NULL && copy_of_path != NULL) {
+ pefree(copy_of_path, persistent);
+ }
+#endif
+ if (resolved_path) {
+ efree(resolved_path);
+ }
+ return stream;
+}
+/* }}} */
+
+/* {{{ context API */
+PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context)
+{
+ php_stream_context *oldcontext = stream->context;
+ TSRMLS_FETCH();
+
+ stream->context = context;
+
+ if (context) {
+ zend_list_addref(context->rsrc_id);
+ }
+ if (oldcontext) {
+ zend_list_delete(oldcontext->rsrc_id);
+ }
+
+ return oldcontext;
+}
+
+PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
+ char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
+{
+ if (context && context->notifier)
+ context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
+}
+
+PHPAPI void php_stream_context_free(php_stream_context *context)
+{
+ if (context->options) {
+ zval_ptr_dtor(&context->options);
+ context->options = NULL;
+ }
+ if (context->notifier) {
+ php_stream_notification_free(context->notifier);
+ context->notifier = NULL;
+ }
+ if (context->links) {
+ zval_ptr_dtor(&context->links);
+ context->links = NULL;
+ }
+ efree(context);
+}
+
+PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D)
+{
+ php_stream_context *context;
+
+ context = ecalloc(1, sizeof(php_stream_context));
+ context->notifier = NULL;
+ MAKE_STD_ZVAL(context->options);
+ array_init(context->options);
+
+ context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C));
+ return context;
+}
+
+PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
+{
+ return ecalloc(1, sizeof(php_stream_notifier));
+}
+
+PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
+{
+ if (notifier->dtor) {
+ notifier->dtor(notifier);
+ }
+ efree(notifier);
+}
+
+PHPAPI int php_stream_context_get_option(php_stream_context *context,
+ const char *wrappername, const char *optionname, zval ***optionvalue)
+{
+ zval **wrapperhash;
+
+ if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
+ return FAILURE;
+ }
+ return zend_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue);
+}
+
+PHPAPI int php_stream_context_set_option(php_stream_context *context,
+ const char *wrappername, const char *optionname, zval *optionvalue)
+{
+ zval **wrapperhash;
+ zval *category, *copied_val;
+
+ ALLOC_INIT_ZVAL(copied_val);
+ *copied_val = *optionvalue;
+ zval_copy_ctor(copied_val);
+ INIT_PZVAL(copied_val);
+
+ if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
+ MAKE_STD_ZVAL(category);
+ array_init(category);
+ if (FAILURE == zend_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) {
+ return FAILURE;
+ }
+
+ wrapperhash = &category;
+ }
+ return zend_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL);
+}
+
+PHPAPI int php_stream_context_get_link(php_stream_context *context,
+ const char *hostent, php_stream **stream)
+{
+ php_stream **pstream;
+
+ if (!stream || !hostent || !context || !(context->links)) {
+ return FAILURE;
+ }
+ if (SUCCESS == zend_hash_find(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&pstream)) {
+ *stream = *pstream;
+ return SUCCESS;
+ }
+ return FAILURE;
+}
+
+PHPAPI int php_stream_context_set_link(php_stream_context *context,
+ const char *hostent, php_stream *stream)
+{
+ if (!context) {
+ return FAILURE;
+ }
+ if (!context->links) {
+ ALLOC_INIT_ZVAL(context->links);
+ array_init(context->links);
+ }
+ if (!stream) {
+ /* Delete any entry for <hostent> */
+ return zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1);
+ }
+ return zend_hash_update(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&stream, sizeof(php_stream *), NULL);
+}
+
+PHPAPI int php_stream_context_del_link(php_stream_context *context,
+ php_stream *stream)
+{
+ php_stream **pstream;
+ char *hostent;
+ int ret = SUCCESS;
+
+ if (!context || !context->links || !stream) {
+ return FAILURE;
+ }
+
+ for(zend_hash_internal_pointer_reset(Z_ARRVAL_P(context->links));
+ SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(context->links), (void**)&pstream);
+ zend_hash_move_forward(Z_ARRVAL_P(context->links))) {
+ if (*pstream == stream) {
+ if (SUCCESS == zend_hash_get_current_key(Z_ARRVAL_P(context->links), &hostent, NULL, 0)) {
+ if (FAILURE == zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1)) {
+ ret = FAILURE;
+ }
+ } else {
+ ret = FAILURE;
+ }
+ }
+ }
+
+ return ret;
+}
+/* }}} */
+
+/* {{{ php_stream_dirent_alphasort
+ */
+PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b)
+{
+ return strcoll(*a, *b);
+}
+/* }}} */
+
+/* {{{ php_stream_dirent_alphasortr
+ */
+PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b)
+{
+ return strcoll(*b, *a);
+}
+/* }}} */
+
+/* {{{ php_stream_scandir
+ */
+PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context,
+ int (*compare) (const char **a, const char **b) TSRMLS_DC)
+{
+ php_stream *stream;
+ php_stream_dirent sdp;
+ char **vector = NULL;
+ unsigned int vector_size = 0;
+ unsigned int nfiles = 0;
+
+ if (!namelist) {
+ return FAILURE;
+ }
+
+ stream = php_stream_opendir(dirname, REPORT_ERRORS, context);
+ if (!stream) {
+ return FAILURE;
+ }
+
+ while (php_stream_readdir(stream, &sdp)) {
+ if (nfiles == vector_size) {
+ if (vector_size == 0) {
+ vector_size = 10;
+ } else {
+ if(vector_size*2 < vector_size) {
+ /* overflow */
+ efree(vector);
+ return FAILURE;
+ }
+ vector_size *= 2;
+ }
+ vector = (char **) safe_erealloc(vector, vector_size, sizeof(char *), 0);
+ }
+
+ vector[nfiles] = estrdup(sdp.d_name);
+
+ nfiles++;
+ if(vector_size < 10 || nfiles == 0) {
+ /* overflow */
+ efree(vector);
+ return FAILURE;
+ }
+ }
+ php_stream_closedir(stream);
+
+ *namelist = vector;
+
+ if (compare) {
+ qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare);
+ }
+ return nfiles;
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/transports.c b/main/streams/transports.c
new file mode 100644
index 0000000..c24bf97
--- /dev/null
+++ b/main/streams/transports.c
@@ -0,0 +1,532 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_streams_int.h"
+#include "ext/standard/file.h"
+
+static HashTable xport_hash;
+
+PHPAPI HashTable *php_stream_xport_get_hash(void)
+{
+ return &xport_hash;
+}
+
+PHPAPI int php_stream_xport_register(char *protocol, php_stream_transport_factory factory TSRMLS_DC)
+{
+ return zend_hash_update(&xport_hash, protocol, strlen(protocol) + 1, &factory, sizeof(factory), NULL);
+}
+
+PHPAPI int php_stream_xport_unregister(char *protocol TSRMLS_DC)
+{
+ return zend_hash_del(&xport_hash, protocol, strlen(protocol) + 1);
+}
+
+#define ERR_REPORT(out_err, fmt, arg) \
+ if (out_err) { spprintf(out_err, 0, fmt, arg); } \
+ else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, arg); }
+
+#define ERR_RETURN(out_err, local_err, fmt) \
+ if (out_err) { *out_err = local_err; } \
+ else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, local_err ? local_err : "Unspecified error"); \
+ if (local_err) { efree(local_err); local_err = NULL; } \
+ }
+
+PHPAPI php_stream *_php_stream_xport_create(const char *name, long namelen, int options,
+ int flags, const char *persistent_id,
+ struct timeval *timeout,
+ php_stream_context *context,
+ char **error_string,
+ int *error_code
+ STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = NULL;
+ php_stream_transport_factory *factory = NULL;
+ const char *p, *protocol = NULL;
+ int n = 0, failed = 0;
+ char *error_text = NULL;
+ struct timeval default_timeout = { 0, 0 };
+
+ default_timeout.tv_sec = FG(default_socket_timeout);
+
+ if (timeout == NULL) {
+ timeout = &default_timeout;
+ }
+
+ /* check for a cached persistent socket */
+ if (persistent_id) {
+ switch(php_stream_from_persistent_id(persistent_id, &stream TSRMLS_CC)) {
+ case PHP_STREAM_PERSISTENT_SUCCESS:
+ /* use a 0 second timeout when checking if the socket
+ * has already died */
+ if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 0, NULL)) {
+ return stream;
+ }
+ /* dead - kill it */
+ php_stream_pclose(stream);
+ stream = NULL;
+
+ /* fall through */
+
+ case PHP_STREAM_PERSISTENT_FAILURE:
+ default:
+ /* failed; get a new one */
+ ;
+ }
+ }
+
+ for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
+ n++;
+ }
+
+ if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
+ protocol = name;
+ name = p + 3;
+ namelen -= n + 3;
+ } else {
+ protocol = "tcp";
+ n = 3;
+ }
+
+ if (protocol) {
+ char *tmp = estrndup(protocol, n);
+ if (FAILURE == zend_hash_find(&xport_hash, (char*)tmp, n + 1, (void**)&factory)) {
+ char wrapper_name[32];
+
+ if (n >= sizeof(wrapper_name))
+ n = sizeof(wrapper_name) - 1;
+ PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
+
+ ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
+ wrapper_name);
+
+ efree(tmp);
+ return NULL;
+ }
+ efree(tmp);
+ }
+
+ if (factory == NULL) {
+ /* should never happen */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not find a factory !?");
+ return NULL;
+ }
+
+ stream = (*factory)(protocol, n,
+ (char*)name, namelen, persistent_id, options, flags, timeout,
+ context STREAMS_REL_CC TSRMLS_CC);
+
+ if (stream) {
+ php_stream_context_set(stream, context);
+
+ if ((flags & STREAM_XPORT_SERVER) == 0) {
+ /* client */
+
+ if (flags & (STREAM_XPORT_CONNECT|STREAM_XPORT_CONNECT_ASYNC)) {
+ if (-1 == php_stream_xport_connect(stream, name, namelen,
+ flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
+ timeout, &error_text, error_code TSRMLS_CC)) {
+
+ ERR_RETURN(error_string, error_text, "connect() failed: %s");
+
+ failed = 1;
+ }
+ }
+
+ } else {
+ /* server */
+ if (flags & STREAM_XPORT_BIND) {
+ if (0 != php_stream_xport_bind(stream, name, namelen, &error_text TSRMLS_CC)) {
+ ERR_RETURN(error_string, error_text, "bind() failed: %s");
+ failed = 1;
+ } else if (flags & STREAM_XPORT_LISTEN) {
+ zval **zbacklog = NULL;
+ int backlog = 32;
+
+ if (stream->context && php_stream_context_get_option(stream->context, "socket", "backlog", &zbacklog) == SUCCESS) {
+ zval *ztmp = *zbacklog;
+
+ convert_to_long_ex(&ztmp);
+ backlog = Z_LVAL_P(ztmp);
+ if (ztmp != *zbacklog) {
+ zval_ptr_dtor(&ztmp);
+ }
+ }
+
+ if (0 != php_stream_xport_listen(stream, backlog, &error_text TSRMLS_CC)) {
+ ERR_RETURN(error_string, error_text, "listen() failed: %s");
+ failed = 1;
+ }
+ }
+ }
+ }
+ }
+
+ if (failed) {
+ /* failure means that they don't get a stream to play with */
+ if (persistent_id) {
+ php_stream_pclose(stream);
+ } else {
+ php_stream_close(stream);
+ }
+ stream = NULL;
+ }
+
+ return stream;
+}
+
+/* Bind the stream to a local address */
+PHPAPI int php_stream_xport_bind(php_stream *stream,
+ const char *name, long namelen,
+ char **error_text
+ TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+ param.op = STREAM_XPORT_OP_BIND;
+ param.inputs.name = (char*)name;
+ param.inputs.namelen = namelen;
+ param.want_errortext = error_text ? 1 : 0;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ if (error_text) {
+ *error_text = param.outputs.error_text;
+ }
+
+ return param.outputs.returncode;
+ }
+
+ return ret;
+}
+
+/* Connect to a remote address */
+PHPAPI int php_stream_xport_connect(php_stream *stream,
+ const char *name, long namelen,
+ int asynchronous,
+ struct timeval *timeout,
+ char **error_text,
+ int *error_code
+ TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+ param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
+ param.inputs.name = (char*)name;
+ param.inputs.namelen = namelen;
+ param.inputs.timeout = timeout;
+
+ param.want_errortext = error_text ? 1 : 0;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ if (error_text) {
+ *error_text = param.outputs.error_text;
+ }
+ if (error_code) {
+ *error_code = param.outputs.error_code;
+ }
+ return param.outputs.returncode;
+ }
+
+ return ret;
+
+}
+
+/* Prepare to listen */
+PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, char **error_text TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+ param.op = STREAM_XPORT_OP_LISTEN;
+ param.inputs.backlog = backlog;
+ param.want_errortext = error_text ? 1 : 0;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ if (error_text) {
+ *error_text = param.outputs.error_text;
+ }
+
+ return param.outputs.returncode;
+ }
+
+ return ret;
+}
+
+/* Get the next client and their address (as a string) */
+PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
+ char **textaddr, int *textaddrlen,
+ void **addr, socklen_t *addrlen,
+ struct timeval *timeout,
+ char **error_text
+ TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+
+ param.op = STREAM_XPORT_OP_ACCEPT;
+ param.inputs.timeout = timeout;
+ param.want_addr = addr ? 1 : 0;
+ param.want_textaddr = textaddr ? 1 : 0;
+ param.want_errortext = error_text ? 1 : 0;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ *client = param.outputs.client;
+ if (addr) {
+ *addr = param.outputs.addr;
+ *addrlen = param.outputs.addrlen;
+ }
+ if (textaddr) {
+ *textaddr = param.outputs.textaddr;
+ *textaddrlen = param.outputs.textaddrlen;
+ }
+ if (error_text) {
+ *error_text = param.outputs.error_text;
+ }
+
+ return param.outputs.returncode;
+ }
+ return ret;
+}
+
+PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
+ char **textaddr, int *textaddrlen,
+ void **addr, socklen_t *addrlen
+ TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+
+ param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
+ param.want_addr = addr ? 1 : 0;
+ param.want_textaddr = textaddr ? 1 : 0;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ if (addr) {
+ *addr = param.outputs.addr;
+ *addrlen = param.outputs.addrlen;
+ }
+ if (textaddr) {
+ *textaddr = param.outputs.textaddr;
+ *textaddrlen = param.outputs.textaddrlen;
+ }
+
+ return param.outputs.returncode;
+ }
+ return ret;
+}
+
+PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream TSRMLS_DC)
+{
+ php_stream_xport_crypto_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+ param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
+ param.inputs.method = crypto_method;
+ param.inputs.session = session_stream;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ return param.outputs.returncode;
+ }
+
+ php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto");
+
+ return ret;
+}
+
+PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate TSRMLS_DC)
+{
+ php_stream_xport_crypto_param param;
+ int ret;
+
+ memset(&param, 0, sizeof(param));
+ param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
+ param.inputs.activate = activate;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ return param.outputs.returncode;
+ }
+
+ php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto");
+
+ return ret;
+}
+
+/* Similar to recv() system call; read data from the stream, optionally
+ * peeking, optionally retrieving OOB data */
+PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
+ long flags, void **addr, socklen_t *addrlen, char **textaddr, int *textaddrlen
+ TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret = 0;
+ int recvd_len = 0;
+#if 0
+ int oob;
+
+ if (flags == 0 && addr == NULL) {
+ return php_stream_read(stream, buf, buflen);
+ }
+
+ if (stream->readfilters.head) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot peek or fetch OOB data from a filtered stream");
+ return -1;
+ }
+
+ oob = (flags & STREAM_OOB) == STREAM_OOB;
+
+ if (!oob && addr == NULL) {
+ /* must be peeking at regular data; copy content from the buffer
+ * first, then adjust the pointer/len before handing off to the
+ * stream */
+ recvd_len = stream->writepos - stream->readpos;
+ if (recvd_len > buflen) {
+ recvd_len = buflen;
+ }
+ if (recvd_len) {
+ memcpy(buf, stream->readbuf, recvd_len);
+ buf += recvd_len;
+ buflen -= recvd_len;
+ }
+ /* if we filled their buffer, return */
+ if (buflen == 0) {
+ return recvd_len;
+ }
+ }
+#endif
+
+ /* otherwise, we are going to bypass the buffer */
+
+ memset(&param, 0, sizeof(param));
+
+ param.op = STREAM_XPORT_OP_RECV;
+ param.want_addr = addr ? 1 : 0;
+ param.want_textaddr = textaddr ? 1 : 0;
+ param.inputs.buf = buf;
+ param.inputs.buflen = buflen;
+ param.inputs.flags = flags;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ if (addr) {
+ *addr = param.outputs.addr;
+ *addrlen = param.outputs.addrlen;
+ }
+ if (textaddr) {
+ *textaddr = param.outputs.textaddr;
+ *textaddrlen = param.outputs.textaddrlen;
+ }
+ return recvd_len + param.outputs.returncode;
+ }
+ return recvd_len ? recvd_len : -1;
+}
+
+/* Similar to send() system call; send data to the stream, optionally
+ * sending it as OOB data */
+PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
+ long flags, void *addr, socklen_t addrlen TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret = 0;
+ int oob;
+
+#if 0
+ if (flags == 0 && addr == NULL) {
+ return php_stream_write(stream, buf, buflen);
+ }
+#endif
+
+ oob = (flags & STREAM_OOB) == STREAM_OOB;
+
+ if ((oob || addr) && stream->writefilters.head) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot write OOB data, or data to a targeted address on a filtered stream");
+ return -1;
+ }
+
+ memset(&param, 0, sizeof(param));
+
+ param.op = STREAM_XPORT_OP_SEND;
+ param.want_addr = addr ? 1 : 0;
+ param.inputs.buf = (char*)buf;
+ param.inputs.buflen = buflen;
+ param.inputs.flags = flags;
+ param.inputs.addr = addr;
+ param.inputs.addrlen = addrlen;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ return param.outputs.returncode;
+ }
+ return -1;
+}
+
+/* Similar to shutdown() system call; shut down part of a full-duplex
+ * connection */
+PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how TSRMLS_DC)
+{
+ php_stream_xport_param param;
+ int ret = 0;
+
+ memset(&param, 0, sizeof(param));
+
+ param.op = STREAM_XPORT_OP_SHUTDOWN;
+ param.how = how;
+
+ ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
+
+ if (ret == PHP_STREAM_OPTION_RETURN_OK) {
+ return param.outputs.returncode;
+ }
+ return -1;
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/main/streams/userspace.c b/main/streams/userspace.c
new file mode 100644
index 0000000..69edbaa
--- /dev/null
+++ b/main/streams/userspace.c
@@ -0,0 +1,1676 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ | Sara Golemon <pollita@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_globals.h"
+#include "ext/standard/file.h"
+#include "ext/standard/flock_compat.h"
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#include <stddef.h>
+
+#if HAVE_UTIME
+# ifdef PHP_WIN32
+# include <sys/utime.h>
+# else
+# include <utime.h>
+# endif
+#endif
+
+static int le_protocols;
+
+struct php_user_stream_wrapper {
+ char * protoname;
+ char * classname;
+ zend_class_entry *ce;
+ php_stream_wrapper wrapper;
+};
+
+static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);
+static int user_wrapper_stat_url(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC);
+static int user_wrapper_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC);
+static int user_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC);
+static int user_wrapper_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC);
+static int user_wrapper_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC);
+static int user_wrapper_metadata(php_stream_wrapper *wrapper, char *url, int option, void *value, php_stream_context *context TSRMLS_DC);
+static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, char *filename, char *mode,
+ int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);
+
+static php_stream_wrapper_ops user_stream_wops = {
+ user_wrapper_opener,
+ NULL, /* close - the streams themselves know how */
+ NULL, /* stat - the streams themselves know how */
+ user_wrapper_stat_url,
+ user_wrapper_opendir,
+ "user-space",
+ user_wrapper_unlink,
+ user_wrapper_rename,
+ user_wrapper_mkdir,
+ user_wrapper_rmdir,
+ user_wrapper_metadata
+};
+
+
+static void stream_wrapper_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;
+
+ efree(uwrap->protoname);
+ efree(uwrap->classname);
+ efree(uwrap);
+}
+
+
+PHP_MINIT_FUNCTION(user_streams)
+{
+ le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
+ if (le_protocols == FAILURE)
+ return FAILURE;
+
+ REGISTER_LONG_CONSTANT("STREAM_USE_PATH", USE_PATH, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_IGNORE_URL", IGNORE_URL, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_REPORT_ERRORS", REPORT_ERRORS, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_MUST_SEEK", STREAM_MUST_SEEK, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_URL_STAT_LINK", PHP_STREAM_URL_STAT_LINK, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_URL_STAT_QUIET", PHP_STREAM_URL_STAT_QUIET, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_MKDIR_RECURSIVE", PHP_STREAM_MKDIR_RECURSIVE, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_IS_URL", PHP_STREAM_IS_URL, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_OPTION_BLOCKING", PHP_STREAM_OPTION_BLOCKING, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_OPTION_READ_TIMEOUT", PHP_STREAM_OPTION_READ_TIMEOUT, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_OPTION_READ_BUFFER", PHP_STREAM_OPTION_READ_BUFFER, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_OPTION_WRITE_BUFFER", PHP_STREAM_OPTION_WRITE_BUFFER, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_BUFFER_NONE", PHP_STREAM_BUFFER_NONE, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_BUFFER_LINE", PHP_STREAM_BUFFER_LINE, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_BUFFER_FULL", PHP_STREAM_BUFFER_FULL, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_CAST_AS_STREAM", PHP_STREAM_AS_STDIO, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_CAST_FOR_SELECT", PHP_STREAM_AS_FD_FOR_SELECT, CONST_CS|CONST_PERSISTENT);
+
+ REGISTER_LONG_CONSTANT("STREAM_META_TOUCH", PHP_STREAM_META_TOUCH, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_META_OWNER", PHP_STREAM_META_OWNER, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_META_OWNER_NAME", PHP_STREAM_META_OWNER_NAME, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_META_GROUP", PHP_STREAM_META_GROUP, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_META_GROUP_NAME", PHP_STREAM_META_GROUP_NAME, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("STREAM_META_ACCESS", PHP_STREAM_META_ACCESS, CONST_CS|CONST_PERSISTENT);
+ return SUCCESS;
+}
+
+struct _php_userstream_data {
+ struct php_user_stream_wrapper * wrapper;
+ zval * object;
+};
+typedef struct _php_userstream_data php_userstream_data_t;
+
+/* names of methods */
+#define USERSTREAM_OPEN "stream_open"
+#define USERSTREAM_CLOSE "stream_close"
+#define USERSTREAM_READ "stream_read"
+#define USERSTREAM_WRITE "stream_write"
+#define USERSTREAM_FLUSH "stream_flush"
+#define USERSTREAM_SEEK "stream_seek"
+#define USERSTREAM_TELL "stream_tell"
+#define USERSTREAM_EOF "stream_eof"
+#define USERSTREAM_STAT "stream_stat"
+#define USERSTREAM_STATURL "url_stat"
+#define USERSTREAM_UNLINK "unlink"
+#define USERSTREAM_RENAME "rename"
+#define USERSTREAM_MKDIR "mkdir"
+#define USERSTREAM_RMDIR "rmdir"
+#define USERSTREAM_DIR_OPEN "dir_opendir"
+#define USERSTREAM_DIR_READ "dir_readdir"
+#define USERSTREAM_DIR_REWIND "dir_rewinddir"
+#define USERSTREAM_DIR_CLOSE "dir_closedir"
+#define USERSTREAM_LOCK "stream_lock"
+#define USERSTREAM_CAST "stream_cast"
+#define USERSTREAM_SET_OPTION "stream_set_option"
+#define USERSTREAM_TRUNCATE "stream_truncate"
+#define USERSTREAM_METADATA "stream_metadata"
+
+/* {{{ class should have methods like these:
+
+ function stream_open($path, $mode, $options, &$opened_path)
+ {
+ return true/false;
+ }
+
+ function stream_read($count)
+ {
+ return false on error;
+ else return string;
+ }
+
+ function stream_write($data)
+ {
+ return false on error;
+ else return count written;
+ }
+
+ function stream_close()
+ {
+ }
+
+ function stream_flush()
+ {
+ return true/false;
+ }
+
+ function stream_seek($offset, $whence)
+ {
+ return true/false;
+ }
+
+ function stream_tell()
+ {
+ return (int)$position;
+ }
+
+ function stream_eof()
+ {
+ return true/false;
+ }
+
+ function stream_stat()
+ {
+ return array( just like that returned by fstat() );
+ }
+
+ function stream_cast($castas)
+ {
+ if ($castas == STREAM_CAST_FOR_SELECT) {
+ return $this->underlying_stream;
+ }
+ return false;
+ }
+
+ function stream_set_option($option, $arg1, $arg2)
+ {
+ switch($option) {
+ case STREAM_OPTION_BLOCKING:
+ $blocking = $arg1;
+ ...
+ case STREAM_OPTION_READ_TIMEOUT:
+ $sec = $arg1;
+ $usec = $arg2;
+ ...
+ case STREAM_OPTION_WRITE_BUFFER:
+ $mode = $arg1;
+ $size = $arg2;
+ ...
+ default:
+ return false;
+ }
+ }
+
+ function url_stat(string $url, int $flags)
+ {
+ return array( just like that returned by stat() );
+ }
+
+ function unlink(string $url)
+ {
+ return true / false;
+ }
+
+ function rename(string $from, string $to)
+ {
+ return true / false;
+ }
+
+ function mkdir($dir, $mode, $options)
+ {
+ return true / false;
+ }
+
+ function rmdir($dir, $options)
+ {
+ return true / false;
+ }
+
+ function dir_opendir(string $url, int $options)
+ {
+ return true / false;
+ }
+
+ function dir_readdir()
+ {
+ return string next filename in dir ;
+ }
+
+ function dir_closedir()
+ {
+ release dir related resources;
+ }
+
+ function dir_rewinddir()
+ {
+ reset to start of dir list;
+ }
+
+ function stream_lock($operation)
+ {
+ return true / false;
+ }
+
+ function stream_truncate($new_size)
+ {
+ return true / false;
+ }
+
+ }}} **/
+
+static zval *user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context TSRMLS_DC)
+{
+ zval *object;
+ /* create an instance of our class */
+ ALLOC_ZVAL(object);
+ object_init_ex(object, uwrap->ce);
+ Z_SET_REFCOUNT_P(object, 1);
+ Z_SET_ISREF_P(object);
+
+ if (context) {
+ add_property_resource(object, "context", context->rsrc_id);
+ zend_list_addref(context->rsrc_id);
+ } else {
+ add_property_null(object, "context");
+ }
+
+ if (uwrap->ce->constructor) {
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ zval *retval_ptr;
+
+ fci.size = sizeof(fci);
+ fci.function_table = &uwrap->ce->function_table;
+ fci.function_name = NULL;
+ fci.symbol_table = NULL;
+ fci.object_ptr = object;
+ fci.retval_ptr_ptr = &retval_ptr;
+ fci.param_count = 0;
+ fci.params = NULL;
+ fci.no_separation = 1;
+
+ fcc.initialized = 1;
+ fcc.function_handler = uwrap->ce->constructor;
+ fcc.calling_scope = EG(scope);
+ fcc.called_scope = Z_OBJCE_P(object);
+ fcc.object_ptr = object;
+
+ if (zend_call_function(&fci, &fcc TSRMLS_CC) == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not execute %s::%s()", uwrap->ce->name, uwrap->ce->constructor->common.function_name);
+ zval_dtor(object);
+ FREE_ZVAL(object);
+ return NULL;
+ } else {
+ if (retval_ptr) {
+ zval_ptr_dtor(&retval_ptr);
+ }
+ }
+ }
+ return object;
+}
+
+static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ php_userstream_data_t *us;
+ zval *zfilename, *zmode, *zopened, *zoptions, *zretval = NULL, *zfuncname;
+ zval **args[4];
+ int call_result;
+ php_stream *stream = NULL;
+ zend_bool old_in_user_include;
+
+ /* Try to catch bad usage without preventing flexibility */
+ if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "infinite recursion prevented");
+ return NULL;
+ }
+ FG(user_stream_current_filename) = filename;
+
+ /* if the user stream was registered as local and we are in include context,
+ we add allow_url_include restrictions to allow_url_fopen ones */
+ /* we need only is_url == 0 here since if is_url == 1 and remote wrappers
+ were restricted we wouldn't get here */
+ old_in_user_include = PG(in_user_include);
+ if(uwrap->wrapper.is_url == 0 &&
+ (options & STREAM_OPEN_FOR_INCLUDE) &&
+ !PG(allow_url_include)) {
+ PG(in_user_include) = 1;
+ }
+
+ us = emalloc(sizeof(*us));
+ us->wrapper = uwrap;
+
+ us->object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(us->object == NULL) {
+ FG(user_stream_current_filename) = NULL;
+ PG(in_user_include) = old_in_user_include;
+ efree(us);
+ return NULL;
+ }
+
+ /* call it's stream_open method - set up params first */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, filename, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zmode);
+ ZVAL_STRING(zmode, mode, 1);
+ args[1] = &zmode;
+
+ MAKE_STD_ZVAL(zoptions);
+ ZVAL_LONG(zoptions, options);
+ args[2] = &zoptions;
+
+ MAKE_STD_ZVAL(zopened);
+ Z_SET_REFCOUNT_P(zopened, 1);
+ Z_SET_ISREF_P(zopened);
+ ZVAL_NULL(zopened);
+ args[3] = &zopened;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_OPEN, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ zfuncname,
+ &zretval,
+ 4, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval != NULL && zval_is_true(zretval)) {
+ /* the stream is now open! */
+ stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);
+
+ /* if the opened path is set, copy it out */
+ if (Z_TYPE_P(zopened) == IS_STRING && opened_path) {
+ *opened_path = estrndup(Z_STRVAL_P(zopened), Z_STRLEN_P(zopened));
+ }
+
+ /* set wrapper data to be a reference to our object */
+ stream->wrapperdata = us->object;
+ zval_add_ref(&stream->wrapperdata);
+ } else {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "\"%s::" USERSTREAM_OPEN "\" call failed",
+ us->wrapper->classname);
+ }
+
+ /* destroy everything else */
+ if (stream == NULL) {
+ zval_ptr_dtor(&us->object);
+ efree(us);
+ }
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zopened);
+ zval_ptr_dtor(&zoptions);
+ zval_ptr_dtor(&zmode);
+ zval_ptr_dtor(&zfilename);
+
+ FG(user_stream_current_filename) = NULL;
+
+ PG(in_user_include) = old_in_user_include;
+ return stream;
+}
+
+static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, char *filename, char *mode,
+ int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ php_userstream_data_t *us;
+ zval *zfilename, *zoptions, *zretval = NULL, *zfuncname;
+ zval **args[2];
+ int call_result;
+ php_stream *stream = NULL;
+
+ /* Try to catch bad usage without preventing flexibility */
+ if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "infinite recursion prevented");
+ return NULL;
+ }
+ FG(user_stream_current_filename) = filename;
+
+ us = emalloc(sizeof(*us));
+ us->wrapper = uwrap;
+
+ us->object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(us->object == NULL) {
+ FG(user_stream_current_filename) = NULL;
+ efree(us);
+ return NULL;
+ }
+
+ /* call it's dir_open method - set up params first */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, filename, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zoptions);
+ ZVAL_LONG(zoptions, options);
+ args[1] = &zoptions;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_DIR_OPEN, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ zfuncname,
+ &zretval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval != NULL && zval_is_true(zretval)) {
+ /* the stream is now open! */
+ stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode);
+
+ /* set wrapper data to be a reference to our object */
+ stream->wrapperdata = us->object;
+ zval_add_ref(&stream->wrapperdata);
+ } else {
+ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
+ us->wrapper->classname);
+ }
+
+ /* destroy everything else */
+ if (stream == NULL) {
+ zval_ptr_dtor(&us->object);
+ efree(us);
+ }
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zoptions);
+ zval_ptr_dtor(&zfilename);
+
+ FG(user_stream_current_filename) = NULL;
+
+ return stream;
+}
+
+
+/* {{{ proto bool stream_wrapper_register(string protocol, string classname[, integer flags])
+ Registers a custom URL protocol handler class */
+PHP_FUNCTION(stream_wrapper_register)
+{
+ char *protocol, *classname;
+ int protocol_len, classname_len;
+ struct php_user_stream_wrapper * uwrap;
+ int rsrc_id;
+ long flags = 0;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &protocol, &protocol_len, &classname, &classname_len, &flags) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
+ uwrap->protoname = estrndup(protocol, protocol_len);
+ uwrap->classname = estrndup(classname, classname_len);
+ uwrap->wrapper.wops = &user_stream_wops;
+ uwrap->wrapper.abstract = uwrap;
+ uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
+
+ rsrc_id = ZEND_REGISTER_RESOURCE(NULL, uwrap, le_protocols);
+
+ if (zend_lookup_class(uwrap->classname, classname_len, (zend_class_entry***)&uwrap->ce TSRMLS_CC) == SUCCESS) {
+ uwrap->ce = *(zend_class_entry**)uwrap->ce;
+ if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper TSRMLS_CC) == SUCCESS) {
+ RETURN_TRUE;
+ } else {
+ /* We failed. But why? */
+ if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol, protocol_len + 1)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol %s:// is already defined.", protocol);
+ } else {
+ /* Hash doesn't exist so it must have been an invalid protocol scheme */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", classname, protocol);
+ }
+ }
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "class '%s' is undefined", classname);
+ }
+
+ zend_list_delete(rsrc_id);
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto bool stream_wrapper_unregister(string protocol)
+ Unregister a wrapper for the life of the current request. */
+PHP_FUNCTION(stream_wrapper_unregister)
+{
+ char *protocol;
+ int protocol_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &protocol, &protocol_len) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (php_unregister_url_stream_wrapper_volatile(protocol TSRMLS_CC) == FAILURE) {
+ /* We failed */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to unregister protocol %s://", protocol);
+ RETURN_FALSE;
+ }
+
+ RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto bool stream_wrapper_restore(string protocol)
+ Restore the original protocol handler, overriding if necessary */
+PHP_FUNCTION(stream_wrapper_restore)
+{
+ char *protocol;
+ int protocol_len;
+ php_stream_wrapper **wrapperpp = NULL, *wrapper;
+ HashTable *global_wrapper_hash;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &protocol, &protocol_len) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
+ if (php_stream_get_url_stream_wrappers_hash() == global_wrapper_hash) {
+ php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s:// was never changed, nothing to restore", protocol);
+ RETURN_TRUE;
+ }
+
+ if ((zend_hash_find(global_wrapper_hash, protocol, protocol_len + 1, (void**)&wrapperpp) == FAILURE) || !wrapperpp) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// never existed, nothing to restore", protocol);
+ RETURN_FALSE;
+ }
+
+ /* next line might delete the pointer that wrapperpp points at, so deref it now */
+ wrapper = *wrapperpp;
+
+ /* A failure here could be okay given that the protocol might have been merely unregistered */
+ php_unregister_url_stream_wrapper_volatile(protocol TSRMLS_CC);
+
+ if (php_register_url_stream_wrapper_volatile(protocol, wrapper TSRMLS_CC) == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to restore original %s:// wrapper", protocol);
+ RETURN_FALSE;
+ }
+
+ RETURN_TRUE;
+}
+/* }}} */
+
+static size_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ zval **args[1];
+ zval *zbufptr;
+ size_t didwrite = 0;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1, 0);
+
+ MAKE_STD_ZVAL(zbufptr);
+ ZVAL_STRINGL(zbufptr, (char*)buf, count, 1);;
+ args[0] = &zbufptr;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+ zval_ptr_dtor(&zbufptr);
+
+ didwrite = 0;
+ if (call_result == SUCCESS && retval != NULL) {
+ convert_to_long(retval);
+ didwrite = Z_LVAL_P(retval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
+ us->wrapper->classname);
+ }
+
+ /* don't allow strange buffer overruns due to bogus return */
+ if (didwrite > count) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_WRITE " wrote %ld bytes more data than requested (%ld written, %ld max)",
+ us->wrapper->classname,
+ (long)(didwrite - count), (long)didwrite, (long)count);
+ didwrite = count;
+ }
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return didwrite;
+}
+
+static size_t php_userstreamop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ zval **args[1];
+ int call_result;
+ size_t didread = 0;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ zval *zcount;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1, 0);
+
+ MAKE_STD_ZVAL(zcount);
+ ZVAL_LONG(zcount, count);
+ args[0] = &zcount;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL) {
+ convert_to_string(retval);
+ didread = Z_STRLEN_P(retval);
+ if (didread > count) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_READ " - read %ld bytes more data than requested (%ld read, %ld max) - excess data will be lost",
+ us->wrapper->classname, (long)(didread - count), (long)didread, (long)count);
+ didread = count;
+ }
+ if (didread > 0)
+ memcpy(buf, Z_STRVAL_P(retval), didread);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
+ us->wrapper->classname);
+ }
+ zval_ptr_dtor(&zcount);
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ retval = NULL;
+ }
+
+ /* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) {
+ stream->eof = 1;
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
+ us->wrapper->classname);
+
+ stream->eof = 1;
+ }
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ retval = NULL;
+ }
+
+ return didread;
+}
+
+static int php_userstreamop_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1, 0);
+
+ call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ zval_ptr_dtor(&us->object);
+
+ efree(us);
+
+ return 0;
+}
+
+static int php_userstreamop_flush(php_stream *stream TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && zval_is_true(retval))
+ call_result = 0;
+ else
+ call_result = -1;
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return call_result;
+}
+
+static int php_userstreamop_seek(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result, ret;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ zval **args[2];
+ zval *zoffs, *zwhence;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1, 0);
+
+ MAKE_STD_ZVAL(zoffs);
+ ZVAL_LONG(zoffs, offset);
+ args[0] = &zoffs;
+
+ MAKE_STD_ZVAL(zwhence);
+ ZVAL_LONG(zwhence, whence);
+ args[1] = &zwhence;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ zval_ptr_dtor(&zoffs);
+ zval_ptr_dtor(&zwhence);
+
+ if (call_result == FAILURE) {
+ /* stream_seek is not implemented, so disable seeks for this stream */
+ stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
+ /* there should be no retval to clean up */
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return -1;
+ } else if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ retval = NULL;
+ }
+
+ if (ret) {
+ return ret;
+ }
+
+ /* now determine where we are */
+ ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG) {
+ *newoffs = Z_LVAL_P(retval);
+ ret = 0;
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", us->wrapper->classname);
+ ret = -1;
+ } else {
+ ret = -1;
+ }
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ }
+ return ret;
+}
+
+/* parse the return value from one of the stat functions and store the
+ * relevant fields into the statbuf provided */
+static int statbuf_from_array(zval *array, php_stream_statbuf *ssb TSRMLS_DC)
+{
+ zval **elem;
+
+#define STAT_PROP_ENTRY_EX(name, name2) \
+ if (SUCCESS == zend_hash_find(Z_ARRVAL_P(array), #name, sizeof(#name), (void**)&elem)) { \
+ SEPARATE_ZVAL(elem); \
+ convert_to_long(*elem); \
+ ssb->sb.st_##name2 = Z_LVAL_PP(elem); \
+ }
+
+#define STAT_PROP_ENTRY(name) STAT_PROP_ENTRY_EX(name,name)
+
+ memset(ssb, 0, sizeof(php_stream_statbuf));
+ STAT_PROP_ENTRY(dev);
+ STAT_PROP_ENTRY(ino);
+ STAT_PROP_ENTRY(mode);
+ STAT_PROP_ENTRY(nlink);
+ STAT_PROP_ENTRY(uid);
+ STAT_PROP_ENTRY(gid);
+#if HAVE_ST_RDEV
+ STAT_PROP_ENTRY(rdev);
+#endif
+ STAT_PROP_ENTRY(size);
+#ifdef NETWARE
+ STAT_PROP_ENTRY_EX(atime, atime.tv_sec);
+ STAT_PROP_ENTRY_EX(mtime, mtime.tv_sec);
+ STAT_PROP_ENTRY_EX(ctime, ctime.tv_sec);
+#else
+ STAT_PROP_ENTRY(atime);
+ STAT_PROP_ENTRY(mtime);
+ STAT_PROP_ENTRY(ctime);
+#endif
+#ifdef HAVE_ST_BLKSIZE
+ STAT_PROP_ENTRY(blksize);
+#endif
+#ifdef HAVE_ST_BLOCKS
+ STAT_PROP_ENTRY(blocks);
+#endif
+
+#undef STAT_PROP_ENTRY
+#undef STAT_PROP_ENTRY_EX
+ return SUCCESS;
+}
+
+static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ int ret = -1;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_ARRAY) {
+ if (SUCCESS == statbuf_from_array(retval, ssb TSRMLS_CC))
+ ret = 0;
+ } else {
+ if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
+ us->wrapper->classname);
+ }
+ }
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return ret;
+}
+
+
+static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) {
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ zval *zvalue = NULL;
+ zval **args[3];
+
+ switch (option) {
+ case PHP_STREAM_OPTION_CHECK_LIVENESS:
+ ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1, 0);
+ call_result = call_user_function_ex(NULL, &us->object, &func_name, &retval, 0, NULL, 0, NULL TSRMLS_CC);
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_BOOL) {
+ ret = zval_is_true(retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
+ } else {
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
+ us->wrapper->classname);
+ }
+ break;
+
+ case PHP_STREAM_OPTION_LOCKING:
+ MAKE_STD_ZVAL(zvalue);
+ ZVAL_LONG(zvalue, 0);
+
+ if (value & LOCK_NB) {
+ Z_LVAL_P(zvalue) |= PHP_LOCK_NB;
+ }
+ switch(value & ~LOCK_NB) {
+ case LOCK_SH:
+ Z_LVAL_P(zvalue) |= PHP_LOCK_SH;
+ break;
+ case LOCK_EX:
+ Z_LVAL_P(zvalue) |= PHP_LOCK_EX;
+ break;
+ case LOCK_UN:
+ Z_LVAL_P(zvalue) |= PHP_LOCK_UN;
+ break;
+ }
+
+ args[0] = &zvalue;
+
+ /* TODO wouldblock */
+ ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_BOOL) {
+ ret = !Z_LVAL_P(retval);
+ } else if (call_result == FAILURE) {
+ if (value == 0) {
+ /* lock support test (TODO: more check) */
+ ret = PHP_STREAM_OPTION_RETURN_OK;
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!",
+ us->wrapper->classname);
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ }
+
+ break;
+
+ case PHP_STREAM_OPTION_TRUNCATE_API:
+ ZVAL_STRINGL(&func_name, USERSTREAM_TRUNCATE, sizeof(USERSTREAM_TRUNCATE)-1, 0);
+
+ switch (value) {
+ case PHP_STREAM_TRUNCATE_SUPPORTED:
+ if (zend_is_callable_ex(&func_name, us->object, IS_CALLABLE_CHECK_SILENT,
+ NULL, NULL, NULL, NULL TSRMLS_CC))
+ ret = PHP_STREAM_OPTION_RETURN_OK;
+ else
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ break;
+
+ case PHP_STREAM_TRUNCATE_SET_SIZE: {
+ ptrdiff_t new_size = *(ptrdiff_t*) ptrparam;
+ if (new_size >= 0 && new_size <= (ptrdiff_t)LONG_MAX) {
+ MAKE_STD_ZVAL(zvalue);
+ ZVAL_LONG(zvalue, (long)new_size);
+ args[0] = &zvalue;
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args, 0, NULL TSRMLS_CC);
+ if (call_result == SUCCESS && retval != NULL) {
+ if (Z_TYPE_P(retval) == IS_BOOL) {
+ ret = Z_LVAL_P(retval) ? PHP_STREAM_OPTION_RETURN_OK :
+ PHP_STREAM_OPTION_RETURN_ERR;
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "%s::" USERSTREAM_TRUNCATE " did not return a boolean!",
+ us->wrapper->classname);
+ }
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "%s::" USERSTREAM_TRUNCATE " is not implemented!",
+ us->wrapper->classname);
+ }
+ } else { /* bad new size */
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ }
+ break;
+ }
+ }
+ break;
+
+ case PHP_STREAM_OPTION_READ_BUFFER:
+ case PHP_STREAM_OPTION_WRITE_BUFFER:
+ case PHP_STREAM_OPTION_READ_TIMEOUT:
+ case PHP_STREAM_OPTION_BLOCKING: {
+ zval *zoption = NULL;
+ zval *zptrparam = NULL;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_SET_OPTION, sizeof(USERSTREAM_SET_OPTION)-1, 0);
+
+ ALLOC_INIT_ZVAL(zoption);
+ ZVAL_LONG(zoption, option);
+
+ ALLOC_INIT_ZVAL(zvalue);
+ ALLOC_INIT_ZVAL(zptrparam);
+
+ args[0] = &zoption;
+ args[1] = &zvalue;
+ args[2] = &zptrparam;
+
+ switch(option) {
+ case PHP_STREAM_OPTION_READ_BUFFER:
+ case PHP_STREAM_OPTION_WRITE_BUFFER:
+ ZVAL_LONG(zvalue, value);
+ if (ptrparam) {
+ ZVAL_LONG(zptrparam, *(long *)ptrparam);
+ } else {
+ ZVAL_LONG(zptrparam, BUFSIZ);
+ }
+ break;
+ case PHP_STREAM_OPTION_READ_TIMEOUT: {
+ struct timeval tv = *(struct timeval*)ptrparam;
+ ZVAL_LONG(zvalue, tv.tv_sec);
+ ZVAL_LONG(zptrparam, tv.tv_usec);
+ break;
+ }
+ case PHP_STREAM_OPTION_BLOCKING:
+ ZVAL_LONG(zvalue, value);
+ break;
+ default:
+ break;
+ }
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 3, args, 0, NULL TSRMLS_CC);
+
+ if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_SET_OPTION " is not implemented!",
+ us->wrapper->classname);
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ } else if (retval && zend_is_true(retval)) {
+ ret = PHP_STREAM_OPTION_RETURN_OK;
+ } else {
+ ret = PHP_STREAM_OPTION_RETURN_ERR;
+ }
+
+ if (zoption) {
+ zval_ptr_dtor(&zoption);
+ }
+ if (zptrparam) {
+ zval_ptr_dtor(&zptrparam);
+ }
+
+ break;
+ }
+ }
+
+ /* clean up */
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ }
+
+
+ if (zvalue) {
+ zval_ptr_dtor(&zvalue);
+ }
+
+ return ret;
+}
+
+
+static int user_wrapper_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zfilename, *zfuncname, *zretval;
+ zval **args[1];
+ int call_result;
+ zval *object;
+ int ret = 0;
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ return ret;
+ }
+
+ /* call the unlink method */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, url, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_UNLINK, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
+ ret = Z_LVAL_P(zretval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", uwrap->classname);
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zfilename);
+
+ return ret;
+}
+
+static int user_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zold_name, *znew_name, *zfuncname, *zretval;
+ zval **args[2];
+ int call_result;
+ zval *object;
+ int ret = 0;
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ return ret;
+ }
+
+ /* call the rename method */
+ MAKE_STD_ZVAL(zold_name);
+ ZVAL_STRING(zold_name, url_from, 1);
+ args[0] = &zold_name;
+
+ MAKE_STD_ZVAL(znew_name);
+ ZVAL_STRING(znew_name, url_to, 1);
+ args[1] = &znew_name;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_RENAME, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
+ ret = Z_LVAL_P(zretval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", uwrap->classname);
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zold_name);
+ zval_ptr_dtor(&znew_name);
+
+ return ret;
+}
+
+static int user_wrapper_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zfilename, *zmode, *zoptions, *zfuncname, *zretval;
+ zval **args[3];
+ int call_result;
+ zval *object;
+ int ret = 0;
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ return ret;
+ }
+
+ /* call the mkdir method */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, url, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zmode);
+ ZVAL_LONG(zmode, mode);
+ args[1] = &zmode;
+
+ MAKE_STD_ZVAL(zoptions);
+ ZVAL_LONG(zoptions, options);
+ args[2] = &zoptions;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_MKDIR, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 3, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
+ ret = Z_LVAL_P(zretval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", uwrap->classname);
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval) {
+ zval_ptr_dtor(&zretval);
+ }
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zfilename);
+ zval_ptr_dtor(&zmode);
+ zval_ptr_dtor(&zoptions);
+
+ return ret;
+}
+
+static int user_wrapper_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zfilename, *zoptions, *zfuncname, *zretval;
+ zval **args[3];
+ int call_result;
+ zval *object;
+ int ret = 0;
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ return ret;
+ }
+
+ /* call the rmdir method */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, url, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zoptions);
+ ZVAL_LONG(zoptions, options);
+ args[1] = &zoptions;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_RMDIR, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
+ ret = Z_LVAL_P(zretval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", uwrap->classname);
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval) {
+ zval_ptr_dtor(&zretval);
+ }
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zfilename);
+ zval_ptr_dtor(&zoptions);
+
+ return ret;
+}
+
+static int user_wrapper_metadata(php_stream_wrapper *wrapper, char *url, int option, void *value, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zfilename, *zoption, *zvalue, *zfuncname, *zretval;
+ zval **args[3];
+ int call_result;
+ zval *object;
+ int ret = 0;
+
+ MAKE_STD_ZVAL(zvalue);
+ switch(option) {
+ case PHP_STREAM_META_TOUCH:
+ array_init(zvalue);
+ if(value) {
+ struct utimbuf *newtime = (struct utimbuf *)value;
+ add_index_long(zvalue, 0, newtime->modtime);
+ add_index_long(zvalue, 1, newtime->actime);
+ }
+ break;
+ case PHP_STREAM_META_GROUP:
+ case PHP_STREAM_META_OWNER:
+ case PHP_STREAM_META_ACCESS:
+ ZVAL_LONG(zvalue, *(long *)value);
+ break;
+ case PHP_STREAM_META_GROUP_NAME:
+ case PHP_STREAM_META_OWNER_NAME:
+ ZVAL_STRING(zvalue, value, 1);
+ break;
+ default:
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option);
+ zval_ptr_dtor(&zvalue);
+ return ret;
+ }
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ zval_ptr_dtor(&zvalue);
+ return ret;
+ }
+
+ /* call the mkdir method */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, url, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zoption);
+ ZVAL_LONG(zoption, option);
+ args[1] = &zoption;
+
+ args[2] = &zvalue;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_METADATA, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 3, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
+ ret = Z_LVAL_P(zretval);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", uwrap->classname);
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval) {
+ zval_ptr_dtor(&zretval);
+ }
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zfilename);
+ zval_ptr_dtor(&zoption);
+ zval_ptr_dtor(&zvalue);
+
+ return ret;
+}
+
+
+static int user_wrapper_stat_url(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
+ zval *zfilename, *zfuncname, *zretval, *zflags;
+ zval **args[2];
+ int call_result;
+ zval *object;
+ int ret = -1;
+
+ /* create an instance of our class */
+ object = user_stream_create_object(uwrap, context TSRMLS_CC);
+ if(object == NULL) {
+ return ret;
+ }
+
+ /* call it's stat_url method - set up params first */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, url, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zflags);
+ ZVAL_LONG(zflags, flags);
+ args[1] = &zflags;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_STATURL, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &object,
+ zfuncname,
+ &zretval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval != NULL && Z_TYPE_P(zretval) == IS_ARRAY) {
+ /* We got the info we needed */
+ if (SUCCESS == statbuf_from_array(zretval, ssb TSRMLS_CC))
+ ret = 0;
+ } else {
+ if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
+ uwrap->classname);
+ }
+ }
+
+ /* clean up */
+ zval_ptr_dtor(&object);
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zfilename);
+ zval_ptr_dtor(&zflags);
+
+ return ret;
+
+}
+
+static size_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ size_t didread = 0;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ php_stream_dirent *ent = (php_stream_dirent*)buf;
+
+ /* avoid problems if someone mis-uses the stream */
+ if (count != sizeof(php_stream_dirent))
+ return 0;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) != IS_BOOL) {
+ convert_to_string(retval);
+ PHP_STRLCPY(ent->d_name, Z_STRVAL_P(retval), sizeof(ent->d_name), Z_STRLEN_P(retval));
+
+ didread = sizeof(php_stream_dirent);
+ } else if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
+ us->wrapper->classname);
+ }
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return didread;
+}
+
+static int php_userstreamop_closedir(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1, 0);
+
+ call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ zval_ptr_dtor(&us->object);
+
+ efree(us);
+
+ return 0;
+}
+
+static int php_userstreamop_rewinddir(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1, 0);
+
+ call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return 0;
+
+}
+
+static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr TSRMLS_DC)
+{
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ zval func_name;
+ zval *retval = NULL;
+ zval *zcastas = NULL;
+ zval **args[1];
+ php_stream * intstream = NULL;
+ int call_result;
+ int ret = FAILURE;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_CAST, sizeof(USERSTREAM_CAST)-1, 0);
+
+ ALLOC_INIT_ZVAL(zcastas);
+ switch(castas) {
+ case PHP_STREAM_AS_FD_FOR_SELECT:
+ ZVAL_LONG(zcastas, PHP_STREAM_AS_FD_FOR_SELECT);
+ break;
+ default:
+ ZVAL_LONG(zcastas, PHP_STREAM_AS_STDIO);
+ break;
+ }
+ args[0] = &zcastas;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args, 0, NULL TSRMLS_CC);
+
+ do {
+ if (call_result == FAILURE) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
+ us->wrapper->classname);
+ break;
+ }
+ if (retval == NULL || !zend_is_true(retval)) {
+ break;
+ }
+ php_stream_from_zval_no_verify(intstream, &retval);
+ if (!intstream) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource",
+ us->wrapper->classname);
+ break;
+ }
+ if (intstream == stream) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself",
+ us->wrapper->classname);
+ intstream = NULL;
+ break;
+ }
+ ret = php_stream_cast(intstream, castas, retptr, 1);
+ } while (0);
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ }
+ if (zcastas) {
+ zval_ptr_dtor(&zcastas);
+ }
+
+ return ret;
+}
+
+php_stream_ops php_stream_userspace_ops = {
+ php_userstreamop_write, php_userstreamop_read,
+ php_userstreamop_close, php_userstreamop_flush,
+ "user-space",
+ php_userstreamop_seek,
+ php_userstreamop_cast,
+ php_userstreamop_stat,
+ php_userstreamop_set_option,
+};
+
+php_stream_ops php_stream_userspace_dir_ops = {
+ NULL, /* write */
+ php_userstreamop_readdir,
+ php_userstreamop_closedir,
+ NULL, /* flush */
+ "user-space-dir",
+ php_userstreamop_rewinddir,
+ NULL, /* cast */
+ NULL, /* stat */
+ NULL /* set_option */
+};
+
+
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
new file mode 100644
index 0000000..beffc73
--- /dev/null
+++ b/main/streams/xp_socket.c
@@ -0,0 +1,838 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "ext/standard/file.h"
+#include "streams/php_streams_int.h"
+#include "php_network.h"
+
+#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
+# undef AF_UNIX
+#endif
+
+#if defined(AF_UNIX)
+#include <sys/un.h>
+#endif
+
+#ifndef MSG_DONTWAIT
+# define MSG_DONTWAIT 0
+#endif
+
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+php_stream_ops php_stream_generic_socket_ops;
+PHPAPI php_stream_ops php_stream_socket_ops;
+php_stream_ops php_stream_udp_socket_ops;
+#ifdef AF_UNIX
+php_stream_ops php_stream_unix_socket_ops;
+php_stream_ops php_stream_unixdg_socket_ops;
+#endif
+
+
+static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC);
+
+/* {{{ Generic socket stream operations */
+static size_t php_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+ int didwrite;
+ struct timeval *ptimeout;
+
+ if (sock->socket == -1) {
+ return 0;
+ }
+
+ if (sock->timeout.tv_sec == -1)
+ ptimeout = NULL;
+ else
+ ptimeout = &sock->timeout;
+
+retry:
+ didwrite = send(sock->socket, buf, count, (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0);
+
+ if (didwrite <= 0) {
+ long err = php_socket_errno();
+ char *estr;
+
+ if (sock->is_blocked && err == EWOULDBLOCK) {
+ int retval;
+
+ sock->timeout_event = 0;
+
+ do {
+ retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout);
+
+ if (retval == 0) {
+ sock->timeout_event = 1;
+ break;
+ }
+
+ if (retval > 0) {
+ /* writable now; retry */
+ goto retry;
+ }
+
+ err = php_socket_errno();
+ } while (err == EINTR);
+ }
+ estr = php_socket_strerror(err, NULL, 0);
+ php_error_docref(NULL TSRMLS_CC, E_NOTICE, "send of %ld bytes failed with errno=%ld %s",
+ (long)count, err, estr);
+ efree(estr);
+ }
+
+ if (didwrite > 0) {
+ php_stream_notify_progress_increment(stream->context, didwrite, 0);
+ }
+
+ if (didwrite < 0) {
+ didwrite = 0;
+ }
+
+ return didwrite;
+}
+
+static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock TSRMLS_DC)
+{
+ int retval;
+ struct timeval *ptimeout;
+
+ if (sock->socket == -1) {
+ return;
+ }
+
+ sock->timeout_event = 0;
+
+ if (sock->timeout.tv_sec == -1)
+ ptimeout = NULL;
+ else
+ ptimeout = &sock->timeout;
+
+ while(1) {
+ retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout);
+
+ if (retval == 0)
+ sock->timeout_event = 1;
+
+ if (retval >= 0)
+ break;
+
+ if (php_socket_errno() != EINTR)
+ break;
+ }
+}
+
+static size_t php_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+ int nr_bytes = 0;
+
+ if (sock->socket == -1) {
+ return 0;
+ }
+
+ if (sock->is_blocked) {
+ php_sock_stream_wait_for_data(stream, sock TSRMLS_CC);
+ if (sock->timeout_event)
+ return 0;
+ }
+
+ nr_bytes = recv(sock->socket, buf, count, (sock->is_blocked && sock->timeout.tv_sec != -1) ? MSG_DONTWAIT : 0);
+
+ stream->eof = (nr_bytes == 0 || (nr_bytes == -1 && php_socket_errno() != EWOULDBLOCK));
+
+ if (nr_bytes > 0) {
+ php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
+ }
+
+ if (nr_bytes < 0) {
+ nr_bytes = 0;
+ }
+
+ return nr_bytes;
+}
+
+
+static int php_sockop_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+#ifdef PHP_WIN32
+ int n;
+#endif
+
+ if (close_handle) {
+
+#ifdef PHP_WIN32
+ if (sock->socket == -1)
+ sock->socket = SOCK_ERR;
+#endif
+ if (sock->socket != SOCK_ERR) {
+#ifdef PHP_WIN32
+ /* prevent more data from coming in */
+ shutdown(sock->socket, SHUT_RD);
+
+ /* try to make sure that the OS sends all data before we close the connection.
+ * Essentially, we are waiting for the socket to become writeable, which means
+ * that all pending data has been sent.
+ * We use a small timeout which should encourage the OS to send the data,
+ * but at the same time avoid hanging indefintely.
+ * */
+ do {
+ n = php_pollfd_for_ms(sock->socket, POLLOUT, 500);
+ } while (n == -1 && php_socket_errno() == EINTR);
+#endif
+ closesocket(sock->socket);
+ sock->socket = SOCK_ERR;
+ }
+
+ }
+
+ pefree(sock, php_stream_is_persistent(stream));
+
+ return 0;
+}
+
+static int php_sockop_flush(php_stream *stream TSRMLS_DC)
+{
+#if 0
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+ return fsync(sock->socket);
+#endif
+ return 0;
+}
+
+static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+#if ZEND_WIN32
+ return 0;
+#else
+ return fstat(sock->socket, &ssb->sb);
+#endif
+}
+
+static inline int sock_sendto(php_netstream_data_t *sock, char *buf, size_t buflen, int flags,
+ struct sockaddr *addr, socklen_t addrlen
+ TSRMLS_DC)
+{
+ int ret;
+ if (addr) {
+ ret = sendto(sock->socket, buf, buflen, flags, addr, addrlen);
+ return (ret == SOCK_CONN_ERR) ? -1 : ret;
+ }
+ return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
+}
+
+static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags,
+ char **textaddr, long *textaddrlen,
+ struct sockaddr **addr, socklen_t *addrlen
+ TSRMLS_DC)
+{
+ php_sockaddr_storage sa;
+ socklen_t sl = sizeof(sa);
+ int ret;
+ int want_addr = textaddr || addr;
+
+ if (want_addr) {
+ ret = recvfrom(sock->socket, buf, buflen, flags, (struct sockaddr*)&sa, &sl);
+ ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
+ php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl,
+ textaddr, textaddrlen, addr, addrlen TSRMLS_CC);
+ } else {
+ ret = recv(sock->socket, buf, buflen, flags);
+ ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
+ }
+
+ return ret;
+}
+
+static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
+{
+ int oldmode, flags;
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+ php_stream_xport_param *xparam;
+
+ switch(option) {
+ case PHP_STREAM_OPTION_CHECK_LIVENESS:
+ {
+ struct timeval tv;
+ char buf;
+ int alive = 1;
+
+ if (value == -1) {
+ if (sock->timeout.tv_sec == -1) {
+ tv.tv_sec = FG(default_socket_timeout);
+ tv.tv_usec = 0;
+ } else {
+ tv = sock->timeout;
+ }
+ } else {
+ tv.tv_sec = value;
+ tv.tv_usec = 0;
+ }
+
+ if (sock->socket == -1) {
+ alive = 0;
+ } else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
+ if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) {
+ alive = 0;
+ }
+ }
+ return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
+ }
+
+ case PHP_STREAM_OPTION_BLOCKING:
+ oldmode = sock->is_blocked;
+ if (SUCCESS == php_set_sock_blocking(sock->socket, value TSRMLS_CC)) {
+ sock->is_blocked = value;
+ return oldmode;
+ }
+ return PHP_STREAM_OPTION_RETURN_ERR;
+
+ case PHP_STREAM_OPTION_READ_TIMEOUT:
+ sock->timeout = *(struct timeval*)ptrparam;
+ sock->timeout_event = 0;
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_OPTION_META_DATA_API:
+ add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event);
+ add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked);
+ add_assoc_bool((zval *)ptrparam, "eof", stream->eof);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case PHP_STREAM_OPTION_XPORT_API:
+ xparam = (php_stream_xport_param *)ptrparam;
+
+ switch (xparam->op) {
+ case STREAM_XPORT_OP_LISTEN:
+ xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1;
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case STREAM_XPORT_OP_GET_NAME:
+ xparam->outputs.returncode = php_network_get_sock_name(sock->socket,
+ xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
+ xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
+ xparam->want_addr ? &xparam->outputs.addr : NULL,
+ xparam->want_addr ? &xparam->outputs.addrlen : NULL
+ TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case STREAM_XPORT_OP_GET_PEER_NAME:
+ xparam->outputs.returncode = php_network_get_peer_name(sock->socket,
+ xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
+ xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
+ xparam->want_addr ? &xparam->outputs.addr : NULL,
+ xparam->want_addr ? &xparam->outputs.addrlen : NULL
+ TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case STREAM_XPORT_OP_SEND:
+ flags = 0;
+ if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
+ flags |= MSG_OOB;
+ }
+ xparam->outputs.returncode = sock_sendto(sock,
+ xparam->inputs.buf, xparam->inputs.buflen,
+ flags,
+ xparam->inputs.addr,
+ xparam->inputs.addrlen TSRMLS_CC);
+ if (xparam->outputs.returncode == -1) {
+ char *err = php_socket_strerror(php_socket_errno(), NULL, 0);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "%s\n", err);
+ efree(err);
+ }
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case STREAM_XPORT_OP_RECV:
+ flags = 0;
+ if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
+ flags |= MSG_OOB;
+ }
+ if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) {
+ flags |= MSG_PEEK;
+ }
+ xparam->outputs.returncode = sock_recvfrom(sock,
+ xparam->inputs.buf, xparam->inputs.buflen,
+ flags,
+ xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
+ xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
+ xparam->want_addr ? &xparam->outputs.addr : NULL,
+ xparam->want_addr ? &xparam->outputs.addrlen : NULL
+ TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+
+#ifdef HAVE_SHUTDOWN
+# ifndef SHUT_RD
+# define SHUT_RD 0
+# endif
+# ifndef SHUT_WR
+# define SHUT_WR 1
+# endif
+# ifndef SHUT_RDWR
+# define SHUT_RDWR 2
+# endif
+ case STREAM_XPORT_OP_SHUTDOWN: {
+ static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
+
+ xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]);
+ return PHP_STREAM_OPTION_RETURN_OK;
+ }
+#endif
+
+ default:
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ }
+
+ default:
+ return PHP_STREAM_OPTION_RETURN_NOTIMPL;
+ }
+}
+
+static int php_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+
+ switch(castas) {
+ case PHP_STREAM_AS_STDIO:
+ if (ret) {
+ *(FILE**)ret = fdopen(sock->socket, stream->mode);
+ if (*ret)
+ return SUCCESS;
+ return FAILURE;
+ }
+ return SUCCESS;
+ case PHP_STREAM_AS_FD_FOR_SELECT:
+ case PHP_STREAM_AS_FD:
+ case PHP_STREAM_AS_SOCKETD:
+ if (ret)
+ *(int*)ret = sock->socket;
+ return SUCCESS;
+ default:
+ return FAILURE;
+ }
+}
+/* }}} */
+
+/* These may look identical, but we need them this way so that
+ * we can determine which type of socket we are dealing with
+ * by inspecting stream->ops.
+ * A "useful" side-effect is that the user's scripts can then
+ * make similar decisions using stream_get_meta_data.
+ * */
+php_stream_ops php_stream_generic_socket_ops = {
+ php_sockop_write, php_sockop_read,
+ php_sockop_close, php_sockop_flush,
+ "generic_socket",
+ NULL, /* seek */
+ php_sockop_cast,
+ php_sockop_stat,
+ php_sockop_set_option,
+};
+
+
+php_stream_ops php_stream_socket_ops = {
+ php_sockop_write, php_sockop_read,
+ php_sockop_close, php_sockop_flush,
+ "tcp_socket",
+ NULL, /* seek */
+ php_sockop_cast,
+ php_sockop_stat,
+ php_tcp_sockop_set_option,
+};
+
+php_stream_ops php_stream_udp_socket_ops = {
+ php_sockop_write, php_sockop_read,
+ php_sockop_close, php_sockop_flush,
+ "udp_socket",
+ NULL, /* seek */
+ php_sockop_cast,
+ php_sockop_stat,
+ php_tcp_sockop_set_option,
+};
+
+#ifdef AF_UNIX
+php_stream_ops php_stream_unix_socket_ops = {
+ php_sockop_write, php_sockop_read,
+ php_sockop_close, php_sockop_flush,
+ "unix_socket",
+ NULL, /* seek */
+ php_sockop_cast,
+ php_sockop_stat,
+ php_tcp_sockop_set_option,
+};
+php_stream_ops php_stream_unixdg_socket_ops = {
+ php_sockop_write, php_sockop_read,
+ php_sockop_close, php_sockop_flush,
+ "udg_socket",
+ NULL, /* seek */
+ php_sockop_cast,
+ php_sockop_stat,
+ php_tcp_sockop_set_option,
+};
+#endif
+
+
+/* network socket operations */
+
+#ifdef AF_UNIX
+static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr TSRMLS_DC)
+{
+ memset(unix_addr, 0, sizeof(*unix_addr));
+ unix_addr->sun_family = AF_UNIX;
+
+ /* we need to be binary safe on systems that support an abstract
+ * namespace */
+ if (xparam->inputs.namelen >= sizeof(unix_addr->sun_path)) {
+ /* On linux, when the path begins with a NUL byte we are
+ * referring to an abstract namespace. In theory we should
+ * allow an extra byte below, since we don't need the NULL.
+ * BUT, to get into this branch of code, the name is too long,
+ * so we don't care. */
+ xparam->inputs.namelen = sizeof(unix_addr->sun_path) - 1;
+ php_error_docref(NULL TSRMLS_CC, E_NOTICE,
+ "socket path exceeded the maximum allowed length of %lu bytes "
+ "and was truncated", (unsigned long)sizeof(unix_addr->sun_path));
+ }
+
+ memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen);
+
+ return 1;
+}
+#endif
+
+static inline char *parse_ip_address_ex(const char *str, int str_len, int *portno, int get_err, char **err TSRMLS_DC)
+{
+ char *colon;
+ char *host = NULL;
+
+#ifdef HAVE_IPV6
+ char *p;
+
+ if (*(str) == '[' && str_len > 1) {
+ /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
+ p = memchr(str + 1, ']', str_len - 2);
+ if (!p || *(p + 1) != ':') {
+ if (get_err) {
+ spprintf(err, 0, "Failed to parse IPv6 address \"%s\"", str);
+ }
+ return NULL;
+ }
+ *portno = atoi(p + 2);
+ return estrndup(str + 1, p - str - 1);
+ }
+#endif
+ if (str_len) {
+ colon = memchr(str, ':', str_len - 1);
+ } else {
+ colon = NULL;
+ }
+ if (colon) {
+ *portno = atoi(colon + 1);
+ host = estrndup(str, colon - str);
+ } else {
+ if (get_err) {
+ spprintf(err, 0, "Failed to parse address \"%s\"", str);
+ }
+ return NULL;
+ }
+
+ return host;
+}
+
+static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno TSRMLS_DC)
+{
+ return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text TSRMLS_CC);
+}
+
+static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
+ php_stream_xport_param *xparam TSRMLS_DC)
+{
+ char *host = NULL;
+ int portno, err;
+
+#ifdef AF_UNIX
+ if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
+ struct sockaddr_un unix_addr;
+
+ sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
+
+ if (sock->socket == SOCK_ERR) {
+ if (xparam->want_errortext) {
+ spprintf(&xparam->outputs.error_text, 0, "Failed to create unix%s socket %s",
+ stream->ops == &php_stream_unix_socket_ops ? "" : "datagram",
+ strerror(errno));
+ }
+ return -1;
+ }
+
+ parse_unix_address(xparam, &unix_addr TSRMLS_CC);
+
+ return bind(sock->socket, (struct sockaddr *)&unix_addr, sizeof(unix_addr));
+ }
+#endif
+
+ host = parse_ip_address(xparam, &portno TSRMLS_CC);
+
+ if (host == NULL) {
+ return -1;
+ }
+
+ sock->socket = php_network_bind_socket_to_local_addr(host, portno,
+ stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
+ xparam->want_errortext ? &xparam->outputs.error_text : NULL,
+ &err
+ TSRMLS_CC);
+
+ if (host) {
+ efree(host);
+ }
+
+ return sock->socket == -1 ? -1 : 0;
+}
+
+static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
+ php_stream_xport_param *xparam TSRMLS_DC)
+{
+ char *host = NULL, *bindto = NULL;
+ int portno, bindport = 0;
+ int err = 0;
+ int ret;
+ zval **tmpzval = NULL;
+
+#ifdef AF_UNIX
+ if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
+ struct sockaddr_un unix_addr;
+
+ sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
+
+ if (sock->socket == SOCK_ERR) {
+ if (xparam->want_errortext) {
+ spprintf(&xparam->outputs.error_text, 0, "Failed to create unix socket");
+ }
+ return -1;
+ }
+
+ parse_unix_address(xparam, &unix_addr TSRMLS_CC);
+
+ ret = php_network_connect_socket(sock->socket,
+ (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
+ xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
+ xparam->want_errortext ? &xparam->outputs.error_text : NULL,
+ &err);
+
+ xparam->outputs.error_code = err;
+
+ goto out;
+ }
+#endif
+
+ host = parse_ip_address(xparam, &portno TSRMLS_CC);
+
+ if (host == NULL) {
+ return -1;
+ }
+
+ if (stream->context && php_stream_context_get_option(stream->context, "socket", "bindto", &tmpzval) == SUCCESS) {
+ if (Z_TYPE_PP(tmpzval) != IS_STRING) {
+ if (xparam->want_errortext) {
+ spprintf(&xparam->outputs.error_text, 0, "local_addr context option is not a string.");
+ }
+ efree(host);
+ return -1;
+ }
+ bindto = parse_ip_address_ex(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text TSRMLS_CC);
+ }
+
+ /* Note: the test here for php_stream_udp_socket_ops is important, because we
+ * want the default to be TCP sockets so that the openssl extension can
+ * re-use this code. */
+
+ sock->socket = php_network_connect_socket_to_host(host, portno,
+ stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
+ xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
+ xparam->inputs.timeout,
+ xparam->want_errortext ? &xparam->outputs.error_text : NULL,
+ &err,
+ bindto,
+ bindport
+ TSRMLS_CC);
+
+ ret = sock->socket == -1 ? -1 : 0;
+ xparam->outputs.error_code = err;
+
+ if (host) {
+ efree(host);
+ }
+ if (bindto) {
+ efree(bindto);
+ }
+
+#ifdef AF_UNIX
+out:
+#endif
+
+ if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) {
+ /* indicates pending connection */
+ return 1;
+ }
+
+ return ret;
+}
+
+static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
+ php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC)
+{
+ int clisock;
+
+ xparam->outputs.client = NULL;
+
+ clisock = php_network_accept_incoming(sock->socket,
+ xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
+ xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
+ xparam->want_addr ? &xparam->outputs.addr : NULL,
+ xparam->want_addr ? &xparam->outputs.addrlen : NULL,
+ xparam->inputs.timeout,
+ xparam->want_errortext ? &xparam->outputs.error_text : NULL,
+ &xparam->outputs.error_code
+ TSRMLS_CC);
+
+ if (clisock >= 0) {
+ php_netstream_data_t *clisockdata;
+
+ clisockdata = emalloc(sizeof(*clisockdata));
+
+ if (clisockdata == NULL) {
+ close(clisock);
+ /* technically a fatal error */
+ } else {
+ memcpy(clisockdata, sock, sizeof(*clisockdata));
+ clisockdata->socket = clisock;
+
+ xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
+ if (xparam->outputs.client) {
+ xparam->outputs.client->context = stream->context;
+ if (stream->context) {
+ zend_list_addref(stream->context->rsrc_id);
+ }
+ }
+ }
+ }
+
+ return xparam->outputs.client == NULL ? -1 : 0;
+}
+
+static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
+{
+ php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
+ php_stream_xport_param *xparam;
+
+ switch(option) {
+ case PHP_STREAM_OPTION_XPORT_API:
+ xparam = (php_stream_xport_param *)ptrparam;
+
+ switch(xparam->op) {
+ case STREAM_XPORT_OP_CONNECT:
+ case STREAM_XPORT_OP_CONNECT_ASYNC:
+ xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+ case STREAM_XPORT_OP_BIND:
+ xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+
+
+ case STREAM_XPORT_OP_ACCEPT:
+ xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC TSRMLS_CC);
+ return PHP_STREAM_OPTION_RETURN_OK;
+ default:
+ /* fall through */
+ ;
+ }
+ }
+ return php_sockop_set_option(stream, option, value, ptrparam TSRMLS_CC);
+}
+
+
+PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, long protolen,
+ char *resourcename, long resourcenamelen,
+ const char *persistent_id, int options, int flags,
+ struct timeval *timeout,
+ php_stream_context *context STREAMS_DC TSRMLS_DC)
+{
+ php_stream *stream = NULL;
+ php_netstream_data_t *sock;
+ php_stream_ops *ops;
+
+ /* which type of socket ? */
+ if (strncmp(proto, "tcp", protolen) == 0) {
+ ops = &php_stream_socket_ops;
+ } else if (strncmp(proto, "udp", protolen) == 0) {
+ ops = &php_stream_udp_socket_ops;
+ }
+#ifdef AF_UNIX
+ else if (strncmp(proto, "unix", protolen) == 0) {
+ ops = &php_stream_unix_socket_ops;
+ } else if (strncmp(proto, "udg", protolen) == 0) {
+ ops = &php_stream_unixdg_socket_ops;
+ }
+#endif
+ else {
+ /* should never happen */
+ return NULL;
+ }
+
+ sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
+ memset(sock, 0, sizeof(php_netstream_data_t));
+
+ sock->is_blocked = 1;
+ sock->timeout.tv_sec = FG(default_socket_timeout);
+ sock->timeout.tv_usec = 0;
+
+ /* we don't know the socket until we have determined if we are binding or
+ * connecting */
+ sock->socket = -1;
+
+ stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
+
+ if (stream == NULL) {
+ pefree(sock, persistent_id ? 1 : 0);
+ return NULL;
+ }
+
+ if (flags == 0) {
+ return stream;
+ }
+
+ return stream;
+}
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */