summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGustavo André dos Santos Lopes <cataphract@php.net>2012-03-19 16:28:10 +0000
committerGustavo André dos Santos Lopes <cataphract@php.net>2012-03-19 16:34:31 +0000
commit9a460497da3cc2b755f4628350756427fc0a1051 (patch)
tree03d3031a78763be85b261a90ee6a68673300642b
parent53e3467ff233af4a40626f86ea8a61880722beb8 (diff)
downloadphp-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.phpt1
-rw-r--r--ext/standard/tests/streams/bug60455_03.phpt2
-rw-r--r--ext/standard/tests/streams/bug60455_04.phpt32
-rw-r--r--ext/standard/tests/streams/bug60817.phpt36
-rwxr-xr-xmain/streams/streams.c130
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 */