xref: /PHP-7.1/ext/standard/url.c (revision ccd4716e)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Jim Winstead <jimw@php.net>                                  |
16    +----------------------------------------------------------------------+
17  */
18 /* $Id$ */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <sys/types.h>
24 
25 #include "php.h"
26 
27 #include "url.h"
28 #include "file.h"
29 #ifdef _OSD_POSIX
30 #ifndef APACHE
31 #error On this EBCDIC platform, PHP is only supported as an Apache module.
32 #else /*APACHE*/
33 #ifndef CHARSET_EBCDIC
34 #define CHARSET_EBCDIC /* this machine uses EBCDIC, not ASCII! */
35 #endif
36 #include "ebcdic.h"
37 #endif /*APACHE*/
38 #endif /*_OSD_POSIX*/
39 
40 /* {{{ free_url
41  */
php_url_free(php_url * theurl)42 PHPAPI void php_url_free(php_url *theurl)
43 {
44 	if (theurl->scheme)
45 		efree(theurl->scheme);
46 	if (theurl->user)
47 		efree(theurl->user);
48 	if (theurl->pass)
49 		efree(theurl->pass);
50 	if (theurl->host)
51 		efree(theurl->host);
52 	if (theurl->path)
53 		efree(theurl->path);
54 	if (theurl->query)
55 		efree(theurl->query);
56 	if (theurl->fragment)
57 		efree(theurl->fragment);
58 	efree(theurl);
59 }
60 /* }}} */
61 
62 /* {{{ php_replace_controlchars
63  */
php_replace_controlchars_ex(char * str,size_t len)64 PHPAPI char *php_replace_controlchars_ex(char *str, size_t len)
65 {
66 	unsigned char *s = (unsigned char *)str;
67 	unsigned char *e = (unsigned char *)str + len;
68 
69 	if (!str) {
70 		return (NULL);
71 	}
72 
73 	while (s < e) {
74 
75 		if (iscntrl(*s)) {
76 			*s='_';
77 		}
78 		s++;
79 	}
80 
81 	return (str);
82 }
83 /* }}} */
84 
php_replace_controlchars(char * str)85 PHPAPI char *php_replace_controlchars(char *str)
86 {
87 	return php_replace_controlchars_ex(str, strlen(str));
88 }
89 
php_url_parse(char const * str)90 PHPAPI php_url *php_url_parse(char const *str)
91 {
92 	return php_url_parse_ex(str, strlen(str));
93 }
94 
95 /* {{{ php_url_parse
96  */
php_url_parse_ex(char const * str,size_t length)97 PHPAPI php_url *php_url_parse_ex(char const *str, size_t length)
98 {
99 	char port_buf[6];
100 	php_url *ret = ecalloc(1, sizeof(php_url));
101 	char const *s, *e, *p, *pp, *ue;
102 
103 	s = str;
104 	ue = s + length;
105 
106 	/* parse scheme */
107 	if ((e = memchr(s, ':', length)) && e != s) {
108 		/* validate scheme */
109 		p = s;
110 		while (p < e) {
111 			/* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */
112 			if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') {
113 				if (e + 1 < ue && e < s + strcspn(s, "?#")) {
114 					goto parse_port;
115 				} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
116 					s += 2;
117 					e = 0;
118 					goto parse_host;
119 				} else {
120 					goto just_path;
121 				}
122 			}
123 			p++;
124 		}
125 
126 		if (e + 1 == ue) { /* only scheme is available */
127 			ret->scheme = estrndup(s, (e - s));
128 			php_replace_controlchars_ex(ret->scheme, (e - s));
129 			return ret;
130 		}
131 
132 		/*
133 		 * certain schemas like mailto: and zlib: may not have any / after them
134 		 * this check ensures we support those.
135 		 */
136 		if (*(e+1) != '/') {
137 			/* check if the data we get is a port this allows us to
138 			 * correctly parse things like a.com:80
139 			 */
140 			p = e + 1;
141 			while (p < ue && isdigit(*p)) {
142 				p++;
143 			}
144 
145 			if ((p == ue || *p == '/') && (p - e) < 7) {
146 				goto parse_port;
147 			}
148 
149 			ret->scheme = estrndup(s, (e-s));
150 			php_replace_controlchars_ex(ret->scheme, (e - s));
151 
152 			s = e + 1;
153 			goto just_path;
154 		} else {
155 			ret->scheme = estrndup(s, (e-s));
156 			php_replace_controlchars_ex(ret->scheme, (e - s));
157 
158 			if (e + 2 < ue && *(e + 2) == '/') {
159 				s = e + 3;
160 				if (!strncasecmp("file", ret->scheme, sizeof("file"))) {
161 					if (e + 3 < ue && *(e + 3) == '/') {
162 						/* support windows drive letters as in:
163 						   file:///c:/somedir/file.txt
164 						*/
165 						if (e + 5 < ue && *(e + 5) == ':') {
166 							s = e + 4;
167 						}
168 						goto just_path;
169 					}
170 				}
171 			} else {
172 				s = e + 1;
173 				goto just_path;
174 			}
175 		}
176 	} else if (e) { /* no scheme; starts with colon: look for port */
177 		parse_port:
178 		p = e + 1;
179 		pp = p;
180 
181 		while (pp < ue && pp - p < 6 && isdigit(*pp)) {
182 			pp++;
183 		}
184 
185 		if (pp - p > 0 && pp - p < 6 && (pp == ue || *pp == '/')) {
186 			zend_long port;
187 			memcpy(port_buf, p, (pp - p));
188 			port_buf[pp - p] = '\0';
189 			port = ZEND_STRTOL(port_buf, NULL, 10);
190 			if (port > 0 && port <= 65535) {
191 				ret->port = (unsigned short) port;
192 				if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
193 				    s += 2;
194 				}
195 			} else {
196 				if (ret->scheme) efree(ret->scheme);
197 				efree(ret);
198 				return NULL;
199 			}
200 		} else if (p == pp && pp == ue) {
201 			if (ret->scheme) efree(ret->scheme);
202 			efree(ret);
203 			return NULL;
204 		} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
205 			s += 2;
206 		} else {
207 			goto just_path;
208 		}
209 	} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
210 		s += 2;
211 	} else {
212 		goto just_path;
213 	}
214 
215 	parse_host:
216 	/* Binary-safe strcspn(s, "/?#") */
217 	e = ue;
218 	if ((p = memchr(s, '/', e - s))) {
219 		e = p;
220 	}
221 	if ((p = memchr(s, '?', e - s))) {
222 		e = p;
223 	}
224 	if ((p = memchr(s, '#', e - s))) {
225 		e = p;
226 	}
227 
228 	/* check for login and password */
229 	if ((p = zend_memrchr(s, '@', (e-s)))) {
230 		if ((pp = memchr(s, ':', (p-s)))) {
231 			ret->user = estrndup(s, (pp-s));
232 			php_replace_controlchars_ex(ret->user, (pp - s));
233 
234 			pp++;
235 			ret->pass = estrndup(pp, (p-pp));
236 			php_replace_controlchars_ex(ret->pass, (p-pp));
237 		} else {
238 			ret->user = estrndup(s, (p-s));
239 			php_replace_controlchars_ex(ret->user, (p-s));
240 		}
241 
242 		s = p + 1;
243 	}
244 
245 	/* check for port */
246 	if (s < ue && *s == '[' && *(e-1) == ']') {
247 		/* Short circuit portscan,
248 		   we're dealing with an
249 		   IPv6 embedded address */
250 		p = NULL;
251 	} else {
252 		p = zend_memrchr(s, ':', (e-s));
253 	}
254 
255 	if (p) {
256 		if (!ret->port) {
257 			p++;
258 			if (e-p > 5) { /* port cannot be longer then 5 characters */
259 				if (ret->scheme) efree(ret->scheme);
260 				if (ret->user) efree(ret->user);
261 				if (ret->pass) efree(ret->pass);
262 				efree(ret);
263 				return NULL;
264 			} else if (e - p > 0) {
265 				zend_long port;
266 				memcpy(port_buf, p, (e - p));
267 				port_buf[e - p] = '\0';
268 				port = ZEND_STRTOL(port_buf, NULL, 10);
269 				if (port > 0 && port <= 65535) {
270 					ret->port = (unsigned short)port;
271 				} else {
272 					if (ret->scheme) efree(ret->scheme);
273 					if (ret->user) efree(ret->user);
274 					if (ret->pass) efree(ret->pass);
275 					efree(ret);
276 					return NULL;
277 				}
278 			}
279 			p--;
280 		}
281 	} else {
282 		p = e;
283 	}
284 
285 	/* check if we have a valid host, if we don't reject the string as url */
286 	if ((p-s) < 1) {
287 		if (ret->scheme) efree(ret->scheme);
288 		if (ret->user) efree(ret->user);
289 		if (ret->pass) efree(ret->pass);
290 		efree(ret);
291 		return NULL;
292 	}
293 
294 	ret->host = estrndup(s, (p-s));
295 	php_replace_controlchars_ex(ret->host, (p - s));
296 
297 	if (e == ue) {
298 		return ret;
299 	}
300 
301 	s = e;
302 
303 	just_path:
304 
305 	e = ue;
306 	p = memchr(s, '#', (e - s));
307 	if (p) {
308 		p++;
309 		if (p < e) {
310 			ret->fragment = estrndup(p, (e - p));
311 			php_replace_controlchars_ex(ret->fragment, (e - p));
312 		}
313 		e = p-1;
314 	}
315 
316 	p = memchr(s, '?', (e - s));
317 	if (p) {
318 		p++;
319 		if (p < e) {
320 			ret->query = estrndup(p, (e - p));
321 			php_replace_controlchars_ex(ret->query, (e - p));
322 		}
323 		e = p-1;
324 	}
325 
326 	if (s < e || s == ue) {
327 		ret->path = estrndup(s, (e - s));
328 		php_replace_controlchars_ex(ret->path, (e - s));
329 	}
330 
331 	return ret;
332 }
333 /* }}} */
334 
335 /* {{{ proto mixed parse_url(string url, [int url_component])
336    Parse a URL and return its components */
PHP_FUNCTION(parse_url)337 PHP_FUNCTION(parse_url)
338 {
339 	char *str;
340 	size_t str_len;
341 	php_url *resource;
342 	zend_long key = -1;
343 
344 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
345 		return;
346 	}
347 
348 	resource = php_url_parse_ex(str, str_len);
349 	if (resource == NULL) {
350 		/* @todo Find a method to determine why php_url_parse_ex() failed */
351 		RETURN_FALSE;
352 	}
353 
354 	if (key > -1) {
355 		switch (key) {
356 			case PHP_URL_SCHEME:
357 				if (resource->scheme != NULL) RETVAL_STRING(resource->scheme);
358 				break;
359 			case PHP_URL_HOST:
360 				if (resource->host != NULL) RETVAL_STRING(resource->host);
361 				break;
362 			case PHP_URL_PORT:
363 				if (resource->port != 0) RETVAL_LONG(resource->port);
364 				break;
365 			case PHP_URL_USER:
366 				if (resource->user != NULL) RETVAL_STRING(resource->user);
367 				break;
368 			case PHP_URL_PASS:
369 				if (resource->pass != NULL) RETVAL_STRING(resource->pass);
370 				break;
371 			case PHP_URL_PATH:
372 				if (resource->path != NULL) RETVAL_STRING(resource->path);
373 				break;
374 			case PHP_URL_QUERY:
375 				if (resource->query != NULL) RETVAL_STRING(resource->query);
376 				break;
377 			case PHP_URL_FRAGMENT:
378 				if (resource->fragment != NULL) RETVAL_STRING(resource->fragment);
379 				break;
380 			default:
381 				php_error_docref(NULL, E_WARNING, "Invalid URL component identifier " ZEND_LONG_FMT, key);
382 				RETVAL_FALSE;
383 		}
384 		goto done;
385 	}
386 
387 	/* allocate an array for return */
388 	array_init(return_value);
389 
390     /* add the various elements to the array */
391 	if (resource->scheme != NULL)
392 		add_assoc_string(return_value, "scheme", resource->scheme);
393 	if (resource->host != NULL)
394 		add_assoc_string(return_value, "host", resource->host);
395 	if (resource->port != 0)
396 		add_assoc_long(return_value, "port", resource->port);
397 	if (resource->user != NULL)
398 		add_assoc_string(return_value, "user", resource->user);
399 	if (resource->pass != NULL)
400 		add_assoc_string(return_value, "pass", resource->pass);
401 	if (resource->path != NULL)
402 		add_assoc_string(return_value, "path", resource->path);
403 	if (resource->query != NULL)
404 		add_assoc_string(return_value, "query", resource->query);
405 	if (resource->fragment != NULL)
406 		add_assoc_string(return_value, "fragment", resource->fragment);
407 done:
408 	php_url_free(resource);
409 }
410 /* }}} */
411 
412 /* {{{ php_htoi
413  */
php_htoi(char * s)414 static int php_htoi(char *s)
415 {
416 	int value;
417 	int c;
418 
419 	c = ((unsigned char *)s)[0];
420 	if (isupper(c))
421 		c = tolower(c);
422 	value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;
423 
424 	c = ((unsigned char *)s)[1];
425 	if (isupper(c))
426 		c = tolower(c);
427 	value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;
428 
429 	return (value);
430 }
431 /* }}} */
432 
433 /* rfc1738:
434 
435    ...The characters ";",
436    "/", "?", ":", "@", "=" and "&" are the characters which may be
437    reserved for special meaning within a scheme...
438 
439    ...Thus, only alphanumerics, the special characters "$-_.+!*'(),", and
440    reserved characters used for their reserved purposes may be used
441    unencoded within a URL...
442 
443    For added safety, we only leave -_. unencoded.
444  */
445 
446 static unsigned char hexchars[] = "0123456789ABCDEF";
447 
448 /* {{{ php_url_encode
449  */
php_url_encode(char const * s,size_t len)450 PHPAPI zend_string *php_url_encode(char const *s, size_t len)
451 {
452 	register unsigned char c;
453 	unsigned char *to;
454 	unsigned char const *from, *end;
455 	zend_string *start;
456 
457 	from = (unsigned char *)s;
458 	end = (unsigned char *)s + len;
459 	start = zend_string_safe_alloc(3, len, 0, 0);
460 	to = (unsigned char*)ZSTR_VAL(start);
461 
462 	while (from < end) {
463 		c = *from++;
464 
465 		if (c == ' ') {
466 			*to++ = '+';
467 #ifndef CHARSET_EBCDIC
468 		} else if ((c < '0' && c != '-' && c != '.') ||
469 				   (c < 'A' && c > '9') ||
470 				   (c > 'Z' && c < 'a' && c != '_') ||
471 				   (c > 'z')) {
472 			to[0] = '%';
473 			to[1] = hexchars[c >> 4];
474 			to[2] = hexchars[c & 15];
475 			to += 3;
476 #else /*CHARSET_EBCDIC*/
477 		} else if (!isalnum(c) && strchr("_-.", c) == NULL) {
478 			/* Allow only alphanumeric chars and '_', '-', '.'; escape the rest */
479 			to[0] = '%';
480 			to[1] = hexchars[os_toascii[c] >> 4];
481 			to[2] = hexchars[os_toascii[c] & 15];
482 			to += 3;
483 #endif /*CHARSET_EBCDIC*/
484 		} else {
485 			*to++ = c;
486 		}
487 	}
488 	*to = '\0';
489 
490 	start = zend_string_truncate(start, to - (unsigned char*)ZSTR_VAL(start), 0);
491 
492 	return start;
493 }
494 /* }}} */
495 
496 /* {{{ proto string urlencode(string str)
497    URL-encodes string */
PHP_FUNCTION(urlencode)498 PHP_FUNCTION(urlencode)
499 {
500 	zend_string *in_str;
501 
502 	ZEND_PARSE_PARAMETERS_START(1, 1)
503 		Z_PARAM_STR(in_str)
504 	ZEND_PARSE_PARAMETERS_END();
505 
506 	RETURN_STR(php_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str)));
507 }
508 /* }}} */
509 
510 /* {{{ proto string urldecode(string str)
511    Decodes URL-encoded string */
PHP_FUNCTION(urldecode)512 PHP_FUNCTION(urldecode)
513 {
514 	zend_string *in_str, *out_str;
515 
516 	ZEND_PARSE_PARAMETERS_START(1, 1)
517 		Z_PARAM_STR(in_str)
518 	ZEND_PARSE_PARAMETERS_END();
519 
520 	out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0);
521 	ZSTR_LEN(out_str) = php_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str));
522 
523     RETURN_NEW_STR(out_str);
524 }
525 /* }}} */
526 
527 /* {{{ php_url_decode
528  */
php_url_decode(char * str,size_t len)529 PHPAPI size_t php_url_decode(char *str, size_t len)
530 {
531 	char *dest = str;
532 	char *data = str;
533 
534 	while (len--) {
535 		if (*data == '+') {
536 			*dest = ' ';
537 		}
538 		else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1))
539 				 && isxdigit((int) *(data + 2))) {
540 #ifndef CHARSET_EBCDIC
541 			*dest = (char) php_htoi(data + 1);
542 #else
543 			*dest = os_toebcdic[(char) php_htoi(data + 1)];
544 #endif
545 			data += 2;
546 			len -= 2;
547 		} else {
548 			*dest = *data;
549 		}
550 		data++;
551 		dest++;
552 	}
553 	*dest = '\0';
554 	return dest - str;
555 }
556 /* }}} */
557 
558 /* {{{ php_raw_url_encode
559  */
php_raw_url_encode(char const * s,size_t len)560 PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len)
561 {
562 	register size_t x, y;
563 	zend_string *str;
564 
565 	str = zend_string_safe_alloc(3, len, 0, 0);
566 	for (x = 0, y = 0; len--; x++, y++) {
567 		ZSTR_VAL(str)[y] = (unsigned char) s[x];
568 #ifndef CHARSET_EBCDIC
569 		if ((ZSTR_VAL(str)[y] < '0' && ZSTR_VAL(str)[y] != '-' && ZSTR_VAL(str)[y] != '.') ||
570 			(ZSTR_VAL(str)[y] < 'A' && ZSTR_VAL(str)[y] > '9') ||
571 			(ZSTR_VAL(str)[y] > 'Z' && ZSTR_VAL(str)[y] < 'a' && ZSTR_VAL(str)[y] != '_') ||
572 			(ZSTR_VAL(str)[y] > 'z' && ZSTR_VAL(str)[y] != '~')) {
573 			ZSTR_VAL(str)[y++] = '%';
574 			ZSTR_VAL(str)[y++] = hexchars[(unsigned char) s[x] >> 4];
575 			ZSTR_VAL(str)[y] = hexchars[(unsigned char) s[x] & 15];
576 #else /*CHARSET_EBCDIC*/
577 		if (!isalnum(ZSTR_VAL(str)[y]) && strchr("_-.~", ZSTR_VAL(str)[y]) != NULL) {
578 			ZSTR_VAL(str)[y++] = '%';
579 			ZSTR_VAL(str)[y++] = hexchars[os_toascii[(unsigned char) s[x]] >> 4];
580 			ZSTR_VAL(str)[y] = hexchars[os_toascii[(unsigned char) s[x]] & 15];
581 #endif /*CHARSET_EBCDIC*/
582 		}
583 	}
584 	ZSTR_VAL(str)[y] = '\0';
585 	str = zend_string_truncate(str, y, 0);
586 
587 	return str;
588 }
589 /* }}} */
590 
591 /* {{{ proto string rawurlencode(string str)
592    URL-encodes string */
593 PHP_FUNCTION(rawurlencode)
594 {
595 	zend_string *in_str;
596 
597 	ZEND_PARSE_PARAMETERS_START(1, 1)
598 		Z_PARAM_STR(in_str)
599 	ZEND_PARSE_PARAMETERS_END();
600 
601 	RETURN_STR(php_raw_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str)));
602 }
603 /* }}} */
604 
605 /* {{{ proto string rawurldecode(string str)
606    Decodes URL-encodes string */
607 PHP_FUNCTION(rawurldecode)
608 {
609 	zend_string *in_str, *out_str;
610 
611 	ZEND_PARSE_PARAMETERS_START(1, 1)
612 		Z_PARAM_STR(in_str)
613 	ZEND_PARSE_PARAMETERS_END();
614 
615 	out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0);
616 	ZSTR_LEN(out_str) = php_raw_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str));
617 
618     RETURN_NEW_STR(out_str);
619 }
620 /* }}} */
621 
622 /* {{{ php_raw_url_decode
623  */
624 PHPAPI size_t php_raw_url_decode(char *str, size_t len)
625 {
626 	char *dest = str;
627 	char *data = str;
628 
629 	while (len--) {
630 		if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1))
631 			&& isxdigit((int) *(data + 2))) {
632 #ifndef CHARSET_EBCDIC
633 			*dest = (char) php_htoi(data + 1);
634 #else
635 			*dest = os_toebcdic[(char) php_htoi(data + 1)];
636 #endif
637 			data += 2;
638 			len -= 2;
639 		} else {
640 			*dest = *data;
641 		}
642 		data++;
643 		dest++;
644 	}
645 	*dest = '\0';
646 	return dest - str;
647 }
648 /* }}} */
649 
650 /* {{{ proto array get_headers(string url[, int format[, resource context]])
651    fetches all the headers sent by the server in response to a HTTP request */
652 PHP_FUNCTION(get_headers)
653 {
654 	char *url;
655 	size_t url_len;
656 	php_stream *stream;
657 	zval *prev_val, *hdr = NULL, *h;
658 	HashTable *hashT;
659 	zend_long format = 0;
660 	zval *zcontext = NULL;
661 	php_stream_context *context;
662 
663 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lr!", &url, &url_len, &format, &zcontext) == FAILURE) {
664 		return;
665 	}
666 
667 	context = php_stream_context_from_zval(zcontext, 0);
668 
669 	if (!(stream = php_stream_open_wrapper_ex(url, "r", REPORT_ERRORS | STREAM_USE_URL | STREAM_ONLY_GET_HEADERS, NULL, context))) {
670 		RETURN_FALSE;
671 	}
672 
673 	if (Z_TYPE(stream->wrapperdata) != IS_ARRAY) {
674 		php_stream_close(stream);
675 		RETURN_FALSE;
676 	}
677 
678 	array_init(return_value);
679 
680 	/* check for curl-wrappers that provide headers via a special "headers" element */
681 	if ((h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1)) != NULL && Z_TYPE_P(h) == IS_ARRAY) {
682 		/* curl-wrappers don't load data until the 1st read */
683 		if (!Z_ARRVAL_P(h)->nNumOfElements) {
684 			php_stream_getc(stream);
685 		}
686 		h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1);
687 		hashT = Z_ARRVAL_P(h);
688 	} else {
689 		hashT = HASH_OF(&stream->wrapperdata);
690 	}
691 
692 	ZEND_HASH_FOREACH_VAL(hashT, hdr) {
693 		if (Z_TYPE_P(hdr) != IS_STRING) {
694 			continue;
695 		}
696 		if (!format) {
697 no_name_header:
698 			add_next_index_str(return_value, zend_string_copy(Z_STR_P(hdr)));
699 		} else {
700 			char c;
701 			char *s, *p;
702 
703 			if ((p = strchr(Z_STRVAL_P(hdr), ':'))) {
704 				c = *p;
705 				*p = '\0';
706 				s = p + 1;
707 				while (isspace((int)*(unsigned char *)s)) {
708 					s++;
709 				}
710 
711 				if ((prev_val = zend_hash_str_find(Z_ARRVAL_P(return_value), Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)))) == NULL) {
712 					add_assoc_stringl_ex(return_value, Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)), s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr))));
713 				} else { /* some headers may occur more than once, therefor we need to remake the string into an array */
714 					convert_to_array(prev_val);
715 					add_next_index_stringl(prev_val, s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr))));
716 				}
717 
718 				*p = c;
719 			} else {
720 				goto no_name_header;
721 			}
722 		}
723 	} ZEND_HASH_FOREACH_END();
724 
725 	php_stream_close(stream);
726 }
727 /* }}} */
728 
729 /*
730  * Local variables:
731  * tab-width: 4
732  * c-basic-offset: 4
733  * End:
734  * vim600: sw=4 ts=4 fdm=marker
735  * vim<600: sw=4 ts=4
736  */
737