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