]> granicus.if.org Git - php/commitdiff
MFH: 45a6f8d for 5.4.
authorGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 19 Mar 2012 16:28:10 +0000 (16:28 +0000)
committerGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 19 Mar 2012 16:34:31 +0000 (16:34 +0000)
- 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.

ext/standard/tests/streams/bug60455_02.phpt
ext/standard/tests/streams/bug60455_03.phpt
ext/standard/tests/streams/bug60455_04.phpt [new file with mode: 0644]
ext/standard/tests/streams/bug60817.phpt [new file with mode: 0644]
main/streams/streams.c

index 6e06e9fa3fe2b77138dd27bb2f9d517542811c87..0ddf346eba2cfd9dd5ee77bbbbd35fd013d9d78a 100644 (file)
@@ -28,3 +28,4 @@ while (!feof($f)) {
 }
 --EXPECT--
 string(1) "a"
+bool(false)
index 5d7ba1f2480070b921fe2eababab5a097c54c612..2429d31008c191004d2c36dbf20538e5ad9058b0 100644 (file)
@@ -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 (file)
index 0000000..3a82298
--- /dev/null
@@ -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 (file)
index 0000000..2d4cf26
--- /dev/null
@@ -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"
index 161430754e74dff3810eaa0c13b47301762fd69f..89fa3640f307148037d995b4a4e941ec9982dd01 100755 (executable)
@@ -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 */