diff options
author | Gustavo André dos Santos Lopes <cataphract@php.net> | 2012-03-19 16:28:10 +0000 |
---|---|---|
committer | Gustavo André dos Santos Lopes <cataphract@php.net> | 2012-03-19 16:34:31 +0000 |
commit | 9a460497da3cc2b755f4628350756427fc0a1051 (patch) | |
tree | 03d3031a78763be85b261a90ee6a68673300642b | |
parent | 53e3467ff233af4a40626f86ea8a61880722beb8 (diff) | |
download | php-git-9a460497da3cc2b755f4628350756427fc0a1051.tar.gz |
MFH: 45a6f8d for 5.4.
- Further fix for bug #60455 (stream_get_line misbehaves if EOF is not detected
together with the last read).
- Fixed bug #60817 (stream_get_line() reads from stream even when there is
already sufficient data buffered). stream_get_line() now behaves more like
fgets(), as is documented.
-rw-r--r-- | ext/standard/tests/streams/bug60455_02.phpt | 1 | ||||
-rw-r--r-- | ext/standard/tests/streams/bug60455_03.phpt | 2 | ||||
-rw-r--r-- | ext/standard/tests/streams/bug60455_04.phpt | 32 | ||||
-rw-r--r-- | ext/standard/tests/streams/bug60817.phpt | 36 | ||||
-rwxr-xr-x | main/streams/streams.c | 130 |
5 files changed, 153 insertions, 48 deletions
diff --git a/ext/standard/tests/streams/bug60455_02.phpt b/ext/standard/tests/streams/bug60455_02.phpt index 6e06e9fa3f..0ddf346eba 100644 --- a/ext/standard/tests/streams/bug60455_02.phpt +++ b/ext/standard/tests/streams/bug60455_02.phpt @@ -28,3 +28,4 @@ while (!feof($f)) { } --EXPECT-- string(1) "a" +bool(false) diff --git a/ext/standard/tests/streams/bug60455_03.phpt b/ext/standard/tests/streams/bug60455_03.phpt index 5d7ba1f248..2429d31008 100644 --- a/ext/standard/tests/streams/bug60455_03.phpt +++ b/ext/standard/tests/streams/bug60455_03.phpt @@ -47,7 +47,9 @@ while (!feof($f)) { --EXPECT-- string(1) "a" string(1) "b" +bool(false) string(1) "a" string(0) "" +bool(false) string(1) "a" string(0) "" diff --git a/ext/standard/tests/streams/bug60455_04.phpt b/ext/standard/tests/streams/bug60455_04.phpt new file mode 100644 index 0000000000..3a82298dbc --- /dev/null +++ b/ext/standard/tests/streams/bug60455_04.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #60455: stream_get_line and 1-line with maxlen size followed by 0-length +read with EOL indication +--FILE-- +<?php +class TestStream { + private $s = 0; + function stream_open($path, $mode, $options, &$opened_path) { + return true; + } + function stream_read($count) { + if ($this->s++ == 0) + return "a\n"; + + return ""; + } + function stream_eof() { + return $this->s >= 2; + } + +} + +stream_wrapper_register("test", "TestStream"); + +$f = fopen("test://", "r"); +while (!feof($f)) { + $line = stream_get_line($f, 2, "\n"); + var_dump($line); +} +--EXPECT-- +string(1) "a" +bool(false) diff --git a/ext/standard/tests/streams/bug60817.phpt b/ext/standard/tests/streams/bug60817.phpt new file mode 100644 index 0000000000..2d4cf2682b --- /dev/null +++ b/ext/standard/tests/streams/bug60817.phpt @@ -0,0 +1,36 @@ +--TEST-- +Bug #60817: stream_get_line() reads from stream even when there is already sufficient data buffered +--FILE-- +<?php +class TestStream { //data, empty data, empty data + eof + private $s = 0; + function stream_open($path, $mode, $options, &$opened_path) { + return true; + } + function stream_read($count) { + echo "Read done\n"; + if ($this->s++ == 0) + return "a\nbb\ncc"; + + return ""; + } + function stream_eof() { + return $this->s >= 2; + } + +} + +stream_wrapper_register("test", "TestStream"); + +$f = fopen("test://", "r"); +while (!feof($f)) { + $line = stream_get_line($f, 99, "\n"); + var_dump($line); +} + +--EXPECT-- +Read done +string(1) "a" +string(2) "bb" +Read done +string(2) "cc" diff --git a/main/streams/streams.c b/main/streams/streams.c index 161430754e..89fa3640f3 100755 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -996,77 +996,111 @@ PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, 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 && delim[0] != '\0'; + + if (maxlen == 0) { + return NULL; + } - len = stream->writepos - stream->readpos; + if (has_delim) { + found_delim = _php_stream_search_delim( + stream, maxlen, 0, delim, delim_len TSRMLS_CC); + } - /* make sure the stream read buffer has maxlen bytes */ - while (len < maxlen) { + 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; - size_t just_read; - toread = MIN(maxlen - len, stream->chunk_size); + to_read_now = MIN(maxlen - buffered_len, stream->chunk_size); - php_stream_fill_read_buffer(stream, len + toread TSRMLS_CC); + php_stream_fill_read_buffer(stream, buffered_len + to_read_now TSRMLS_CC); - just_read = (stream->writepos - stream->readpos) - len; - len += just_read; + 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 (delim_len == 1) { - e = memchr(stream->readbuf + stream->readpos, *delim, seek_len); - } else { - e = php_memnstr(stream->readbuf + stream->readpos, delim, delim_len, (stream->readbuf + stream->readpos + seek_len)); - } - - 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; + 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 */ + found_delim = _php_stream_search_delim( + stream, maxlen, buffered_len, delim, delim_len TSRMLS_CC); + if (found_delim) { + break; } - toread = maxlen; - } else { - toread = e - (char *) stream->readbuf - stream->readpos; - /* we found the delimiter, so advance the read pointer past it */ - skip = 1; } + buffered_len += just_read; } - if (toread > maxlen && maxlen > 0) { - toread = maxlen; + 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); + } } - buf = emalloc(toread + 1); - *returned_len = php_stream_read(stream, buf, toread); + 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 (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 */ |