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
159if (@stream_wrapper_register("bogus", "class_not_exist")) {
160	die("Registered a non-existent class!!!???");
161}
162echo "Not Registered\n";
163
164if (!stream_wrapper_register("test", "mystream")) {
165	die("test wrapper registration failed");
166}
167echo "Registered\n";
168
169if (!stream_wrapper_register("bogon", "uselessstream")) {
170	die("bogon wrapper registration failed");
171}
172echo "Registered\n";
173
174$b = @fopen("bogon://url", "rb");
175if (is_resource($b)) {
176	die("Opened a bogon??");
177}
178
179$fp = fopen("test://DATA", "rb");
180if (!$fp || !is_resource($fp)) {
181	die("Failed to open resource");
182}
183
184/* some default seeks that will cause buffer/cache misses */
185$seeks = array(
186	array(SEEK_SET, 0, 0),
187	array(SEEK_CUR, 8450, 8450),
188	array(SEEK_CUR, -7904, 546),
189	array(SEEK_CUR, 12456, 13002),
190
191	/* end up at BOF so that randomly generated seek offsets
192	 * below will know where they are supposed to be */
193	array(SEEK_SET, 0, 0)
194);
195
196$whence_map = array(
197	SEEK_CUR,
198	SEEK_SET,
199	SEEK_END
200);
201$whence_names = array(
202	SEEK_CUR => "SEEK_CUR",
203	SEEK_SET => "SEEK_SET",
204	SEEK_END => "SEEK_END"
205	);
206
207/* generate some random seek offsets */
208$position = 0;
209for ($i = 0; $i < 256; $i++) {
210	$whence = $whence_map[array_rand($whence_map, 1)];
211	switch($whence) {
212		case SEEK_SET:
213			$offset = rand(0, $DATALEN - 1);
214			$position = $offset;
215			break;
216		case SEEK_END:
217			$offset = -rand(0, $DATALEN - 1);
218			$position = $DATALEN + $offset;
219			break;
220		case SEEK_CUR:
221			$offset = rand(0, $DATALEN - 1);
222			$offset -= $position;
223			$position += $offset;
224			break;
225	}
226
227	$seeks[] = array($whence, $offset, $position);
228}
229
230/* we compare the results of fgets using differing line lengths to
231 * test the fgets layer also */
232$line_lengths = array(1024, 256, 64, 16);
233$fail_count = 0;
234
235ob_start();
236foreach($line_lengths as $line_length) {
237	/* now compare the real stream with the user stream */
238	$j = 0;
239	rewind($tf);
240	rewind($fp);
241	foreach($seeks as $seekdata) {
242		list($whence, $offset, $position) = $seekdata;
243
244		$rpb = ftell($tf);
245		$rr = (int)fseek($tf, $offset, $whence);
246		$rpa = ftell($tf);
247		$rline = fgets($tf, $line_length);
248		(int)fseek($tf, - strlen($rline), SEEK_CUR);
249
250		$upb = ftell($fp);
251		$ur = (int)fseek($fp, $offset, $whence);
252		$upa = ftell($fp);
253		$uline = fgets($fp, $line_length);
254		(int)fseek($fp, - strlen($uline), SEEK_CUR);
255
256		printf("\n--[%d] whence=%s offset=%d line_length=%d position_should_be=%d --\n",
257			$j, $whence_names[$whence], $offset, $line_length, $position);
258		printf("REAL: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $rpb, $rpa, ftell($tf), $rr, strlen($rline), $rline);
259		printf("USER: pos=(%d,%d,%d) ret=%d line[%d]=`%s'\n", $upb, $upa, ftell($fp), $ur, strlen($uline), $uline);
260
261		if ($rr != $ur || $rline != $uline || $rpa != $position || $upa != $position) {
262			$fail_count++;
263			echo "###################################### FAIL!\n";
264			$dat = stream_get_meta_data($fp);
265			var_dump($dat);
266			break;
267		}
268
269		$j++;
270	}
271	if ($fail_count)
272		break;
273}
274
275if ($fail_count == 0) {
276	ob_end_clean();
277	echo "SEEK: OK\n";
278} else {
279	echo "SEEK: FAIL\n";
280	ob_end_flush();
281}
282
283$fail_count = 0;
284
285fseek($fp, $DATALEN / 2, SEEK_SET);
286fseek($tf, $DATALEN / 2, SEEK_SET);
287
288if (ftell($fp) != ftell($tf)) {
289	echo "SEEK: positions do not match!\n";
290}
291
292$n = 0;
293while(!feof($fp)) {
294	$uline = fgets($fp, 1024);
295	$rline = fgets($tf, 1024);
296
297	if ($uline != $rline) {
298		echo "FGETS: FAIL\niter=$n user=$uline [pos=" . ftell($fp) . "]\nreal=$rline [pos=" . ftell($tf) . "]\n";
299		$fail_count++;
300		break;
301	}
302}
303
304if ($fail_count == 0) {
305	echo "FGETS: OK\n";
306}
307
308/* One final test to see if the position is respected when opened for append */
309$fp = fopen("test://lyrics", "a+");
310rewind($fp);
311var_dump(ftell($fp));
312$data = fgets($fp);
313fclose($fp);
314echo $data . "\n";
315
316?>
317--EXPECT--
318Not Registered
319Registered
320Registered
321SEEK: OK
322FGETS: OK
323int(0)
324...and the road becomes my bride
325