--- embedaddon/php/main/streams/streams.c 2012/02/21 23:48:05 1.1.1.1 +++ embedaddon/php/main/streams/streams.c 2013/10/14 08:02:43 1.1.1.4 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2012 The PHP Group | + | 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 | @@ -19,7 +19,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: streams.c,v 1.1.1.1 2012/02/21 23:48:05 misho Exp $ */ +/* $Id: streams.c,v 1.1.1.4 2013/10/14 08:02:43 misho Exp $ */ #define _GNU_SOURCE #include "php.h" @@ -105,6 +105,15 @@ PHP_RSHUTDOWN_FUNCTION(streams) 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; @@ -148,20 +157,35 @@ PHPAPI int php_stream_from_persistent_id(const char *p /* }}} */ +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; - php_stream_wrapper orig_wrapper; if (wrapper) { - if (wrapper->err_count > 0) { - int i; - size_t l; + zend_llist *err_list = php_get_wrapper_errors_list(wrapper TSRMLS_CC); + if (err_list) { + size_t l = 0; int brlen; - char *br; + 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; @@ -171,17 +195,21 @@ void php_stream_display_wrapper_errors(php_stream_wrap br = "\n"; } - for (i = 0, l = 0; i < wrapper->err_count; i++) { - l += strlen(wrapper->err_stack[i]); - if (i < wrapper->err_count - 1) { + 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 (i = 0; i < wrapper->err_count; i++) { - strcat(msg, wrapper->err_stack[i]); - if (i < wrapper->err_count - 1) { + 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); } } @@ -189,7 +217,7 @@ void php_stream_display_wrapper_errors(php_stream_wrap free_msg = 1; } else { if (wrapper == &php_plain_files_wrapper) { - msg = strerror(errno); + msg = strerror(errno); /* TODO: not ts on linux */ } else { msg = "operation failed"; } @@ -199,16 +227,7 @@ void php_stream_display_wrapper_errors(php_stream_wrap } php_strip_url_passwd(tmp); - if (wrapper) { - /* see bug #52935 */ - orig_wrapper = *wrapper; - wrapper->err_stack = NULL; - wrapper->err_count = 0; - } php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg); - if (wrapper) { - *wrapper = orig_wrapper; - } efree(tmp); if (free_msg) { efree(msg); @@ -217,21 +236,16 @@ void php_stream_display_wrapper_errors(php_stream_wrap void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) { - if (wrapper) { - /* tidy up the error stack */ - int i; - - for (i = 0; i < wrapper->err_count; i++) { - efree(wrapper->err_stack[i]); - } - if (wrapper->err_stack) { - efree(wrapper->err_stack); - } - wrapper->err_stack = NULL; - wrapper->err_count = 0; + 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; @@ -245,11 +259,25 @@ PHPAPI void php_stream_wrapper_log_error(php_stream_wr php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer); efree(buffer); } else { - /* append to stack */ - wrapper->err_stack = erealloc(wrapper->err_stack, (wrapper->err_count + 1) * sizeof(char *)); - if (wrapper->err_stack) { - wrapper->err_stack[wrapper->err_count++] = buffer; + 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); } } @@ -305,38 +333,106 @@ fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", 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 remove_rsrc = 1; int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0; int release_cast = 1; - php_stream_context *context = stream->context; + 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 -fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%08x\n", stream->ops->label, stream, stream->orig_path, stream->in_free, close_options); + { + 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 - /* recursion protection */ if (stream->in_free) { - return 1; + /* 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* @@ -357,14 +453,15 @@ fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opt #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, remove_rsrc); + 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 && remove_rsrc) { + 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. @@ -374,8 +471,8 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handl } /* Remove stream from any context link list */ - if (stream->context && stream->context->links) { - php_stream_context_del_link(stream->context, stream); + if (context && context->links) { + php_stream_context_del_link(context, stream); } if (close_options & PHP_STREAM_FREE_CALL_DTOR) { @@ -899,77 +996,119 @@ PHPAPI char *_php_stream_get_line(php_stream *stream, 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 *e, *buf; - size_t toread, len; - int skip = 0; + char *ret_buf, /* returned buffer */ + *found_delim = NULL; + size_t buffered_len, + tent_ret_len; /* tentative returned length */ + int has_delim = delim_len > 0; - len = stream->writepos - stream->readpos; + if (maxlen == 0) { + return NULL; + } - /* make sure the stream read buffer has maxlen bytes */ - while (len < maxlen) { + if (has_delim) { + found_delim = _php_stream_search_delim( + stream, maxlen, 0, delim, delim_len TSRMLS_CC); + } - size_t just_read; - toread = MIN(maxlen - len, stream->chunk_size); + 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; - php_stream_fill_read_buffer(stream, len + toread TSRMLS_CC); + to_read_now = MIN(maxlen - buffered_len, stream->chunk_size); - just_read = (stream->writepos - stream->readpos) - len; - len += just_read; + 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 (delim_len == 0 || !delim) { - toread = maxlen; - } else { - size_t seek_len; - - /* set the maximum number of bytes we're allowed to read from buffer */ - seek_len = stream->writepos - stream->readpos; - if (seek_len > maxlen) { - seek_len = maxlen; + 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 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 (delim_len == 1) { - e = memchr(stream->readbuf + stream->readpos, *delim, seek_len); + 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 { - e = php_memnstr(stream->readbuf + stream->readpos, delim, delim_len, (stream->readbuf + stream->readpos + seek_len)); + tent_ret_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen); } - - if (!e) { - /* return with error if the delimiter string 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 (seek_len < maxlen && !stream->eof) { - return NULL; - } - toread = maxlen; - } else { - toread = e - (char *) stream->readbuf - stream->readpos; - /* we found the delimiter, so advance the read pointer past it */ - skip = 1; - } } - if (toread > maxlen && maxlen > 0) { - toread = 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); - buf = emalloc(toread + 1); - *returned_len = php_stream_read(stream, buf, toread); - - if (skip) { + if (found_delim) { stream->readpos += delim_len; stream->position += delim_len; } - buf[*returned_len] = '\0'; - return buf; + ret_buf[*returned_len] = '\0'; + return ret_buf; } /* Writes a buffer directly to a stream, using multiple of the chunk size */ @@ -1306,7 +1445,12 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, len += ret; ptr += ret; } - *ptr = '\0'; + if (len) { + *ptr = '\0'; + } else { + pefree(*buf, persistent); + *buf = NULL; + } return len; } @@ -1345,12 +1489,12 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, } /* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */ -PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC) +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 didread, didwrite, towrite; size_t dummy; php_stream_statbuf ssbuf; @@ -1385,16 +1529,16 @@ PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream 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); + didwrite = php_stream_write(dest, p, mapped); php_stream_mmap_unmap_ex(src, mapped); - *len = mapped; + *len = didwrite; - /* we've got at least 1 byte to read. - * less than 1 is an error */ - - if (mapped > 0) { + /* we've got at least 1 byte to read + * less than 1 is an error + * AND read bytes match written */ + if (mapped > 0 && mapped == didwrite) { return SUCCESS; } return FAILURE; @@ -1412,7 +1556,6 @@ PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream if (didread) { /* extra paranoid */ - size_t didwrite, towrite; char *writeptr; towrite = didread; @@ -1492,6 +1635,12 @@ void php_shutdown_stream_hashes(TSRMLS_D) 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) @@ -1740,7 +1889,7 @@ PHPAPI int _php_stream_mkdir(char *path, int mode, int { php_stream_wrapper *wrapper = NULL; - wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC); + wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) { return 0; } @@ -1755,7 +1904,7 @@ PHPAPI int _php_stream_rmdir(char *path, int options, { php_stream_wrapper *wrapper = NULL; - wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC); + wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC); if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) { return 0; } @@ -1784,7 +1933,7 @@ PHPAPI int _php_stream_stat_path(char *path, int flags } } - wrapper = php_stream_locate_url_wrapper(path, &path_to_open, ENFORCE_SAFE_MODE TSRMLS_CC); + 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) { @@ -1869,7 +2018,6 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *p char *resolved_path = NULL; char *copy_of_path = NULL; - if (opened_path) { *opened_path = NULL; } @@ -2046,7 +2194,7 @@ PHPAPI void php_stream_context_free(php_stream_context efree(context); } -PHPAPI php_stream_context *php_stream_context_alloc(void) +PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D) { php_stream_context *context; @@ -2055,7 +2203,7 @@ PHPAPI php_stream_context *php_stream_context_alloc(vo MAKE_STD_ZVAL(context->options); array_init(context->options); - context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context()); + context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C)); return context; } @@ -2191,14 +2339,14 @@ PHPAPI int _php_stream_scandir(char *dirname, char **n php_stream *stream; php_stream_dirent sdp; char **vector = NULL; - int vector_size = 0; - int nfiles = 0; + unsigned int vector_size = 0; + unsigned int nfiles = 0; if (!namelist) { return FAILURE; } - stream = php_stream_opendir(dirname, ENFORCE_SAFE_MODE | REPORT_ERRORS, context); + stream = php_stream_opendir(dirname, REPORT_ERRORS, context); if (!stream) { return FAILURE; } @@ -2208,20 +2356,32 @@ PHPAPI int _php_stream_scandir(char *dirname, char **n if (vector_size == 0) { vector_size = 10; } else { + if(vector_size*2 < vector_size) { + /* overflow */ + php_stream_closedir(stream); + efree(vector); + return FAILURE; + } vector_size *= 2; } - vector = (char **) erealloc(vector, vector_size * sizeof(char *)); + vector = (char **) safe_erealloc(vector, vector_size, sizeof(char *), 0); } vector[nfiles] = estrdup(sdp.d_name); nfiles++; + if(vector_size < 10 || nfiles == 0) { + /* overflow */ + php_stream_closedir(stream); + efree(vector); + return FAILURE; + } } php_stream_closedir(stream); *namelist = vector; - if (compare) { + if (nfiles > 0 && compare) { qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare); } return nfiles;