Skip to content

Commit ba9650d

Browse files
committed
Fix bug #52335 (fseek() on memory stream behavior different then file)
This changes memory stream to allow seeking past end which makes it the same as seeking on files. It means the position is allowed to be higher than the string length. The size only increases if data is appended to the past position. The space between the previous string and position is filled with zero bytes. Fixes GH-9441 Closes GH-12058
1 parent ea10e79 commit ba9650d

File tree

6 files changed

+118
-23
lines changed

6 files changed

+118
-23
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ PHP NEWS
2020
. Fixed GH-11982 (str_getcsv returns null byte for unterminated enclosure).
2121
(Jakub Zelenka)
2222

23+
- Streams:
24+
. Fixed bug #52335 (fseek() on memory stream behavior different than file).
25+
(Jakub Zelenka)
26+
2327
17 Aug 2023, PHP 8.3.0beta3
2428

2529
- Core:

UPGRADING

+3
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,9 @@ PHP 8.3 UPGRADE NOTES
648648
- Streams:
649649
. Blocking fread() on socket connection returns immediately if there are
650650
any buffered data instead of waiting for more data.
651+
. Memory stream no longer fails if seek offset is past the end. Instead
652+
the memory is increase on the next write and date between the old end and
653+
offset is filled with zero bytes in the same way how it works for files.
651654

652655
========================================
653656
14. Performance Improvements

ext/standard/tests/file/bug52335.phpt

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
--TEST--
2+
Bug #52335 (fseek() on memory stream behavior different then file)
3+
--FILE--
4+
<?php
5+
6+
echo "Read mode\n";
7+
$fpr = fopen("php://memory", "r");
8+
var_dump(fseek($fpr, 20));
9+
var_dump(feof($fpr));
10+
var_dump(ftell($fpr));
11+
var_dump(feof($fpr));
12+
var_dump(fread($fpr, 2));
13+
var_dump(feof($fpr));
14+
var_dump(fseek($fpr, 24));
15+
var_dump(feof($fpr));
16+
var_dump(ftell($fpr));
17+
fclose($fpr);
18+
19+
echo "Read write mode\n";
20+
$fprw = fopen("php://memory", "r+");
21+
var_dump(fwrite($fprw, "data"));
22+
var_dump(fseek($fprw, 20, SEEK_END));
23+
var_dump(feof($fprw));
24+
var_dump(ftell($fprw));
25+
var_dump(feof($fprw));
26+
var_dump(fread($fprw, 2));
27+
var_dump(feof($fprw));
28+
var_dump(fseek($fprw, 20));
29+
var_dump(fwrite($fprw, " and more data"));
30+
var_dump(feof($fprw));
31+
var_dump(ftell($fprw));
32+
var_dump(fread($fprw, 10));
33+
var_dump(fseek($fprw, 16, SEEK_CUR));
34+
var_dump(ftell($fprw));
35+
var_dump(fseek($fprw, 0));
36+
var_dump(bin2hex(stream_get_contents($fprw)));
37+
fclose($fprw);
38+
39+
?>
40+
--EXPECT--
41+
Read mode
42+
int(0)
43+
bool(false)
44+
int(20)
45+
bool(false)
46+
string(0) ""
47+
bool(true)
48+
int(0)
49+
bool(false)
50+
int(24)
51+
Read write mode
52+
int(4)
53+
int(0)
54+
bool(false)
55+
int(24)
56+
bool(false)
57+
string(0) ""
58+
bool(true)
59+
int(0)
60+
int(14)
61+
bool(false)
62+
int(34)
63+
string(0) ""
64+
int(0)
65+
int(50)
66+
int(0)
67+
string(68) "646174610000000000000000000000000000000020616e64206d6f72652064617461"

ext/standard/tests/file/gh9441.phpt

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Bug GH-9441 (fseek does not work with php://input when data is not preread)
3+
--INI--
4+
enable_post_data_reading=0
5+
--POST_RAW--
6+
Content-Type: application/unknown
7+
a=123456789&b=ZYX
8+
--FILE--
9+
<?php
10+
$input = fopen("php://input", "r");
11+
var_dump(fseek($input, 10));
12+
var_dump(ftell($input));
13+
var_dump(fread($input, 10));
14+
var_dump(file_get_contents("php://input"));
15+
?>
16+
--EXPECT--
17+
int(0)
18+
int(10)
19+
string(7) "9&b=ZYX"
20+
string(17) "a=123456789&b=ZYX"

ext/standard/tests/file/stream_rfc2397_007.phpt

+4-4
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ int(0)
125125
int(3)
126126
bool(false)
127127
===S:10,C===
128-
int(-1)
129-
bool(false)
128+
int(0)
129+
int(13)
130130
bool(false)
131131
===S:-1,E===
132132
int(0)
@@ -137,6 +137,6 @@ int(0)
137137
int(6)
138138
bool(false)
139139
===S:1,E===
140-
int(-1)
141-
bool(false)
140+
int(0)
141+
int(7)
142142
bool(false)

main/streams/memory.c

+20-19
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,17 @@ static ssize_t php_stream_memory_write(php_stream *stream, const char *buf, size
4949

5050
if (ms->mode & TEMP_STREAM_READONLY) {
5151
return (ssize_t) -1;
52-
} else if (ms->mode & TEMP_STREAM_APPEND) {
53-
ms->fpos = ZSTR_LEN(ms->data);
5452
}
55-
if (ms->fpos + count > ZSTR_LEN(ms->data)) {
53+
size_t data_len = ZSTR_LEN(ms->data);
54+
if (ms->mode & TEMP_STREAM_APPEND) {
55+
ms->fpos = data_len;
56+
}
57+
if (ms->fpos + count > data_len) {
5658
ms->data = zend_string_realloc(ms->data, ms->fpos + count, 0);
59+
if (ms->fpos > data_len) {
60+
/* zero the bytes added due to seek past end position */
61+
memset(ZSTR_VAL(ms->data) + data_len, 0, ms->fpos - data_len);
62+
}
5763
} else {
5864
ms->data = zend_string_separate(ms->data, 0);
5965
}
@@ -73,7 +79,7 @@ static ssize_t php_stream_memory_read(php_stream *stream, char *buf, size_t coun
7379
php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
7480
assert(ms != NULL);
7581

76-
if (ms->fpos == ZSTR_LEN(ms->data)) {
82+
if (ms->fpos >= ZSTR_LEN(ms->data)) {
7783
stream->eof = 1;
7884
count = 0;
7985
} else {
@@ -132,20 +138,14 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
132138
return 0;
133139
}
134140
} else {
135-
if (ms->fpos + (size_t)(offset) > ZSTR_LEN(ms->data)) {
136-
ms->fpos = ZSTR_LEN(ms->data);
137-
*newoffs = -1;
138-
return -1;
139-
} else {
140-
ms->fpos = ms->fpos + offset;
141-
*newoffs = ms->fpos;
142-
stream->eof = 0;
143-
return 0;
144-
}
141+
stream->eof = 0;
142+
ms->fpos = ms->fpos + offset;
143+
*newoffs = ms->fpos;
144+
return 0;
145145
}
146146
case SEEK_SET:
147-
if (ZSTR_LEN(ms->data) < (size_t)(offset)) {
148-
ms->fpos = ZSTR_LEN(ms->data);
147+
if (offset < 0) {
148+
ms->fpos = 0;
149149
*newoffs = -1;
150150
return -1;
151151
} else {
@@ -156,9 +156,10 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
156156
}
157157
case SEEK_END:
158158
if (offset > 0) {
159-
ms->fpos = ZSTR_LEN(ms->data);
160-
*newoffs = -1;
161-
return -1;
159+
ms->fpos = ZSTR_LEN(ms->data) + offset;
160+
*newoffs = ms->fpos;
161+
stream->eof = 0;
162+
return 0;
162163
} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
163164
ms->fpos = 0;
164165
*newoffs = -1;

0 commit comments

Comments
 (0)