flush
[m6w6/ext-http] / src / php_http_url.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #if PHP_HTTP_HAVE_LIBIDN2
16 # include <idn2.h>
17 #endif
18 #if PHP_HTTP_HAVE_LIBIDN
19 # include <idna.h>
20 #endif
21 #if PHP_HTTP_HAVE_LIBICU
22 # include <unicode/uidna.h>
23 #endif
24
25 #ifdef PHP_HTTP_HAVE_WCHAR
26 # include <wchar.h>
27 # include <wctype.h>
28 #endif
29
30 #ifdef HAVE_ARPA_INET_H
31 # include <arpa/inet.h>
32 #endif
33
34 #include "php_http_utf8.h"
35
36 static inline char *localhostname(void)
37 {
38 char hostname[1024] = {0};
39
40 #ifdef PHP_WIN32
41 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
42 return estrdup(hostname);
43 }
44 #elif defined(HAVE_GETHOSTNAME)
45 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
46 # if defined(HAVE_GETDOMAINNAME)
47 size_t hlen = strlen(hostname);
48 if (hlen <= lenof(hostname) - lenof("(none)")) {
49 hostname[hlen++] = '.';
50 if (SUCCESS == getdomainname(&hostname[hlen], lenof(hostname) - hlen)) {
51 if (!strcmp(&hostname[hlen], "(none)")) {
52 hostname[hlen - 1] = '\0';
53 }
54 return estrdup(hostname);
55 }
56 }
57 # endif
58 if (strcmp(hostname, "(none)")) {
59 return estrdup(hostname);
60 }
61 }
62 #endif
63 return estrndup("localhost", lenof("localhost"));
64 }
65
66 #define url(buf) ((php_http_url_t *) (buf).data)
67
68 static php_http_url_t *php_http_url_from_env(void)
69 {
70 zval *https, *zhost, *zport;
71 long port;
72 php_http_buffer_t buf;
73
74 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
75 php_http_buffer_account(&buf, sizeof(php_http_url_t));
76 memset(buf.data, 0, buf.used);
77
78 /* scheme */
79 url(buf)->scheme = &buf.data[buf.used];
80 https = php_http_env_get_server_var(ZEND_STRL("HTTPS"), 1);
81 if (https && !strcasecmp(Z_STRVAL_P(https), "ON")) {
82 php_http_buffer_append(&buf, "https", sizeof("https"));
83 } else {
84 php_http_buffer_append(&buf, "http", sizeof("http"));
85 }
86
87 /* host */
88 url(buf)->host = &buf.data[buf.used];
89 if ((((zhost = php_http_env_get_server_var(ZEND_STRL("HTTP_HOST"), 1)) ||
90 (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_NAME"), 1)) ||
91 (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_ADDR"), 1)))) && Z_STRLEN_P(zhost)) {
92 size_t stop_at = strspn(Z_STRVAL_P(zhost), "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.");
93
94 php_http_buffer_append(&buf, Z_STRVAL_P(zhost), stop_at);
95 php_http_buffer_append(&buf, "", 1);
96 } else {
97 char *host_str = localhostname();
98
99 php_http_buffer_append(&buf, host_str, strlen(host_str) + 1);
100 efree(host_str);
101 }
102
103 /* port */
104 zport = php_http_env_get_server_var(ZEND_STRL("SERVER_PORT"), 1);
105 if (zport && IS_LONG == is_numeric_string(Z_STRVAL_P(zport), Z_STRLEN_P(zport), &port, NULL, 0)) {
106 url(buf)->port = port;
107 }
108
109 /* path */
110 if (SG(request_info).request_uri && SG(request_info).request_uri[0]) {
111 const char *q = strchr(SG(request_info).request_uri, '?');
112
113 url(buf)->path = &buf.data[buf.used];
114
115 if (q) {
116 php_http_buffer_append(&buf, SG(request_info).request_uri, q - SG(request_info).request_uri);
117 php_http_buffer_append(&buf, "", 1);
118 } else {
119 php_http_buffer_append(&buf, SG(request_info).request_uri, strlen(SG(request_info).request_uri) + 1);
120 }
121 }
122
123 /* query */
124 if (SG(request_info).query_string && SG(request_info).query_string[0]) {
125 url(buf)->query = &buf.data[buf.used];
126 php_http_buffer_append(&buf, SG(request_info).query_string, strlen(SG(request_info).query_string) + 1);
127 }
128
129 return url(buf);
130 }
131
132 #define url_isset(u,n) \
133 ((u)&&(u)->n)
134 #define url_append(buf, append) do { \
135 char *_ptr = (buf)->data; \
136 php_http_url_t *_url = (php_http_url_t *) _ptr, _mem = *_url; \
137 append; \
138 /* relocate */ \
139 if (_ptr != (buf)->data) { \
140 ptrdiff_t diff = (buf)->data - _ptr; \
141 _url = (php_http_url_t *) (buf)->data; \
142 if (_mem.scheme) _url->scheme += diff; \
143 if (_mem.user) _url->user += diff; \
144 if (_mem.pass) _url->pass += diff; \
145 if (_mem.host) _url->host += diff; \
146 if (_mem.path) _url->path += diff; \
147 if (_mem.query) _url->query += diff; \
148 if (_mem.fragment) _url->fragment += diff; \
149 } \
150 } while (0)
151 #define url_copy(n) do { \
152 if (url_isset(new_url, n)) { \
153 url(buf)->n = &buf.data[buf.used]; \
154 url_append(&buf, php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1)); \
155 } else if (url_isset(old_url, n)) { \
156 url(buf)->n = &buf.data[buf.used]; \
157 url_append(&buf, php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1)); \
158 } \
159 } while (0)
160
161 php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_url_t *new_url, unsigned flags)
162 {
163 php_http_url_t *tmp_url = NULL;
164 php_http_buffer_t buf;
165
166 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
167 php_http_buffer_account(&buf, sizeof(php_http_url_t));
168 memset(buf.data, 0, buf.used);
169
170 /* set from env if requested */
171 if (flags & PHP_HTTP_URL_FROM_ENV) {
172 php_http_url_t *env_url = php_http_url_from_env();
173
174 old_url = tmp_url = php_http_url_mod(env_url, old_url, flags ^ PHP_HTTP_URL_FROM_ENV);
175 php_http_url_free(&env_url);
176 }
177
178 url_copy(scheme);
179
180 if (!(flags & PHP_HTTP_URL_STRIP_USER)) {
181 url_copy(user);
182 }
183
184 if (!(flags & PHP_HTTP_URL_STRIP_PASS)) {
185 url_copy(pass);
186 }
187
188 url_copy(host);
189
190 if (!(flags & PHP_HTTP_URL_STRIP_PORT)) {
191 url(buf)->port = url_isset(new_url, port) ? new_url->port : ((old_url) ? old_url->port : 0);
192 }
193
194 if (!(flags & PHP_HTTP_URL_STRIP_PATH)) {
195 if ((flags & PHP_HTTP_URL_JOIN_PATH) && url_isset(old_url, path) && url_isset(new_url, path) && *new_url->path != '/') {
196 size_t old_path_len = strlen(old_url->path), new_path_len = strlen(new_url->path);
197 char *path = ecalloc(1, old_path_len + new_path_len + 1 + 1);
198
199 strcat(path, old_url->path);
200 if (path[old_path_len - 1] != '/') {
201 php_dirname(path, old_path_len);
202 strcat(path, "/");
203 }
204 strcat(path, new_url->path);
205
206 url(buf)->path = &buf.data[buf.used];
207 if (path[0] != '/') {
208 url_append(&buf, php_http_buffer_append(&buf, "/", 1));
209 }
210 url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1));
211 efree(path);
212 } else {
213 const char *path = NULL;
214
215 if (url_isset(new_url, path)) {
216 path = new_url->path;
217 } else if (url_isset(old_url, path)) {
218 path = old_url->path;
219 }
220
221 if (path) {
222 url(buf)->path = &buf.data[buf.used];
223
224 url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1));
225 }
226
227
228 }
229 }
230
231 if (!(flags & PHP_HTTP_URL_STRIP_QUERY)) {
232 if ((flags & PHP_HTTP_URL_JOIN_QUERY) && url_isset(new_url, query) && url_isset(old_url, query)) {
233 zval qarr, qstr;
234
235 array_init(&qarr);
236
237 ZVAL_STRING(&qstr, old_url->query);
238 php_http_querystring_update(&qarr, &qstr, NULL);
239 zval_ptr_dtor(&qstr);
240 ZVAL_STRING(&qstr, new_url->query);
241 php_http_querystring_update(&qarr, &qstr, NULL);
242 zval_ptr_dtor(&qstr);
243
244 ZVAL_NULL(&qstr);
245 php_http_querystring_update(&qarr, NULL, &qstr);
246
247 url(buf)->query = &buf.data[buf.used];
248 url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1));
249
250 zval_dtor(&qstr);
251 zval_dtor(&qarr);
252 } else {
253 url_copy(query);
254 }
255 }
256
257 if (!(flags & PHP_HTTP_URL_STRIP_FRAGMENT)) {
258 url_copy(fragment);
259 }
260
261 /* done with copy & combine & strip */
262
263 if (flags & PHP_HTTP_URL_FROM_ENV) {
264 /* free old_url we tainted above */
265 php_http_url_free(&tmp_url);
266 }
267
268 /* replace directory references if path is not a single slash */
269 if ((flags & PHP_HTTP_URL_SANITIZE_PATH)
270 && url(buf)->path
271 && url(buf)->path[0] && url(buf)->path[1]) {
272 char *ptr, *end = url(buf)->path + strlen(url(buf)->path) + 1;
273
274 for (ptr = strchr(url(buf)->path, '/'); ptr; ptr = strchr(ptr, '/')) {
275 switch (ptr[1]) {
276 case '/':
277 memmove(&ptr[1], &ptr[2], end - &ptr[2]);
278 break;
279
280 case '.':
281 switch (ptr[2]) {
282 case '\0':
283 ptr[1] = '\0';
284 break;
285
286 case '/':
287 memmove(&ptr[1], &ptr[3], end - &ptr[3]);
288 break;
289
290 case '.':
291 if (ptr[3] == '/') {
292 char *pos = &ptr[4];
293 while (ptr != url(buf)->path) {
294 if (*--ptr == '/') {
295 break;
296 }
297 }
298 memmove(&ptr[1], pos, end - pos);
299 break;
300 } else if (!ptr[3]) {
301 /* .. at the end */
302 ptr[1] = '\0';
303 }
304 /* no break */
305
306 default:
307 /* something else */
308 ++ptr;
309 break;
310 }
311 break;
312
313 default:
314 ++ptr;
315 break;
316 }
317 }
318 }
319 /* unset default ports */
320 if (url(buf)->port) {
321 if ( ((url(buf)->port == 80) && url(buf)->scheme && !strcmp(url(buf)->scheme, "http"))
322 || ((url(buf)->port ==443) && url(buf)->scheme && !strcmp(url(buf)->scheme, "https"))
323 ) {
324 url(buf)->port = 0;
325 }
326 }
327
328 return url(buf);
329 }
330
331 char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent)
332 {
333 php_http_buffer_t buf;
334
335 php_http_buffer_init_ex(&buf, PHP_HTTP_BUFFER_DEFAULT_SIZE, persistent ?
336 PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
337
338 if (url->scheme && *url->scheme) {
339 php_http_buffer_appendl(&buf, url->scheme);
340 php_http_buffer_appends(&buf, "://");
341 } else if ((url->user && *url->user) || (url->host && *url->host)) {
342 php_http_buffer_appends(&buf, "//");
343 }
344
345 if (url->user && *url->user) {
346 php_http_buffer_appendl(&buf, url->user);
347 if (url->pass && *url->pass) {
348 php_http_buffer_appends(&buf, ":");
349 php_http_buffer_appendl(&buf, url->pass);
350 }
351 php_http_buffer_appends(&buf, "@");
352 }
353
354 if (url->host && *url->host) {
355 php_http_buffer_appendl(&buf, url->host);
356 if (url->port) {
357 php_http_buffer_appendf(&buf, ":%hu", url->port);
358 }
359 }
360
361 if (url->path && *url->path) {
362 if (*url->path != '/') {
363 php_http_buffer_appends(&buf, "/");
364 }
365 php_http_buffer_appendl(&buf, url->path);
366 } else if (buf.used) {
367 php_http_buffer_appends(&buf, "/");
368 }
369
370 if (url->query && *url->query) {
371 php_http_buffer_appends(&buf, "?");
372 php_http_buffer_appendl(&buf, url->query);
373 }
374
375 if (url->fragment && *url->fragment) {
376 php_http_buffer_appends(&buf, "#");
377 php_http_buffer_appendl(&buf, url->fragment);
378 }
379
380 php_http_buffer_shrink(&buf);
381 php_http_buffer_fix(&buf);
382
383 if (url_len) {
384 *url_len = buf.used;
385 }
386
387 if (url_str) {
388 *url_str = buf.data;
389 }
390
391 return buf.data;
392 }
393
394 char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len)
395 {
396 php_http_buffer_t buf;
397
398 php_http_buffer_init(&buf);
399
400 if (url->user && *url->user) {
401 php_http_buffer_appendl(&buf, url->user);
402 if (url->pass && *url->pass) {
403 php_http_buffer_appends(&buf, ":");
404 php_http_buffer_appendl(&buf, url->pass);
405 }
406 php_http_buffer_appends(&buf, "@");
407 }
408
409 if (url->host && *url->host) {
410 php_http_buffer_appendl(&buf, url->host);
411 if (url->port) {
412 php_http_buffer_appendf(&buf, ":%hu", url->port);
413 }
414 }
415
416 php_http_buffer_shrink(&buf);
417 php_http_buffer_fix(&buf);
418
419 if (url_len) {
420 *url_len = buf.used;
421 }
422
423 if (url_str) {
424 *url_str = buf.data;
425 }
426
427 return buf.data;
428 }
429
430 php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags)
431 {
432 zend_string *zs;
433 php_http_url_t *purl;
434
435 switch (Z_TYPE_P(value)) {
436 case IS_ARRAY:
437 case IS_OBJECT:
438 purl = php_http_url_from_struct(HASH_OF(value));
439 break;
440
441 default:
442 zs = zval_get_string(value);
443 purl = php_http_url_parse(zs->val, zs->len, flags);
444 zend_string_release(zs);
445 }
446
447 return purl;
448 }
449
450 php_http_url_t *php_http_url_from_struct(HashTable *ht)
451 {
452 zval *e;
453 php_http_buffer_t buf;
454
455 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
456 php_http_buffer_account(&buf, sizeof(php_http_url_t));
457 memset(buf.data, 0, buf.used);
458
459 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("scheme")))) {
460 zend_string *zs = zval_get_string(e);
461 url(buf)->scheme = &buf.data[buf.used];
462 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
463 zend_string_release(zs);
464 }
465 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("user")))) {
466 zend_string *zs = zval_get_string(e);
467 url(buf)->user = &buf.data[buf.used];
468 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
469 zend_string_release(zs);
470 }
471 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("pass")))) {
472 zend_string *zs = zval_get_string(e);
473 url(buf)->pass = &buf.data[buf.used];
474 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
475 zend_string_release(zs);
476 }
477 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("host")))) {
478 zend_string *zs = zval_get_string(e);
479 url(buf)->host = &buf.data[buf.used];
480 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
481 zend_string_release(zs);
482 }
483 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("port")))) {
484 url(buf)->port = (unsigned short) zval_get_long(e);
485 }
486 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("path")))) {
487 zend_string *zs = zval_get_string(e);
488 url(buf)->path = &buf.data[buf.used];
489 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
490 zend_string_release(zs);
491 }
492 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("query")))) {
493 zend_string *zs = zval_get_string(e);
494 url(buf)->query = &buf.data[buf.used];
495 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
496 zend_string_release(zs);
497 }
498 if ((e = zend_hash_str_find_ind(ht, ZEND_STRL("fragment")))) {
499 zend_string *zs = zval_get_string(e);
500 url(buf)->fragment = &buf.data[buf.used];
501 url_append(&buf, php_http_buffer_append(&buf, zs->val, zs->len + 1));
502 zend_string_release(zs);
503 }
504
505 return url(buf);
506 }
507
508 HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct)
509 {
510 HashTable *ht = NULL;
511 zval tmp;
512
513 if (strct) {
514 switch (Z_TYPE_P(strct)) {
515 default:
516 zval_dtor(strct);
517 array_init(strct);
518 /* no break */
519 case IS_ARRAY:
520 case IS_OBJECT:
521 ht = HASH_OF(strct);
522 break;
523 }
524 } else {
525 ALLOC_HASHTABLE(ht);
526 zend_hash_init(ht, 8, NULL, ZVAL_PTR_DTOR, 0);
527 }
528
529 #define url_struct_add(part) \
530 if (!strct || Z_TYPE_P(strct) == IS_ARRAY) { \
531 zend_hash_str_update(ht, part, lenof(part), &tmp); \
532 } else { \
533 zend_update_property(Z_OBJCE_P(strct), strct, part, lenof(part), &tmp); \
534 zval_ptr_dtor(&tmp); \
535 }
536
537 if (url) {
538 if (url->scheme) {
539 ZVAL_STRING(&tmp, url->scheme);
540 url_struct_add("scheme");
541 }
542 if (url->user) {
543 ZVAL_STRING(&tmp, url->user);
544 url_struct_add("user");
545 }
546 if (url->pass) {
547 ZVAL_STRING(&tmp, url->pass);
548 url_struct_add("pass");
549 }
550 if (url->host) {
551 ZVAL_STRING(&tmp, url->host);
552 url_struct_add("host");
553 }
554 if (url->port) {
555 ZVAL_LONG(&tmp, url->port);
556 url_struct_add("port");
557 }
558 if (url->path) {
559 ZVAL_STRING(&tmp, url->path);
560 url_struct_add("path");
561 }
562 if (url->query) {
563 ZVAL_STRING(&tmp, url->query);
564 url_struct_add("query");
565 }
566 if (url->fragment) {
567 ZVAL_STRING(&tmp, url->fragment);
568 url_struct_add("fragment");
569 }
570 }
571
572 return ht;
573 }
574
575 ZEND_RESULT_CODE php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len)
576 {
577 const char *arg_sep_str = "&";
578 size_t arg_sep_len = 1;
579 php_http_buffer_t *qstr = php_http_buffer_new();
580
581 php_http_url_argsep(&arg_sep_str, &arg_sep_len);
582
583 if (SUCCESS != php_http_url_encode_hash_ex(hash, qstr, arg_sep_str, arg_sep_len, "=", 1, pre_encoded_str, pre_encoded_len)) {
584 php_http_buffer_free(&qstr);
585 return FAILURE;
586 }
587
588 php_http_buffer_data(qstr, encoded_str, encoded_len);
589 php_http_buffer_free(&qstr);
590
591 return SUCCESS;
592 }
593
594 ZEND_RESULT_CODE php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len)
595 {
596 if (pre_encoded_len && pre_encoded_str) {
597 php_http_buffer_append(qstr, pre_encoded_str, pre_encoded_len);
598 }
599
600 if (!php_http_params_to_string(qstr, hash, arg_sep_str, arg_sep_len, "", 0, val_sep_str, val_sep_len, PHP_HTTP_PARAMS_QUERY)) {
601 return FAILURE;
602 }
603
604 return SUCCESS;
605 }
606
607 struct parse_state {
608 php_http_url_t url;
609 const char *ptr;
610 const char *end;
611 size_t maxlen;
612 off_t offset;
613 unsigned flags;
614 char buffer[1]; /* last member */
615 };
616
617 void php_http_url_free(php_http_url_t **url)
618 {
619 if (*url) {
620 efree(*url);
621 *url = NULL;
622 }
623 }
624
625 php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent)
626 {
627 php_http_url_t *cpy;
628 const char *end = NULL, *url_ptr = (const char *) url;
629 char *cpy_ptr;
630
631 end = MAX(url->scheme, end);
632 end = MAX(url->pass, end);
633 end = MAX(url->user, end);
634 end = MAX(url->host, end);
635 end = MAX(url->path, end);
636 end = MAX(url->query, end);
637 end = MAX(url->fragment, end);
638
639 if (end) {
640 end += strlen(end) + 1;
641 cpy_ptr = pecalloc(1, end - url_ptr, persistent);
642 cpy = (php_http_url_t *) cpy_ptr;
643
644 memcpy(cpy_ptr + sizeof(*cpy), url_ptr + sizeof(*url), end - url_ptr - sizeof(*url));
645
646 cpy->scheme = url->scheme ? cpy_ptr + (url->scheme - url_ptr) : NULL;
647 cpy->pass = url->pass ? cpy_ptr + (url->pass - url_ptr) : NULL;
648 cpy->user = url->user ? cpy_ptr + (url->user - url_ptr) : NULL;
649 cpy->host = url->host ? cpy_ptr + (url->host - url_ptr) : NULL;
650 cpy->path = url->path ? cpy_ptr + (url->path - url_ptr) : NULL;
651 cpy->query = url->query ? cpy_ptr + (url->query - url_ptr) : NULL;
652 cpy->fragment = url->fragment ? cpy_ptr + (url->fragment - url_ptr) : NULL;
653 } else {
654 cpy = ecalloc(1, sizeof(*url));
655 }
656
657 cpy->port = url->port;
658
659 return cpy;
660 }
661
662 static size_t parse_mb_utf8(unsigned *wc, const char *ptr, const char *end)
663 {
664 unsigned wchar;
665 size_t consumed = utf8towc(&wchar, (const unsigned char *) ptr, end - ptr);
666
667 if (!consumed || consumed == (size_t) -1) {
668 return 0;
669 }
670
671 if (wc) {
672 *wc = wchar;
673 }
674 return consumed;
675 }
676
677 #ifdef PHP_HTTP_HAVE_WCHAR
678 static size_t parse_mb_loc(unsigned *wc, const char *ptr, const char *end)
679 {
680 wchar_t wchar;
681 size_t consumed = 0;
682 #if defined(HAVE_MBRTOWC)
683 mbstate_t ps;
684
685 memset(&ps, 0, sizeof(ps));
686 consumed = mbrtowc(&wchar, ptr, end - ptr, &ps);
687 #elif defined(HAVE_MBTOWC)
688 consumed = mbtowc(&wchar, ptr, end - ptr);
689 #endif
690
691 if (!consumed || consumed == (size_t) -1) {
692 return 0;
693 }
694
695 if (wc) {
696 *wc = wchar;
697 }
698 return consumed;
699 }
700 #endif
701
702 typedef enum parse_mb_what {
703 PARSE_SCHEME,
704 PARSE_USERINFO,
705 PARSE_HOSTINFO,
706 PARSE_PATH,
707 PARSE_QUERY,
708 PARSE_FRAGMENT
709 } parse_mb_what_t;
710
711 static const char * const parse_what[] = {
712 "scheme",
713 "userinfo",
714 "hostinfo",
715 "path",
716 "query",
717 "fragment"
718 };
719
720 static const char parse_xdigits[] = "0123456789ABCDEF";
721
722 static size_t parse_mb(struct parse_state *state, parse_mb_what_t what, const char *ptr, const char *end, const char *begin, zend_bool silent)
723 {
724 unsigned wchar;
725 size_t consumed = 0;
726
727 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
728 consumed = parse_mb_utf8(&wchar, ptr, end);
729 }
730 #ifdef PHP_HTTP_HAVE_WCHAR
731 else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
732 consumed = parse_mb_loc(&wchar, ptr, end);
733 }
734 #endif
735
736 while (consumed) {
737 if (!(state->flags & PHP_HTTP_URL_PARSE_TOPCT) || what == PARSE_HOSTINFO || what == PARSE_SCHEME) {
738 if (what == PARSE_HOSTINFO && (state->flags & PHP_HTTP_URL_PARSE_TOIDN)) {
739 /* idna */
740 } else if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
741 if (!isualnum(wchar)) {
742 break;
743 }
744 #ifdef PHP_HTTP_HAVE_WCHAR
745 } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
746 if (!iswalnum(wchar)) {
747 break;
748 }
749 #endif
750 }
751 PHP_HTTP_DUFF(consumed, state->buffer[state->offset++] = *ptr++);
752 } else {
753 int i = 0;
754
755 PHP_HTTP_DUFF(consumed,
756 state->buffer[state->offset++] = '%';
757 state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) >> 4];
758 state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) & 0xf];
759 ++i;
760 );
761 }
762
763 return consumed;
764 }
765
766 if (!silent) {
767 if (consumed) {
768 php_error_docref(NULL, E_WARNING,
769 "Failed to parse %s; unexpected multibyte sequence 0x%x at pos %u in '%s'",
770 parse_what[what], wchar, (unsigned) (ptr - begin), begin);
771 } else {
772 php_error_docref(NULL, E_WARNING,
773 "Failed to parse %s; unexpected byte 0x%02x at pos %u in '%s'",
774 parse_what[what], (unsigned char) *ptr, (unsigned) (ptr - begin), begin);
775 }
776 }
777
778 return 0;
779 }
780
781 static ZEND_RESULT_CODE parse_userinfo(struct parse_state *state, const char *ptr)
782 {
783 size_t mb;
784 const char *password = NULL, *end = state->ptr, *tmp = ptr;
785
786 state->url.user = &state->buffer[state->offset];
787
788 do {
789 switch (*ptr) {
790 case ':':
791 if (password) {
792 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
793 php_error_docref(NULL, E_WARNING,
794 "Failed to parse password; duplicate ':' at pos %u in '%s'",
795 (unsigned) (ptr - tmp), tmp);
796 }
797 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
798 return FAILURE;
799 }
800 state->buffer[state->offset++] = *ptr;
801 break;
802 }
803 password = ptr + 1;
804 state->buffer[state->offset++] = 0;
805 state->url.pass = &state->buffer[state->offset];
806 break;
807
808 case '%':
809 if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
810 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
811 php_error_docref(NULL, E_WARNING,
812 "Failed to parse userinfo; invalid percent encoding at pos %u in '%s'",
813 (unsigned) (ptr - tmp), tmp);
814 }
815 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
816 return FAILURE;
817 }
818 state->buffer[state->offset++] = *ptr++;
819 break;
820 }
821 state->buffer[state->offset++] = *ptr++;
822 state->buffer[state->offset++] = *ptr++;
823 state->buffer[state->offset++] = *ptr;
824 break;
825
826 default:
827 if ((mb = parse_mb(state, PARSE_USERINFO, ptr, end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
828 ptr += mb - 1;
829 break;
830 }
831 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
832 return FAILURE;
833 }
834 /* no break */
835 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
836 case '+': case ',': case ';': case '=': /* sub-delims */
837 case '-': case '.': case '_': case '~': /* unreserved */
838 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
839 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
840 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
841 case 'V': case 'W': case 'X': case 'Y': case 'Z':
842 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
843 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
844 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
845 case 'v': case 'w': case 'x': case 'y': case 'z':
846 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
847 case '7': case '8': case '9':
848 /* allowed */
849 state->buffer[state->offset++] = *ptr;
850 break;
851
852 }
853 } while(++ptr != end);
854
855
856 state->buffer[state->offset++] = 0;
857
858 return SUCCESS;
859 }
860
861 #if defined(PHP_WIN32) || defined(HAVE_UIDNA_IDNTOASCII)
862 typedef size_t (*parse_mb_func)(unsigned *wc, const char *ptr, const char *end);
863 static ZEND_RESULT_CODE to_utf16(parse_mb_func fn, const char *u8, uint16_t **u16, size_t *len)
864 {
865 size_t offset = 0, u8_len = strlen(u8);
866
867 *u16 = ecalloc(4 * sizeof(uint16_t), u8_len + 1);
868 *len = 0;
869
870 while (offset < u8_len) {
871 unsigned wc;
872 uint16_t buf[2], *ptr = buf;
873 size_t consumed = fn(&wc, &u8[offset], &u8[u8_len]);
874
875 if (!consumed) {
876 efree(*u16);
877 php_error_docref(NULL, E_WARNING, "Failed to parse UTF-8 at pos %zu of '%s'", offset, u8);
878 return FAILURE;
879 } else {
880 offset += consumed;
881 }
882
883 switch (wctoutf16(buf, wc)) {
884 case 2:
885 (*u16)[(*len)++] = *ptr++;
886 /* no break */
887 case 1:
888 (*u16)[(*len)++] = *ptr++;
889 break;
890 case 0:
891 default:
892 efree(*u16);
893 php_error_docref(NULL, E_WARNING, "Failed to convert UTF-32 'U+%X' to UTF-16", wc);
894 return FAILURE;
895 }
896 }
897
898 return SUCCESS;
899 }
900 #endif
901
902 #ifndef MAXHOSTNAMELEN
903 # define MAXHOSTNAMELEN 256
904 #endif
905
906 #if PHP_HTTP_HAVE_LIBIDN2
907 static ZEND_RESULT_CODE parse_gidn_2008(struct parse_state *state, size_t prev_len)
908 {
909 char *idn = NULL;
910 int rv = -1;
911
912 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
913 rv = idn2_lookup_u8((const unsigned char *) state->url.host, (unsigned char **) &idn, IDN2_NFC_INPUT);
914 }
915 # ifdef PHP_HTTP_HAVE_WCHAR
916 else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
917 rv = idn2_lookup_ul(state->url.host, &idn, 0);
918 }
919 # endif
920 if (rv != IDN2_OK) {
921 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
922 php_error_docref(NULL, E_WARNING, "Failed to parse IDN (IDNA2008); %s", idn2_strerror(rv));
923 }
924 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
925 return FAILURE;
926 }
927 } else {
928 size_t idnlen = strlen(idn);
929 memcpy(state->url.host, idn, idnlen + 1);
930 free(idn);
931 state->offset += idnlen - prev_len;
932 }
933 return SUCCESS;
934 }
935 #endif
936
937 #if PHP_HTTP_HAVE_LIBIDN
938 static ZEND_RESULT_CODE parse_gidn_2003(struct parse_state *state, size_t prev_len)
939 {
940 char *idn = NULL;
941 int rv = -1;
942
943 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
944 rv = idna_to_ascii_8z(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES);
945 }
946 # ifdef PHP_HTTP_HAVE_WCHAR
947 else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
948 rv = idna_to_ascii_lz(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES);
949 }
950 # endif
951 if (rv != IDNA_SUCCESS) {
952 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
953 php_error_docref(NULL, E_WARNING, "Failed to parse IDN (IDNA2003); %s", idna_strerror(rv));
954 }
955 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
956 return FAILURE;
957 }
958 } else {
959 size_t idnlen = strlen(idn);
960 memcpy(state->url.host, idn, idnlen + 1);
961 free(idn);
962 state->offset += idnlen - prev_len;
963 }
964 return SUCCESS;
965 }
966 #endif
967
968 #ifdef HAVE_UIDNA_IDNTOASCII
969 # if PHP_HTTP_HAVE_LIBICU
970 # include <unicode/uidna.h>
971 # else
972 typedef uint16_t UChar;
973 typedef enum { U_ZERO_ERROR = 0 } UErrorCode;
974 int32_t uidna_IDNToASCII(const UChar *src, int32_t srcLength, UChar *dest, int32_t destCapacity, int32_t options, void *parseError, UErrorCode *status);
975 # endif
976 static ZEND_RESULT_CODE parse_uidn_2003(struct parse_state *state)
977 {
978 char *host_ptr = state->url.host, ebuf[64] = {0}, *error = NULL;
979 uint16_t *uhost_str, ahost_str[MAXHOSTNAMELEN], *ahost_ptr;
980 size_t uhost_len, ahost_len;
981 UErrorCode rc = U_ZERO_ERROR;
982
983 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
984 if (SUCCESS != to_utf16(parse_mb_utf8, state->url.host, &uhost_str, &uhost_len)) {
985 error = "failed to convert to UTF-16";
986 goto error;
987 }
988 #ifdef PHP_HTTP_HAVE_WCHAR
989 } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
990 if (SUCCESS != to_utf16(parse_mb_loc, state->url.host, &uhost_str, &uhost_len)) {
991 error = "failed to convert to UTF-16";
992 goto error;
993 }
994 #endif
995 } else {
996 error = "codepage not specified";
997 goto error;
998 }
999
1000 ahost_len = uidna_IDNToASCII(uhost_str, uhost_len, ahost_str, MAXHOSTNAMELEN, 3, NULL, &rc);
1001 efree(uhost_str);
1002
1003 if (error > U_ZERO_ERROR) {
1004 goto error;
1005 }
1006
1007 ahost_ptr = ahost_str;
1008 PHP_HTTP_DUFF(ahost_len, *host_ptr++ = *ahost_ptr++);
1009 *host_ptr = '\0';
1010 state->offset += host_ptr - state->url.host;
1011
1012 return SUCCESS;
1013
1014 error:
1015 if (!error) {
1016 slprintf(ebuf, sizeof(ebuf)-1, "errorcode: %d", rc);
1017 error = ebuf;
1018 }
1019 php_error_docref(NULL, E_WARNING, "Failed to parse IDN (ICU IDNA2003); %s", error);
1020
1021 return FAILURE;
1022 }
1023 #endif
1024
1025 #ifdef HAVE_UIDNA_IDNTOASCII
1026 # if PHP_HTTP_HAVE_LIBICU
1027 # include <unicode/uidna.h>
1028 # endif
1029 static ZEND_RESULT_CODE parse_uidn_2008(struct parse_state *state)
1030 {
1031 char *host_ptr, *error = NULL, ebuf[64] = {0};
1032 UErrorCode rc = U_ZERO_ERROR;
1033 UIDNAInfo info = UIDNA_INFO_INITIALIZER;
1034 UIDNA *uidna = uidna_openUTS46(UIDNA_ALLOW_UNASSIGNED|UIDNA_USE_STD3_RULES, &rc);
1035
1036 if (!uidna || U_FAILURE(rc)) {
1037 return FAILURE;
1038 }
1039
1040 host_ptr = state->url.host;
1041
1042 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
1043 char ahost_str[MAXHOSTNAMELEN], *ahost_ptr = &ahost_str[0];
1044 size_t ahost_len = uidna_nameToASCII_UTF8(uidna, host_ptr, -1, ahost_str, sizeof(ahost_str)-1, &info, &rc);
1045
1046 if (U_FAILURE(rc) || info.errors) {
1047 goto error;
1048 }
1049 PHP_HTTP_DUFF(ahost_len, *host_ptr++ = *ahost_ptr++);
1050 #ifdef PHP_HTTP_HAVE_WCHAR
1051 } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
1052 uint16_t *uhost_str, whost_str[MAXHOSTNAMELEN], *whost_ptr = &whost_str[0];
1053 size_t uhost_len, whost_len;
1054
1055 if (SUCCESS != to_utf16(parse_mb_loc, host_ptr, &uhost_str, &uhost_len)) {
1056 error = "could not convert to UTF-16";
1057 goto error;
1058 }
1059
1060 whost_len = uidna_nameToASCII(uidna, uhost_str, uhost_len, whost_str, sizeof(whost_str)-1, &info, &rc);
1061 whost_ptr = whost_str;
1062 if (U_FAILURE(rc) || info.errors) {
1063 goto error;
1064 }
1065 PHP_HTTP_DUFF(whost_len, *host_ptr++ = *whost_ptr++);
1066 #endif
1067 } else {
1068 error = "codepage not specified";
1069 goto error;
1070 }
1071
1072 *host_ptr = '\0';
1073 state->offset += host_ptr - state->url.host;
1074
1075 uidna_close(uidna);
1076 return SUCCESS;
1077
1078 error:
1079 if (!error) {
1080 if (U_FAILURE(rc)) {
1081 slprintf(ebuf, sizeof(ebuf)-1, "%s", u_errorName(rc));
1082 error = ebuf;
1083 } else if (info.errors) {
1084 slprintf(ebuf, sizeof(ebuf)-1, "ICU IDNA error codes: 0x%x", info.errors);
1085 error = ebuf;
1086 } else {
1087 error = "unknown error";
1088 }
1089 }
1090 php_error_docref(NULL, E_WARNING, "Failed to parse IDN (ICU IDNA2008); %s", error);
1091
1092 uidna_close(uidna);
1093 return FAILURE;
1094 }
1095 #endif
1096
1097 #if 0 && defined(PHP_WIN32)
1098 static ZEND_RESULT_CODE parse_widn_2003(struct parse_state *state)
1099 {
1100 char *host_ptr;
1101 uint16_t *uhost_str, ahost_str[MAXHOSTNAMELEN], *ahost_ptr;
1102 size_t uhost_len;
1103
1104 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
1105 if (SUCCESS != to_utf16(parse_mb_utf8, state->url.host, &uhost_str, &uhost_len)) {
1106 php_error_docref(NULL, E_WARNING, "Failed to parse IDN");
1107 return FAILURE;
1108 }
1109 #ifdef PHP_HTTP_HAVE_WCHAR
1110 } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
1111 if (SUCCESS != to_utf16(parse_mb_loc, state->url.host, &uhost_str, &uhost_len)) {
1112 php_error_docref(NULL, E_WARNING, "Failed to parse IDN");
1113 return FAILURE;
1114 }
1115 #endif
1116 } else {
1117 php_error_docref(NULL, E_WARNING, "Failed to parse IDN");
1118 return FAILURE;
1119 }
1120
1121 if (!IdnToAscii(IDN_ALLOW_UNASSIGNED|IDN_USE_STD3_ASCII_RULES, uhost_str, uhost_len, ahost_str, MAXHOSTNAMELEN)) {
1122 efree(uhost_str);
1123 php_error_docref(NULL, E_WARNING, "Failed to parse IDN");
1124 return FAILURE;
1125 }
1126
1127 efree(uhost_str);
1128 host_ptr = state->url.host;
1129 ahost_ptr = ahost_str;
1130 PHP_HTTP_DUFF(wcslen(ahost_str), *host_ptr++ = *ahost_ptr++);
1131 efree(ahost_str);
1132
1133 *host_ptr = '\0';
1134 state->offset += host_ptr - state->url.host;
1135
1136 return SUCCESS;
1137 }
1138 #endif
1139
1140 static ZEND_RESULT_CODE parse_idna(struct parse_state *state, size_t len)
1141 {
1142 if ((state->flags & PHP_HTTP_URL_PARSE_TOIDN_2008)
1143 || !(state->flags & PHP_HTTP_URL_PARSE_TOIDN_2003)
1144 ) {
1145 #if HAVE_UIDNA_NAMETOASCII_UTF8
1146 return parse_uidn_2008(state);
1147 #elif PHP_HTTP_HAVE_LIBIDN2
1148 return parse_gidn_2008(state, len);
1149 #endif
1150 }
1151
1152 if ((state->flags & PHP_HTTP_URL_PARSE_TOIDN_2003)
1153 || !(state->flags & PHP_HTTP_URL_PARSE_TOIDN_2008)
1154 ) {
1155 #if HAVE_UIDNA_IDNTOASCII
1156 return parse_uidn_2003(state);
1157 #elif PHP_HTTP_HAVE_LIBIDN
1158 return parse_gidn_2003(state, len);
1159 #endif
1160 }
1161
1162 #if 0 && defined(PHP_WIN32)
1163 return parse_widn_2003(state);
1164 #endif
1165
1166 #if HAVE_UIDNA_NAMETOASCII_UTF8
1167 return parse_uidn_2008(state);
1168 #elif PHP_HTTP_HAVE_LIBIDN2
1169 return parse_gidn_2008(state, len);
1170 #elif HAVE_UIDNA_IDNTOASCII
1171 return parse_uidn_2003(state);
1172 #elif PHP_HTTP_HAVE_LIBIDN
1173 return parse_gidn_2003(state, len);
1174 #endif
1175
1176 return SUCCESS;
1177 }
1178
1179 #ifdef HAVE_INET_PTON
1180 static const char *parse_ip6(struct parse_state *state, const char *ptr)
1181 {
1182 unsigned pos = 0;
1183 const char *error = NULL, *end = state->ptr, *tmp = memchr(ptr, ']', end - ptr);
1184
1185 if (tmp) {
1186 size_t addrlen = tmp - ptr + 1;
1187 char buf[16], *addr = estrndup(ptr + 1, addrlen - 2);
1188 int rv = inet_pton(AF_INET6, addr, buf);
1189
1190 if (rv == 1) {
1191 state->buffer[state->offset] = '[';
1192 state->url.host = &state->buffer[state->offset];
1193 inet_ntop(AF_INET6, buf, state->url.host + 1, state->maxlen - state->offset);
1194 state->offset += strlen(state->url.host);
1195 state->buffer[state->offset++] = ']';
1196 state->buffer[state->offset++] = 0;
1197 ptr = tmp + 1;
1198 } else if (rv == -1) {
1199 pos = 1;
1200 error = strerror(errno);
1201 } else {
1202 error = "unexpected '['";
1203 }
1204 efree(addr);
1205 } else {
1206 pos = end - ptr;
1207 error = "expected ']'";
1208 }
1209
1210 if (error) {
1211 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1212 php_error_docref(NULL, E_WARNING, "Failed to parse hostinfo; %s at pos %u in '%s'", error, pos, ptr);
1213 }
1214 return NULL;
1215 }
1216
1217 return ptr;
1218 }
1219 #endif
1220
1221 static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *ptr)
1222 {
1223 size_t mb, len = state->offset;
1224 const char *end = state->ptr, *tmp = ptr, *port = NULL, *label = NULL;
1225
1226 #ifdef HAVE_INET_PTON
1227 if (*ptr == '[' && !(ptr = parse_ip6(state, ptr))) {
1228 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1229 return FAILURE;
1230 }
1231 ptr = tmp;
1232 }
1233 #endif
1234
1235 if (ptr != end) do {
1236 switch (*ptr) {
1237 case ':':
1238 if (port) {
1239 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1240 php_error_docref(NULL, E_WARNING,
1241 "Failed to parse port; unexpected ':' at pos %u in '%s'",
1242 (unsigned) (ptr - tmp), tmp);
1243 }
1244 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1245 return FAILURE;
1246 }
1247 }
1248 port = ptr + 1;
1249 break;
1250
1251 case '%':
1252 if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
1253 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1254 php_error_docref(NULL, E_WARNING,
1255 "Failed to parse hostinfo; invalid percent encoding at pos %u in '%s'",
1256 (unsigned) (ptr - tmp), tmp);
1257 }
1258 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1259 return FAILURE;
1260 }
1261 state->buffer[state->offset++] = *ptr++;
1262 break;
1263 }
1264 state->buffer[state->offset++] = *ptr++;
1265 state->buffer[state->offset++] = *ptr++;
1266 state->buffer[state->offset++] = *ptr;
1267 break;
1268
1269 case '.':
1270 if (port || !label) {
1271 /* sort of a compromise, just ensure we don't end up
1272 * with a dot at the beginning or two consecutive dots
1273 */
1274 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1275 php_error_docref(NULL, E_WARNING,
1276 "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
1277 port ? "port" : "host",
1278 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
1279 }
1280 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1281 return FAILURE;
1282 }
1283 break;
1284 }
1285 state->buffer[state->offset++] = *ptr;
1286 label = NULL;
1287 break;
1288
1289 case '-':
1290 if (!label) {
1291 /* sort of a compromise, just ensure we don't end up
1292 * with a hyphen at the beginning
1293 */
1294 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1295 php_error_docref(NULL, E_WARNING,
1296 "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
1297 port ? "port" : "host",
1298 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
1299 }
1300 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1301 return FAILURE;
1302 }
1303 break;
1304 }
1305 /* no break */
1306 case '_': case '~': /* unreserved */
1307 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1308 case '+': case ',': case ';': case '=': /* sub-delims */
1309 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1310 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1311 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1312 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1313 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1314 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1315 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1316 case 'v': case 'w': case 'x': case 'y': case 'z':
1317 if (port) {
1318 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1319 php_error_docref(NULL, E_WARNING,
1320 "Failed to parse port; unexpected char '%c' at pos %u in '%s'",
1321 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
1322 }
1323 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1324 return FAILURE;
1325 }
1326 break;
1327 }
1328 /* no break */
1329 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1330 case '7': case '8': case '9':
1331 /* allowed */
1332 if (port) {
1333 state->url.port *= 10;
1334 state->url.port += *ptr - '0';
1335 } else {
1336 label = ptr;
1337 state->buffer[state->offset++] = *ptr;
1338 }
1339 break;
1340
1341 default:
1342 if (ptr == end) {
1343 break;
1344 } else if (port) {
1345 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1346 php_error_docref(NULL, E_WARNING,
1347 "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'",
1348 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
1349 }
1350 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1351 return FAILURE;
1352 }
1353 break;
1354 } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
1355 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1356 return FAILURE;
1357 }
1358 break;
1359 }
1360 label = ptr;
1361 ptr += mb - 1;
1362 }
1363 } while (++ptr != end);
1364
1365 if (!state->url.host) {
1366 len = state->offset - len;
1367 state->url.host = &state->buffer[state->offset - len];
1368 state->buffer[state->offset++] = 0;
1369 }
1370
1371 if (state->flags & PHP_HTTP_URL_PARSE_TOIDN) {
1372 return parse_idna(state, len);
1373 }
1374
1375 return SUCCESS;
1376 }
1377
1378 static const char *parse_authority(struct parse_state *state)
1379 {
1380 const char *tmp = state->ptr, *host = NULL;
1381
1382 do {
1383 switch (*state->ptr) {
1384 case '@':
1385 /* userinfo delimiter */
1386 if (host) {
1387 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1388 php_error_docref(NULL, E_WARNING,
1389 "Failed to parse userinfo; unexpected '@'");
1390 }
1391 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1392 return NULL;
1393 }
1394 break;
1395 }
1396 host = state->ptr + 1;
1397 if (tmp != state->ptr && SUCCESS != parse_userinfo(state, tmp)) {
1398 return NULL;
1399 }
1400 tmp = state->ptr + 1;
1401 break;
1402
1403 case '/':
1404 case '?':
1405 case '#':
1406 case '\0':
1407 EOD:
1408 /* host delimiter */
1409 if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) {
1410 return NULL;
1411 }
1412 return state->ptr;
1413 }
1414 } while (++state->ptr <= state->end);
1415
1416 --state->ptr;
1417 goto EOD;
1418 }
1419
1420 static const char *parse_path(struct parse_state *state)
1421 {
1422 size_t mb;
1423 const char *tmp;
1424
1425 /* is there actually a path to parse? */
1426 if (!*state->ptr) {
1427 return state->ptr;
1428 }
1429 tmp = state->ptr;
1430 state->url.path = &state->buffer[state->offset];
1431
1432 do {
1433 switch (*state->ptr) {
1434 case '#':
1435 case '?':
1436 goto done;
1437
1438 case '%':
1439 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
1440 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1441 php_error_docref(NULL, E_WARNING,
1442 "Failed to parse path; invalid percent encoding at pos %u in '%s'",
1443 (unsigned) (state->ptr - tmp), tmp);
1444 }
1445 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1446 return NULL;
1447 }
1448 state->buffer[state->offset++] = *state->ptr;
1449 break;
1450 }
1451 state->buffer[state->offset++] = *state->ptr++;
1452 state->buffer[state->offset++] = *state->ptr++;
1453 state->buffer[state->offset++] = *state->ptr;
1454 break;
1455
1456 case '/': /* yeah, well */
1457 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1458 case '+': case ',': case ';': case '=': /* sub-delims */
1459 case '-': case '.': case '_': case '~': /* unreserved */
1460 case ':': case '@': /* pchar */
1461 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1462 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1463 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1464 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1465 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1466 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1467 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1468 case 'v': case 'w': case 'x': case 'y': case 'z':
1469 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1470 case '7': case '8': case '9':
1471 /* allowed */
1472 state->buffer[state->offset++] = *state->ptr;
1473 break;
1474
1475 default:
1476 if (!(mb = parse_mb(state, PARSE_PATH, state->ptr, state->end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
1477 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1478 return NULL;
1479 }
1480 break;
1481 }
1482 state->ptr += mb - 1;
1483 }
1484 } while (++state->ptr < state->end);
1485
1486 done:
1487 /* did we have any path component ? */
1488 if (tmp != state->ptr) {
1489 state->buffer[state->offset++] = 0;
1490 } else {
1491 state->url.path = NULL;
1492 }
1493 return state->ptr;
1494 }
1495
1496 static const char *parse_query(struct parse_state *state)
1497 {
1498 size_t mb;
1499 const char *tmp = state->ptr + !!*state->ptr;
1500
1501 /* is there actually a query to parse? */
1502 if (*state->ptr != '?') {
1503 return state->ptr;
1504 }
1505
1506 /* skip initial '?' */
1507 tmp = ++state->ptr;
1508 state->url.query = &state->buffer[state->offset];
1509
1510 while (state->ptr < state->end) {
1511 switch (*state->ptr) {
1512 case '#':
1513 goto done;
1514
1515 case '%':
1516 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
1517 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1518 php_error_docref(NULL, E_WARNING,
1519 "Failed to parse query; invalid percent encoding at pos %u in '%s'",
1520 (unsigned) (state->ptr - tmp), tmp);
1521 }
1522 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1523 return NULL;
1524 }
1525 /* fallthrough, pct-encode the percent sign */
1526 } else {
1527 state->buffer[state->offset++] = *state->ptr++;
1528 state->buffer[state->offset++] = *state->ptr++;
1529 state->buffer[state->offset++] = *state->ptr;
1530 break;
1531 }
1532 /* no break */
1533 case '{': case '}':
1534 case '<': case '>':
1535 case '[': case ']':
1536 case '|': case '\\': case '^': case '`': case '"': case ' ':
1537 /* RFC1738 unsafe */
1538 if (state->flags & PHP_HTTP_URL_PARSE_TOPCT) {
1539 state->buffer[state->offset++] = '%';
1540 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) >> 4];
1541 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) & 0xf];
1542 break;
1543 }
1544 /* no break */
1545
1546 case '?': case '/': /* yeah, well */
1547 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1548 case '+': case ',': case ';': case '=': /* sub-delims */
1549 case '-': case '.': case '_': case '~': /* unreserved */
1550 case ':': case '@': /* pchar */
1551 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1552 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1553 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1554 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1555 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1556 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1557 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1558 case 'v': case 'w': case 'x': case 'y': case 'z':
1559 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1560 case '7': case '8': case '9':
1561 /* allowed */
1562 state->buffer[state->offset++] = *state->ptr;
1563 break;
1564
1565 default:
1566 if (!(mb = parse_mb(state, PARSE_QUERY, state->ptr, state->end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
1567 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1568 return NULL;
1569 }
1570 break;
1571 }
1572 state->ptr += mb - 1;
1573 }
1574
1575 ++state->ptr;
1576 }
1577
1578 done:
1579 state->buffer[state->offset++] = 0;
1580 return state->ptr;
1581 }
1582
1583 static const char *parse_fragment(struct parse_state *state)
1584 {
1585 size_t mb;
1586 const char *tmp;
1587
1588 /* is there actually a fragment to parse? */
1589 if (*state->ptr != '#') {
1590 return state->ptr;
1591 }
1592
1593 /* skip initial '#' */
1594 tmp = ++state->ptr;
1595 state->url.fragment = &state->buffer[state->offset];
1596
1597 do {
1598 switch (*state->ptr) {
1599 case '#':
1600 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1601 php_error_docref(NULL, E_WARNING,
1602 "Failed to parse fragment; invalid fragment identifier at pos %u in '%s'",
1603 (unsigned) (state->ptr - tmp), tmp);
1604 }
1605 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1606 return NULL;
1607 }
1608 state->buffer[state->offset++] = *state->ptr;
1609 break;
1610
1611 case '%':
1612 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
1613 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1614 php_error_docref(NULL, E_WARNING,
1615 "Failed to parse fragment; invalid percent encoding at pos %u in '%s'",
1616 (unsigned) (state->ptr - tmp), tmp);
1617 }
1618 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1619 return NULL;
1620 }
1621 /* fallthrough */
1622 } else {
1623 state->buffer[state->offset++] = *state->ptr++;
1624 state->buffer[state->offset++] = *state->ptr++;
1625 state->buffer[state->offset++] = *state->ptr;
1626 break;
1627 }
1628 /* no break */
1629
1630 case '{': case '}':
1631 case '<': case '>':
1632 case '[': case ']':
1633 case '|': case '\\': case '^': case '`': case '"': case ' ':
1634 /* RFC1738 unsafe */
1635 if (state->flags & PHP_HTTP_URL_PARSE_TOPCT) {
1636 state->buffer[state->offset++] = '%';
1637 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) >> 4];
1638 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) & 0xf];
1639 break;
1640 }
1641 /* no break */
1642
1643 case '?': case '/':
1644 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1645 case '+': case ',': case ';': case '=': /* sub-delims */
1646 case '-': case '.': case '_': case '~': /* unreserved */
1647 case ':': case '@': /* pchar */
1648 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1649 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1650 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1651 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1652 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1653 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1654 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1655 case 'v': case 'w': case 'x': case 'y': case 'z':
1656 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1657 case '7': case '8': case '9':
1658 /* allowed */
1659 state->buffer[state->offset++] = *state->ptr;
1660 break;
1661
1662 default:
1663 if (!(mb = parse_mb(state, PARSE_FRAGMENT, state->ptr, state->end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
1664 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1665 return NULL;
1666 }
1667 break;
1668 }
1669 state->ptr += mb - 1;
1670 }
1671 } while (++state->ptr < state->end);
1672
1673 state->buffer[state->offset++] = 0;
1674 return state->ptr;
1675 }
1676
1677 static const char *parse_hier(struct parse_state *state)
1678 {
1679 if (*state->ptr == '/') {
1680 if (state->end - state->ptr > 1) {
1681 if (*(state->ptr + 1) == '/') {
1682 state->ptr += 2;
1683 if (!(state->ptr = parse_authority(state))) {
1684 return NULL;
1685 }
1686 }
1687 }
1688 }
1689 return parse_path(state);
1690 }
1691
1692 static const char *parse_scheme(struct parse_state *state)
1693 {
1694 size_t mb;
1695 const char *tmp = state->ptr;
1696
1697 do {
1698 switch (*state->ptr) {
1699 case ':':
1700 /* scheme delimiter */
1701 state->url.scheme = &state->buffer[0];
1702 state->buffer[state->offset++] = 0;
1703 return ++state->ptr;
1704
1705 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1706 case '7': case '8': case '9':
1707 case '+': case '-': case '.':
1708 if (state->ptr == tmp) {
1709 goto softfail;
1710 }
1711 /* no break */
1712 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1713 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1714 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1715 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1716 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1717 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1718 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1719 case 'v': case 'w': case 'x': case 'y': case 'z':
1720 /* scheme part */
1721 state->buffer[state->offset++] = *state->ptr;
1722 break;
1723
1724 default:
1725 if (!(mb = parse_mb(state, PARSE_SCHEME, state->ptr, state->end, tmp, 1))) {
1726 goto softfail;
1727 }
1728 state->ptr += mb - 1;
1729 }
1730 } while (++state->ptr != state->end);
1731
1732 softfail:
1733 state->offset = 0;
1734 return state->ptr = tmp;
1735 }
1736
1737 php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags)
1738 {
1739 size_t maxlen = 3 * len + 8 /* null bytes for all components */;
1740 struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen);
1741
1742 state->end = str + len;
1743 state->ptr = str;
1744 state->flags = flags;
1745 state->maxlen = maxlen;
1746
1747 if (!parse_scheme(state)) {
1748 php_error_docref(NULL, E_WARNING, "Failed to parse URL scheme: '%s'", state->ptr);
1749 efree(state);
1750 return NULL;
1751 }
1752
1753 if (!parse_hier(state)) {
1754 efree(state);
1755 return NULL;
1756 }
1757
1758 if (!parse_query(state)) {
1759 php_error_docref(NULL, E_WARNING, "Failed to parse URL query: '%s'", state->ptr);
1760 efree(state);
1761 return NULL;
1762 }
1763
1764 if (!parse_fragment(state)) {
1765 php_error_docref(NULL, E_WARNING, "Failed to parse URL fragment: '%s'", state->ptr);
1766 efree(state);
1767 return NULL;
1768 }
1769
1770 return (php_http_url_t *) state;
1771 }
1772
1773 php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags)
1774 {
1775 size_t maxlen = 3 * len;
1776 struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen);
1777
1778 state->end = str + len;
1779 state->ptr = str;
1780 state->flags = flags;
1781 state->maxlen = maxlen;
1782
1783 if (!(state->ptr = parse_authority(state))) {
1784 efree(state);
1785 return NULL;
1786 }
1787
1788 if (state->ptr != state->end) {
1789 if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
1790 php_error_docref(NULL, E_WARNING,
1791 "Failed to parse URL authority, unexpected character at pos %u in '%s'",
1792 (unsigned) (state->ptr - str), str);
1793 }
1794 if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
1795 efree(state);
1796 return NULL;
1797 }
1798 }
1799
1800 return (php_http_url_t *) state;
1801 }
1802
1803 static zend_class_entry *php_http_url_class_entry;
1804 static zend_class_entry *php_http_env_url_class_entry;
1805
1806 zend_class_entry *php_http_url_get_class_entry(void)
1807 {
1808 return php_http_url_class_entry;
1809 }
1810
1811 zend_class_entry *php_http_get_env_url_class_entry(void)
1812 {
1813 return php_http_env_url_class_entry;
1814 }
1815
1816 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0)
1817 ZEND_ARG_INFO(0, old_url)
1818 ZEND_ARG_INFO(0, new_url)
1819 ZEND_ARG_INFO(0, flags)
1820 ZEND_END_ARG_INFO();
1821 PHP_METHOD(HttpUrl, __construct)
1822 {
1823 zval *new_url = NULL, *old_url = NULL;
1824 zend_long flags = 0;
1825 zend_error_handling zeh;
1826
1827 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|z!z!l", &old_url, &new_url, &flags), invalid_arg, return);
1828
1829 /* always set http\Url::FROM_ENV for instances of http\Env\Url */
1830 if (instanceof_function(Z_OBJCE_P(getThis()), php_http_env_url_class_entry)) {
1831 flags |= PHP_HTTP_URL_FROM_ENV;
1832 }
1833
1834 if (flags & PHP_HTTP_URL_SILENT_ERRORS) {
1835 zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh);
1836 } else if (flags & PHP_HTTP_URL_IGNORE_ERRORS) {
1837 zend_replace_error_handling(EH_NORMAL, NULL, &zeh);
1838 } else {
1839 zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
1840 }
1841 {
1842 php_http_url_t *res_purl, *new_purl = NULL, *old_purl = NULL;
1843
1844 if (new_url) {
1845 new_purl = php_http_url_from_zval(new_url, flags);
1846 if (!new_purl) {
1847 zend_restore_error_handling(&zeh);
1848 return;
1849 }
1850 }
1851 if (old_url) {
1852 old_purl = php_http_url_from_zval(old_url, flags);
1853 if (!old_purl) {
1854 if (new_purl) {
1855 php_http_url_free(&new_purl);
1856 }
1857 zend_restore_error_handling(&zeh);
1858 return;
1859 }
1860 }
1861
1862 res_purl = php_http_url_mod(old_purl, new_purl, flags);
1863 php_http_url_to_struct(res_purl, getThis());
1864
1865 php_http_url_free(&res_purl);
1866 if (old_purl) {
1867 php_http_url_free(&old_purl);
1868 }
1869 if (new_purl) {
1870 php_http_url_free(&new_purl);
1871 }
1872 }
1873 zend_restore_error_handling(&zeh);
1874 }
1875
1876 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_mod, 0, 0, 1)
1877 ZEND_ARG_INFO(0, more_url_parts)
1878 ZEND_ARG_INFO(0, flags)
1879 ZEND_END_ARG_INFO();
1880 PHP_METHOD(HttpUrl, mod)
1881 {
1882 zval *new_url = NULL;
1883 zend_long flags = PHP_HTTP_URL_JOIN_PATH | PHP_HTTP_URL_JOIN_QUERY | PHP_HTTP_URL_SANITIZE_PATH;
1884 zend_error_handling zeh;
1885
1886 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "z!|l", &new_url, &flags), invalid_arg, return);
1887
1888 if (flags & PHP_HTTP_URL_SILENT_ERRORS) {
1889 zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh);
1890 } else if (flags & PHP_HTTP_URL_IGNORE_ERRORS) {
1891 zend_replace_error_handling(EH_NORMAL, NULL, &zeh);
1892 } else {
1893 zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
1894 }
1895 {
1896 php_http_url_t *new_purl = NULL, *old_purl = NULL;
1897
1898 if (new_url) {
1899 new_purl = php_http_url_from_zval(new_url, flags);
1900 if (!new_purl) {
1901 zend_restore_error_handling(&zeh);
1902 return;
1903 }
1904 }
1905
1906 if ((old_purl = php_http_url_from_struct(HASH_OF(getThis())))) {
1907 php_http_url_t *res_purl;
1908
1909 ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis()));
1910
1911 res_purl = php_http_url_mod(old_purl, new_purl, flags);
1912 php_http_url_to_struct(res_purl, return_value);
1913
1914 php_http_url_free(&res_purl);
1915 php_http_url_free(&old_purl);
1916 }
1917 if (new_purl) {
1918 php_http_url_free(&new_purl);
1919 }
1920 }
1921 zend_restore_error_handling(&zeh);
1922 }
1923
1924 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_toString, 0, 0, 0)
1925 ZEND_END_ARG_INFO();
1926 PHP_METHOD(HttpUrl, toString)
1927 {
1928 if (SUCCESS == zend_parse_parameters_none()) {
1929 php_http_url_t *purl;
1930
1931 if ((purl = php_http_url_from_struct(HASH_OF(getThis())))) {
1932 char *str;
1933 size_t len;
1934
1935 php_http_url_to_string(purl, &str, &len, 0);
1936 php_http_url_free(&purl);
1937 RETURN_STR(php_http_cs2zs(str, len));
1938 }
1939 }
1940 RETURN_EMPTY_STRING();
1941 }
1942
1943 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_toArray, 0, 0, 0)
1944 ZEND_END_ARG_INFO();
1945 PHP_METHOD(HttpUrl, toArray)
1946 {
1947 php_http_url_t *purl;
1948
1949 if (SUCCESS != zend_parse_parameters_none()) {
1950 return;
1951 }
1952
1953 /* strip any non-URL properties */
1954 purl = php_http_url_from_struct(HASH_OF(getThis()));
1955 php_http_url_to_struct(purl, return_value);
1956 php_http_url_free(&purl);
1957 }
1958
1959 static zend_function_entry php_http_url_methods[] = {
1960 PHP_ME(HttpUrl, __construct, ai_HttpUrl___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
1961 PHP_ME(HttpUrl, mod, ai_HttpUrl_mod, ZEND_ACC_PUBLIC)
1962 PHP_ME(HttpUrl, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
1963 ZEND_MALIAS(HttpUrl, __toString, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
1964 PHP_ME(HttpUrl, toArray, ai_HttpUrl_toArray, ZEND_ACC_PUBLIC)
1965 EMPTY_FUNCTION_ENTRY
1966 };
1967
1968 PHP_MINIT_FUNCTION(http_url)
1969 {
1970 zend_class_entry ce = {0};
1971
1972 INIT_NS_CLASS_ENTRY(ce, "http", "Url", php_http_url_methods);
1973 php_http_url_class_entry = zend_register_internal_class(&ce);
1974
1975 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC);
1976 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC);
1977 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC);
1978 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC);
1979 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC);
1980 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC);
1981 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC);
1982 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC);
1983
1984 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE);
1985 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH);
1986 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY);
1987 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER);
1988 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS);
1989 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH);
1990 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT);
1991 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH);
1992 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY);
1993 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT);
1994 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL);
1995 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV);
1996 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SANITIZE_PATH"), PHP_HTTP_URL_SANITIZE_PATH);
1997
1998 #ifdef PHP_HTTP_HAVE_WCHAR
1999 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBLOC"), PHP_HTTP_URL_PARSE_MBLOC);
2000 #endif
2001 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBUTF8"), PHP_HTTP_URL_PARSE_MBUTF8);
2002 #if PHP_HTTP_HAVE_LIBIDN2 || PHP_HTTP_HAVE_LIBIDN || HAVE_UIDNA_IDNTOASCII || HAVE_UIDNA_NAMETOASCII_UTF8
2003 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN"), PHP_HTTP_URL_PARSE_TOIDN);
2004 # if PHP_HTTP_HAVE_IDNA2003
2005 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN_2003"), PHP_HTTP_URL_PARSE_TOIDN_2003);
2006 # endif
2007 # if PHP_HTTP_HAVE_IDNA2008
2008 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN_2008"), PHP_HTTP_URL_PARSE_TOIDN_2008);
2009 # endif
2010 #endif
2011 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT);
2012
2013 INIT_NS_CLASS_ENTRY(ce, "http\\Env", "Url", php_http_url_methods);
2014 php_http_env_url_class_entry = zend_register_internal_class_ex(&ce, php_http_url_class_entry);
2015
2016 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("IGNORE_ERRORS"), PHP_HTTP_URL_IGNORE_ERRORS);
2017 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SILENT_ERRORS"), PHP_HTTP_URL_SILENT_ERRORS);
2018
2019 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STDFLAGS"), PHP_HTTP_URL_STDFLAGS);
2020
2021 return SUCCESS;
2022 }
2023
2024
2025 /*
2026 * Local variables:
2027 * tab-width: 4
2028 * c-basic-offset: 4
2029 * End:
2030 * vim600: noet sw=4 ts=4 fdm=marker
2031 * vim<600: noet sw=4 ts=4
2032 */
2033