1--TEST--
2User-space streams
3--FILE--
4<?php
5
6/* This is a fairly aggressive test that looks at
7 * user streams and also gives the seek/gets/buffer
8 * layer of streams a thorough testing */
9
10$lyrics = <<<EOD
11...and the road becomes my bride
12I have stripped of all but pride
13so in her I do confide
14and she keeps me satisfied
15gives me all I need
16...and with dust in throat I crave
17to the game you stay a slave
18rover  wanderer
19nomad  vagabond
20call me what you will
21   But Ill take my time anywhere
22   Free to speak my mind anywhere
23   and Ill redefine anywhere
24      Anywhere I roam
25      Where I lay my head is home
26...and the earth becomes my throne
27I adapt to the unknown
28under wandering stars Ive grown
29by myself but not alone
30I ask no one
31...and my ties are severed clean
32the less I have the more I gain
33off the beaten path I reign
34rover  wanderer
35nomad  vagabond
36call me what you will
37   But Ill take my time anywhere
38   Free to speak my mind anywhere
39   and Ill never mind anywhere
40      Anywhere I roam
41      Where I lay my head is home
42   But Ill take my time anywhere
43   Free to speak my mind anywhere
44   and Ill take my find anywhere
45      Anywhere I roam
46      Where I lay my head is home
47   carved upon my stone
48   my body lie but still I roam
49      Wherever I may roam.
50
51Wherever I May Roam
52
53EOD;
54
55/* repeat the data a few times so that it grows larger than
56 * the default cache chunk size and that we have something
57 * to seek around... */
58$DATA = "";
59for ($i = 0; $i < 30; $i++) {
60    if ($i % 2 == 0)
61        $DATA .= str_rot13($lyrics);
62    else
63        $DATA .= $lyrics;
64}
65
66/* store the data in a regular file so that we can compare
67 * the results */
68$tf = tmpfile();
69fwrite($tf, $DATA);
70$n = ftell($tf);
71rewind($tf) or die("failed to rewind tmp file!");
72if (ftell($tf) != 0)
73    die("tmpfile is not at start!");
74$DATALEN = strlen($DATA);
75if ($n != $DATALEN)
76    die("tmpfile stored $n bytes; should be $DATALEN!");
77
78class uselessstream
79{
80}
81
82class mystream
83{
84    public $path;
85    public $mode;
86    public $options;
87
88    public $position;
89    public $varname;
90
91    function stream_open($path, $mode, $options, &$opened_path)
92    {
93        $this->path = $path;
94        $this->mode = $mode;
95        $this->options = $options;
96
97        $split = parse_url($path);
98        $this->varname = $split["host"];
99
100        if (strchr($mode, 'a'))
101            $this->position = strlen($GLOBALS[$this->varname]);
102        else
103            $this->position = 0;
104
105        return true;
106    }
107
108    function stream_read($count)
109    {
110        $ret = substr($GLOBALS[$this->varname], $this->position, $count);
111        $this->position += strlen($ret);
112        return $ret;
113    }
114
115    function stream_tell()
116    {
117        return $this->position;
118    }
119
120    function stream_eof()
121    {
122        return $this->position >= strlen($GLOBALS[$this->varname]);
123    }
124
125    function stream_seek($offset, $whence)
126    {
127        switch($whence) {
128            case SEEK_SET:
129                if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) {
130                    $this->position = $offset;
131                    return true;
132                } else {
133                    return false;
134                }
135                break;
136            case SEEK_CUR:
137                if ($offset >= 0) {
138                    $this->position += $offset;
139                    return true;
140                } else {
141                    return false;
142                }
143                break;
144            case SEEK_END:
145                if (strlen($GLOBALS[$this->varname]) + $offset >= 0) {
146                    $this->position = strlen($GLOBALS[$this->varname]) + $offset;
147                    return true;
148                } else {
149                    return false;
150                }
151                break;
152            default:
153                return false;
154        }
155    }
156
157}
158
159try {
160    stream_wrapper_register("bogus", "class_not_exist");
161    die("Registered a non-existent class!!!???");
162} catch (\TypeError $e) {
163    echo $e->getMessage() . \PHP_EOL;
164}
165echo "Not Registered\n";
166
167if (!stream_wrapper_register("test", "mystream")) {
168    die("test wrapper registration failed");
169}
170echo "Registered\n";
171
172if (!stream_wrapper_register("bogon", "uselessstream")) {
173    die("bogon wrapper registration failed");
174}
175echo "Registered\n";
176
177$b = @fopen("bogon://url", "rb");
178if (is_resource($b)) {
179    die("Opened a bogon??");
180}
181
182$fp = fopen("test://DATA", "rb");
183if (!$fp || !is_resource($fp)) {
184    die("Failed to open resource");
185}
186
187/* some default seeks that will cause buffer/cache misses */
188$seeks = array(
189    array(SEEK_SET, 0, 0),
190    array(SEEK_CUR, 8450, 8450),
191    array(SEEK_CUR, -7904, 546),
192    array(SEEK_CUR, 12456, 13002),
193
194    /* end up at BOF so that randomly generated seek offsets
195     * below will know where they are supposed to be */
196    array(SEEK_SET, 0, 0)
197);
198
199$whence_map = array(
200    SEEK_CUR,
201    SEEK_SET,
202    SEEK_END
203);
204$whence_names = array(
205    SEEK_CUR => "SEEK_CUR",
206    SEEK_SET => "SEEK_SET",
207    SEEK_END => "SEEK_END"
208    );
209
210/* generate some random seek offsets */
211$position = 0;
212for ($i = 0; $i < 256; $i++) {
213    $whence = $whence_map[array_rand($whence_map, 1)];
214    switch($whence) {
215        case SEEK_SET:
216            $offset = rand(0, $DATALEN - 1);
217            $position = $offset;
218            break;
219        case SEEK_END:
220            $offset = -rand(0, $DATALEN - 1);
221            $position = $DATALEN + $offset;
222            break;
223        case SEEK_CUR:
224            $offset = rand(0, $DATALEN - 1);
225            $offset -= $position;
226            $position += $offset;
227            break;
228    }
229
230    $seeks[] = array($whence, $offset, $position);
231}
232
233/* we compare the results of fgets using differing line lengths to
234 * test the fgets layer also */
235$line_lengths = array(1024, 256, 64, 16);
236$fail_count = 0;
237
238ob_start();
239foreach($line_lengths as $line_length) {
240    /* now compare the real stream with the user stream */
241    $j = 0;
242    rewind($tf);
243    rewind($fp);
244    foreach($seeks as $seekdata) {
245        list($whence, $offset, $position) = $seekdata;
246
247        $rpb = ftell($tf);
248        $rr = (int)fseek($tf, $offset, $whence);
249        $rpa = ftell($tf);
250        $rline = fgets($tf, $line_length);
251        (int)fseek($tf, - strlen($rline), SEEK_CUR);
252
253        $upb = ftell($fp);
254        $ur = (int)fseek($fp, $offset, $whence);
255        $upa = ftell($fp);
256        $uline = fgets($fp, $line_length);
257        (int)fseek($fp, - strlen($uline), SEEK_CUR);
258
259        printf("\n--[%d] whence=%s offset=%d line_length=%d position_should_be=%d --\n",
260            $j, $whence_names[$whence], $offset, $line_length, $position);
261        printf("REAL: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $rpb, $rpa, ftell($tf), $rr, strlen($rline), $rline);
262        printf("USER: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $upb, $upa, ftell($fp), $ur, strlen($uline), $uline);
263
264        if ($rr != $ur || $rline != $uline || $rpa != $position || $upa != $position) {
265            $fail_count++;
266            echo "###################################### FAIL!\n";
267            $dat = stream_get_meta_data($fp);
268            var_dump($dat);
269            break;
270        }
271
272        $j++;
273    }
274    if ($fail_count)
275        break;
276}
277
278if ($fail_count == 0) {
279    ob_end_clean();
280    echo "SEEK: OK\n";
281} else {
282    echo "SEEK: FAIL\n";
283    ob_end_flush();
284}
285
286$fail_count = 0;
287
288fseek($fp, $DATALEN / 2, SEEK_SET);
289fseek($tf, $DATALEN / 2, SEEK_SET);
290
291if (ftell($fp) != ftell($tf)) {
292    echo "SEEK: positions do not match!\n";
293}
294
295$n = 0;
296while(!feof($fp)) {
297    $uline = fgets($fp, 1024);
298    $rline = fgets($tf, 1024);
299
300    if ($uline != $rline) {
301        echo "FGETS: FAIL\niter=$n user=$uline [pos=" . ftell($fp) . "]\nreal=$rline [pos=" . ftell($tf) . "]\n";
302        $fail_count++;
303        break;
304    }
305}
306
307if ($fail_count == 0) {
308    echo "FGETS: OK\n";
309}
310
311/* One final test to see if the position is respected when opened for append */
312$fp = fopen("test://lyrics", "a+");
313rewind($fp);
314var_dump(ftell($fp));
315$data = fgets($fp);
316fclose($fp);
317echo $data . "\n";
318
319?>
320--EXPECT--
321stream_wrapper_register(): Argument #2 ($class) must be a valid class name, class_not_exist given
322Not Registered
323Registered
324Registered
325SEEK: OK
326FGETS: OK
327int(0)
328...and the road becomes my bride
329