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 $context;
85    public $path;
86    public $mode;
87    public $options;
88
89    public $position;
90    public $varname;
91
92    function stream_open($path, $mode, $options, &$opened_path)
93    {
94        $this->path = $path;
95        $this->mode = $mode;
96        $this->options = $options;
97
98        $split = parse_url($path);
99        $this->varname = $split["host"];
100
101        if (strchr($mode, 'a'))
102            $this->position = strlen($GLOBALS[$this->varname]);
103        else
104            $this->position = 0;
105
106        return true;
107    }
108
109    function stream_read($count)
110    {
111        $ret = substr($GLOBALS[$this->varname], $this->position, $count);
112        $this->position += strlen($ret);
113        return $ret;
114    }
115
116    function stream_tell()
117    {
118        return $this->position;
119    }
120
121    function stream_eof()
122    {
123        return $this->position >= strlen($GLOBALS[$this->varname]);
124    }
125
126    function stream_seek($offset, $whence)
127    {
128        switch($whence) {
129            case SEEK_SET:
130                if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) {
131                    $this->position = $offset;
132                    return true;
133                } else {
134                    return false;
135                }
136                break;
137            case SEEK_CUR:
138                if ($offset >= 0) {
139                    $this->position += $offset;
140                    return true;
141                } else {
142                    return false;
143                }
144                break;
145            case SEEK_END:
146                if (strlen($GLOBALS[$this->varname]) + $offset >= 0) {
147                    $this->position = strlen($GLOBALS[$this->varname]) + $offset;
148                    return true;
149                } else {
150                    return false;
151                }
152                break;
153            default:
154                return false;
155        }
156    }
157
158}
159
160try {
161    stream_wrapper_register("bogus", "class_not_exist");
162    die("Registered a non-existent class!!!???");
163} catch (\TypeError $e) {
164    echo $e->getMessage() . \PHP_EOL;
165}
166echo "Not Registered\n";
167
168if (!stream_wrapper_register("test", "mystream")) {
169    die("test wrapper registration failed");
170}
171echo "Registered\n";
172
173if (!stream_wrapper_register("bogon", "uselessstream")) {
174    die("bogon wrapper registration failed");
175}
176echo "Registered\n";
177
178$b = @fopen("bogon://url", "rb");
179if (is_resource($b)) {
180    die("Opened a bogon??");
181}
182
183$fp = fopen("test://DATA", "rb");
184if (!$fp || !is_resource($fp)) {
185    die("Failed to open resource");
186}
187
188/* some default seeks that will cause buffer/cache misses */
189$seeks = array(
190    array(SEEK_SET, 0, 0),
191    array(SEEK_CUR, 8450, 8450),
192    array(SEEK_CUR, -7904, 546),
193    array(SEEK_CUR, 12456, 13002),
194
195    /* end up at BOF so that randomly generated seek offsets
196     * below will know where they are supposed to be */
197    array(SEEK_SET, 0, 0)
198);
199
200$whence_map = array(
201    SEEK_CUR,
202    SEEK_SET,
203    SEEK_END
204);
205$whence_names = array(
206    SEEK_CUR => "SEEK_CUR",
207    SEEK_SET => "SEEK_SET",
208    SEEK_END => "SEEK_END"
209    );
210
211/* generate some random seek offsets */
212$position = 0;
213for ($i = 0; $i < 256; $i++) {
214    $whence = $whence_map[array_rand($whence_map, 1)];
215    switch($whence) {
216        case SEEK_SET:
217            $offset = rand(0, $DATALEN - 1);
218            $position = $offset;
219            break;
220        case SEEK_END:
221            $offset = -rand(0, $DATALEN - 1);
222            $position = $DATALEN + $offset;
223            break;
224        case SEEK_CUR:
225            $offset = rand(0, $DATALEN - 1);
226            $offset -= $position;
227            $position += $offset;
228            break;
229    }
230
231    $seeks[] = array($whence, $offset, $position);
232}
233
234/* we compare the results of fgets using differing line lengths to
235 * test the fgets layer also */
236$line_lengths = array(1024, 256, 64, 16);
237$fail_count = 0;
238
239ob_start();
240foreach($line_lengths as $line_length) {
241    /* now compare the real stream with the user stream */
242    $j = 0;
243    rewind($tf);
244    rewind($fp);
245    foreach($seeks as $seekdata) {
246        list($whence, $offset, $position) = $seekdata;
247
248        $rpb = ftell($tf);
249        $rr = (int)fseek($tf, $offset, $whence);
250        $rpa = ftell($tf);
251        $rline = fgets($tf, $line_length);
252        (int)fseek($tf, - strlen($rline), SEEK_CUR);
253
254        $upb = ftell($fp);
255        $ur = (int)fseek($fp, $offset, $whence);
256        $upa = ftell($fp);
257        $uline = fgets($fp, $line_length);
258        (int)fseek($fp, - strlen($uline), SEEK_CUR);
259
260        printf("\n--[%d] whence=%s offset=%d line_length=%d position_should_be=%d --\n",
261            $j, $whence_names[$whence], $offset, $line_length, $position);
262        printf("REAL: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $rpb, $rpa, ftell($tf), $rr, strlen($rline), $rline);
263        printf("USER: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $upb, $upa, ftell($fp), $ur, strlen($uline), $uline);
264
265        if ($rr != $ur || $rline != $uline || $rpa != $position || $upa != $position) {
266            $fail_count++;
267            echo "###################################### FAIL!\n";
268            $dat = stream_get_meta_data($fp);
269            var_dump($dat);
270            break;
271        }
272
273        $j++;
274    }
275    if ($fail_count)
276        break;
277}
278
279if ($fail_count == 0) {
280    ob_end_clean();
281    echo "SEEK: OK\n";
282} else {
283    echo "SEEK: FAIL\n";
284    ob_end_flush();
285}
286
287$fail_count = 0;
288
289fseek($fp, $DATALEN / 2, SEEK_SET);
290fseek($tf, $DATALEN / 2, SEEK_SET);
291
292if (ftell($fp) != ftell($tf)) {
293    echo "SEEK: positions do not match!\n";
294}
295
296$n = 0;
297while(!feof($fp)) {
298    $uline = fgets($fp, 1024);
299    $rline = fgets($tf, 1024);
300
301    if ($uline != $rline) {
302        echo "FGETS: FAIL\niter=$n user=$uline [pos=" . ftell($fp) . "]\nreal=$rline [pos=" . ftell($tf) . "]\n";
303        $fail_count++;
304        break;
305    }
306}
307
308if ($fail_count == 0) {
309    echo "FGETS: OK\n";
310}
311
312/* One final test to see if the position is respected when opened for append */
313$fp = fopen("test://lyrics", "a+");
314rewind($fp);
315var_dump(ftell($fp));
316$data = fgets($fp);
317fclose($fp);
318echo $data . "\n";
319
320?>
321--EXPECT--
322stream_wrapper_register(): Argument #2 ($class) must be a valid class name, class_not_exist given
323Not Registered
324Registered
325Registered
326SEEK: OK
327FGETS: OK
328int(0)
329...and the road becomes my bride
330