X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=blobdiff_plain;f=php_http_url.c;h=05850e8c42a74482c90a8725147ebf4d4d36f751;hp=c4eb90b5d34d395ccb2acd9dacf42f235b4af0f2;hb=31636a8ec8578e7e4d45da180d55e1260bb527db;hpb=731b6a841374147b766c850b2a90c6bfade6f9f1 diff --git a/php_http_url.c b/php_http_url.c index c4eb90b..05850e8 100644 --- a/php_http_url.c +++ b/php_http_url.c @@ -12,6 +12,21 @@ #include "php_http_api.h" +#ifdef PHP_HTTP_HAVE_IDN +# include +#endif + +#ifdef PHP_HTTP_HAVE_WCHAR +# include +# include +#endif + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#include "php_http_utf8.h" + static inline char *localhostname(void) { char hostname[1024] = {0}; @@ -42,94 +57,6 @@ static inline char *localhostname(void) return estrndup("localhost", lenof("localhost")); } -static inline unsigned port(const char *scheme) -{ - unsigned port = 80; - -#if defined(ZTS) && defined(HAVE_GETSERVBYPORT_R) - int rc; - size_t len = 0xff; - char *buf = NULL; - struct servent *se_res = NULL, se_buf = {0}; - - do { - buf = erealloc(buf, len); - rc = getservbyname_r(scheme, "tcp", &se_buf, buf, len, &se_res); - len *= 2; - } while (rc == ERANGE && len <= 0xfff); - - if (!rc) { - port = ntohs(se_res->s_port); - } - - efree(buf); -#elif !defined(ZTS) && defined(HAVE_GETSERVBYPORT) - struct servent *se; - - if ((se = getservbyname(scheme, "tcp")) && se->s_port) { - port = ntohs(se->s_port); - } -#endif - - return port; -} -static inline char *scheme(unsigned port) -{ - char *scheme; -#if defined(ZTS) && defined(HAVE_GETSERVBYPORT_R) - int rc; - size_t len = 0xff; - char *buf = NULL; - struct servent *se_res = NULL, se_buf = {0}; -#elif !defined(ZTS) && defined(HAVE_GETSERVBYPORT) - struct servent *se; -#endif - - switch (port) { - case 443: - scheme = estrndup("https", lenof("https")); - break; - -#if defined(ZTS) && !defined(HAVE_GETSERVBYPORT_R) - default: -#elif !defined(ZTS) && !defined(HAVE_GETSERVBYPORT) - default: -#endif - case 80: - case 0: - scheme = estrndup("http", lenof("http")); - break; - -#if defined(ZTS) && defined(HAVE_GETSERVBYPORT_R) - default: - do { - buf = erealloc(buf, len); - rc = getservbyport_r(htons(port), "tcp", &se_buf, buf, len, &se_res); - len *= 2; - } while (rc == ERANGE && len <= 0xfff); - - if (!rc && se_res) { - scheme = estrdup(se_res->s_name); - } else { - scheme = estrndup("http", lenof("http")); - } - - efree(buf); - break; - -#elif !defined(ZTS) && defined(HAVE_GETSERVBYPORT) - default: - if ((se = getservbyport(htons(port), "tcp")) && se->s_name) { - scheme = estrdup(se->s_name); - } else { - scheme = estrndup("http", lenof("http")); - } - break; -#endif - } - return scheme; -} - static php_url *php_http_url_from_env(php_url *url TSRMLS_DC) { zval *https, *zhost, *zport; @@ -150,7 +77,7 @@ static php_url *php_http_url_from_env(php_url *url TSRMLS_DC) if (https && !strcasecmp(Z_STRVAL_P(https), "ON")) { url->scheme = estrndup("https", lenof("https")); } else { - url->scheme = scheme(url->port); + url->scheme = estrndup("http", lenof("http")); } /* host */ @@ -339,7 +266,6 @@ void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php if (url->port) { if ( ((url->port == 80) && !strcmp(url->scheme, "http")) || ((url->port ==443) && !strcmp(url->scheme, "https")) - || ( url->port == port(url->scheme)) ) { url->port = 0; } @@ -388,6 +314,672 @@ STATUS php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, con return SUCCESS; } +struct parse_state { + php_http_url_t url; +#ifdef ZTS + void ***ts; +#endif + const char *ptr; + const char *end; + size_t maxlen; + off_t offset; + unsigned flags; + char buffer[1]; /* last member */ +}; + +void php_http_url_free(php_http_url_t **url) +{ + if (*url) { + efree(*url); + *url = NULL; + } +} + +static size_t parse_mb_utf8(unsigned *wc, const char *ptr, const char *end) +{ + unsigned wchar; + size_t consumed = utf8towc(&wchar, (const unsigned char *) ptr, end - ptr); + + if (!consumed || consumed == (size_t) -1) { + return 0; + } + + if (wc) { + *wc = wchar; + } + return consumed; +} + +#ifdef PHP_HTTP_HAVE_WCHAR +static size_t parse_mb_loc(unsigned *wc, const char *ptr, const char *end) +{ + wchar_t wchar; + size_t consumed = 0; +#if defined(HAVE_MBRTOWC) + mbstate_t ps = {0}; + + consumed = mbrtowc(&wchar, ptr, end - ptr, &ps); +#elif defined(HAVE_MBTOWC) + consumed = mbtowc(&wchar, ptr, end - ptr); +#endif + + if (!consumed || consumed == (size_t) -1) { + return 0; + } + + if (wc) { + *wc = wchar; + } + return consumed; +} +#endif + +typedef enum parse_mb_what { + PARSE_SCHEME, + PARSE_USERINFO, + PARSE_HOSTINFO, + PARSE_PATH, + PARSE_QUERY, + PARSE_FRAGMENT +} parse_mb_what_t; + +static const char * const parse_what[] = { + "scheme", + "userinfo", + "hostinfo", + "path", + "query", + "fragment" +}; + +static const char parse_xdigits[] = "0123456789ABCDEF"; + +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) +{ + unsigned wchar; + size_t consumed = 0; + + if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) { + consumed = parse_mb_utf8(&wchar, ptr, end); + } +#ifdef PHP_HTTP_HAVE_WCHAR + else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) { + consumed = parse_mb_loc(&wchar, ptr, end); + } +#endif + + while (consumed) { + if (!(state->flags & PHP_HTTP_URL_PARSE_TOPCT) || what == PARSE_HOSTINFO || what == PARSE_SCHEME) { + if (what == PARSE_HOSTINFO && (state->flags & PHP_HTTP_URL_PARSE_TOIDN)) { + /* idna */ + } else if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) { + if (!isualnum(wchar)) { + break; + } +#ifdef PHP_HTTP_HAVE_WCHAR + } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) { + if (!iswalnum(wchar)) { + break; + } +#endif + } + PHP_HTTP_DUFF(consumed, state->buffer[state->offset++] = *ptr++); + } else { + int i = 0; + + PHP_HTTP_DUFF(consumed, + state->buffer[state->offset++] = '%'; + state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) >> 4]; + state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) & 0xf]; + ++i; + ); + } + + return consumed; + } + + if (!silent) { + TSRMLS_FETCH_FROM_CTX(state->ts); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse %s; unexpected byte 0x%02x at pos %u in '%s'", + parse_what[what], (unsigned char) *ptr, (unsigned) (ptr - begin), begin); + } + + return 0; +} + +static STATUS parse_userinfo(struct parse_state *state, const char *ptr) +{ + size_t mb; + const char *password = NULL, *end = state->ptr, *tmp = ptr; + TSRMLS_FETCH_FROM_CTX(state->ts); + + state->url.user = &state->buffer[state->offset]; + + do { + switch (*ptr) { + case ':': + if (password) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse password; duplicate ':' at pos %u in '%s'", + (unsigned) (ptr - tmp), tmp); + return FAILURE; + } + password = ptr + 1; + state->buffer[state->offset++] = 0; + state->url.pass = &state->buffer[state->offset]; + break; + + case '%': + if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse userinfo; invalid percent encoding at pos %u in '%s'", + (unsigned) (ptr - tmp), tmp); + return FAILURE; + } + state->buffer[state->offset++] = *ptr++; + state->buffer[state->offset++] = *ptr++; + state->buffer[state->offset++] = *ptr; + break; + + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': /* sub-delims */ + case '-': case '.': case '_': case '~': /* unreserved */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + /* allowed */ + state->buffer[state->offset++] = *ptr; + break; + + default: + if (!(mb = parse_mb(state, PARSE_USERINFO, ptr, end, tmp, 0))) { + return FAILURE; + } + ptr += mb - 1; + } + } while(++ptr != end); + + + state->buffer[state->offset++] = 0; + + return SUCCESS; +} + +static STATUS parse_hostinfo(struct parse_state *state, const char *ptr) +{ + size_t mb, len; + const char *end = state->ptr, *tmp = ptr, *port = NULL; + TSRMLS_FETCH_FROM_CTX(state->ts); + + +#ifdef HAVE_INET_PTON + if (*ptr == '[') { + char *error = NULL, *tmp = memchr(ptr, ']', end - ptr); + + if (tmp) { + size_t addrlen = tmp - ptr + 1; + char buf[16], *addr = estrndup(ptr + 1, addrlen - 2); + int rv = inet_pton(AF_INET6, addr, buf); + + efree(addr); + if (rv == 1) { + state->buffer[state->offset] = '['; + state->url.host = &state->buffer[state->offset]; + inet_ntop(AF_INET6, buf, state->url.host + 1, state->maxlen - state->offset); + state->offset += strlen(state->url.host); + state->buffer[state->offset++] = ']'; + state->buffer[state->offset++] = 0; + ptr = tmp + 1; + } else if (rv == -1) { + error = strerror(errno); + } else { + error = "unexpected '['"; + } + } else { + error = "expected ']'"; + } + + if (error) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse hostinfo; %s", error); + return FAILURE; + } + } +#endif + if (ptr != end) do { + switch (*ptr) { + case ':': + if (port) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse port; unexpected ':' at pos %u in '%s'", + (unsigned) (ptr - tmp), tmp); + return FAILURE; + } + port = ptr + 1; + break; + + case '%': + if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse hostinfo; invalid percent encoding at pos %u in '%s'", + (unsigned) (ptr - tmp), tmp); + return FAILURE; + } + state->buffer[state->offset++] = *ptr++; + state->buffer[state->offset++] = *ptr++; + state->buffer[state->offset++] = *ptr; + break; + + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': /* sub-delims */ + case '-': case '.': case '_': case '~': /* unreserved */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + if (port) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse port; unexpected char '%c' at pos %u in '%s'", + (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp); + return FAILURE; + } + /* no break */ + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + /* allowed */ + if (port) { + state->url.port *= 10; + state->url.port += *ptr - '0'; + } else { + state->buffer[state->offset++] = *ptr; + } + break; + + default: + if (port) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'", + (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp); + return FAILURE; + } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, 0))) { + return FAILURE; + } + ptr += mb - 1; + } + } while (++ptr != end); + + if (!state->url.host) { + len = (port ? port - tmp - 1 : end - tmp); + state->url.host = &state->buffer[state->offset - len]; + state->buffer[state->offset++] = 0; + } + +#ifdef PHP_HTTP_HAVE_IDN + if (state->flags & PHP_HTTP_URL_PARSE_TOIDN) { + char *idn = NULL; + int rv = -1; + + if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) { + rv = idna_to_ascii_8z(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES); + } +# ifdef PHP_HTTP_HAVE_WCHAR + else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) { + rv = idna_to_ascii_lz(state->url.host, &idn, IDNA_ALLOW_UNASSIGNED|IDNA_USE_STD3_ASCII_RULES); + } +# endif + if (rv != IDNA_SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse IDN; %s", idna_strerror(rv)); + return FAILURE; + } else { + size_t idnlen = strlen(idn); + memcpy(state->url.host, idn, idnlen + 1); + free(idn); + state->offset += idnlen - len; + } + } +#endif + + return SUCCESS; +} + +static const char *parse_authority(struct parse_state *state) +{ + const char *tmp = state->ptr, *host = NULL; + + do { + switch (*state->ptr) { + case '@': + /* userinfo delimiter */ + if (host) { + TSRMLS_FETCH_FROM_CTX(state->ts); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse userinfo; unexpected '@'"); + return NULL; + } + host = state->ptr + 1; + if (tmp != state->ptr && SUCCESS != parse_userinfo(state, tmp)) { + return NULL; + } + tmp = state->ptr + 1; + break; + + case '/': + case '?': + case '#': + case '\0': + /* host delimiter */ + if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) { + return NULL; + } + return state->ptr; + } + } while (++state->ptr <= state->end); + + return NULL; +} + +static const char *parse_path(struct parse_state *state) +{ + size_t mb; + const char *tmp; + TSRMLS_FETCH_FROM_CTX(state->ts); + + /* is there actually a path to parse? */ + if (!*state->ptr) { + return state->ptr; + } + tmp = state->ptr; + state->url.path = &state->buffer[state->offset]; + + do { + switch (*state->ptr) { + case '#': + case '?': + case '\0': + /* did we have any path component ? */ + if (tmp != state->ptr) { + state->buffer[state->offset++] = 0; + } else { + state->url.path = NULL; + } + return state->ptr; + + case '%': + if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse path; invalid percent encoding at pos %u in '%s'", + (unsigned) (state->ptr - tmp), tmp); + return NULL; + } + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr; + break; + + case '/': /* yeah, well */ + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': /* sub-delims */ + case '-': case '.': case '_': case '~': /* unreserved */ + case ':': case '@': /* pchar */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + /* allowed */ + state->buffer[state->offset++] = *state->ptr; + break; + + default: + if (!(mb = parse_mb(state, PARSE_PATH, state->ptr, state->end, tmp, 0))) { + return NULL; + } + state->ptr += mb - 1; + } + } while (++state->ptr <= state->end); + + return NULL; +} + +static const char *parse_query(struct parse_state *state) +{ + size_t mb; + const char *tmp = state->ptr + !!*state->ptr; + TSRMLS_FETCH_FROM_CTX(state->ts); + + /* is there actually a query to parse? */ + if (*state->ptr != '?') { + return state->ptr; + } + + /* skip initial '?' */ + tmp = ++state->ptr; + state->url.query = &state->buffer[state->offset]; + + do { + switch (*state->ptr) { + case '#': + case '\0': + state->buffer[state->offset++] = 0; + return state->ptr; + + case '%': + if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse query; invalid percent encoding at pos %u in '%s'", + (unsigned) (state->ptr - tmp), tmp); + return NULL; + } + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr; + break; + + case '?': case '/': /* yeah, well */ + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': /* sub-delims */ + case '-': case '.': case '_': case '~': /* unreserved */ + case ':': case '@': /* pchar */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + /* allowed */ + state->buffer[state->offset++] = *state->ptr; + break; + + default: + if (!(mb = parse_mb(state, PARSE_QUERY, state->ptr, state->end, tmp, 0))) { + return NULL; + } + state->ptr += mb - 1; + } + } while (++state->ptr <= state->end); + + return NULL; +} + +static const char *parse_fragment(struct parse_state *state) +{ + size_t mb; + const char *tmp; + TSRMLS_FETCH_FROM_CTX(state->ts); + + /* is there actually a fragment to parse? */ + if (*state->ptr != '#') { + return state->ptr; + } + + /* skip initial '#' */ + tmp = ++state->ptr; + state->url.fragment = &state->buffer[state->offset]; + + do { + switch (*state->ptr) { + case '\0': + state->buffer[state->offset++] = 0; + return state->ptr; + + case '%': + if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse fragment; invalid percent encoding at pos %u in '%s'", + (unsigned) (state->ptr - tmp), tmp); + return NULL; + } + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr++; + state->buffer[state->offset++] = *state->ptr; + break; + + case '?': case '/': + case '!': case '$': case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case ';': case '=': /* sub-delims */ + case '-': case '.': case '_': case '~': /* unreserved */ + case ':': case '@': /* pchar */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + /* allowed */ + state->buffer[state->offset++] = *state->ptr; + break; + + default: + if (!(mb = parse_mb(state, PARSE_FRAGMENT, state->ptr, state->end, tmp, 0))) { + return NULL; + } + state->ptr += mb - 1; + } + } while (++state->ptr <= state->end); + + return NULL; +} + +static const char *parse_hier(struct parse_state *state) +{ + if (*state->ptr == '/') { + if (state->end - state->ptr > 1) { + if (*(state->ptr + 1) == '/') { + state->ptr += 2; + if (!(state->ptr = parse_authority(state))) { + return NULL; + } + } + } + } + return parse_path(state); +} + +static const char *parse_scheme(struct parse_state *state) +{ + size_t mb; + const char *tmp = state->ptr; + + do { + switch (*state->ptr) { + case ':': + /* scheme delimiter */ + state->url.scheme = &state->buffer[0]; + state->buffer[state->offset++] = 0; + return ++state->ptr; + + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + case '+': case '-': case '.': + if (state->ptr == tmp) { + return tmp; + } + /* no break */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + /* scheme part */ + state->buffer[state->offset++] = *state->ptr; + break; + + default: + if (!(mb = parse_mb(state, PARSE_SCHEME, state->ptr, state->end, tmp, 1))) { + /* soft fail; parse path next */ + return tmp; + } + state->ptr += mb - 1; + } + } while (++state->ptr != state->end); + + return tmp; +} + +php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags TSRMLS_DC) +{ + size_t maxlen = 3 * len; + struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen); + + state->end = str + len; + state->ptr = str; + state->flags = flags; + state->maxlen = maxlen; + TSRMLS_SET_CTX(state->ts); + + if (!parse_scheme(state)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse URL scheme: '%s'", state->ptr); + efree(state); + return NULL; + } + + if (!parse_hier(state)) { + efree(state); + return NULL; + } + + if (!parse_query(state)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse URL query: '%s'", state->ptr); + efree(state); + return NULL; + } + + if (!parse_fragment(state)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse URL fragment: '%s'", state->ptr); + efree(state); + return NULL; + } + + return (php_http_url_t *) state; +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0) ZEND_ARG_INFO(0, old_url) ZEND_ARG_INFO(0, new_url) @@ -550,12 +1142,67 @@ PHP_METHOD(HttpUrl, toArray) php_url_free(purl); } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_parse, 0, 0, 1) + ZEND_ARG_INFO(0, url) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO(); +PHP_METHOD(HttpUrl, parse) +{ + char *str; + int len; + long flags = 0; + php_http_url_t *url; + zend_error_handling zeh; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &len, &flags), invalid_arg, return); + + zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC); + if ((url = php_http_url_parse(str, len, flags TSRMLS_CC))) { + object_init_ex(return_value, php_http_url_class_entry); + if (url->scheme) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("scheme"), url->scheme TSRMLS_CC); + } + if (url->user) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("user"), url->user TSRMLS_CC); + } + if (url->pass) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("pass"), url->pass TSRMLS_CC); + } + if (url->host) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("host"), url->host TSRMLS_CC); + } + if (url->port) { + zend_update_property_long(php_http_url_class_entry, return_value, + ZEND_STRL("port"), url->port TSRMLS_CC); + } + if (url->path) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("path"), url->path TSRMLS_CC); + } + if (url->query) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("query"), url->query TSRMLS_CC); + } + if (url->fragment) { + zend_update_property_string(php_http_url_class_entry, return_value, + ZEND_STRL("fragment"), url->fragment TSRMLS_CC); + } + php_http_url_free(&url); + } + zend_restore_error_handling(&zeh TSRMLS_CC); +} + static zend_function_entry php_http_url_methods[] = { PHP_ME(HttpUrl, __construct, ai_HttpUrl___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(HttpUrl, mod, ai_HttpUrl_mod, ZEND_ACC_PUBLIC) PHP_ME(HttpUrl, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC) ZEND_MALIAS(HttpUrl, __toString, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC) PHP_ME(HttpUrl, toArray, ai_HttpUrl_toArray, ZEND_ACC_PUBLIC) + PHP_ME(HttpUrl, parse, ai_HttpUrl_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) EMPTY_FUNCTION_ENTRY }; @@ -591,6 +1238,15 @@ PHP_MINIT_FUNCTION(http_url) zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV TSRMLS_CC); zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SANITIZE_PATH"), PHP_HTTP_URL_SANITIZE_PATH TSRMLS_CC); +#ifdef PHP_HTTP_HAVE_WCHAR + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBLOC"), PHP_HTTP_URL_PARSE_MBLOC TSRMLS_CC); +#endif + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBUTF8"), PHP_HTTP_URL_PARSE_MBUTF8 TSRMLS_CC); +#ifdef PHP_HTTP_HAVE_IDN + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN"), PHP_HTTP_URL_PARSE_TOIDN TSRMLS_CC); +#endif + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT TSRMLS_CC); + return SUCCESS; }