summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--ext/standard/tests/file/bug27619.phpt18
-rw-r--r--main/streams/filter.c75
-rw-r--r--main/streams/php_stream_filter_api.h9
-rwxr-xr-xmain/streams/streams.c3
5 files changed, 102 insertions, 4 deletions
diff --git a/NEWS b/NEWS
index 84c841b08f..7f1b89cd89 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,7 @@ PHP NEWS
(Dmitry, Andi)
- Fixed bug #27628 (Simplify the process of making a POST request via stream
context). (Ilia)
+- Fixed bug #27619 (filters not applied to pre-buffered stream data). (Sara)
- Fixed bug #27469 (serialize() objects of incomplete class). (Dmitry)
- Fixed bug #27457 (handling of numeric indexes in strtr()). (Dmitry)
diff --git a/ext/standard/tests/file/bug27619.phpt b/ext/standard/tests/file/bug27619.phpt
new file mode 100644
index 0000000000..095a18c199
--- /dev/null
+++ b/ext/standard/tests/file/bug27619.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Bug #27619 (filters not applied to pre-buffered data)
+--FILE--
+<?php
+ $fp = tmpfile();
+ fwrite($fp, "this is a lowercase string.\n");
+ rewind($fp);
+
+ /* Echo out the first four bytes 'this' without applying filter
+ Remainder will get sucked into the read buffer though. */
+ echo fread($fp, 4);
+
+ stream_filter_append($fp, "string.toupper");
+
+ fpassthru($fp);
+?>
+--EXPECT--
+this IS A LOWERCASE STRING.
diff --git a/main/streams/filter.c b/main/streams/filter.c
index ff0549972c..708889f97c 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -283,7 +283,7 @@ PHPAPI void php_stream_filter_free(php_stream_filter *filter TSRMLS_DC)
pefree(filter, filter->is_persistent);
}
-PHPAPI void php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter)
+PHPAPI void php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter TSRMLS_DC)
{
filter->next = chain->head;
filter->prev = NULL;
@@ -297,8 +297,10 @@ PHPAPI void php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream
filter->chain = chain;
}
-PHPAPI void php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter)
+PHPAPI void php_stream_filter_append(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) {
@@ -308,6 +310,75 @@ PHPAPI void php_stream_filter_append(php_stream_filter_chain *chain, php_stream_
}
chain->tail = filter;
filter->chain = chain;
+
+ if ((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, 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 > stream->writepos || consumed < 0) {
+ /* No behaving filter should cause this. */
+ status = PSFS_ERR_FATAL;
+ }
+
+ switch (status) {
+ case PSFS_ERR_FATAL:
+ /* If this first cycle simply fails then there's something wrong with the filter.
+ Pull the filter off the chain and leave the read buffer alone. */
+ if (chain->head == filter) {
+ chain->head = NULL;
+ chain->tail = NULL;
+ } else {
+ filter->prev->next = NULL;
+ chain->tail = filter->prev;
+ }
+ 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. Not adding to filterchain.");
+ break;
+ 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:
+ /* Put any filtered data onto the readbuffer stack.
+ Previously read data has been at least partially consumed. */
+ stream->readpos += consumed;
+
+ if (stream->writepos == stream->readpos) {
+ /* Entirely consumed */
+ 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;
+ }
+ }
+
}
PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor TSRMLS_DC)
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index dcab09efd6..2cbe6a775a 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -100,6 +100,9 @@ typedef struct _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 {
@@ -118,14 +121,16 @@ struct _php_stream_filter {
/* stack filter onto a stream */
BEGIN_EXTERN_C()
-PHPAPI void php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI void php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
+PHPAPI void _php_stream_filter_prepend(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 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_fitler_prepend(chain, filter) _php_stream_filter_prepend((chain), (filter) TSRMLS_CC)
+#define php_stream_fitler_append(chain, filter) _php_stream_filter_append((chain), (filter) TSRMLS_CC)
#define php_stream_is_filtered(stream) ((stream)->readfilters.head || (stream)->writefilters.head)
diff --git a/main/streams/streams.c b/main/streams/streams.c
index af5caa93a7..fd67eea27d 100755
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -216,6 +216,9 @@ PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const
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