Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ PHP NEWS
error in PHP 9. (alexandre-daubois)
. Fixed OSS-Fuzz #439125710 (Pipe cannot be used in write context).
(nielsdos)
. Added support for configuring the URI parser for the FTP/FTPS as well as
the SSL/TLS stream wrappers as described in
https://wiki.php.net/rfc/url_parsing_api#plugability. (kocsismate)

- Filter:
. Added support for configuring the URI parser for FILTER_VALIDATE_URL
as described in https://wiki.php.net/rfc/url_parsing_api#plugability.
(kocsismate)

- ODBC:
. Remove ODBCVER and assume ODBC 3.5. (Calvin Buckley)
Expand All @@ -23,6 +31,11 @@ PHP NEWS
- Session:
. Added support for partitioned cookies. (nielsdos)

- SOAP:
. Added support for configuring the URI parser for SoapClient::_doRequest()
as described in https://wiki.php.net/rfc/url_parsing_api#plugability.
(kocsismate)

- SPL:
. Deprecate ArrayObject and ArrayIterator with objects. (Girgias)

Expand Down
3 changes: 3 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ PHP 8.5 INTERNALS UPGRADE NOTES
. ext/standard/php_smart_string.h and ext/standard/php_smart_string_public.h
were removed. Use the corresponding headers in Zend/ instead.

- URI
. Internal API for URI handling was added via the php_uri_*() functions.

========================
2. Build system changes
========================
Expand Down
64 changes: 40 additions & 24 deletions ext/filter/logical_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
+----------------------------------------------------------------------+
*/

#include "zend_exceptions.h"
#include "php_filter.h"
#include "filter_private.h"
#include "ext/standard/url.h"
#include "ext/pcre/php_pcre.h"
#include "ext/uri/php_uri.h"

#include "zend_multiply.h"

Expand Down Expand Up @@ -89,6 +90,8 @@
#define FORMAT_IPV4 4
#define FORMAT_IPV6 6

#define URL_OPTION_URI_PARSER_CLASS "uri_parser_class"

static bool _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]);

static bool php_filter_parse_int(const char *str, size_t str_len, zend_long *ret) { /* {{{ */
Expand Down Expand Up @@ -591,7 +594,6 @@ static bool php_filter_is_valid_ipv6_hostname(const zend_string *s)

void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
{
php_url *url;
size_t old_len = Z_STRLEN_P(value);

php_filter_url(value, flags, option_array, charset);
Expand All @@ -600,52 +602,66 @@ void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
RETURN_VALIDATION_FAILED
}

/* Use parse_url - if it returns false, we return NULL */
url = php_url_parse_ex(Z_STRVAL_P(value), Z_STRLEN_P(value));
/* Parse options */
zval *option_val;
zend_string *parser_name;
int parser_name_set;
FETCH_STR_OPTION(parser_name, URL_OPTION_URI_PARSER_CLASS);

uri_handler_t *uri_handler = php_uri_get_handler(parser_name_set ? parser_name : NULL);
if (uri_handler == NULL) {
zend_value_error("%s(): \"uri_parser_class\" option has invalid value", get_active_function_name());
RETURN_VALIDATION_FAILED
}

if (url == NULL) {
/* Parse the URI - if it fails, we return NULL */
php_uri *uri = php_uri_parse_to_struct(uri_handler, Z_STRVAL_P(value), Z_STRLEN_P(value), URI_COMPONENT_READ_RAW, true);
if (uri == NULL) {
RETURN_VALIDATION_FAILED
}

if (url->scheme != NULL &&
(zend_string_equals_literal_ci(url->scheme, "http") || zend_string_equals_literal_ci(url->scheme, "https"))) {
if (uri->scheme != NULL &&
(zend_string_equals_literal_ci(uri->scheme, "http") || zend_string_equals_literal_ci(uri->scheme, "https"))) {

if (url->host == NULL) {
goto bad_url;
if (uri->host == NULL) {
php_uri_struct_free(uri);
RETURN_VALIDATION_FAILED
}

if (
/* Skipping these checks is possible because the new URI implementations perform comprehensive validations. */
strcmp(uri_handler->name, URI_PARSER_PHP) == 0 &&
/* An IPv6 enclosed by square brackets is a valid hostname.*/
!php_filter_is_valid_ipv6_hostname(url->host) &&
!php_filter_is_valid_ipv6_hostname(uri->host) &&
/* Validate domain.
* This includes a loose check for an IPv4 address. */
!php_filter_validate_domain_ex(url->host, FILTER_FLAG_HOSTNAME)
!php_filter_validate_domain_ex(uri->host, FILTER_FLAG_HOSTNAME)
) {
php_url_free(url);
php_uri_struct_free(uri);
RETURN_VALIDATION_FAILED
}
}

if (
url->scheme == NULL ||
/* some schemas allow the host to be empty */
(url->host == NULL && (!zend_string_equals_literal(url->scheme, "mailto") && !zend_string_equals_literal(url->scheme, "news") && !zend_string_equals_literal(url->scheme, "file"))) ||
((flags & FILTER_FLAG_PATH_REQUIRED) && url->path == NULL) || ((flags & FILTER_FLAG_QUERY_REQUIRED) && url->query == NULL)
if (uri->scheme == NULL ||
/* some schemes allow the host to be empty */
(uri->host == NULL && (!zend_string_equals_literal(uri->scheme, "mailto") && !zend_string_equals_literal(uri->scheme, "news") && !zend_string_equals_literal(uri->scheme, "file"))) ||
((flags & FILTER_FLAG_PATH_REQUIRED) && uri->path == NULL) || ((flags & FILTER_FLAG_QUERY_REQUIRED) && uri->query == NULL)
) {
bad_url:
php_url_free(url);
php_uri_struct_free(uri);
RETURN_VALIDATION_FAILED
}

if ((url->user != NULL && !is_userinfo_valid(url->user))
|| (url->pass != NULL && !is_userinfo_valid(url->pass))
if (strcmp(uri_handler->name, URI_PARSER_PHP) == 0 &&
(
(uri->user != NULL && !is_userinfo_valid(uri->user)) ||
(uri->password != NULL && !is_userinfo_valid(uri->password))
)
) {
php_url_free(url);
php_uri_struct_free(uri);
RETURN_VALIDATION_FAILED

}

php_url_free(url);
php_uri_struct_free(uri);
}
/* }}} */

Expand Down
184 changes: 184 additions & 0 deletions ext/filter/tests/062.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
--TEST--
filter_var() and FILTER_VALIDATE_URL with different URI parsers
--EXTENSIONS--
filter
--FILE--
<?php

function validateUrls(string $parserName)
{
$values = [
'http://example.com/index.html',
'http://www.example.com/index.php',
'http://www.example/img/test.png',
'http://www.example/img/dir/',
'http://www.example/img/dir',
'http://www.thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com/',
'http://toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong.com',
'http://eauBcFReEmjLcoZwI0RuONNnwU4H9r151juCaqTI5VeIP5jcYIqhx1lh5vV00l2rTs6y7hOp7rYw42QZiq6VIzjcYrRm8gFRMk9U9Wi1grL8Mr5kLVloYLthHgyA94QK3SaXCATklxgo6XvcbXIqAGG7U0KxTr8hJJU1p2ZQ2mXHmp4DhYP8N9SRuEKzaCPcSIcW7uj21jZqBigsLsNAXEzU8SPXZjmVQVtwQATPWeWyGW4GuJhjP4Q8o0.com',
'http://kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.CQ1oT5Uq3jJt6Uhy3VH9u3Gi5YhfZCvZVKgLlaXNFhVKB1zJxvunR7SJa.com.',
'http://kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58R.example.com',
'http://[2001:0db8:0000:85a3:0000:0000:ac1f:8001]',
'http://[2001:db8:0:85a3:0:0:ac1f:8001]:123/me.html',
'http://[2001:db8:0:85a3::ac1f:8001]/',
'http://[::1]',
'http://cont-ains.h-yph-en-s.com',
'http://..com',
'http://a.-bc.com',
'http://ab.cd-.com',
'http://-.abc.com',
'http://abc.-.abc.com',
'http://underscore_.example.com',
'http//www.example/wrong/url/',
'http:/www.example',
'file:///tmp/test.c',
'ftp://ftp.example.com/tmp/',
'/tmp/test.c',
'/',
'http://',
'http:/',
'http:',
'http',
'',
-1,
[],
'mailto:[email protected]',
'news:news.php.net',
'file://foo/bar',
"http://\r\n/bar",
"http://example.com:qq",
"http://example.com:-2",
"http://example.com:65536",
"http://example.com:65537",
];

foreach ($values as $value) {
var_dump(filter_var($value, FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
}

var_dump(filter_var("qwe", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
var_dump(filter_var("http://qwe", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
var_dump(filter_var("http://", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
var_dump(filter_var("/tmp/test", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
var_dump(filter_var("http://www.example.com", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName]));
var_dump(filter_var("http://www.example.com", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName, "flags" => FILTER_FLAG_PATH_REQUIRED]));
var_dump(filter_var("http://www.example.com/path/at/the/server/", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName, "flags" => FILTER_FLAG_PATH_REQUIRED]));
var_dump(filter_var("http://www.example.com/index.html", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName, "flags" => FILTER_FLAG_QUERY_REQUIRED]));
var_dump(filter_var("http://www.example.com/index.php?a=b&c=d", FILTER_VALIDATE_URL, ["uri_parser_class" => $parserName, "flags" => FILTER_FLAG_QUERY_REQUIRED]));
}

echo "RFC3986:\n";
validateUrls(Uri\Rfc3986Uri::class);

echo "\nWHATWG:\n";
validateUrls(Uri\WhatWgUri::class);

echo "Done\n";
?>
--EXPECT--
RFC3986:
string(29) "http://example.com/index.html"
string(32) "http://www.example.com/index.php"
string(31) "http://www.example/img/test.png"
string(27) "http://www.example/img/dir/"
string(26) "http://www.example/img/dir"
string(79) "http://www.thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com/"
bool(false)
bool(false)
string(261) "http://kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.CQ1oT5Uq3jJt6Uhy3VH9u3Gi5YhfZCvZVKgLlaXNFhVKB1zJxvunR7SJa.com."
bool(false)
string(48) "http://[2001:0db8:0000:85a3:0000:0000:ac1f:8001]"
string(50) "http://[2001:db8:0:85a3:0:0:ac1f:8001]:123/me.html"
string(36) "http://[2001:db8:0:85a3::ac1f:8001]/"
string(12) "http://[::1]"
string(31) "http://cont-ains.h-yph-en-s.com"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(18) "file:///tmp/test.c"
string(26) "ftp://ftp.example.com/tmp/"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(18) "mailto:[email protected]"
string(17) "news:news.php.net"
string(14) "file://foo/bar"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(10) "http://qwe"
bool(false)
bool(false)
string(22) "http://www.example.com"
bool(false)
string(42) "http://www.example.com/path/at/the/server/"
bool(false)
string(40) "http://www.example.com/index.php?a=b&c=d"

WHATWG:
string(29) "http://example.com/index.html"
string(32) "http://www.example.com/index.php"
string(31) "http://www.example/img/test.png"
string(27) "http://www.example/img/dir/"
string(26) "http://www.example/img/dir"
string(79) "http://www.thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com/"
bool(false)
bool(false)
string(261) "http://kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.kDTvHt1PPDgX5EiP2MwiXjcoWNOhhTuOVAUWJ3TmpBYCC9QoJV114LMYrV3Zl58.CQ1oT5Uq3jJt6Uhy3VH9u3Gi5YhfZCvZVKgLlaXNFhVKB1zJxvunR7SJa.com."
bool(false)
string(48) "http://[2001:0db8:0000:85a3:0000:0000:ac1f:8001]"
string(50) "http://[2001:db8:0:85a3:0:0:ac1f:8001]:123/me.html"
string(36) "http://[2001:db8:0:85a3::ac1f:8001]/"
string(12) "http://[::1]"
string(31) "http://cont-ains.h-yph-en-s.com"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(18) "file:///tmp/test.c"
string(26) "ftp://ftp.example.com/tmp/"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(18) "mailto:[email protected]"
string(17) "news:news.php.net"
string(14) "file://foo/bar"
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
string(10) "http://qwe"
bool(false)
bool(false)
string(22) "http://www.example.com"
bool(false)
string(42) "http://www.example.com/path/at/the/server/"
bool(false)
string(40) "http://www.example.com/index.php?a=b&c=d"
Done
Loading