diff --git a/NEWS b/NEWS index 3e24c2fa0bc6..0961ddaa9fbf 100644 --- a/NEWS +++ b/NEWS @@ -24,10 +24,10 @@ PHP NEWS . Fixed bug GH-22046 (The unserialize function can lead to segfault when non-Serializable internal classes are serialized back with the C format). (kocsismate) - . Fixed bug GH-22292 (AST pretty printing does not correctly handle - invalid variable names). (timwolla) - . Fixed bug GH-22291 (AST pretty printing does not correctly handle braces - in string interpolation). (timwolla) + . Fixed bug GH-22292 (AST pretty printing does not correctly handle invalid + variable names). (timwolla) + . Fixed bug GH-22291 (AST pretty printing does not correctly handle braces in + string interpolation). (timwolla) . Fixed bug GH-22373 (AST pretty-printing drops meaningful parentheses surrounding property access). (timwolla) @@ -44,17 +44,16 @@ PHP NEWS . Update timelib to 2022.16. (Derick) - DOM: - . Removed LIBXML_XINCLUDE from valid options for XMLDocument, - as it was a no-op. (ndossche) + . Removed LIBXML_XINCLUDE from valid options for XMLDocument, as it was a + no-op. (ndossche) . Readonly DOM properties are now declared with asymmetric visibility (public private(set)). ReflectionProperty::isWritable() reports them correctly, and external writes raise "Cannot modify private(set) property" instead of the previous readonly modification error. (David Carlier) - . Fixed Dom\Notation nodes missing tree connection, so that - ownerDocument, parentNode, isConnected and baseURI now return correct - values, and textContent returns NULL per the DOM specification. - (jordikroon) + . Fixed Dom\Notation nodes missing tree connection, so that ownerDocument, + parentNode, isConnected and baseURI now return correct values, and + textContent returns NULL per the DOM specification. (jordikroon) - EXIF: . Added support for reading EXIF metadata from WebP images (GH-19904). @@ -71,8 +70,8 @@ PHP NEWS - GMP: . gmp_fact() reject values larger than unsigned long. (David Carlier) - . gmp_pow/binomial/root/rootrem and shift/pow operators reject values - larger than unsigned long. (David Carlier) + . gmp_pow/binomial/root/rootrem and shift/pow operators reject values larger + than unsigned long. (David Carlier) . GMP exponentiation and shift operators now emit a deprecation warning when converting a float right operand to int loses precision. (Weilin Du) @@ -111,8 +110,8 @@ PHP NEWS (Juan Morales) - Fibers: - . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI - small value). (David Carlier) + . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI small + value). (David Carlier) - Mail: . Fixed bug GH-20862 (null pointer dereference in @@ -158,12 +157,12 @@ PHP NEWS (KentarouTakeda) - PGSQL: - . Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell() - if the server supports it. (KentarouTakeda) - . pg_fetch_object() now surfaces non-instantiable class errors - before fetching, resolves the constructor via the get_constructor - handler, and reports the empty-constructor ValueError on the - $constructor_args argument. (David Carlier) + . Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell() if the server + supports it. (KentarouTakeda) + . pg_fetch_object() now surfaces non-instantiable class errors before + fetching, resolves the constructor via the get_constructor handler, and + reports the empty-constructor ValueError on the $constructor_args argument. + (David Carlier) - Phar: . Support reference values in Phar::mungServer(). (ndossche) @@ -186,8 +185,7 @@ PHP NEWS . Fixed bug GH-20217 (ReflectionClass::isIterable() incorrectly returns true for classes with property hooks). (alexandre-daubois) . Added ReflectionConstant::inNamespace(). (Khaled Alam) - . Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable(). - (ilutov) + . Added ReflectionProperty::isReadable() and ::isWritable(). (ilutov) . Fixed bug GH-21362 (ReflectionMethod::invoke/invokeArgs() did not verify Closure instance identity for Closure::__invoke()). (Ilia Alshanetsky) . Added ReflectionParameter::getDocComment(). (chschneider) @@ -206,6 +204,10 @@ PHP NEWS . Changed defaults of session.use_strict_mode (now 1), session.cookie_httponly (now 1) and session.cookie_samesite (now "Lax"). (jorgsowa) +- Shmop: + . Fixed bug GH-9945 (shmop_open() silently truncates keys outside the key_t + range). (Weilin Du) + - Soap: . Soap::__setCookie() when cookie name is a digit is now not stored and represented as a string anymore but a int. (David Carlier) @@ -229,12 +231,16 @@ PHP NEWS - SPL: . DirectoryIterator key can now work better with filesystem supporting larger directory indexing. (David Carlier) - . Fixed bug GH-21831 (SplObjectStorage::removeAllExcept() use-after-free - with re-entrant getHash()). (Pratik Bhujel) + . Fixed bug GH-21831 (SplObjectStorage::removeAllExcept() use-after-free with + re-entrant getHash()). (Pratik Bhujel) . Fix bugs GH-8561, GH-8562, GH-8563, and GH-8564 (Fixing various SplFileObject iterator desync bugs). (iliaal) - . Fix bug GH-22062 (SplDoublyLinkedList iterator UAF - via destructor releasing next node). (David Carlier) + . Fix bug GH-22062 (SplDoublyLinkedList iterator UAF via destructor releasing + next node). (David Carlier) + +- Sysvshm: + . Fixed shm_attach() to throw ValueError for keys outside the key_t range. + (Weilin Du) - Sqlite3: . Fix NUL byte truncation in sqlite3 TEXT column handling. (ndossche) @@ -261,19 +267,19 @@ PHP NEWS . linkinfo() now raises a ValueError when the argument is an empty string. (Weilin Du) . getenv() and putenv() now raises a ValueError when the first argument - contains null bytes. (Weilin Du) - . dl() now raises a ValueError when the $extension_filename argument - contains null bytes. (Weilin Du) - . openlog() now raises a ValueError when the $prefix argument contains - null bytes. (Weilin Du) - . parse_str() now raises a ValueError when the $string argument contains - null bytes. (Weilin Du) - . proc_open() now raises a ValueError when the $cwd argument contains - null bytes. (Weilin Du) + contains NUL bytes. (Weilin Du) + . dl() now raises a ValueError when the $extension_filename argument contains + NUL bytes. (Weilin Du) + . openlog() now raises a ValueError when the $prefix argument contains NUL + bytes. (Weilin Du) + . parse_str() now raises a ValueError when the $string argument contains NUL + bytes. (Weilin Du) + . proc_open() now raises a ValueError when the $cwd argument contains NUL + bytes. (Weilin Du) . ini_get_all() now includes the built-in default value in the details. (sebastian) - . Fixed bug GH-22171 (Invalid auth header generation in - http(s) stream wrapper). (David Carlier) + . Fixed bug GH-22171 (Invalid auth header generation in http(s) stream + wrapper). (David Carlier) - Streams: . Added new stream errors API including new StreamException, StreamError @@ -295,10 +301,11 @@ PHP NEWS filters). (Jakub Zelenka) - URI: - . Added Uri\Rfc3986\Uri:getUriType() and Uri\WhatWg\Url:isSpecialScheme(). + . Added Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme(). (kocsismate) - . Added Uri\Rfc3986\Uri:getHostType() and Uri\WhatWg\Url:getHostType(). + . Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType(). (kocsismate) + . Added Uri\Rfc3986\UriBuilder. (kocsismate) - Zip: . Fixed ZipArchive callback being called after executor has shut down. diff --git a/UPGRADING b/UPGRADING index 18afee1a5c9d..1ae5bbe1f40a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -30,9 +30,8 @@ PHP 8.6 UPGRADE NOTES accurately. - GD: - . imagesetstyle(), imagefilter() and imagecrop() filter their - array arguments types/values and raise a TypeError/ValueError - accordingly. + . imagesetstyle(), imagefilter() and imagecrop() filter their array arguments + types / values and raise a TypeError / ValueError accordingly. - Intl: . Passing a non-stringable object as a time zone to Intl APIs that accept @@ -63,13 +62,13 @@ PHP 8.6 UPGRADE NOTES This is consistent with other preg_* functions. - Phar: - . Phar::mungServer() now raises a ValueError when an invalid - argument value is passed instead of being silently ignored. - . Phar::addEmptyDir() now rejects `/.phar` paths in addition to `.phar` + . Phar::mungServer() now raises a ValueError when an invalid argument value + is passed instead of being silently ignored. + . Phar::addEmptyDir() now rejects "/.phar" paths in addition to ".phar" paths, and raises the same BadMethodCallException for attempts to create the reserved magic ".phar" directory through that form. . Phar::addEmptyDir() now treats non-magic names that merely share the - `.phar` prefix as ordinary directories. + ".phar" prefix as ordinary directories. - PGSQL: . pg_fetch_object() now reports the ValueError for a non-empty @@ -86,11 +85,11 @@ PHP 8.6 UPGRADE NOTES - Session: . Setting session.cookie_path, session.cookie_domain, or session.cache_limiter - to a value containing null bytes now emits a warning and leaves the setting - unchanged. Previously, null bytes were silently accepted: for cookie_path and - cookie_domain this caused the SAPI to drop the Set-Cookie header; for - cache_limiter the value was silently truncated at the null byte. - . A ValueError is not thrown if $name is a string containing null bytes in + to a value containing NUL bytes now emits a warning and leaves the setting + unchanged. Previously, NUL bytes were silently accepted: for cookie_path + and cookie_domain this caused the SAPI to drop the Set-Cookie header; for + cache_limiter the value was silently truncated at the NUL byte. + . A ValueError is not thrown if $name is a string containing NUL bytes in session_module_name(). . session_encode() now returns an empty string instead of false for empty sessions. It only returns false now when the session data could not be @@ -132,49 +131,48 @@ PHP 8.6 UPGRADE NOTES . SplObjectStorage::getHash() implementations may no longer mutate any SplObjectStorage instance. Attempting to do so now throws an Error. . SplFileObject::next() now advances the stream when no prior current() - call has cached a line. A subsequent current() call returns the new - line rather than the previous one. - . SplFileObject::fgets() no longer caches the returned line for - subsequent current() calls. current() now re-reads from the current - stream position instead of returning the line fgets() just returned. - . SplFileObject::next() past EOF no longer increments key() without - bound. SplFileObject::seek() past EOF now produces the same key() - value as SplTempFileObject; the two previously returned different - values. + call has cached a line. A subsequent current() call returns the new line + rather than the previous one. + . SplFileObject::fgets() no longer caches the returned line for subsequent + current() calls. current() now re-reads from the current stream position + instead of returning the line fgets() just returned. + . SplFileObject::next() past EOF no longer increments key() without bound. + SplFileObject::seek() past EOF now produces the same key() value as + SplTempFileObject; the two previously returned different values. - Standard: . Form feed (\f) is now added in the default trimmed characters of trim(), - rtrim() and ltrim(). RFC: https://wiki.php.net/rfc/trim_form_feed - . array_filter() now raises a ValueError when an invalid $mode - argument value is passed. + rtrim() and ltrim(). + RFC: https://wiki.php.net/rfc/trim_form_feed + . array_filter() now raises a ValueError when an invalid $mode argument value + is passed. . array_change_key_case() now raises a ValueError when an invalid $case argument value is passed. . getenv() and putenv() now raises a ValueError when the first argument - contains null bytes. - . dl() now raises a ValueError when the $extension_filename argument - contains null bytes. - . openlog() now raises a ValueError when the $prefix argument contains - null bytes. - . parse_str() now raises a ValueError when the $string argument contains - null bytes. + contains NUL bytes. + . dl() now raises a ValueError when the $extension_filename argument contains + NUL bytes. + . openlog() now raises a ValueError when the $prefix argument contains NUL + bytes. + . parse_str() now raises a ValueError when the $string argument contains NUL + bytes. . linkinfo() now raises a ValueError when the $path argument is empty. - . pathinfo() now raises a ValueError when an invalid $flag - argument value is passed. - . scandir() now raises a ValueError when an invalid $sorting_order - argument value is passed. - . proc_open() now raises a ValueError when the $cwd argument contains - null bytes. + . pathinfo() now raises a ValueError when an invalid $flag argument value is + passed. + . scandir() now raises a ValueError when an invalid $sorting_order argument + value is passed. + . proc_open() now raises a ValueError when the $cwd argument contains NUL + bytes. - Zip: - . ZipArchive::extractTo now raises a TypeError for the - files argument if one or more of the entries is not - a string. + . ZipArchive::extractTo now raises a TypeError for the files argument if one + or more of the entries is not a string. - Zlib: - . deflate_init() now raises a TypeError when the value for option - "level", "memory", "window", or "strategy" is not of type int. - . inflate_init() now raises a TypeError when the value for option - "window" is not of type int. + . deflate_init() now raises a TypeError when the value for option "level", + "memory", "window", or "strategy" is not of type int. + . inflate_init() now raises a TypeError when the value for option "window" is + not of type int. ======================================== 2. New Features @@ -183,14 +181,14 @@ PHP 8.6 UPGRADE NOTES - Core: . It is now possible to use reference assign on WeakMap without the key needing to be present beforehand. - . It is now possible to define the `__debugInfo()` magic method on enums. + . It is now possible to define the __debugInfo() magic method on enums. RFC: https://wiki.php.net/rfc/debugable-enums - Curl: - . curl_getinfo() return array now includes a new size_delivered key, - which indicates the total number of bytes passed to the download - write callback. This value can also be obtained by passing - CURLINFO_SIZE_DELIVERED as the $option parameter. + . curl_getinfo() return array now includes a new size_delivered key, which + indicates the total number of bytes passed to the download write callback. + This value can also be obtained by passing CURLINFO_SIZE_DELIVERED as the + $option parameter. Requires libcurl 8.20.0 or later. - Fileinfo: @@ -250,10 +248,12 @@ PHP 8.6 UPGRADE NOTES value is one of the following strings "preserve", "reset", or "strict". - URI: - . Added Uri\Rfc3986\Uri:getUriType() and Uri\WhatWg\Url:isSpecialScheme(). + . Added Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme(). RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection - . Added Uri\Rfc3986\Uri:getHostType() and Uri\WhatWg\Url:getHostType(). + . Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType(). RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection + . Added Uri\Rfc3986\UriBuilder. + RFC: https://wiki.php.net/rfc/uri_followup#uri_building ======================================== 3. Changes in SAPI modules @@ -282,21 +282,20 @@ PHP 8.6 UPGRADE NOTES ======================================== - GMP: - . gmp_fact() now throws a ValueError() if $num does not fit into - a unsigned long. + . gmp_fact() now throws a ValueError() if $num does not fit into a unsigned + long. . gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() now throw a ValueError if their second argument does not fit into an unsigned long. - . The shift (<<, >>) and exponentiation (**) operators on GMP objects - now throw a ValueError if the right operand does not fit into an - unsigned long. - . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose - message includes the function name and argument index ($modulus). + . The shift (<<, >>) and exponentiation (**) operators on GMP objects now + throw a ValueError if the right operand does not fit into an unsigned long. + . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose message + includes the function name and argument index ($modulus). - mysqli: - . The return structure of mysqli_get_charset() no longer contains - the undocumented "comment" element. The value of "charsetnr" is - now set to a constant 0 as this number was an implementation detail - that should not have been exposed to the public. + . The return structure of mysqli_get_charset() no longer contains the + undocumented "comment" element. The value of "charsetnr" is now set to a + constant 0 as this number was an implementation detail that should not have + been exposed to the public. - OpenSSL: . Output of openssl_x509_parse() contains criticalExtensions listing all @@ -316,9 +315,9 @@ PHP 8.6 UPGRADE NOTES - Standard: . ini_get_all() now includes a "builtin_default_value" element for each - directive when $details is true. It holds the built-in default value of - the directive (or null if it has none), independent of values set in - php.ini, on the command line, or at runtime. + directive when $details is true. It holds the built-in default value of the + directive (or null if it has none), independent of values set in php.ini, + on the command line, or at runtime. ======================================== 6. New Functions @@ -326,30 +325,30 @@ PHP 8.6 UPGRADE NOTES - Reflection: . ReflectionConstant::inNamespace() - . Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable(). + . ReflectionProperty::isReadable() and ReflectionProperty::isWritable() RFC: https://wiki.php.net/rfc/isreadable-iswriteable - . Added ReflectionParameter::getDocComment(). + . ReflectionParameter::getDocComment(). RFC: https://wiki.php.net/rfc/parameter-doccomments - Intl: - . `grapheme_strrev()` returns strrev for grapheme cluster unit. + . grapheme_strrev() returns strrev for grapheme cluster unit. RFC: https://wiki.php.net/rfc/grapheme_strrev - mysqli: - . Added `mysqli::quote_string()` and `mysqli_quote_string()`. + . Added mysqli::quote_string() and mysqli_quote_string(). RFC: https://wiki.php.net/rfc/mysqli_quote_string - Standard: - . `clamp()` returns the given value if in range, else return the nearest + . clamp() returns the given value if in range, else return the nearest bound. RFC: https://wiki.php.net/rfc/clamp_v2 - . `stream_last_errors()` and `stream_clear_errors()`. + . stream_last_errors() and stream_clear_errors(). RFC: https://wiki.php.net/rfc/stream_errors - . stream_socket_get_crypto_status() + . stream_socket_get_crypto_status(). - Zip: - . Added ZipArchive::openString() method. - . Added ZipArchive::closeString() method. + . ZipArchive::openString() + . ZipArchive::closeString() ======================================== 7. New Classes and Interfaces @@ -469,7 +468,7 @@ PHP 8.6 UPGRADE NOTES ======================================== - Core: - . `printf()` using only `%s` and `%d` will be compiled into the equivalent + . printf() using only "%s" and "%d" will be compiled into the equivalent string interpolation, avoiding the overhead of a function call and repeatedly parsing the format string. . Arguments are now passed more efficiently to known constructors (e.g. when diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 1f24fb82fcf9..2f0d016a99cd 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -65,6 +65,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES zend_enum_RoundingMode parameter. . Added Z_PARAM_ENUM(). . Added zend_enum_fetch_case_id(). + . Added zend_enum_get_case_by_id(). . ZEND_INI_GET_ADDR() is now a void* pointer instead of a char* pointer. This more correctly represents the generic nature of the returned pointer and allows to remove explicit casts, but possibly breaks pointer arithmetic diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 7f0b58575387..ecebe7a5d51b 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -673,6 +673,11 @@ ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char * return zend_enum_case_from_class_constant(c); } +ZEND_API zend_object *zend_enum_get_case_by_id(zend_class_entry *ce, int id) { + zend_class_constant *c = Z_PTR(CE_CONSTANTS_TABLE(ce)->arData[id - 1].val); + return zend_enum_case_from_class_constant(c); +} + void zend_enum_startup(void) { for (size_t i = 0; i < sizeof(zarginfo_class_UnitEnum_cases)/sizeof(zend_arg_info); i++) { diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h index 6bffb8866507..f252c938eb83 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -55,6 +55,7 @@ ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, z ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value); ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name); ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name); +ZEND_API zend_object *zend_enum_get_case_by_id(zend_class_entry *ce, int id); ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from); static zend_always_inline int zend_enum_fetch_case_id(zend_object *zobj) diff --git a/ext/gd/tests/imageloadfont_short_read.phpt b/ext/gd/tests/imageloadfont_short_read.phpt index 5a7f6a14c9bb..406c64b99595 100644 --- a/ext/gd/tests/imageloadfont_short_read.phpt +++ b/ext/gd/tests/imageloadfont_short_read.phpt @@ -7,7 +7,9 @@ gd /* A user-space wrapper returns one byte per read, so php_stream_read() hands * imageloadfont()'s header loop a short read on every iteration. The header * (4 ints) plus a single 1x1 glyph byte form a valid font, which only loads - * when each short read lands at the correct byte offset. */ + * when each short read lands at the correct byte offset. imageloadfont() reads + * the header as native-endian ints, so pack the fields with 'i' (not 'V') to + * keep the font valid on big-endian hosts too. */ class drip { public $context; @@ -16,7 +18,7 @@ class drip public function stream_open($path, $mode, $options, &$opened): bool { - $this->data = pack('V4', 1, 32, 1, 1) . "\x00"; + $this->data = pack('i4', 1, 32, 1, 1) . "\x00"; return true; } diff --git a/ext/intl/intl_convertcpp.cpp b/ext/intl/intl_convertcpp.cpp index 70d28e5c23ca..b305ff201c74 100644 --- a/ext/intl/intl_convertcpp.cpp +++ b/ext/intl/intl_convertcpp.cpp @@ -62,6 +62,10 @@ zend_string* intl_charFromString(const UnicodeString &from, UErrorCode *status) //the number of UTF-8 code units is not larger than that of UTF-16 code //units * 3 + if (UNEXPECTED(from.length() > INT32_MAX / 3)) { + *status = U_BUFFER_OVERFLOW_ERROR; + return NULL; + } int32_t capacity = from.length() * 3; if (from.isEmpty()) { diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index da0c4b216ffe..9befe134cded 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -577,6 +577,7 @@ static int php_ldap_control_from_array(LDAP *ld, LDAPControl** ctrl, const HashT uint32_t num_keys = zend_hash_num_elements(Z_ARRVAL_P(val)); sort_keys = safe_emalloc((num_keys+1), sizeof(LDAPSortKey*), 0); + memset(sort_keys, 0, (num_keys+1) * sizeof(LDAPSortKey*)); tmpstrings1 = safe_emalloc(num_keys, sizeof(zend_string*), 0); tmpstrings2 = safe_emalloc(num_keys, sizeof(zend_string*), 0); num_tmpstrings1 = 0; diff --git a/ext/ldap/tests/ldap_sort_control_missing_attr.phpt b/ext/ldap/tests/ldap_sort_control_missing_attr.phpt new file mode 100644 index 000000000000..0d6c3921f008 --- /dev/null +++ b/ext/ldap/tests/ldap_sort_control_missing_attr.phpt @@ -0,0 +1,30 @@ +--TEST-- +ldap_search(): malformed sort control (sort key missing "attr") must not free uninitialized memory +--EXTENSIONS-- +ldap +--FILE-- + LDAP_CONTROL_SORTREQUEST, + 'value' => [ + ['attr' => 'cn'], + ['reverse' => true], + ], + ], + ]); +} catch (\ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "ok\n"; +?> +--EXPECT-- +ldap_search(): Sort key list must have an "attr" key +ok diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 2ba732c4540e..794e75a1716e 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -152,6 +152,7 @@ typedef psetid_t cpu_set_t; #include "Zend/zend_max_execution_timer.h" #include "pcntl_arginfo.h" +#include "pcntl_decl.h" static zend_class_entry *QosClass_ce; ZEND_DECLARE_MODULE_GLOBALS(pcntl) @@ -1839,28 +1840,28 @@ static qos_class_t qos_enum_to_pthread(zend_enum_Pcntl_QosClass entry) static zend_object *qos_lval_to_zval(qos_class_t qos_class) { - const char *entryname; + int entry_id; switch (qos_class) { case QOS_CLASS_USER_INTERACTIVE: - entryname = "UserInteractive"; + entry_id = ZEND_ENUM_Pcntl_QosClass_UserInteractive; break; case QOS_CLASS_USER_INITIATED: - entryname = "UserInitiated"; + entry_id = ZEND_ENUM_Pcntl_QosClass_UserInitiated; break; case QOS_CLASS_UTILITY: - entryname = "Utility"; + entry_id = ZEND_ENUM_Pcntl_QosClass_Utility; break; case QOS_CLASS_BACKGROUND: - entryname = "Background"; + entry_id = ZEND_ENUM_Pcntl_QosClass_Background; break; case QOS_CLASS_DEFAULT: default: - entryname = "Default"; + entry_id = ZEND_ENUM_Pcntl_QosClass_Default; break; } - return zend_enum_get_case_cstr(QosClass_ce, entryname); + return zend_enum_get_case_by_id(QosClass_ce, entry_id); } PHP_FUNCTION(pcntl_getqos_class) diff --git a/ext/posix/posix.c b/ext/posix/posix.c index b44b73cc8dcf..ff91c7ba9177 100644 --- a/ext/posix/posix.c +++ b/ext/posix/posix.c @@ -926,13 +926,33 @@ static void php_posix_passwd_to_array(struct passwd *pw, zval *return_value) /* { ZEND_ASSERT(Z_TYPE_P(return_value) == IS_ARRAY); - add_assoc_string(return_value, "name", pw->pw_name); - add_assoc_string(return_value, "passwd", pw->pw_passwd); - add_assoc_long (return_value, "uid", pw->pw_uid); - add_assoc_long (return_value, "gid", pw->pw_gid); - add_assoc_string(return_value, "gecos", pw->pw_gecos); - add_assoc_string(return_value, "dir", pw->pw_dir); - add_assoc_string(return_value, "shell", pw->pw_shell); + if (pw->pw_name) { + add_assoc_string(return_value, "name", pw->pw_name); + } else { + add_assoc_null(return_value, "name"); + } + if (pw->pw_passwd) { + add_assoc_string(return_value, "passwd", pw->pw_passwd); + } else { + add_assoc_null(return_value, "passwd"); + } + add_assoc_long(return_value, "uid", pw->pw_uid); + add_assoc_long(return_value, "gid", pw->pw_gid); + if (pw->pw_gecos) { + add_assoc_string(return_value, "gecos", pw->pw_gecos); + } else { + add_assoc_null(return_value, "gecos"); + } + if (pw->pw_dir) { + add_assoc_string(return_value, "dir", pw->pw_dir); + } else { + add_assoc_null(return_value, "dir"); + } + if (pw->pw_shell) { + add_assoc_string(return_value, "shell", pw->pw_shell); + } else { + add_assoc_null(return_value, "shell"); + } } /* }}} */ diff --git a/ext/shmop/shmop.c b/ext/shmop/shmop.c index c3ed217443f4..645ce172fdcd 100644 --- a/ext/shmop/shmop.c +++ b/ext/shmop/shmop.c @@ -129,13 +129,20 @@ PHP_MINFO_FUNCTION(shmop) /* {{{ gets and attaches a shared memory segment */ PHP_FUNCTION(shmop_open) { - zend_long key, mode, size; + zend_long key_arg, mode, size; + key_t key; php_shmop *shmop; struct shmid_ds shm; char *flags; size_t flags_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "lsll", &key, &flags, &flags_len, &mode, &size) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lsll", &key_arg, &flags, &flags_len, &mode, &size) == FAILURE) { + RETURN_THROWS(); + } + + key = (key_t) key_arg; + if ((zend_long) key != key_arg) { + zend_argument_value_error(1, "is out of range"); RETURN_THROWS(); } diff --git a/ext/shmop/tests/gh9945.phpt b/ext/shmop/tests/gh9945.phpt new file mode 100644 index 000000000000..f22f79149bfb --- /dev/null +++ b/ext/shmop/tests/gh9945.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-9945: shmop_open() must reject keys outside the key_t range +--EXTENSIONS-- +shmop +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +shmop_open(): Argument #1 ($key) is out of range diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index e8433e7c4f67..02874c5c7584 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -626,7 +626,7 @@ int make_http_soap_request( } } else if (FG(user_agent)) { smart_str_append_const(&soap_headers, "User-Agent: "); - smart_str_appends(&soap_headers, FG(user_agent)); + smart_str_append(&soap_headers, FG(user_agent)); smart_str_append_const(&soap_headers, "\r\n"); } else { smart_str_append_const(&soap_headers, "User-Agent: PHP-SOAP/"PHP_VERSION"\r\n"); diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 0610e79196f9..e80917a77067 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -40,16 +40,7 @@ ZEND_TLS zend_string *spl_autoload_extensions; static zend_class_entry * spl_find_ce_by_name(zend_string *name, bool autoload) { - zend_class_entry *ce; - - if (!autoload) { - zend_string *lc_name = zend_string_tolower(name); - - ce = zend_hash_find_ptr(EG(class_table), lc_name); - zend_string_release(lc_name); - } else { - ce = zend_lookup_class(name); - } + zend_class_entry *ce = zend_lookup_class_ex(name, NULL, autoload ? 0 : ZEND_FETCH_CLASS_NO_AUTOLOAD); if (ce == NULL) { php_error_docref(NULL, E_WARNING, "Class %s does not exist%s", ZSTR_VAL(name), autoload ? " and could not be loaded" : ""); return NULL; @@ -334,7 +325,12 @@ PHP_FUNCTION(spl_autoload) pos_len = ZSTR_LEN(file_exts); } - lc_name = zend_string_tolower(class_name); + if (ZSTR_VAL(class_name)[0] == '\\') { + lc_name = zend_string_alloc(ZSTR_LEN(class_name) - 1, 0); + zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(class_name) + 1, ZSTR_LEN(class_name) - 1); + } else { + lc_name = zend_string_tolower(class_name); + } while (pos && *pos && !EG(exception)) { pos1 = strchr(pos, ','); if (pos1) { diff --git a/ext/spl/tests/class_parents_leading_backslash.phpt b/ext/spl/tests/class_parents_leading_backslash.phpt new file mode 100644 index 000000000000..be538b79db9d --- /dev/null +++ b/ext/spl/tests/class_parents_leading_backslash.phpt @@ -0,0 +1,72 @@ +--TEST-- +class_parents()/class_implements()/class_uses() accept a leading backslash regardless of $autoload +--FILE-- + +--EXPECT-- +array(1) { + ["ParentC"]=> + string(7) "ParentC" +} +array(1) { + ["ParentC"]=> + string(7) "ParentC" +} +array(1) { + ["Iface"]=> + string(5) "Iface" +} +array(1) { + ["Tr"]=> + string(2) "Tr" +} +array(1) { + ["MyNS\NSParentC"]=> + string(14) "MyNS\NSParentC" +} +array(1) { + ["MyNS\NSParentC"]=> + string(14) "MyNS\NSParentC" +} +array(1) { + ["MyNS\NSIface"]=> + string(12) "MyNS\NSIface" +} +array(1) { + ["MyNS\NSTr"]=> + string(9) "MyNS\NSTr" +} diff --git a/ext/spl/tests/spl_autoload_leading_backslash.phpt b/ext/spl/tests/spl_autoload_leading_backslash.phpt new file mode 100644 index 000000000000..ff8ae3234eee --- /dev/null +++ b/ext/spl/tests/spl_autoload_leading_backslash.phpt @@ -0,0 +1,18 @@ +--TEST-- +spl_autoload(): a leading "\" in the class name is ignored +--FILE-- + +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/standard/file.c b/ext/standard/file.c index db0fc45385dd..f9474af68735 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -147,8 +147,8 @@ static PHP_INI_MH(OnUpdateAutoDetectLineEndings) } PHP_INI_BEGIN() - STD_PHP_INI_ENTRY("user_agent", NULL, PHP_INI_ALL, OnUpdateString, user_agent, php_file_globals, file_globals) - STD_PHP_INI_ENTRY("from", NULL, PHP_INI_ALL, OnUpdateString, from_address, php_file_globals, file_globals) + STD_PHP_INI_ENTRY("user_agent", NULL, PHP_INI_ALL, OnUpdateStr, user_agent, php_file_globals, file_globals) + STD_PHP_INI_ENTRY("from", NULL, PHP_INI_ALL, OnUpdateStr, from_address, php_file_globals, file_globals) STD_PHP_INI_ENTRY("default_socket_timeout", "60", PHP_INI_ALL, OnUpdateLong, default_socket_timeout, php_file_globals, file_globals) STD_PHP_INI_BOOLEAN("auto_detect_line_endings", "0", PHP_INI_ALL, OnUpdateAutoDetectLineEndings, auto_detect_line_endings, php_file_globals, file_globals) PHP_INI_END() diff --git a/ext/standard/file.h b/ext/standard/file.h index ac218169599c..9ba5f5b8b93d 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -93,8 +93,8 @@ typedef struct { size_t def_chunk_size; bool auto_detect_line_endings; zend_long default_socket_timeout; - char *user_agent; /* for the http wrapper */ - char *from_address; /* for the ftp and http wrappers */ + zend_string *user_agent; /* for the http wrapper */ + zend_string *from_address; /* for the ftp and http wrappers */ const char *user_stream_current_filename; /* for simple recursion protection */ php_stream_context *default_context; HashTable *stream_wrappers; /* per-request copy of url_stream_wrappers_hash */ diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index f99ae5e4b4e1..457d1410aabe 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -273,7 +273,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char /* if the user has configured who they are, send that as the password */ if (FG(from_address)) { - php_stream_printf(stream, "PASS %s\r\n", FG(from_address)); + php_stream_printf(stream, "PASS %s\r\n", ZSTR_VAL(FG(from_address))); } else { php_stream_write_string(stream, "PASS anonymous\r\n"); } diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 9e3db6716048..195394c7ed8d 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -351,6 +351,23 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w return NULL; } +static inline void smart_str_append_header_value(smart_str *dest, const zend_string *value, const char *header_name) +{ + const char *src = ZSTR_VAL(value); + size_t len = ZSTR_LEN(value); + size_t i = 0; + while (i < len && src[i] != '\r' && src[i] != '\n') { + i++; + } + if (i < len) { + smart_str_appendl(dest, src, i); + php_error_docref(NULL, E_WARNING, + "Header %s value contains newline characters and has been truncated", header_name); + } else { + smart_str_append(dest, value); + } +} + static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context, int redirect_max, int flags, @@ -361,7 +378,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, int use_ssl; int use_proxy = 0; zend_string *tmp = NULL; - char *ua_str = NULL; + zend_string *ua_str = NULL; zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; int reqok = 0; char *http_header_line = NULL; @@ -788,7 +805,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* if the user has configured who they are, send a From: line */ if (!(have_header & HTTP_HEADER_FROM) && FG(from_address)) { smart_str_appends(&req_buf, "From: "); - smart_str_appends(&req_buf, FG(from_address)); + smart_str_append_header_value(&req_buf, FG(from_address), "From"); smart_str_appends(&req_buf, "\r\n"); } @@ -817,30 +834,15 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (context && (ua_zval = php_stream_context_get_option(context, "http", "user_agent")) != NULL && Z_TYPE_P(ua_zval) == IS_STRING) { - ua_str = Z_STRVAL_P(ua_zval); + ua_str = Z_STR_P(ua_zval); } else if (FG(user_agent)) { ua_str = FG(user_agent); } - if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) { -#define _UA_HEADER "User-Agent: %s\r\n" - char *ua; - size_t ua_len; - - ua_len = sizeof(_UA_HEADER) + strlen(ua_str); - - /* ensure the header is only sent if user_agent is not blank */ - if (ua_len > sizeof(_UA_HEADER)) { - ua = emalloc(ua_len + 1); - if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) { - ua[ua_len] = 0; - smart_str_appendl(&req_buf, ua, ua_len); - } else { - php_stream_wrapper_warn_nt(wrapper, context, options, InvalidHeader, - "Cannot construct User-agent header"); - } - efree(ua); - } + if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str && ZSTR_LEN(ua_str)) { + smart_str_appends(&req_buf, "User-Agent: "); + smart_str_append_header_value(&req_buf, ua_str, "User-Agent"); + smart_str_appends(&req_buf, "\r\n"); } if (user_headers) { diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c index b1eb7513f1cb..837470a9f753 100644 --- a/ext/standard/io_poll.c +++ b/ext/standard/io_poll.c @@ -18,6 +18,7 @@ #include "php_network.h" #include "php_poll.h" #include "io_poll_arginfo.h" +#include "io_poll_decl.h" /* Class entries */ static zend_class_entry *php_io_poll_backend_class_entry; @@ -118,38 +119,31 @@ static zend_result php_io_poll_events_to_event_enums(uint32_t events, zval *even array_init(event_enums); if (events & PHP_POLL_READ) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Read")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Read)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_WRITE) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Write")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Write)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_ERROR) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Error")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Error)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_HUP) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "HangUp")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_HangUp)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_RDHUP) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "ReadHangUp")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_ReadHangUp)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_ONESHOT) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "OneShot")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_OneShot)); add_next_index_zval(event_enums, &enum_case); } if (events & PHP_POLL_ET) { - ZVAL_OBJ(&enum_case, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "EdgeTriggered")); - GC_ADDREF(Z_OBJ(enum_case)); + ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_EdgeTriggered)); add_next_index_zval(event_enums, &enum_case); } diff --git a/ext/standard/tests/http/gh17976.phpt b/ext/standard/tests/http/gh17976.phpt new file mode 100644 index 000000000000..bba9a7a0fcf3 --- /dev/null +++ b/ext/standard/tests/http/gh17976.phpt @@ -0,0 +1,65 @@ +--TEST-- +GH-17976 (CRLF injection via from and user_agent INI settings in HTTP wrapper) +--INI-- +allow_url_fopen=1 +--FILE-- + ["tcp_nodelay" => true] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + for ($i = 0; $i < 4; $i++) { + $conn = stream_socket_accept($server); + $result = fread($conn, 4096); + fwrite($conn, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n" . base64_encode($result)); + fclose($conn); + } +CODE; + +$clientCode = <<<'CODE' + // Test 1: from INI with CRLF + ini_set("from", "test\r\nInjected-From: evil"); + ini_set("user_agent", "clean_ua"); + $raw = base64_decode(file_get_contents("http://{{ ADDR }}/")); + echo (str_contains($raw, "Injected-From:") ? "FAIL" : "OK") . ": from INI\n"; + + // Test 2: user_agent INI with CRLF + ini_restore("from"); + ini_set("user_agent", "test\r\nInjected-UA: evil"); + $raw = base64_decode(file_get_contents("http://{{ ADDR }}/")); + echo (str_contains($raw, "Injected-UA:") ? "FAIL" : "OK") . ": user_agent INI\n"; + + // Test 3: user_agent context option with CRLF + ini_restore("user_agent"); + $ctx = stream_context_create(["http" => [ + "user_agent" => "test\nInjected-Ctx: evil" + ]]); + $raw = base64_decode(file_get_contents("http://{{ ADDR }}/", false, $ctx)); + echo (str_contains($raw, "Injected-Ctx:") ? "FAIL" : "OK") . ": user_agent context\n"; + + // Test 4: user_agent INI with a NUL byte before the CRLF + ini_set("user_agent", "ua\0valid\r\nInjected-Nul: evil"); + $raw = base64_decode(file_get_contents("http://{{ ADDR }}/")); + echo (str_contains($raw, "Injected-Nul:") ? "FAIL" : "OK") . ": user_agent NUL+CRLF\n"; +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Warning: file_get_contents(): Header From value contains newline characters and has been truncated in %s on line %d +OK: from INI + +Warning: file_get_contents(): Header User-Agent value contains newline characters and has been truncated in %s on line %d +OK: user_agent INI + +Warning: file_get_contents(): Header User-Agent value contains newline characters and has been truncated in %s on line %d +OK: user_agent context + +Warning: file_get_contents(): Header User-Agent value contains newline characters and has been truncated in %s on line %d +OK: user_agent NUL+CRLF diff --git a/ext/sysvshm/sysvshm.c b/ext/sysvshm/sysvshm.c index 59ca49a974e6..6ca06be935d9 100644 --- a/ext/sysvshm/sysvshm.c +++ b/ext/sysvshm/sysvshm.c @@ -126,10 +126,17 @@ PHP_FUNCTION(shm_attach) sysvshm_shm *shm_list_ptr; char *shm_ptr; sysvshm_chunk_head *chunk_ptr; - zend_long shm_key, shm_id, shm_size, shm_flag = 0666; + zend_long shm_key_arg, shm_id, shm_size, shm_flag = 0666; + key_t shm_key; bool shm_size_is_null = true; - if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "l|l!l", &shm_key, &shm_size, &shm_size_is_null, &shm_flag)) { + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "l|l!l", &shm_key_arg, &shm_size, &shm_size_is_null, &shm_flag)) { + RETURN_THROWS(); + } + + shm_key = (key_t) shm_key_arg; + if ((zend_long) shm_key != shm_key_arg) { + zend_argument_value_error(1, "is out of range"); RETURN_THROWS(); } @@ -145,17 +152,17 @@ PHP_FUNCTION(shm_attach) /* get the id from a specified key or create new shared memory */ if ((shm_id = shmget(shm_key, 0, 0)) < 0) { if (shm_size < (zend_long)sizeof(sysvshm_chunk_head)) { - php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": memorysize too small", shm_key); + php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": memorysize too small", shm_key_arg); RETURN_FALSE; } if ((shm_id = shmget(shm_key, shm_size, shm_flag | IPC_CREAT | IPC_EXCL)) < 0) { - php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key, strerror(errno)); + php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key_arg, strerror(errno)); RETURN_FALSE; } } if ((shm_ptr = shmat(shm_id, NULL, 0)) == (void *) -1) { - php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key, strerror(errno)); + php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key_arg, strerror(errno)); RETURN_FALSE; } diff --git a/ext/sysvshm/tests/gh9945.phpt b/ext/sysvshm/tests/gh9945.phpt new file mode 100644 index 000000000000..8185d39b0981 --- /dev/null +++ b/ext/sysvshm/tests/gh9945.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-9945: shm_attach() must reject keys outside the key_t range +--EXTENSIONS-- +sysvshm +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +shm_attach(): Argument #1 ($key) is out of range diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 58f34a370151..74a559fd591c 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -30,6 +30,7 @@ #include "php_uri_arginfo.h" #include "uriparser/Uri.h" +zend_class_entry *php_uri_ce_rfc3986_uri_builder; zend_class_entry *php_uri_ce_rfc3986_uri; zend_class_entry *php_uri_ce_rfc3986_uri_type; zend_class_entry *php_uri_ce_rfc3986_uri_host_type; @@ -46,6 +47,9 @@ zend_class_entry *php_uri_ce_whatwg_url_validation_error; static zend_object_handlers object_handlers_rfc3986_uri; static zend_object_handlers object_handlers_whatwg_uri; +typedef zend_result (*php_uri_component_validator_string)(const zend_string *component); +typedef zend_result (*php_uri_component_validator_long)(zend_long component); + static const zend_module_dep uri_deps[] = { ZEND_MOD_REQUIRED("lexbor") ZEND_MOD_END @@ -53,6 +57,23 @@ static const zend_module_dep uri_deps[] = { static zend_array uri_parsers; +static zend_always_inline zval *php_uri_deref(zval *zv) +{ + if (UNEXPECTED(Z_TYPE_P(zv) == IS_REFERENCE)) { + return Z_REFVAL_P(zv); + } + + return zv; +} + +#define Z_RFC3986_URI_PROP_SCHEME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0)) +#define Z_RFC3986_URI_PROP_USERINFO_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) +#define Z_RFC3986_URI_PROP_HOST_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) +#define Z_RFC3986_URI_PROP_PORT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) +#define Z_RFC3986_URI_PROP_PATH_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4)) +#define Z_RFC3986_URI_PROP_QUERY_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) +#define Z_RFC3986_URI_PROP_FRAGMENT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) + static HashTable *uri_get_debug_properties(php_uri_object *object) { const HashTable *std_properties = zend_std_get_properties(&object->std); @@ -1044,6 +1065,186 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo) RETURN_ARR(uri_get_debug_properties(uri_object)); } +PHP_METHOD(Uri_Rfc3986_UriBuilder, reset) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + convert_to_null(Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS)); + convert_to_null(Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS)); + convert_to_null(Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS)); + convert_to_null(Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS)); + zval_ptr_dtor(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS)); + ZVAL_EMPTY_STRING(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS)); + convert_to_null(Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS)); + convert_to_null(Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS)); + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length, + const php_uri_component_validator_string validator +) { + zend_string *component; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(component) + ZEND_PARSE_PARAMETERS_END(); + + if (validator(component) == FAILURE) { + RETURN_THROWS(); + } + + zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length, + const php_uri_component_validator_string validator +) { + zend_string *component; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_NULL(component) + ZEND_PARSE_PARAMETERS_END(); + + if (component == NULL) { + zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length); + } else { + if (validator(component) == FAILURE) { + RETURN_THROWS(); + } + + zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + } + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1) static void php_uri_builder_set_component_long_or_null( + INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length, + const php_uri_component_validator_long validator +) { + zend_long component; + bool component_is_null; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG_OR_NULL(component, component_is_null) + ZEND_PARSE_PARAMETERS_END(); + + if (component_is_null) { + zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length); + } else { + if (validator(component) == FAILURE) { + RETURN_THROWS(); + } + + zend_update_property_long(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + } + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setScheme) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("scheme"), + php_uri_parser_rfc3986_validate_scheme + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("userinfo"), + php_uri_parser_rfc3986_validate_userinfo + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setHost) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("host"), + php_uri_parser_rfc3986_validate_host + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setPort) +{ + php_uri_builder_set_component_long_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("port"), + php_uri_parser_rfc3986_validate_port + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setPath) +{ + php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("path"), + php_uri_parser_rfc3986_validate_path + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setQuery) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("query"), + php_uri_parser_rfc3986_validate_query + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setFragment) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("fragment"), + php_uri_parser_rfc3986_validate_fragment + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, build) +{ + zval *base_url = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(base_url, php_uri_ce_rfc3986_uri) + ZEND_PARSE_PARAMETERS_END(); + + const zval *scheme = Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS); + const zval *userinfo = Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS); + const zval *host = Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS); + const zval *port = Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS); + const zval *path = Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS); + const zval *query = Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS); + const zval *fragment = Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS); + + php_uri_parser_rfc3986_uris *base_uris = NULL; + if (base_url != NULL) { + base_uris = Z_URI_OBJECT_P(base_url)->uri; + } + + php_uri_parser_rfc3986_uris *uriparser_uris = php_uri_parser_rfc3986_build_from_zval( + base_uris, scheme, userinfo, host, port, path, query, fragment + ); + if (uriparser_uris == NULL) { + RETURN_THROWS(); + } + + object_init_ex(return_value, php_uri_ce_rfc3986_uri); + php_uri_object *uri_object = Z_URI_OBJECT_P(return_value); + uri_object->parser = &php_uri_parser_rfc3986; + uri_object->uri = uriparser_uris; +} + PHPAPI php_uri_object *php_uri_object_create(zend_class_entry *class_type, const php_uri_parser *parser) { php_uri_object *uri_object = zend_object_alloc(sizeof(*uri_object), class_type); @@ -1113,6 +1314,8 @@ PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser) static PHP_MINIT_FUNCTION(uri) { + php_uri_ce_rfc3986_uri_builder = register_class_Uri_Rfc3986_UriBuilder(); + php_uri_ce_rfc3986_uri = register_class_Uri_Rfc3986_Uri(); php_uri_ce_rfc3986_uri->create_object = php_uri_object_create_rfc3986; php_uri_ce_rfc3986_uri->default_object_handlers = &object_handlers_rfc3986_uri; diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index b0b83fcf83ec..d00ef45cb86e 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -45,6 +45,35 @@ enum UriHostType case RegisteredName; } + final class UriBuilder + { + private ?string $scheme = null; + private ?string $userinfo = null; + private ?string $host = null; + private ?int $port = null; + private string $path = ""; + private ?string $query = null; + private ?string $fragment = null; + + public function reset(): static {} + + public function setScheme(?string $scheme): static {} + + public function setUserInfo(#[\SensitiveParameter] ?string $userInfo): static {} + + public function setHost(?string $host): static {} + + public function setPort(?int $port): static {} + + public function setPath(string $path): static {} + + public function setQuery(?string $query): static {} + + public function setFragment(?string $fragment): static {} + + public function build(?\Uri\Rfc3986\Uri $baseUrl = null): \Uri\Rfc3986\Uri {} + } + /** @strict-properties */ final readonly class Uri { diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index 0fb464ee74aa..e63e495526f5 100644 --- a/ext/uri/php_uri_arginfo.h +++ b/ext/uri/php_uri_arginfo.h @@ -1,7 +1,42 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: a3b4696ac001d537cc34b818715c7eb382c17c5b + * Stub hash: 1a396b7a5b170b968d84a2c64a5bcf7d962653db * Has decl header: yes */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setScheme, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setUserInfo, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, userInfo, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setHost, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setPort, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setPath, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setQuery, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setFragment, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_build, 0, 0, Uri\\Rfc3986\\\125ri, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\Rfc3986\\\125ri, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_parse, 0, 1, IS_STATIC, 1) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\Rfc3986\\\125ri, 1, "null") @@ -20,9 +55,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawScheme arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withScheme, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withScheme arginfo_class_Uri_Rfc3986_UriBuilder_setScheme #define arginfo_class_Uri_Rfc3986_Uri_getUserInfo arginfo_class_Uri_Rfc3986_Uri_getScheme @@ -47,41 +80,31 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getHostType, 0, 0, Uri\\Rfc3986\\\125riHostType, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withHost, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withHost arginfo_class_Uri_Rfc3986_UriBuilder_setHost ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPort, 0, 0, IS_LONG, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPort, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withPort arginfo_class_Uri_Rfc3986_UriBuilder_setPort ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPath, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawPath arginfo_class_Uri_Rfc3986_Uri_getPath -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPath, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath #define arginfo_class_Uri_Rfc3986_Uri_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawQuery arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withQuery, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withQuery arginfo_class_Uri_Rfc3986_UriBuilder_setQuery #define arginfo_class_Uri_Rfc3986_Uri_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawFragment arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withFragment, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withFragment arginfo_class_Uri_Rfc3986_UriBuilder_setFragment ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, uri, Uri\\Rfc3986\\\125ri, 0) @@ -158,23 +181,23 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_WhatWg_Url_getHostType, 0, 0, Uri\\WhatWg\\\125rlHostType, 1) ZEND_END_ARG_INFO() -#define arginfo_class_Uri_WhatWg_Url_withHost arginfo_class_Uri_Rfc3986_Uri_withHost +#define arginfo_class_Uri_WhatWg_Url_withHost arginfo_class_Uri_Rfc3986_UriBuilder_setHost #define arginfo_class_Uri_WhatWg_Url_getPort arginfo_class_Uri_Rfc3986_Uri_getPort -#define arginfo_class_Uri_WhatWg_Url_withPort arginfo_class_Uri_Rfc3986_Uri_withPort +#define arginfo_class_Uri_WhatWg_Url_withPort arginfo_class_Uri_Rfc3986_UriBuilder_setPort #define arginfo_class_Uri_WhatWg_Url_getPath arginfo_class_Uri_Rfc3986_Uri_getPath -#define arginfo_class_Uri_WhatWg_Url_withPath arginfo_class_Uri_Rfc3986_Uri_withPath +#define arginfo_class_Uri_WhatWg_Url_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath #define arginfo_class_Uri_WhatWg_Url_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme -#define arginfo_class_Uri_WhatWg_Url_withQuery arginfo_class_Uri_Rfc3986_Uri_withQuery +#define arginfo_class_Uri_WhatWg_Url_withQuery arginfo_class_Uri_Rfc3986_UriBuilder_setQuery #define arginfo_class_Uri_WhatWg_Url_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme -#define arginfo_class_Uri_WhatWg_Url_withFragment arginfo_class_Uri_Rfc3986_Uri_withFragment +#define arginfo_class_Uri_WhatWg_Url_withFragment arginfo_class_Uri_Rfc3986_UriBuilder_setFragment ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, url, Uri\\WhatWg\\\125rl, 0) @@ -196,6 +219,15 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize +ZEND_METHOD(Uri_Rfc3986_UriBuilder, reset); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setScheme); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setHost); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setPort); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setPath); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setQuery); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setFragment); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, build); ZEND_METHOD(Uri_Rfc3986_Uri, parse); ZEND_METHOD(Uri_Rfc3986_Uri, __construct); ZEND_METHOD(Uri_Rfc3986_Uri, getUriType); @@ -251,6 +283,19 @@ ZEND_METHOD(Uri_WhatWg_Url, __serialize); ZEND_METHOD(Uri_WhatWg_Url, __unserialize); ZEND_METHOD(Uri_WhatWg_Url, __debugInfo); +static const zend_function_entry class_Uri_Rfc3986_UriBuilder_methods[] = { + ZEND_ME(Uri_Rfc3986_UriBuilder, reset, arginfo_class_Uri_Rfc3986_UriBuilder_reset, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setScheme, arginfo_class_Uri_Rfc3986_UriBuilder_setScheme, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setUserInfo, arginfo_class_Uri_Rfc3986_UriBuilder_setUserInfo, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setHost, arginfo_class_Uri_Rfc3986_UriBuilder_setHost, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setPort, arginfo_class_Uri_Rfc3986_UriBuilder_setPort, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setPath, arginfo_class_Uri_Rfc3986_UriBuilder_setPath, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setQuery, arginfo_class_Uri_Rfc3986_UriBuilder_setQuery, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setFragment, arginfo_class_Uri_Rfc3986_UriBuilder_setFragment, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, build, arginfo_class_Uri_Rfc3986_UriBuilder_build, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_Uri_Rfc3986_Uri_methods[] = { ZEND_ME(Uri_Rfc3986_Uri, parse, arginfo_class_Uri_Rfc3986_Uri_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(Uri_Rfc3986_Uri, __construct, arginfo_class_Uri_Rfc3986_Uri___construct, ZEND_ACC_PUBLIC) @@ -403,6 +448,49 @@ static zend_class_entry *register_class_Uri_Rfc3986_UriHostType(void) return class_entry; } +static zend_class_entry *register_class_Uri_Rfc3986_UriBuilder(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Uri\\Rfc3986", "UriBuilder", class_Uri_Rfc3986_UriBuilder_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_scheme_default_value; + ZVAL_NULL(&property_scheme_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_SCHEME), &property_scheme_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_userinfo_default_value; + ZVAL_NULL(&property_userinfo_default_value); + zend_string *property_userinfo_name = zend_string_init("userinfo", sizeof("userinfo") - 1, true); + zend_declare_typed_property(class_entry, property_userinfo_name, &property_userinfo_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_string_release_ex(property_userinfo_name, true); + + zval property_host_default_value; + ZVAL_NULL(&property_host_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_HOST), &property_host_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_port_default_value; + ZVAL_NULL(&property_port_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PORT), &property_port_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG|MAY_BE_NULL)); + + zval property_path_default_value; + ZVAL_EMPTY_STRING(&property_path_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PATH), &property_path_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + zval property_query_default_value; + ZVAL_NULL(&property_query_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_QUERY), &property_query_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_fragment_default_value; + ZVAL_NULL(&property_fragment_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &property_fragment_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + + zend_add_parameter_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setuserinfo", sizeof("setuserinfo") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + return class_entry; +} + static zend_class_entry *register_class_Uri_Rfc3986_Uri(void) { zend_class_entry ce, *class_entry; diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index b79d092ae726..9106f6acd15f 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -17,6 +17,7 @@ #include "php_uri_decl.h" +extern zend_class_entry *php_uri_ce_rfc3986_uri_builder; extern zend_class_entry *php_uri_ce_rfc3986_uri; extern zend_class_entry *php_uri_ce_rfc3986_uri_type; extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type; diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h index d1fd58d04b2c..784ac5b4c0e5 100644 --- a/ext/uri/php_uri_decl.h +++ b/ext/uri/php_uri_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: a3b4696ac001d537cc34b818715c7eb382c17c5b */ + * Stub hash: 1a396b7a5b170b968d84a2c64a5bcf7d962653db */ -#ifndef ZEND_PHP_URI_DECL_a3b4696ac001d537cc34b818715c7eb382c17c5b_H -#define ZEND_PHP_URI_DECL_a3b4696ac001d537cc34b818715c7eb382c17c5b_H +#ifndef ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H +#define ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H typedef enum zend_enum_Uri_UriComparisonMode { ZEND_ENUM_Uri_UriComparisonMode_IncludeFragment = 1, @@ -63,4 +63,4 @@ typedef enum zend_enum_Uri_WhatWg_UrlHostType { ZEND_ENUM_Uri_WhatWg_UrlHostType_Empty = 5, } zend_enum_Uri_WhatWg_UrlHostType; -#endif /* ZEND_PHP_URI_DECL_a3b4696ac001d537cc34b818715c7eb382c17c5b_H */ +#endif /* ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H */ diff --git a/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt new file mode 100644 index 000000000000..9c11fb2e6682 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder all components - success - calling reset() afterwards +--FILE-- +setScheme("https") + ->setUserInfo("user:info") + ->setHost("example.com") + ->setPort(443) + ->setPath("/foo/bar/baz") + ->setQuery("foo=1&bar=baz") + ->setFragment("fragment"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +$uri = $builder->reset()->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(68) "https://user:info@example.com:443/foo/bar/baz?foo=1&bar=baz#fragment" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + string(4) "user" + ["password"]=> + string(4) "info" + ["host"]=> + string(11) "example.com" + ["port"]=> + int(443) + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + string(8) "fragment" +} +bool(true) +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt b/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt new file mode 100644 index 000000000000..284de4e6a522 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder basic - error - with base URL +--FILE-- +setPath("/foo/bar/baz"); + +try { + $builder->build(new Uri\Rfc3986\Uri("/foo/bar")); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified base URI must be absolute diff --git a/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt b/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt new file mode 100644 index 000000000000..b7a84dc00cf1 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder basic - success - with base URL +--FILE-- +setPath("/foo/bar/baz"); +$uri = $builder->build(new Uri\Rfc3986\Uri("https://example.com")); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(31) "https://example.com/foo/bar/baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt new file mode 100644 index 000000000000..8ed2a8938e9a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains invalid special character +--FILE-- +setFragment("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified fragment is malformed diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt new file mode 100644 index 000000000000..2a15d5e85a82 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains Unicode character +--FILE-- +setFragment("főő"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified fragment is malformed diff --git a/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt b/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt new file mode 100644 index 000000000000..a59d9322e1b5 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - success - basic +--FILE-- +setFragment("foo"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(4) "#foo" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + string(3) "foo" +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt b/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt new file mode 100644 index 000000000000..69fc4d6bca49 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - success - null +--FILE-- +setFragment("foo"); +$builder->setFragment(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt new file mode 100644 index 000000000000..45eef13faf71 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - missing IPv6 closing brace +--FILE-- +setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt new file mode 100644 index 000000000000..ae06238a1f8f --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoding octet in registered name +--FILE-- +setHost("ex%3mple.co"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt new file mode 100644 index 000000000000..cc514ec69191 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoded octet in IPv6 +--FILE-- +setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt new file mode 100644 index 000000000000..561a66c40375 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - invalid IPv4 address falls back to a registered name +--FILE-- +setHost("192.168.%30.1"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri->getHostType()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(15) "//192.168.%30.1" +enum(Uri\Rfc3986\UriHostType::RegisteredName) +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(13) "192.168.%30.1" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt new file mode 100644 index 000000000000..7ae3b9010851 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv4 address +--FILE-- +setHost("192.168.0.1"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(13) "//192.168.0.1" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "192.168.0.1" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt new file mode 100644 index 000000000000..f0216f7e7e09 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv6 address +--FILE-- +setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(43) "//[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(41) "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt new file mode 100644 index 000000000000..470ef1da97dc --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPvFuture address +--FILE-- +setHost("[v1.2001:db8::1]"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(18) "//[v1.2001:db8::1]" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(16) "[v1.2001:db8::1]" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/host_success_null.phpt b/ext/uri/tests/rfc3986/builder/host_success_null.phpt new file mode 100644 index 000000000000..87217d0ef285 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_null.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - null +--FILE-- +setHost("example.com"); +$builder->setHost(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt new file mode 100644 index 000000000000..81e93ccfc10a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - Registered name +--FILE-- +setHost("www.example.com"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(17) "//www.example.com" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(15) "www.example.com" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt new file mode 100644 index 000000000000..8cce8d471aa1 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains a colon in the first path segment when the URI doesn't contain a scheme +--FILE-- +setPath("fo:o/bar/baz"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The path must not begin with ":" when the URI does not contain a scheme diff --git a/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt new file mode 100644 index 000000000000..b1f0a2518f1c --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains leading double slash when the host is not present +--FILE-- +setPath("//foo/bar/baz"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The path must not begin with "//" when the URI does not contain a host diff --git a/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt new file mode 100644 index 000000000000..97b6c3e8b854 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - missing a leading slash when the URI contains a host +--FILE-- +setPath("foo/bar/baz"); +$builder->setHost("example.com"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified path is malformed diff --git a/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt new file mode 100644 index 000000000000..039b22770246 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains invalid special character +--FILE-- +setPath("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified path is malformed diff --git a/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt new file mode 100644 index 000000000000..00ed0c953550 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - empty string +--FILE-- +setPath(""); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt new file mode 100644 index 000000000000..e1fc689ba2f9 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - contains a colon in the first segment when the scheme is present +--FILE-- +setScheme("https"); +$builder->setPath(":foo/bar/baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(%d) "https::foo/bar/baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(12) ":foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt new file mode 100644 index 000000000000..d00fc87b4400 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - begins with double slashes when the URI contains a host +--FILE-- +setHost("example.com"); +$builder->setPath("//foo/bar/baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(26) "//example.com//foo/bar/baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(13) "//foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt new file mode 100644 index 000000000000..4b40e84c78f3 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - error - missing host +--FILE-- +setPort(443); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: Cannot set a port without having a host diff --git a/ext/uri/tests/rfc3986/builder/port_error_negative.phpt b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt new file mode 100644 index 000000000000..4cbc0e1c69d7 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - error - negative number +--FILE-- +setPort(-1); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified port is malformed diff --git a/ext/uri/tests/rfc3986/builder/port_success_large.phpt b/ext/uri/tests/rfc3986/builder/port_success_large.phpt new file mode 100644 index 000000000000..14d252a7d9c5 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_success_large.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - success - large number +--FILE-- +setPort(PHP_INT_MAX); +$builder->setHost("example.com"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(%d) "//example.com:%d" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + int(%d) + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/port_success_null.phpt b/ext/uri/tests/rfc3986/builder/port_success_null.phpt new file mode 100644 index 000000000000..6c2ffaf7109a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_success_null.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - success - null +--FILE-- +setPort(433); +$builder->setPort(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt new file mode 100644 index 000000000000..ecfced24a44c --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains invalid special character +--FILE-- +setQuery("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified query is malformed diff --git a/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt new file mode 100644 index 000000000000..4c212cddd631 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains Unicode character +--FILE-- +setQuery("főő"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified query is malformed diff --git a/ext/uri/tests/rfc3986/builder/query_success_basic.phpt b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt new file mode 100644 index 000000000000..423b03b0b01d --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - success - basic +--FILE-- +setQuery("foo=1&bar=baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(14) "?foo=1&bar=baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/query_success_null.phpt b/ext/uri/tests/rfc3986/builder/query_success_null.phpt new file mode 100644 index 000000000000..8e233c02c84b --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_success_null.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - success - null +--FILE-- +setQuery("foo"); +$builder->setQuery(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt new file mode 100644 index 000000000000..13594c163363 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - empty +--FILE-- +setScheme(""); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt new file mode 100644 index 000000000000..c178a0fa1f1e --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - first character is not alpha +--FILE-- +setScheme("1"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt new file mode 100644 index 000000000000..4941db44aa7d --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - contains invalid special character +--FILE-- +setScheme(":"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt new file mode 100644 index 000000000000..279ecb273623 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - basic +--FILE-- +setScheme("scheme"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(7) "scheme:" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt new file mode 100644 index 000000000000..1a9510156b08 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - null +--FILE-- +setScheme("https"); +$builder->setScheme(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt new file mode 100644 index 000000000000..f387bc09f2e1 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - contains digit & special characters +--FILE-- +setScheme("my-12+34.scheme"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(16) "my-12+34.scheme:" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(15) "my-12+34.scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt new file mode 100644 index 000000000000..a6919e8ffd0a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - missing host +--FILE-- +setUserInfo("user:pass"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: Cannot set a userinfo without having a host diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt new file mode 100644 index 000000000000..0830a57864d7 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - invalid percent encoding +--FILE-- +setUserInfo("%3"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified userinfo is malformed diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt new file mode 100644 index 000000000000..9d3fb2544daf --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - contains invalid special character +--FILE-- +setUserInfo("<>"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified userinfo is malformed diff --git a/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt new file mode 100644 index 000000000000..3decca97e791 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - success - null +--FILE-- +setUserInfo("user:pass"); +$builder->setUserInfo(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); +var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString()))); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 123f244a51d7..88201914d593 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -125,19 +125,19 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_uri_type_read(php_uri_parser_ { const UriUriA *uriparser_uri = get_uri_for_reading(uri, PHP_URI_COMPONENT_READ_MODE_RAW); - const char *type; + zend_object *value; if (has_text_range(&uriparser_uri->scheme)) { - type = "Uri"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_Uri); } else if (has_text_range(&uriparser_uri->hostText)) { - type = "NetworkPathReference"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_NetworkPathReference); } else if (uriparser_uri->absolutePath) { - type = "AbsolutePathReference"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_AbsolutePathReference); } else { - type = "RelativePathReference"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_RelativePathReference); } - ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_rfc3986_uri_type, type)); + ZVAL_OBJ_COPY(retval, value); } ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) @@ -290,19 +290,19 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_host_type_read(php_uri_parser return; } - const char *type; + zend_object *value; if (uriparser_uri->hostData.ip4 != NULL) { - type = "IPv4"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IPv4); } else if (uriparser_uri->hostData.ip6 != NULL) { - type = "IPv6"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IPv6); } else if (has_text_range(&uriparser_uri->hostData.ipFuture)) { - type = "IPvFuture"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IPvFuture); } else { - type = "RegisteredName"; + value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_RegisteredName); } - ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_rfc3986_uri_host_type, type)); + ZVAL_OBJ_COPY(retval, value); } static zend_result php_uri_parser_rfc3986_host_write(void *uri, const zval *value, zval *errors) @@ -545,12 +545,35 @@ static php_uri_parser_rfc3986_uris *uriparser_create_uris(void) return uriparser_uris; } -php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, bool silent) +static zend_result php_uri_parser_rfc3986_add_base_url( + UriUriA *tmp, const UriUriA *uri, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent +) { + const int result = uriAddBaseUriExMmA(tmp, uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm); + if (result != URI_SUCCESS) { + if (!silent) { + switch (result) { + case URI_ERROR_ADDBASE_REL_BASE: + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0); + break; + default: + /* This should be unreachable in practice. */ + zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0); + break; + } + } + + return FAILURE; + } + + return SUCCESS; +} + +php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, const size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent) { UriUriA uri = {0}; /* Parse the URI. */ - int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm); + const int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm); if (result != URI_SUCCESS) { if (!silent) { switch (result) { @@ -572,20 +595,7 @@ php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str /* Combine the parsed URI with the base URI and store the result in 'tmp', * since the target and source URLs must be distinct. */ - int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm); - if (result != URI_SUCCESS) { - if (!silent) { - switch (result) { - case URI_ERROR_ADDBASE_REL_BASE: - zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0); - break; - default: - /* This should be unreachable in practice. */ - zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0); - break; - } - } - + if (php_uri_parser_rfc3986_add_base_url(&tmp, &uri, uriparser_base_urls, silent) == FAILURE) { goto fail; } @@ -683,6 +693,189 @@ static void php_uri_parser_rfc3986_destroy(void *uri) efree(uriparser_uris); } +static zend_always_inline zend_result php_uri_parser_rfc3986_validate_component_result(const bool well_formed, const char *component_name) +{ + if (well_formed) { + return SUCCESS; + } + + zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", component_name); + return FAILURE; +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme) +{ + const char *p = ZSTR_VAL(scheme); + const size_t len = ZSTR_LEN(scheme); + const bool well_formed = uriIsWellFormedSchemeA(p, p + len) == URI_TRUE; + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "scheme"); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo) +{ + const char *p = ZSTR_VAL(userinfo); + const size_t len = ZSTR_LEN(userinfo); + const bool well_formed = uriIsWellFormedUserInfoA(p, p + len) == URI_TRUE; + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "userinfo"); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_host(const zend_string *host) +{ + const char *p = ZSTR_VAL(host); + const size_t len = ZSTR_LEN(host); + + if (len == 0) { + return SUCCESS; + } + + if (p[0] == '[') { + if (p[len - 1] != ']') { + return php_uri_parser_rfc3986_validate_component_result(false, "host"); + } + + if (len >= 2 && (p[1] == 'v' || p[1] == 'V')) { + return php_uri_parser_rfc3986_validate_component_result( + uriIsWellFormedHostIpFutureA(p + 1, p + len - 1) == URI_SUCCESS, + "host" + ); + } + + return php_uri_parser_rfc3986_validate_component_result( + uriIsWellFormedHostIp6A(p + 1, p + len - 1) == URI_SUCCESS, + "host" + ); + } + + if (uriIsWellFormedHostIp4A(p, p + len) == URI_TRUE) { + return SUCCESS; + } + + return php_uri_parser_rfc3986_validate_component_result( + uriIsWellFormedHostRegNameA(p, p + len) == URI_TRUE, + "host" + ); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_port(const zend_long port) +{ + char buf[MAX_LENGTH_OF_LONG + 1]; + const char *res = zend_print_long_to_buf(buf + sizeof(buf) - 1, port); + + const bool well_formed = uriIsWellFormedPortA(res, res + strlen(res)); + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "port"); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_path(const zend_string *path) +{ + const char *p = ZSTR_VAL(path); + const size_t len = ZSTR_LEN(path); + /* The build() method checks whether the path begins with a "/" when there's a host. + * In order to skip doing the same check, a false hasHost argument is passed to uriIsWellFormedPathA(). */ + const bool well_formed = uriIsWellFormedPathA(p, p + len, /* hasHost */ false); + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "path"); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_query(const zend_string *query) +{ + const char *p = ZSTR_VAL(query); + const size_t len = ZSTR_LEN(query); + const bool well_formed = uriIsWellFormedQueryA(p, p + len); + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "query"); +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment) +{ + const char *p = ZSTR_VAL(fragment); + const size_t len = ZSTR_LEN(fragment); + const bool well_formed = uriIsWellFormedFragmentA(p, p + len); + + return php_uri_parser_rfc3986_validate_component_result(well_formed, "fragment"); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_build_from_zval( + const php_uri_parser_rfc3986_uris *uriparser_base_uris, + const zval *scheme, const zval *userinfo, const zval *host, const zval *port, + const zval *path, const zval *query, const zval *fragment +) { + php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris(); + + if (Z_STRLEN_P(path) > 0) { + /* The first segment of the path must not contain ":" if the URI does not contain a scheme */ + if (Z_TYPE_P(scheme) == IS_NULL) { + const char *p = Z_STRVAL_P(path); + while (*p != '\0' && *p != '/') { + if (*p == ':') { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \":\" when the URI does not contain a scheme", 0); + goto failure; + } + + p++; + } + } + + /* The path must not begin with "//" if the URI does not contain a host */ + if (Z_TYPE_P(host) == IS_NULL && zend_string_starts_with_literal(Z_STR_P(path), "//")) { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \"//\" when the URI does not contain a host", 0); + goto failure; + } + } + + zend_result result = php_uri_parser_rfc3986_scheme_write(uriparser_uris, scheme, NULL); + if (result == FAILURE) { + goto failure; + } + result = php_uri_parser_rfc3986_host_write(uriparser_uris, host, NULL); + if (result == FAILURE) { + goto failure; + } + /* Intentionally writing userinfo after host to avoid error when the userinfo is set but the host is missing */ + result = php_uri_parser_rfc3986_userinfo_write(uriparser_uris, userinfo, NULL); + if (result == FAILURE) { + goto failure; + } + /* Intentionally writing userinfo after host to avoid error when the port is set but the host is missing */ + result = php_uri_parser_rfc3986_port_write(uriparser_uris, port, NULL); + if (result == FAILURE) { + goto failure; + } + result = php_uri_parser_rfc3986_path_write(uriparser_uris, path, NULL); + if (result == FAILURE) { + goto failure; + } + result = php_uri_parser_rfc3986_query_write(uriparser_uris, query, NULL); + if (result == FAILURE) { + goto failure; + } + result = php_uri_parser_rfc3986_fragment_write(uriparser_uris, fragment, NULL); + if (result == FAILURE) { + goto failure; + } + + if (uriparser_base_uris != NULL) { + UriUriA tmp = {0}; + + if (php_uri_parser_rfc3986_add_base_url(&tmp, &uriparser_uris->uri, uriparser_base_uris, false) == FAILURE) { + goto failure; + } + + uriMakeOwnerMmA(&tmp, mm); + uriFreeUriMembersMmA(&uriparser_uris->uri, mm); + uriparser_uris->uri = tmp; + } + + return uriparser_uris; + +failure: + uriFreeUriMembersMmA(&uriparser_uris->uri, mm); + efree(uriparser_uris); + return NULL; +} + PHPAPI const php_uri_parser php_uri_parser_rfc3986 = { .name = PHP_URI_PARSER_RFC3986, .parse = php_uri_parser_rfc3986_parse, diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h index a5fde7074323..9a9f0b38a0c4 100644 --- a/ext/uri/uri_parser_rfc3986.h +++ b/ext/uri/uri_parser_rfc3986.h @@ -29,4 +29,18 @@ zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *u php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_url, bool silent); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_host(const zend_string *host); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_port(zend_long port); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_path(const zend_string *path); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_query(const zend_string *query); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment); + +ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_build_from_zval( + const php_uri_parser_rfc3986_uris *uriparser_base_uris, + const zval *scheme, const zval *userinfo, const zval *host, const zval *port, + const zval *path, const zval *query, const zval *fragment +); + #endif diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 8cb3fe6cba01..d1875c4946de 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -39,6 +39,7 @@ #include "zend_mm_custom_handlers.h" #include "ext/uri/php_uri.h" #include "zend_observer.h" +#include "test_decl.h" #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) # include @@ -622,7 +623,7 @@ static ZEND_FUNCTION(zend_get_unit_enum) { ZEND_PARSE_PARAMETERS_NONE(); - RETURN_OBJ_COPY(zend_enum_get_case_cstr(zend_test_unit_enum, "Foo")); + RETURN_OBJ_COPY(zend_enum_get_case_by_id(zend_test_unit_enum, ZEND_ENUM_ZendTestUnitEnum_Foo)); } static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 72f7428594c3..87de69c35c0b 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -45,6 +45,7 @@ static void php_stream_error_create_object(zval *zv, php_stream_error_entry *ent case_name = "Generic"; } + /* TODO: migrate to zend_enum_get_case_by_id() */ zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name); ZEND_ASSERT(enum_obj != NULL);