LCOV - code coverage report
Current view: top level - http - php_http_url.c (source / functions) Hit Total Coverage
Test: PHP Code Coverage Lines: 450 565 79.6 %
Date: 2014-11-03 12:21:11 Functions: 24 24 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.11