diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2013-03-14 05:42:27 +0000 |
---|---|---|
committer | <> | 2013-04-03 16:25:08 +0000 |
commit | c4dd7a1a684490673e25aaf4fabec5df138854c4 (patch) | |
tree | 4d57c44caae4480efff02b90b9be86f44bf25409 /main/streams | |
download | php2-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.c | 418 | ||||
-rw-r--r-- | main/streams/filter.c | 544 | ||||
-rw-r--r-- | main/streams/glob_wrapper.c | 291 | ||||
-rw-r--r-- | main/streams/memory.c | 758 | ||||
-rw-r--r-- | main/streams/mmap.c | 75 | ||||
-rw-r--r-- | main/streams/php_stream_context.h | 136 | ||||
-rw-r--r-- | main/streams/php_stream_filter_api.h | 162 | ||||
-rw-r--r-- | main/streams/php_stream_glob_wrapper.h | 44 | ||||
-rw-r--r-- | main/streams/php_stream_mmap.h | 88 | ||||
-rw-r--r-- | main/streams/php_stream_plain_wrapper.h | 66 | ||||
-rw-r--r-- | main/streams/php_stream_transport.h | 211 | ||||
-rw-r--r-- | main/streams/php_stream_userspace.h | 35 | ||||
-rw-r--r-- | main/streams/php_streams_int.h | 71 | ||||
-rw-r--r-- | main/streams/plain_wrapper.c | 1505 | ||||
-rw-r--r-- | main/streams/streams.c | 2397 | ||||
-rw-r--r-- | main/streams/transports.c | 532 | ||||
-rw-r--r-- | main/streams/userspace.c | 1676 | ||||
-rw-r--r-- | main/streams/xp_socket.c | 838 |
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 **)®entry, &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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 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, ¶m); + + 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(¶m, 0, sizeof(param)); + + param.op = STREAM_XPORT_OP_SHUTDOWN; + param.how = how; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m); + + 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 + */ |