flush WIP
[m6w6/ext-http] / 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 #ifdef PHP_HTTP_HAVE_IDN
16 # include <idna.h>
17 #endif
18
19 #ifdef PHP_HTTP_HAVE_WCHAR
20 # include <wchar.h>
21 # include <wctype.h>
22 #endif
23
24 #ifdef HAVE_ARPA_INET_H
25 # include <arpa/inet.h>
26 #endif
27
28 #include "php_http_utf8.h"
29
30 static inline char *localhostname(void)
31 {
32 char hostname[1024] = {0};
33
34 #ifdef PHP_WIN32
35 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
36 return estrdup(hostname);
37 }
38 #elif defined(HAVE_GETHOSTNAME)
39 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
40 # if defined(HAVE_GETDOMAINNAME)
41 size_t hlen = strlen(hostname);
42 if (hlen <= lenof(hostname) - lenof("(none)")) {
43 hostname[hlen++] = '.';
44 if (SUCCESS == getdomainname(&hostname[hlen], lenof(hostname) - hlen)) {
45 if (!strcmp(&hostname[hlen], "(none)")) {
46 hostname[hlen - 1] = '\0';
47 }
48 return estrdup(hostname);
49 }
50 }
51 # endif
52 if (strcmp(hostname, "(none)")) {
53 return estrdup(hostname);
54 }
55 }
56 #endif
57 return estrndup("localhost", lenof("localhost"));
58 }
59
60 #define url(buf) ((php_http_url_t *) buf.data)
61
62 static php_http_url_t *php_http_url_from_env(void)
63 {
64 zval *https, *zhost, *zport;
65 long port;
66 php_http_buffer_t buf;
67
68 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
69 php_http_buffer_account(&buf, sizeof(php_http_url_t));
70 memset(buf.data, 0, buf.used);
71
72 /* scheme */
73 url(buf)->scheme = &buf.data[buf.used];
74 https = php_http_env_get_server_var(ZEND_STRL("HTTPS"), 1);
75 if (https && !strcasecmp(Z_STRVAL_P(https), "ON")) {
76 php_http_buffer_append(&buf, "https", sizeof("https"));
77 } else {
78 php_http_buffer_append(&buf, "http", sizeof("http"));
79 }
80
81 /* host */
82 url(buf)->host = &buf.data[buf.used];
83 if ((((zhost = php_http_env_get_server_var(ZEND_STRL("HTTP_HOST"), 1)) ||
84 (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_NAME"), 1)) ||
85 (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_ADDR"), 1)))) && Z_STRLEN_P(zhost)) {
86 size_t stop_at = strspn(Z_STRVAL_P(zhost), "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.");
87
88 php_http_buffer_append(&buf, Z_STRVAL_P(zhost), stop_at);
89 php_http_buffer_append(&buf, "", 1);
90 } else {
91 char *host_str = localhostname();
92
93 php_http_buffer_append(&buf, host_str, strlen(host_str) + 1);
94 efree(host_str);
95 }
96
97 /* port */
98 zport = php_http_env_get_server_var(ZEND_STRL("SERVER_PORT"), 1);
99 if (zport && IS_LONG == is_numeric_string(Z_STRVAL_P(zport), Z_STRLEN_P(zport), &port, NULL, 0)) {
100 url(buf)->port = port;
101 }
102
103 /* path */
104 if (SG(request_info).request_uri && SG(request_info).request_uri[0]) {
105 const char *q = strchr(SG(request_info).request_uri, '?');
106
107 url(buf)->path = &buf.data[buf.used];
108
109 if (q) {
110 php_http_buffer_append(&buf, SG(request_info).request_uri, q - SG(request_info).request_uri);
111 php_http_buffer_append(&buf, "", 1);
112 } else {
113 php_http_buffer_append(&buf, SG(request_info).request_uri, strlen(SG(request_info).request_uri) + 1);
114 }
115 }
116
117 /* query */
118 if (SG(request_info).query_string && SG(request_info).query_string[0]) {
119 url(buf)->query = &buf.data[buf.used];
120 php_http_buffer_append(&buf, SG(request_info).query_string, strlen(SG(request_info).query_string) + 1);
121 }
122
123 return url(buf);
124 }
125
126 #define url_isset(u,n) \
127 ((u)&&(u)->n)
128 #define url_copy(n) do { \
129 if (url_isset(new_url, n)) { \
130 url(buf)->n = &buf.data[buf.used]; \
131 php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1); \
132 } else if (url_isset(old_url, n)) { \
133 url(buf)->n = &buf.data[buf.used]; \
134 php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1); \
135 } \
136 } while (0)
137
138 php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_url_t *new_url, unsigned flags)
139 {
140 php_http_url_t *tmp_url = NULL;
141 php_http_buffer_t buf;
142
143 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
144 php_http_buffer_account(&buf, sizeof(php_http_url_t));
145 memset(buf.data, 0, buf.used);
146
147 /* set from env if requested */
148 if (flags & PHP_HTTP_URL_FROM_ENV) {
149 php_http_url_t *env_url = php_http_url_from_env();
150
151 old_url = tmp_url = php_http_url_mod(env_url, old_url, flags ^ PHP_HTTP_URL_FROM_ENV);
152 php_http_url_free(&env_url);
153 }
154
155 url_copy(scheme);
156
157 if (!(flags & PHP_HTTP_URL_STRIP_USER)) {
158 url_copy(user);
159 }
160
161 if (!(flags & PHP_HTTP_URL_STRIP_PASS)) {
162 url_copy(pass);
163 }
164
165 url_copy(host);
166
167 if (!(flags & PHP_HTTP_URL_STRIP_PORT)) {
168 url(buf)->port = url_isset(new_url, port) ? new_url->port : ((old_url) ? old_url->port : 0);
169 }
170
171 if (!(flags & PHP_HTTP_URL_STRIP_PATH)) {
172 if ((flags & PHP_HTTP_URL_JOIN_PATH) && url_isset(old_url, path) && url_isset(new_url, path) && *new_url->path != '/') {
173 size_t old_path_len = strlen(old_url->path), new_path_len = strlen(new_url->path);
174 char *path = ecalloc(1, old_path_len + new_path_len + 1 + 1);
175
176 strcat(path, old_url->path);
177 if (path[old_path_len - 1] != '/') {
178 php_dirname(path, old_path_len);
179 strcat(path, "/");
180 }
181 strcat(path, new_url->path);
182
183 url(buf)->path = &buf.data[buf.used];
184 if (path[0] != '/') {
185 php_http_buffer_append(&buf, "/", 1);
186 }
187 php_http_buffer_append(&buf, path, strlen(path) + 1);
188 efree(path);
189 } else {
190 const char *path = NULL;
191
192 if (url_isset(new_url, path)) {
193 path = new_url->path;
194 } else if (url_isset(old_url, path)) {
195 path = old_url->path;
196 }
197
198 if (path) {
199 url(buf)->path = &buf.data[buf.used];
200
201 php_http_buffer_append(&buf, path, strlen(path) + 1);
202 }
203
204
205 }
206 }
207
208 if (!(flags & PHP_HTTP_URL_STRIP_QUERY)) {
209 if ((flags & PHP_HTTP_URL_JOIN_QUERY) && url_isset(new_url, query) && url_isset(old_url, query)) {
210 zval qarr, qstr;
211
212 array_init(&qarr);
213
214 ZVAL_STR(&qstr, php_http_cs2zs(old_url->query, strlen(old_url->query)));
215 php_http_querystring_update(&qarr, &qstr, NULL);
216 ZVAL_STR(&qstr, php_http_cs2zs(new_url->query, strlen(new_url->query)));
217 php_http_querystring_update(&qarr, &qstr, NULL);
218
219 ZVAL_NULL(&qstr);
220 php_http_querystring_update(&qarr, NULL, &qstr);
221
222 url(buf)->query = &buf.data[buf.used];
223 php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1);
224
225 zval_dtor(&qstr);
226 zval_dtor(&qarr);
227 } else {
228 url_copy(query);
229 }
230 }
231
232 if (!(flags & PHP_HTTP_URL_STRIP_FRAGMENT)) {
233 url_copy(fragment);
234 }
235
236 /* done with copy & combine & strip */
237
238 if (flags & PHP_HTTP_URL_FROM_ENV) {
239 /* free old_url we tainted above */
240 php_http_url_free(&tmp_url);
241 }
242
243 /* replace directory references if path is not a single slash */
244 if ((flags & PHP_HTTP_URL_SANITIZE_PATH)
245 && url(buf)->path[0] && url(buf)->path[1]) {
246 char *ptr, *end = url(buf)->path + strlen(url(buf)->path) + 1;
247
248 for (ptr = strchr(url(buf)->path, '/'); ptr; ptr = strchr(ptr, '/')) {
249 switch (ptr[1]) {
250 case '/':
251 memmove(&ptr[1], &ptr[2], end - &ptr[2]);
252 break;
253
254 case '.':
255 switch (ptr[2]) {
256 case '\0':
257 ptr[1] = '\0';
258 break;
259
260 case '/':
261 memmove(&ptr[1], &ptr[3], end - &ptr[3]);
262 break;
263
264 case '.':
265 if (ptr[3] == '/') {
266 char *pos = &ptr[4];
267 while (ptr != url(buf)->path) {
268 if (*--ptr == '/') {
269 break;
270 }
271 }
272 memmove(&ptr[1], pos, end - pos);
273 break;
274 } else if (!ptr[3]) {
275 /* .. at the end */
276 ptr[1] = '\0';
277 }
278 /* no break */
279
280 default:
281 /* something else */
282 ++ptr;
283 break;
284 }
285 break;
286
287 default:
288 ++ptr;
289 break;
290 }
291 }
292 }
293 /* unset default ports */
294 if (url(buf)->port) {
295 if ( ((url(buf)->port == 80) && !strcmp(url(buf)->scheme, "http"))
296 || ((url(buf)->port ==443) && !strcmp(url(buf)->scheme, "https"))
297 ) {
298 url(buf)->port = 0;
299 }
300 }
301
302 return url(buf);
303 }
304
305 char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent)
306 {
307 php_http_buffer_t buf;
308
309 php_http_buffer_init_ex(&buf, PHP_HTTP_BUFFER_DEFAULT_SIZE, persistent ?
310 PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
311
312 if (url->scheme && *url->scheme) {
313 php_http_buffer_appendl(&buf, url->scheme);
314 php_http_buffer_appends(&buf, "://");
315 } else if ((url->user && *url->user) || (url->host && *url->host)) {
316 php_http_buffer_appends(&buf, "//");
317 }
318
319 if (url->user && *url->user) {
320 php_http_buffer_appendl(&buf, url->user);
321 if (url->pass && *url->pass) {
322 php_http_buffer_appends(&buf, ":");
323 php_http_buffer_appendl(&buf, url->pass);
324 }
325 php_http_buffer_appends(&buf, "@");
326 }
327
328 if (url->host && *url->host) {
329 php_http_buffer_appendl(&buf, url->host);
330 if (url->port) {
331 php_http_buffer_appendf(&buf, ":%hu", url->port);
332 }
333 }
334
335 if (url->path && *url->path) {
336 if (*url->path != '/') {
337 php_http_buffer_appends(&buf, "/");
338 }
339 php_http_buffer_appendl(&buf, url->path);
340 } else if (buf.used) {
341 php_http_buffer_appends(&buf, "/");
342 }
343
344 if (url->query && *url->query) {
345 php_http_buffer_appends(&buf, "?");
346 php_http_buffer_appendl(&buf, url->query);
347 }
348
349 if (url->fragment && *url->fragment) {
350 php_http_buffer_appends(&buf, "#");
351 php_http_buffer_appendl(&buf, url->fragment);
352 }
353
354 php_http_buffer_shrink(&buf);
355 php_http_buffer_fix(&buf);
356
357 if (url_len) {
358 *url_len = buf.used;
359 }
360
361 if (url_str) {
362 *url_str = buf.data;
363 }
364
365 return buf.data;
366 }
367
368 php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags)
369 {
370 zend_string *zs;
371 php_http_url_t *purl;
372
373 switch (Z_TYPE_P(value)) {
374 case IS_ARRAY:
375 case IS_OBJECT:
376 purl = php_http_url_from_struct(HASH_OF(value));
377 break;
378
379 default:
380 zs = zval_get_string(value);
381 purl = php_http_url_parse(zs->val, zs->len, flags);
382 zend_string_release(zs);
383 }
384
385 return purl;
386 }
387
388 php_http_url_t *php_http_url_from_struct(HashTable *ht)
389 {
390 zval *e;
391 php_http_buffer_t buf;
392
393 php_http_buffer_init_ex(&buf, MAX(PHP_HTTP_BUFFER_DEFAULT_SIZE, sizeof(php_http_url_t)<<2), PHP_HTTP_BUFFER_INIT_PREALLOC);
394 php_http_buffer_account(&buf, sizeof(php_http_url_t));
395 memset(buf.data, 0, buf.used);
396
397 if ((e = zend_hash_str_find(ht, ZEND_STRL("scheme")))) {
398 zend_string *zs = zval_get_string(e);
399 url(buf)->scheme = &buf.data[buf.used];
400 php_http_buffer_append(&buf, zs->val, zs->len + 1);
401 zend_string_release(zs);
402 }
403 if ((e = zend_hash_str_find(ht, ZEND_STRL("user")))) {
404 zend_string *zs = zval_get_string(e);
405 url(buf)->user = &buf.data[buf.used];
406 php_http_buffer_append(&buf, zs->val, zs->len + 1);
407 zend_string_release(zs);
408 }
409 if ((e = zend_hash_str_find(ht, ZEND_STRL("pass")))) {
410 zend_string *zs = zval_get_string(e);
411 url(buf)->pass = &buf.data[buf.used];
412 php_http_buffer_append(&buf, zs->val, zs->len + 1);
413 zend_string_release(zs);
414 }
415 if ((e = zend_hash_str_find(ht, ZEND_STRL("host")))) {
416 zend_string *zs = zval_get_string(e);
417 url(buf)->host = &buf.data[buf.used];
418 php_http_buffer_append(&buf, zs->val, zs->len + 1);
419 zend_string_release(zs);
420 }
421 if ((e = zend_hash_str_find(ht, ZEND_STRL("port")))) {
422 url(buf)->port = (unsigned short) zval_get_long(e);
423 }
424 if ((e = zend_hash_str_find(ht, ZEND_STRL("path")))) {
425 zend_string *zs = zval_get_string(e);
426 url(buf)->path = &buf.data[buf.used];
427 php_http_buffer_append(&buf, zs->val, zs->len + 1);
428 zend_string_release(zs);
429 }
430 if ((e = zend_hash_str_find(ht, ZEND_STRL("query")))) {
431 zend_string *zs = zval_get_string(e);
432 url(buf)->query = &buf.data[buf.used];
433 php_http_buffer_append(&buf, zs->val, zs->len + 1);
434 zend_string_release(zs);
435 }
436 if ((e = zend_hash_str_find(ht, ZEND_STRL("fragment")))) {
437 zend_string *zs = zval_get_string(e);
438 url(buf)->fragment = &buf.data[buf.used];
439 php_http_buffer_append(&buf, zs->val, zs->len + 1);
440 zend_string_release(zs);
441 }
442
443 return url(buf);
444 }
445
446 HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct)
447 {
448 HashTable *ht;
449 zval tmp;
450
451 if (strct) {
452 switch (Z_TYPE_P(strct)) {
453 default:
454 zval_dtor(strct);
455 array_init(strct);
456 /* no break */
457 case IS_ARRAY:
458 case IS_OBJECT:
459 ht = HASH_OF(strct);
460 break;
461 }
462 } else {
463 ALLOC_HASHTABLE(ht);
464 zend_hash_init(ht, 8, NULL, ZVAL_PTR_DTOR, 0);
465 }
466
467 if (url) {
468 if (url->scheme) {
469 ZVAL_STRING(&tmp, url->scheme);
470 zend_hash_str_update(ht, "scheme", lenof("scheme"), &tmp);
471 }
472 if (url->user) {
473 ZVAL_STRING(&tmp, url->user);
474 zend_hash_str_update(ht, "user", lenof("user"), &tmp);
475 }
476 if (url->pass) {
477 ZVAL_STRING(&tmp, url->pass);
478 zend_hash_str_update(ht, "pass", lenof("pass"), &tmp);
479 }
480 if (url->host) {
481 ZVAL_STRING(&tmp, url->host);
482 zend_hash_str_update(ht, "host", lenof("host"), &tmp);
483 }
484 if (url->port) {
485 ZVAL_LONG(&tmp, url->port);
486 zend_hash_str_update(ht, "port", lenof("port"), &tmp);
487 }
488 if (url->path) {
489 ZVAL_STRING(&tmp, url->path);
490 zend_hash_str_update(ht, "path", lenof("path"), &tmp);
491 }
492 if (url->query) {
493 ZVAL_STRING(&tmp, url->query);
494 zend_hash_str_update(ht, "query", lenof("query"), &tmp);
495 }
496 if (url->fragment) {
497 ZVAL_STRING(&tmp, url->fragment);
498 zend_hash_str_update(ht, "fragment", lenof("fragment"), &tmp);
499 }
500 }
501
502 return ht;
503 }
504
505 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)
506 {
507 const char *arg_sep_str;
508 size_t arg_sep_len;
509 php_http_buffer_t *qstr = php_http_buffer_new();
510
511 php_http_url_argsep(&arg_sep_str, &arg_sep_len);
512
513 if (SUCCESS != php_http_url_encode_hash_ex(hash, qstr, arg_sep_str, arg_sep_len, "=", 1, pre_encoded_str, pre_encoded_len)) {
514 php_http_buffer_free(&qstr);
515 return FAILURE;
516 }
517
518 php_http_buffer_data(qstr, encoded_str, encoded_len);
519 php_http_buffer_free(&qstr);
520
521 return SUCCESS;
522 }
523
524 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)
525 {
526 if (pre_encoded_len && pre_encoded_str) {
527 php_http_buffer_append(qstr, pre_encoded_str, pre_encoded_len);
528 }
529
530 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)) {
531 return FAILURE;
532 }
533
534 return SUCCESS;
535 }
536
537 struct parse_state {
538 php_http_url_t url;
539 const char *ptr;
540 const char *end;
541 size_t maxlen;
542 off_t offset;
543 unsigned flags;
544 char buffer[1]; /* last member */
545 };
546
547 void php_http_url_free(php_http_url_t **url)
548 {
549 if (*url) {
550 efree(*url);
551 *url = NULL;
552 }
553 }
554
555 php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent)
556 {
557 php_http_url_t *cpy;
558 const char *end = NULL, *url_ptr = (const char *) url;
559 char *cpy_ptr;
560
561 end = MAX(url->scheme, end);
562 end = MAX(url->pass, end);
563 end = MAX(url->user, end);
564 end = MAX(url->host, end);
565 end = MAX(url->path, end);
566 end = MAX(url->query, end);
567 end = MAX(url->fragment, end);
568
569 if (end) {
570 end += strlen(end) + 1;
571 cpy_ptr = pecalloc(1, end - url_ptr, persistent);
572 cpy = (php_http_url_t *) cpy_ptr;
573
574 memcpy(cpy_ptr + sizeof(*cpy), url_ptr + sizeof(*url), end - url_ptr - sizeof(*url));
575
576 cpy->scheme = url->scheme ? cpy_ptr + (url->scheme - url_ptr) : NULL;
577 cpy->pass = url->pass ? cpy_ptr + (url->pass - url_ptr) : NULL;
578 cpy->user = url->user ? cpy_ptr + (url->user - url_ptr) : NULL;
579 cpy->host = url->host ? cpy_ptr + (url->host - url_ptr) : NULL;
580 cpy->path = url->path ? cpy_ptr + (url->path - url_ptr) : NULL;
581 cpy->query = url->query ? cpy_ptr + (url->query - url_ptr) : NULL;
582 cpy->fragment = url->fragment ? cpy_ptr + (url->fragment - url_ptr) : NULL;
583 } else {
584 cpy = ecalloc(1, sizeof(*url));
585 }
586
587 cpy->port = url->port;
588
589 return cpy;
590 }
591
592 static size_t parse_mb_utf8(unsigned *wc, const char *ptr, const char *end)
593 {
594 unsigned wchar;
595 size_t consumed = utf8towc(&wchar, (const unsigned char *) ptr, end - ptr);
596
597 if (!consumed || consumed == (size_t) -1) {
598 return 0;
599 }
600
601 if (wc) {
602 *wc = wchar;
603 }
604 return consumed;
605 }
606
607 #ifdef PHP_HTTP_HAVE_WCHAR
608 static size_t parse_mb_loc(unsigned *wc, const char *ptr, const char *end)
609 {
610 wchar_t wchar;
611 size_t consumed = 0;
612 #if defined(HAVE_MBRTOWC)
613 mbstate_t ps = {0};
614
615 consumed = mbrtowc(&wchar, ptr, end - ptr, &ps);
616 #elif defined(HAVE_MBTOWC)
617 consumed = mbtowc(&wchar, ptr, end - ptr);
618 #endif
619
620 if (!consumed || consumed == (size_t) -1) {
621 return 0;
622 }
623
624 if (wc) {
625 *wc = wchar;
626 }
627 return consumed;
628 }
629 #endif
630
631 typedef enum parse_mb_what {
632 PARSE_SCHEME,
633 PARSE_USERINFO,
634 PARSE_HOSTINFO,
635 PARSE_PATH,
636 PARSE_QUERY,
637 PARSE_FRAGMENT
638 } parse_mb_what_t;
639
640 static const char * const parse_what[] = {
641 "scheme",
642 "userinfo",
643 "hostinfo",
644 "path",
645 "query",
646 "fragment"
647 };
648
649 static const char parse_xdigits[] = "0123456789ABCDEF";
650
651 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)
652 {
653 unsigned wchar;
654 size_t consumed = 0;
655
656 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
657 consumed = parse_mb_utf8(&wchar, ptr, end);
658 }
659 #ifdef PHP_HTTP_HAVE_WCHAR
660 else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
661 consumed = parse_mb_loc(&wchar, ptr, end);
662 }
663 #endif
664
665 while (consumed) {
666 if (!(state->flags & PHP_HTTP_URL_PARSE_TOPCT) || what == PARSE_HOSTINFO || what == PARSE_SCHEME) {
667 if (what == PARSE_HOSTINFO && (state->flags & PHP_HTTP_URL_PARSE_TOIDN)) {
668 /* idna */
669 } else if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
670 if (!isualnum(wchar)) {
671 break;
672 }
673 #ifdef PHP_HTTP_HAVE_WCHAR
674 } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
675 if (!iswalnum(wchar)) {
676 break;
677 }
678 #endif
679 }
680 PHP_HTTP_DUFF(consumed, state->buffer[state->offset++] = *ptr++);
681 } else {
682 int i = 0;
683
684 PHP_HTTP_DUFF(consumed,
685 state->buffer[state->offset++] = '%';
686 state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) >> 4];
687 state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) & 0xf];
688 ++i;
689 );
690 }
691
692 return consumed;
693 }
694
695 if (!silent) {
696 php_error_docref(NULL, E_WARNING,
697 "Failed to parse %s; unexpected byte 0x%02x at pos %u in '%s'",
698 parse_what[what], (unsigned char) *ptr, (unsigned) (ptr - begin), begin);
699 }
700
701 return 0;
702 }
703
704 static ZEND_RESULT_CODE parse_userinfo(struct parse_state *state, const char *ptr)
705 {
706 size_t mb;
707 const char *password = NULL, *end = state->ptr, *tmp = ptr;
708
709 state->url.user = &state->buffer[state->offset];
710
711 do {
712 switch (*ptr) {
713 case ':':
714 if (password) {
715 php_error_docref(NULL, E_WARNING,
716 "Failed to parse password; duplicate ':' at pos %u in '%s'",
717 (unsigned) (ptr - tmp), tmp);
718 return FAILURE;
719 }
720 password = ptr + 1;
721 state->buffer[state->offset++] = 0;
722 state->url.pass = &state->buffer[state->offset];
723 break;
724
725 case '%':
726 if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
727 php_error_docref(NULL, E_WARNING,
728 "Failed to parse userinfo; invalid percent encoding at pos %u in '%s'",
729 (unsigned) (ptr - tmp), tmp);
730 return FAILURE;
731 }
732 state->buffer[state->offset++] = *ptr++;
733 state->buffer[state->offset++] = *ptr++;
734 state->buffer[state->offset++] = *ptr;
735 break;
736
737 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
738 case '+': case ',': case ';': case '=': /* sub-delims */
739 case '-': case '.': case '_': case '~': /* unreserved */
740 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
741 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
742 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
743 case 'V': case 'W': case 'X': case 'Y': case 'Z':
744 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
745 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
746 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
747 case 'v': case 'w': case 'x': case 'y': case 'z':
748 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
749 case '7': case '8': case '9':
750 /* allowed */
751 state->buffer[state->offset++] = *ptr;
752 break;
753
754 default:
755 if (!(mb = parse_mb(state, PARSE_USERINFO, ptr, end, tmp, 0))) {
756 return FAILURE;
757 }
758 ptr += mb - 1;
759 }
760 } while(++ptr != end);
761
762
763 state->buffer[state->offset++] = 0;
764
765 return SUCCESS;
766 }
767
768 static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *ptr)
769 {
770 size_t mb, len;
771 const char *end = state->ptr, *tmp = ptr, *port = NULL;
772
773
774 #ifdef HAVE_INET_PTON
775 if (*ptr == '[') {
776 char *error = NULL, *tmp = memchr(ptr, ']', end - ptr);
777
778 if (tmp) {
779 size_t addrlen = tmp - ptr + 1;
780 char buf[16], *addr = estrndup(ptr + 1, addrlen - 2);
781 int rv = inet_pton(AF_INET6, addr, buf);
782
783 efree(addr);
784 if (rv == 1) {
785 state->buffer[state->offset] = '[';
786 state->url.host = &state->buffer[state->offset];
787 inet_ntop(AF_INET6, buf, state->url.host + 1, state->maxlen - state->offset);
788 state->offset += strlen(state->url.host);
789 state->buffer[state->offset++] = ']';
790 state->buffer[state->offset++] = 0;
791 ptr = tmp + 1;
792 } else if (rv == -1) {
793 error = strerror(errno);
794 } else {
795 error = "unexpected '['";
796 }
797 } else {
798 error = "expected ']'";
799 }
800
801 if (error) {
802 php_error_docref(NULL, E_WARNING, "Failed to parse hostinfo; %s", error);
803 return FAILURE;
804 }
805 }
806 #endif
807 if (ptr != end) do {
808 switch (*ptr) {
809 case ':':
810 if (port) {
811 php_error_docref(NULL, E_WARNING,
812 "Failed to parse port; unexpected ':' at pos %u in '%s'",
813 (unsigned) (ptr - tmp), tmp);
814 return FAILURE;
815 }
816 port = ptr + 1;
817 break;
818
819 case '%':
820 if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
821 php_error_docref(NULL, E_WARNING,
822 "Failed to parse hostinfo; invalid percent encoding at pos %u in '%s'",
823 (unsigned) (ptr - tmp), tmp);
824 return FAILURE;
825 }
826 state->buffer[state->offset++] = *ptr++;
827 state->buffer[state->offset++] = *ptr++;
828 state->buffer[state->offset++] = *ptr;
829 break;
830
831 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
832 case '+': case ',': case ';': case '=': /* sub-delims */
833 case '-': case '.': case '_': case '~': /* unreserved */
834 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
835 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
836 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
837 case 'V': case 'W': case 'X': case 'Y': case 'Z':
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 if (port) {
843 php_error_docref(NULL, E_WARNING,
844 "Failed to parse port; unexpected char '%c' at pos %u in '%s'",
845 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
846 return FAILURE;
847 }
848 /* no break */
849 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
850 case '7': case '8': case '9':
851 /* allowed */
852 if (port) {
853 state->url.port *= 10;
854 state->url.port += *ptr - '0';
855 } else {
856 state->buffer[state->offset++] = *ptr;
857 }
858 break;
859
860 default:
861 if (port) {
862 php_error_docref(NULL, E_WARNING,
863 "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'",
864 (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
865 return FAILURE;
866 } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, 0))) {
867 return FAILURE;
868 }
869 ptr += mb - 1;
870 }
871 } while (++ptr != end);
872
873 if (!state->url.host) {
874 len = (port ? port - tmp - 1 : end - tmp);
875 state->url.host = &state->buffer[state->offset - len];
876 state->buffer[state->offset++] = 0;
877 }
878
879 #ifdef PHP_HTTP_HAVE_IDN
880 if (state->flags & PHP_HTTP_URL_PARSE_TOIDN) {
881 char *idn = NULL;
882 int rv = -1;
883
884 if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) {
885 rv = idna_to_ascii_8z(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES);
886 }
887 # ifdef PHP_HTTP_HAVE_WCHAR
888 else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) {
889 rv = idna_to_ascii_lz(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES);
890 }
891 # endif
892 if (rv != IDNA_SUCCESS) {
893 php_error_docref(NULL, E_WARNING, "Failed to parse IDN; %s", idna_strerror(rv));
894 return FAILURE;
895 } else {
896 size_t idnlen = strlen(idn);
897 memcpy(state->url.host, idn, idnlen + 1);
898 free(idn);
899 state->offset += idnlen - len;
900 }
901 }
902 #endif
903
904 return SUCCESS;
905 }
906
907 static const char *parse_authority(struct parse_state *state)
908 {
909 const char *tmp = state->ptr, *host = NULL;
910
911 do {
912 switch (*state->ptr) {
913 case '@':
914 /* userinfo delimiter */
915 if (host) {
916 php_error_docref(NULL, E_WARNING,
917 "Failed to parse userinfo; unexpected '@'");
918 return NULL;
919 }
920 host = state->ptr + 1;
921 if (tmp != state->ptr && SUCCESS != parse_userinfo(state, tmp)) {
922 return NULL;
923 }
924 tmp = state->ptr + 1;
925 break;
926
927 case '/':
928 case '?':
929 case '#':
930 case '\0':
931 /* host delimiter */
932 if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) {
933 return NULL;
934 }
935 return state->ptr;
936 }
937 } while (++state->ptr <= state->end);
938
939 return NULL;
940 }
941
942 static const char *parse_path(struct parse_state *state)
943 {
944 size_t mb;
945 const char *tmp;
946
947 /* is there actually a path to parse? */
948 if (!*state->ptr) {
949 return state->ptr;
950 }
951 tmp = state->ptr;
952 state->url.path = &state->buffer[state->offset];
953
954 do {
955 switch (*state->ptr) {
956 case '#':
957 case '?':
958 goto done;
959
960 case '%':
961 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
962 php_error_docref(NULL, E_WARNING,
963 "Failed to parse path; invalid percent encoding at pos %u in '%s'",
964 (unsigned) (state->ptr - tmp), tmp);
965 return NULL;
966 }
967 state->buffer[state->offset++] = *state->ptr++;
968 state->buffer[state->offset++] = *state->ptr++;
969 state->buffer[state->offset++] = *state->ptr;
970 break;
971
972 case '/': /* yeah, well */
973 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
974 case '+': case ',': case ';': case '=': /* sub-delims */
975 case '-': case '.': case '_': case '~': /* unreserved */
976 case ':': case '@': /* pchar */
977 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
978 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
979 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
980 case 'V': case 'W': case 'X': case 'Y': case 'Z':
981 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
982 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
983 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
984 case 'v': case 'w': case 'x': case 'y': case 'z':
985 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
986 case '7': case '8': case '9':
987 /* allowed */
988 state->buffer[state->offset++] = *state->ptr;
989 break;
990
991 default:
992 if (!(mb = parse_mb(state, PARSE_PATH, state->ptr, state->end, tmp, 0))) {
993 return NULL;
994 }
995 state->ptr += mb - 1;
996 }
997 } while (++state->ptr < state->end);
998
999 done:
1000 /* did we have any path component ? */
1001 if (tmp != state->ptr) {
1002 state->buffer[state->offset++] = 0;
1003 } else {
1004 state->url.path = NULL;
1005 }
1006 return state->ptr;
1007 }
1008
1009 static const char *parse_query(struct parse_state *state)
1010 {
1011 size_t mb;
1012 const char *tmp = state->ptr + !!*state->ptr;
1013
1014 /* is there actually a query to parse? */
1015 if (*state->ptr != '?') {
1016 return state->ptr;
1017 }
1018
1019 /* skip initial '?' */
1020 tmp = ++state->ptr;
1021 state->url.query = &state->buffer[state->offset];
1022
1023 do {
1024 switch (*state->ptr) {
1025 case '#':
1026 goto done;
1027
1028 case '%':
1029 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
1030 php_error_docref(NULL, E_WARNING,
1031 "Failed to parse query; invalid percent encoding at pos %u in '%s'",
1032 (unsigned) (state->ptr - tmp), tmp);
1033 return NULL;
1034 }
1035 state->buffer[state->offset++] = *state->ptr++;
1036 state->buffer[state->offset++] = *state->ptr++;
1037 state->buffer[state->offset++] = *state->ptr;
1038 break;
1039
1040 case ']':
1041 case '[':
1042 if (state->flags & PHP_HTTP_URL_PARSE_TOPCT) {
1043 state->buffer[state->offset++] = '%';
1044 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) >> 4];
1045 state->buffer[state->offset++] = parse_xdigits[((unsigned char) *state->ptr) & 0xf];
1046 break;
1047 }
1048 /* no break */
1049
1050 case '?': case '/': /* yeah, well */
1051 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1052 case '+': case ',': case ';': case '=': /* sub-delims */
1053 case '-': case '.': case '_': case '~': /* unreserved */
1054 case ':': case '@': /* pchar */
1055 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1056 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1057 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1058 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1059 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1060 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1061 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1062 case 'v': case 'w': case 'x': case 'y': case 'z':
1063 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1064 case '7': case '8': case '9':
1065 /* allowed */
1066 state->buffer[state->offset++] = *state->ptr;
1067 break;
1068
1069 default:
1070 if (!(mb = parse_mb(state, PARSE_QUERY, state->ptr, state->end, tmp, 0))) {
1071 return NULL;
1072 }
1073 state->ptr += mb - 1;
1074 }
1075 } while (++state->ptr < state->end);
1076
1077 done:
1078 state->buffer[state->offset++] = 0;
1079 return state->ptr;
1080 }
1081
1082 static const char *parse_fragment(struct parse_state *state)
1083 {
1084 size_t mb;
1085 const char *tmp;
1086
1087 /* is there actually a fragment to parse? */
1088 if (*state->ptr != '#') {
1089 return state->ptr;
1090 }
1091
1092 /* skip initial '#' */
1093 tmp = ++state->ptr;
1094 state->url.fragment = &state->buffer[state->offset];
1095
1096 do {
1097 switch (*state->ptr) {
1098 case '%':
1099 if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
1100 php_error_docref(NULL, E_WARNING,
1101 "Failed to parse fragment; invalid percent encoding at pos %u in '%s'",
1102 (unsigned) (state->ptr - tmp), tmp);
1103 return NULL;
1104 }
1105 state->buffer[state->offset++] = *state->ptr++;
1106 state->buffer[state->offset++] = *state->ptr++;
1107 state->buffer[state->offset++] = *state->ptr;
1108 break;
1109
1110 case '?': case '/':
1111 case '!': case '$': case '&': case '\'': case '(': case ')': case '*':
1112 case '+': case ',': case ';': case '=': /* sub-delims */
1113 case '-': case '.': case '_': case '~': /* unreserved */
1114 case ':': case '@': /* pchar */
1115 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1116 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1117 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1118 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1119 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1120 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1121 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1122 case 'v': case 'w': case 'x': case 'y': case 'z':
1123 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1124 case '7': case '8': case '9':
1125 /* allowed */
1126 state->buffer[state->offset++] = *state->ptr;
1127 break;
1128
1129 default:
1130 if (!(mb = parse_mb(state, PARSE_FRAGMENT, state->ptr, state->end, tmp, 0))) {
1131 return NULL;
1132 }
1133 state->ptr += mb - 1;
1134 }
1135 } while (++state->ptr < state->end);
1136
1137 state->buffer[state->offset++] = 0;
1138 return state->ptr;
1139 }
1140
1141 static const char *parse_hier(struct parse_state *state)
1142 {
1143 if (*state->ptr == '/') {
1144 if (state->end - state->ptr > 1) {
1145 if (*(state->ptr + 1) == '/') {
1146 state->ptr += 2;
1147 if (!(state->ptr = parse_authority(state))) {
1148 return NULL;
1149 }
1150 }
1151 }
1152 }
1153 return parse_path(state);
1154 }
1155
1156 static const char *parse_scheme(struct parse_state *state)
1157 {
1158 size_t mb;
1159 const char *tmp = state->ptr;
1160
1161 do {
1162 switch (*state->ptr) {
1163 case ':':
1164 /* scheme delimiter */
1165 state->url.scheme = &state->buffer[0];
1166 state->buffer[state->offset++] = 0;
1167 return ++state->ptr;
1168
1169 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1170 case '7': case '8': case '9':
1171 case '+': case '-': case '.':
1172 if (state->ptr == tmp) {
1173 return tmp;
1174 }
1175 /* no break */
1176 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1177 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1178 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1179 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1180 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1181 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1182 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1183 case 'v': case 'w': case 'x': case 'y': case 'z':
1184 /* scheme part */
1185 state->buffer[state->offset++] = *state->ptr;
1186 break;
1187
1188 default:
1189 if (!(mb = parse_mb(state, PARSE_SCHEME, state->ptr, state->end, tmp, 1))) {
1190 /* soft fail; parse path next */
1191 return tmp;
1192 }
1193 state->ptr += mb - 1;
1194 }
1195 } while (++state->ptr != state->end);
1196
1197 return state->ptr = tmp;
1198 }
1199
1200 php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags)
1201 {
1202 size_t maxlen = 3 * len;
1203 struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen);
1204
1205 state->end = str + len;
1206 state->ptr = str;
1207 state->flags = flags;
1208 state->maxlen = maxlen;
1209
1210 if (!parse_scheme(state)) {
1211 php_error_docref(NULL, E_WARNING, "Failed to parse URL scheme: '%s'", state->ptr);
1212 efree(state);
1213 return NULL;
1214 }
1215
1216 if (!parse_hier(state)) {
1217 efree(state);
1218 return NULL;
1219 }
1220
1221 if (!parse_query(state)) {
1222 php_error_docref(NULL, E_WARNING, "Failed to parse URL query: '%s'", state->ptr);
1223 efree(state);
1224 return NULL;
1225 }
1226
1227 if (!parse_fragment(state)) {
1228 php_error_docref(NULL, E_WARNING, "Failed to parse URL fragment: '%s'", state->ptr);
1229 efree(state);
1230 return NULL;
1231 }
1232
1233 return (php_http_url_t *) state;
1234 }
1235
1236 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0)
1237 ZEND_ARG_INFO(0, old_url)
1238 ZEND_ARG_INFO(0, new_url)
1239 ZEND_ARG_INFO(0, flags)
1240 ZEND_END_ARG_INFO();
1241 PHP_METHOD(HttpUrl, __construct)
1242 {
1243 zval *new_url = NULL, *old_url = NULL;
1244 zend_long flags = PHP_HTTP_URL_FROM_ENV;
1245 zend_error_handling zeh;
1246
1247 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!z!l", &old_url, &new_url, &flags), invalid_arg, return);
1248
1249 zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh);
1250 {
1251 php_http_url_t *res_purl, *new_purl = NULL, *old_purl = NULL;
1252
1253 if (new_url) {
1254 new_purl = php_http_url_from_zval(new_url, flags);
1255 if (!new_purl) {
1256 zend_restore_error_handling(&zeh);
1257 return;
1258 }
1259 }
1260 if (old_url) {
1261 old_purl = php_http_url_from_zval(old_url, flags);
1262 if (!old_purl) {
1263 if (new_purl) {
1264 php_http_url_free(&new_purl);
1265 }
1266 zend_restore_error_handling(&zeh);
1267 return;
1268 }
1269 }
1270
1271 res_purl = php_http_url_mod(old_purl, new_purl, flags);
1272 php_http_url_to_struct(res_purl, getThis());
1273
1274 php_http_url_free(&res_purl);
1275 if (old_purl) {
1276 php_http_url_free(&old_purl);
1277 }
1278 if (new_purl) {
1279 php_http_url_free(&new_purl);
1280 }
1281 }
1282 zend_restore_error_handling(&zeh);
1283 }
1284
1285 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_mod, 0, 0, 1)
1286 ZEND_ARG_INFO(0, more_url_parts)
1287 ZEND_ARG_INFO(0, flags)
1288 ZEND_END_ARG_INFO();
1289 PHP_METHOD(HttpUrl, mod)
1290 {
1291 zval *new_url = NULL;
1292 zend_long flags = PHP_HTTP_URL_JOIN_PATH | PHP_HTTP_URL_JOIN_QUERY;
1293 zend_error_handling zeh;
1294
1295 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!|l", &new_url, &flags), invalid_arg, return);
1296
1297 zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh);
1298 {
1299 php_http_url_t *new_purl = NULL, *old_purl = NULL;
1300
1301 if (new_url) {
1302 new_purl = php_http_url_from_zval(new_url, flags);
1303 if (!new_purl) {
1304 zend_restore_error_handling(&zeh);
1305 return;
1306 }
1307 }
1308
1309 if ((old_purl = php_http_url_from_struct(HASH_OF(getThis())))) {
1310 php_http_url_t *res_purl;
1311
1312 ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis()));
1313
1314 res_purl = php_http_url_mod(old_purl, new_purl, flags);
1315 php_http_url_to_struct(res_purl, return_value);
1316
1317 php_http_url_free(&res_purl);
1318 php_http_url_free(&old_purl);
1319 }
1320 if (new_purl) {
1321 php_http_url_free(&new_purl);
1322 }
1323 }
1324 zend_restore_error_handling(&zeh);
1325 }
1326
1327 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_toString, 0, 0, 0)
1328 ZEND_END_ARG_INFO();
1329 PHP_METHOD(HttpUrl, toString)
1330 {
1331 if (SUCCESS == zend_parse_parameters_none()) {
1332 php_http_url_t *purl;
1333
1334 if ((purl = php_http_url_from_struct(HASH_OF(getThis())))) {
1335 char *str;
1336 size_t len;
1337
1338 php_http_url_to_string(purl, &str, &len, 0);
1339 php_http_url_free(&purl);
1340 RETURN_STR(php_http_cs2zs(str, len));
1341 }
1342 }
1343 RETURN_EMPTY_STRING();
1344 }
1345
1346 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_toArray, 0, 0, 0)
1347 ZEND_END_ARG_INFO();
1348 PHP_METHOD(HttpUrl, toArray)
1349 {
1350 php_http_url_t *purl;
1351
1352 if (SUCCESS != zend_parse_parameters_none()) {
1353 return;
1354 }
1355
1356 /* strip any non-URL properties */
1357 purl = php_http_url_from_struct(HASH_OF(getThis()));
1358 php_http_url_to_struct(purl, return_value);
1359 php_http_url_free(&purl);
1360 }
1361
1362 static zend_function_entry php_http_url_methods[] = {
1363 PHP_ME(HttpUrl, __construct, ai_HttpUrl___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
1364 PHP_ME(HttpUrl, mod, ai_HttpUrl_mod, ZEND_ACC_PUBLIC)
1365 PHP_ME(HttpUrl, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
1366 ZEND_MALIAS(HttpUrl, __toString, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
1367 PHP_ME(HttpUrl, toArray, ai_HttpUrl_toArray, ZEND_ACC_PUBLIC)
1368 EMPTY_FUNCTION_ENTRY
1369 };
1370
1371 zend_class_entry *php_http_url_class_entry;
1372
1373 PHP_MINIT_FUNCTION(http_url)
1374 {
1375 zend_class_entry ce = {0};
1376
1377 INIT_NS_CLASS_ENTRY(ce, "http", "Url", php_http_url_methods);
1378 php_http_url_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
1379
1380 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC TSRMLS_CC);
1381 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC TSRMLS_CC);
1382 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC TSRMLS_CC);
1383 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC TSRMLS_CC);
1384 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC TSRMLS_CC);
1385 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC TSRMLS_CC);
1386 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC TSRMLS_CC);
1387 zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC TSRMLS_CC);
1388
1389 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE TSRMLS_CC);
1390 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH TSRMLS_CC);
1391 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC);
1392 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER TSRMLS_CC);
1393 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS TSRMLS_CC);
1394 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH TSRMLS_CC);
1395 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT TSRMLS_CC);
1396 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH TSRMLS_CC);
1397 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY TSRMLS_CC);
1398 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT TSRMLS_CC);
1399 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL TSRMLS_CC);
1400 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV TSRMLS_CC);
1401 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SANITIZE_PATH"), PHP_HTTP_URL_SANITIZE_PATH TSRMLS_CC);
1402
1403 #ifdef PHP_HTTP_HAVE_WCHAR
1404 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBLOC"), PHP_HTTP_URL_PARSE_MBLOC TSRMLS_CC);
1405 #endif
1406 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBUTF8"), PHP_HTTP_URL_PARSE_MBUTF8 TSRMLS_CC);
1407 #ifdef PHP_HTTP_HAVE_IDN
1408 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN"), PHP_HTTP_URL_PARSE_TOIDN TSRMLS_CC);
1409 #endif
1410 zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT TSRMLS_CC);
1411
1412 return SUCCESS;
1413 }
1414
1415
1416 /*
1417 * Local variables:
1418 * tab-width: 4
1419 * c-basic-offset: 4
1420 * End:
1421 * vim600: noet sw=4 ts=4 fdm=marker
1422 * vim<600: noet sw=4 ts=4
1423 */
1424