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