Commit c7273b9ba89 for php
commit c7273b9ba89cc8c0beaf19c03a77332361e7a89e
Merge: 73e2bad976d 8a94bb3822b
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date: Wed Oct 21 16:30:04 2026 +0200
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
Add NEWS entry
Avoid state corruption via bailout
diff --cc NEWS
index b2ac87b48cc,12d15e699f1..36221c702ae
--- a/NEWS
+++ b/NEWS
@@@ -124,31 -132,12 +124,16 @@@ PH
. Fixed use-after-free when an error handler removes a failing zlib.inflate
filter (GH-24176). (Edmond)
-22 Oct 2026, PHP 8.4.27
+22 Oct 2026, PHP 8.5.12
- - BCMath:
- . Fixed BcMath\Number results that truncate to zero keeping a negative sign
- and comparing less than zero. (Ilia Alshanetsky)
-
- - BZ2:
- . Fixed double close of the descriptor and use-after-free of the inner
- stream when bzopen() is given a stream resource. (Jakub Zelenka)
-
- - CLI
- . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
- request activation). (matyhtf)
- . Fixed bug GH-23764 (Built-in server leaks a file descriptor on every HEAD
- request for a static file). (jakubskopal)
- . Fixed crash in the built-in server when a client is reset before being
- accepted. (David Carlier)
- . Fixed the built-in server truncating an error page and leaking its
- unsent part after a partial socket write. (Ilia Alshanetsky)
-
- - Core
+ - Core:
+ . Fixed GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp (Avoid state corruption via
+ bailout). (CVE-2026-109618, CVE-2026-109619) (Arnaud, ilutov, Jakub Zelenka,
+ ndossche, Nicolas Grekas)
+ . Fixed bug GH-23644 (Optimizer leaves a constant-vs-constant comparison
+ unfolded, crashing the VM in zval_undefined_cv). (ndossche)
+ . Fixed OSS-Fuzz 532353396 (assertion failure with static type). (Girgias)
+ . Fix GH-23662 (Avoid NAN warning in print_r()). (CodedByManish)
. Fix GH-21999: GC inconsistency with lazy object, var_dump(), and object
comparison. (Arnaud)
. Fixed OSS-Fuzz #536440507 (Immutable class incorrect assertion).
@@@ -160,9 -151,34 +145,27 @@@
function and a later request calls it. (Ilia Alshanetsky)
. Fixed System V shared memory emulation for Windows attaching segments past
the end of their mapping. (Ilia Alshanetsky)
- . Fixed bug GH-24050 (GC frees an object still held by a resurrected closure
- when (object) and use share an array). (David Carlier)
- . Fixed use-after-free when a Fiber callable resolves to $this of the method
- creating the Fiber ([A::class, 'm'] or "A::m") and that object dies before
- the Fiber runs. (Edmond Dantes)
. Fixed exception thrown by destructor during GC in a Fiber not being rethrown
into the frame that triggered the GC. (Nicolas Grekas)
- . Fixed bug GH-24139 (NULL pointer dereference in php_ini.c when
- expand_filepath() fails). (lazerg)
+ - BCMath:
+ . Fixed BcMath\Number results that truncate to zero keeping a negative sign
+ and comparing less than zero. (Ilia Alshanetsky)
+
+ - BZ2:
+ . Fixed double close of the descriptor and use-after-free of the inner
+ stream when bzopen() is given a stream resource. (Jakub Zelenka)
+
+ - CLI:
+ . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
+ request activation). (matyhtf)
+ . Fixed bug GH-23764 (Built-in server leaks a file descriptor on every HEAD
+ request for a static file). (jakubskopal)
+ . Fixed crash in the built-in server when a client is reset before being
+ accepted. (David Carlier)
+ . Fixed the built-in server truncating an error page and leaking its
+ unsent part after a partial socket write. (Ilia Alshanetsky)
+
- DOM:
. Fixed use-after-free when re-constructing a DOMXPath whose php:function
registrations are freed while still reachable from the cycle collector.
diff --cc Zend/tests/oom_request_parsing.phpt
index 00000000000,a39e562000f..e532acfe244
mode 000000,100644..100644
--- a/Zend/tests/oom_request_parsing.phpt
+++ b/Zend/tests/oom_request_parsing.phpt
@@@ -1,0 -1,130 +1,130 @@@
+ --TEST--
+ Request parsing doesn't reach hard limit
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+ die("skip php-cgi not available");
+ }
+ if (getenv('SKIP_ASAN')) {
+ die("skip ASan-instrumented realloc() is slow");
+ }
+ ?>
+ --FILE--
+ <?php
+ $base = __DIR__ . '/' . basename(__FILE__, '.php');
+
+ $tiny = $base . '.target.php';
+ file_put_contents($tiny, <<<'TARGET'
+ <?php
+ echo "no OOM\n";
+ TARGET);
+
+ $big = $base . '.big.target.php';
+ file_put_contents($big, "<?php\n" . str_repeat("\$x = 1 + 2;\n", 60000) . "echo \"no OOM\\n\";\n");
+
+ /* The body goes through a file rather than a pipe: some of these are several
+ * megabytes, and the child stops reading as soon as it reports the error. */
+ function oom_post(string $body, string $script, array $ini): void
+ {
+ $bodyFile = tempnam(sys_get_temp_dir(), 'oom_request_parsing');
+ file_put_contents($bodyFile, $body);
+
- $cmd = [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n'];
++ $cmd = [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'fatal_error_backtraces=Off'];
+ foreach ($ini as $entry) {
+ $cmd[] = '-d';
+ $cmd[] = $entry;
+ }
+
+ $p = proc_open(
+ $cmd,
+ [['file', $bodyFile, 'r'], STDOUT, STDERR],
+ $pipes,
+ null,
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => (string) strlen($body),
+ 'SCRIPT_FILENAME' => $script,
+ 'REDIRECT_STATUS' => '1',
+ ]
+ );
+ proc_close($p);
+ unlink($bodyFile);
+ }
+
+ /* Deeply nested variable names, at the default max_input_vars and
+ * max_input_nesting_level. */
+ oom_post(
+ implode('&', array_fill(0, 1000, 'a[]' . str_repeat('[b]', 63) . '=c')),
+ $tiny,
+ ['memory_limit=8M', 'html_errors=0']
+ );
+
+ $ini = ['memory_limit=8M', 'post_max_size=1G', 'max_input_vars=10000000', 'html_errors=0'];
+
+ /* One value with no '&' in it: nothing consumes it, so add_post_vars() never
+ * gets a complete variable to check and only the read loop can notice. */
+ $oneValue = 'a=' . str_repeat('x', 6 * 1024 * 1024);
+
+ /* Many small variables: the limit is reached while registering them. */
+ $manyVars = '';
+ for ($i = 0; $i < 200000; $i++) {
+ $manyVars .= ($i ? '&' : '') . "k$i=v$i";
+ }
+
+ /* A big value followed by many small ones: add_post_var() copies the value
+ * once with estrndup() and again when registering it, and each half fits even
+ * though the pair doesn't. */
+ $mixed = 'a=' . str_repeat('x', 3 * 1024 * 1024);
+ for ($i = 0; $i < 100000; $i++) {
+ $mixed .= "&k$i=v$i";
+ }
+
+ foreach ([$oneValue, $manyVars, $mixed] as $body) {
+ foreach ([$tiny, $big] as $script) {
+ oom_post($body, $script, $ini);
+ }
+ }
+ ?>
+ --CLEAN--
+ <?php
+ $base = __DIR__ . '/' . basename(__FILE__, '.clean.php');
+ @unlink($base . '.target.php');
+ @unlink($base . '.big.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc Zend/zend.c
index acd105ce43e,32028c9c5a0..b86601cd16e
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@@ -1490,23 -1464,13 +1495,23 @@@ ZEND_API ZEND_COLD void zend_error_zstr
/* This is very inefficient for a large number of errors.
* Use pow2 realloc if it becomes a problem. */
- EG(num_errors)++;
- EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors));
- EG(errors)[EG(num_errors)-1] = info;
+ uint32_t new_num_errors = EG(num_errors) + 1;
+ EG(errors) = safe_erealloc(EG(errors), new_num_errors, sizeof(zend_error_info*), 0);
+ EG(errors)[EG(num_errors)] = info;
+ EG(num_errors) = new_num_errors;
+
+ /* Do not process non-fatal recorded error */
+ if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) {
+ return;
+ }
}
+ // Always clear the last backtrace.
+ zval_ptr_dtor(&EG(last_fatal_error_backtrace));
+ ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
+
/* Report about uncaught exception in case of fatal errors */
- if (EG(exception)) {
+ if (EG(exception) && EXPECTED(EG(delayed_oom).state != ZEND_OOM_STATE_HANDLING)) {
zend_execute_data *ex;
const zend_op *opline;
diff --cc Zend/zend_alloc.c
index c5ab74c70bb,3cbdd1e5f5d..ba5a70287a4
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@@ -305,21 -316,10 +316,22 @@@ struct _zend_mm_heap
size_t (*_gc)(void);
void (*_shutdown)(bool full, bool silent);
} custom_heap;
- HashTable *tracked_allocs;
+ union {
+ HashTable *tracked_allocs;
+ struct {
+ bool poison_alloc;
+ uint8_t poison_alloc_value;
+ bool poison_free;
+ uint8_t poison_free_value;
+ uint8_t padding;
+ bool check_freelists_on_shutdown;
+ } debug;
+ };
+ uint64_t max_allocs;
#endif
+#if ZEND_DEBUG
pid_t pid;
+#endif
zend_random_bytes_insecure_state rand_state;
};
diff --cc Zend/zend_alloc.h
index 264e13848d1,315b9f3dcda..0fce08d6c1d
--- a/Zend/zend_alloc.h
+++ b/Zend/zend_alloc.h
@@@ -317,8 -368,17 +369,19 @@@ struct _zend_mm_storage
ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap);
ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void *data, size_t data_size);
+ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap);
+
+ ZEND_API ZEND_COLD void zend_mm_delayed_error_or_panic(bool delay, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
+ ZEND_API void ZEND_FASTCALL zend_handle_delayed_oom(void);
+ ZEND_COLD void zend_mm_oom_info_dtor(zend_oom_info *info);
+
+ /* Exit status of a process that stopped because of ZEND_MM_MAX_ALLOCS, so that
+ * it can be told apart from an ordinary fatal error. */
+ #define ZEND_MM_MAX_ALLOCS_EXIT_STATUS 121
+
+ ZEND_API void zend_mm_set_max_allocs(uint64_t max_allocs);
+ ZEND_API bool zend_mm_max_allocs_hit(void);
+
/*
// The following example shows how to use zend_mm_heap API with custom storage
diff --cc Zend/zend_execute_API.c
index 076471c4e65,30eb6ec8af3..e3c9fc49c90
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@@ -428,6 -428,13 +435,14 @@@ ZEND_API void zend_shutdown_executor_va
}
zend_objects_store_free_object_storage(&EG(objects_store), fast_shutdown);
+
+ /* may be used by zend_error_zstr_at before init_executor */
++ ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
+ EG(record_errors) = false;
+ EG(num_errors) = 0;
+ EG(errors) = NULL;
+ EG(error_handling) = EH_NORMAL;
+ EG(exception) = NULL;
}
void shutdown_executor(void) /* {{{ */
diff --cc Zend/zend_hash.c
index b8c33548f93,3d88d179f7c..0ed782ddd98
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@@ -292,7 -356,21 +355,21 @@@ ZEND_API HashTable* ZEND_FASTCALL _zend
return ht;
}
+ ZEND_API HashTable* ZEND_FASTCALL _zend_fnew_array(uint32_t nSize)
+ {
+ HashTable *ht = femalloc(sizeof(HashTable), /* fallible */ true);
+ if (UNEXPECTED(ht == NULL)) {
+ return NULL;
+ }
+
+ if (UNEXPECTED(_zend_hash_finit_int(ht, nSize, ZVAL_PTR_DTOR, /* persistent */ false, /* fallible */ true) == FAILURE)) {
+ efree(ht);
+ return NULL;
+ }
+ return ht;
+ }
+
-ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(zval *val1, zval *val2)
+ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(const zval *val1, const zval *val2)
{
zval *zv;
HashTable *ht = emalloc(sizeof(HashTable));
diff --cc Zend/zend_hash.h
index 57020bbcad0,3c37cc064e9..ecbcedffcfa
--- a/Zend/zend_hash.h
+++ b/Zend/zend_hash.h
@@@ -26,19 -26,17 +26,20 @@@
#include "zend_string.h"
#include "zend_sort.h"
-#define HASH_KEY_IS_STRING 1
-#define HASH_KEY_IS_LONG 2
-#define HASH_KEY_NON_EXISTENT 3
-
-#define HASH_UPDATE (1<<0)
-#define HASH_ADD (1<<1)
-#define HASH_UPDATE_INDIRECT (1<<2)
-#define HASH_ADD_NEW (1<<3)
-#define HASH_ADD_NEXT (1<<4)
-#define HASH_LOOKUP (1<<5)
+typedef enum {
+ HASH_KEY_IS_STRING = 1,
+ HASH_KEY_IS_LONG,
+ HASH_KEY_NON_EXISTENT
+} zend_hash_key_type;
+
+#define HASH_UPDATE (1<<0) /* Create new entry, or update the existing one. */
+#define HASH_ADD (1<<1) /* Create new entry, or fail if it exists. */
+#define HASH_UPDATE_INDIRECT (1<<2) /* If the given ht entry is an indirect zval, unwrap it before writing to it. \
+ * When used with HASH_ADD, writing is allowed if the target zval is IS_UNDEF. */
+#define HASH_ADD_NEW (1<<3) /* Used when the offset is known not to exist. */
+#define HASH_ADD_NEXT (1<<4) /* Append to an array. (e.g. $array[] = 42;) */
+#define HASH_LOOKUP (1<<5) /* Look up an existing entry, or create one with a NULL value. */
+ #define HASH_FALLIBLE (1<<6) /* Use fallible allocation. Return FAILURE or HASH_NOMEM when allocation fails (for functions with zend_result or zval* types, respectively). */
#define HASH_FLAG_CONSISTENCY ((1<<0) | (1<<1))
#define HASH_FLAG_PACKED (1<<2)
@@@ -343,12 -369,13 +372,13 @@@ ZEND_API void ZEND_FASTCALL zend_hash_r
#endif
ZEND_API HashTable* ZEND_FASTCALL _zend_new_array_0(void);
+ ZEND_API HashTable* ZEND_FASTCALL zend_new_array_singleton(void);
ZEND_API HashTable* ZEND_FASTCALL _zend_new_array(uint32_t size);
-ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(zval *val1, zval *val2);
+ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(const zval *val1, const zval *val2);
ZEND_API uint32_t zend_array_count(HashTable *ht);
-ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
+ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(const HashTable *source);
ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
-ZEND_API HashTable* zend_array_to_list(HashTable *source);
+ZEND_API HashTable* zend_array_to_list(const HashTable *source);
ZEND_API void ZEND_FASTCALL zend_symtable_clean(HashTable *ht);
ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht);
ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, bool always_duplicate);
diff --cc Zend/zend_vm_def.h
index 21074bc688d,df255c5a868..533a7d342a1
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@@ -10530,14 -10424,12 +10537,17 @@@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA)
ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY)
{
zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
+#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL
+ /* opline is &call_interrupt_op. Load orig opline. */
+ LOAD_OPLINE();
+#else
SAVE_OPLINE();
+#endif
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
zend_timeout();
+ } else if (ZEND_HAS_OOM()) {
+ zend_handle_delayed_oom();
+ HANDLE_EXCEPTION();
} else if (zend_interrupt_function) {
zend_interrupt_function(execute_data);
if (EG(exception)) {
diff --cc Zend/zend_vm_execute.h
index 2bc51f2841a,4b313f20cd3..df733f525d5
Binary files differ
diff --cc ext/bz2/bz2.c
index 319518f2ace,f272e6d1741..b67bb8dbd7e
--- a/ext/bz2/bz2.c
+++ b/ext/bz2/bz2.c
@@@ -515,9 -497,21 +515,12 @@@ PHP_FUNCTION(bzcompress
dest_len = (unsigned int) chunk_len;
/* Allocate the destination buffer */
- dest = zend_string_alloc(dest_len, 0);
+ dest = zend_string_falloc(dest_len, 0, /* fallible */ true);
+ if (!dest) {
+ RETURN_OOM();
+ }
- /* Handle the optional arguments */
- if (argc > 1) {
- block_size = zblock_size;
- }
-
- if (argc > 2) {
- work_factor = zwork_factor;
- }
-
- error = BZ2_bzBuffToBuffCompress(ZSTR_VAL(dest), &dest_len, source, source_len, block_size, 0, work_factor);
+ int error = BZ2_bzBuffToBuffCompress(ZSTR_VAL(dest), &dest_len, source, source_len, block_size, 0, work_factor);
if (error != BZ_OK) {
zend_string_efree(dest);
RETURN_LONG(error);
diff --cc ext/curl/tests/oom_curl_exec.phpt
index 00000000000,afbb948fda9..466842e7bbe
mode 000000,100644..100644
--- a/ext/curl/tests/oom_curl_exec.phpt
+++ b/ext/curl/tests/oom_curl_exec.phpt
@@@ -1,0 -1,37 +1,37 @@@
+ --TEST--
+ curl_exec() doesn't reach hard limit
+ --EXTENSIONS--
+ curl
+ --FILE--
+ <?php
+ require __DIR__ . '/server.inc';
+ $host = curl_cli_server_start();
+
+ $extension = ini_get('extension_dir') . '/' . (PHP_OS_FAMILY === 'Windows' ? 'php_' : '') . 'curl.' . PHP_SHLIB_SUFFIX;
+ $args = [];
+ if (is_file($extension)) {
+ $args[] = '-d';
+ $args[] = "extension=$extension";
+ }
+
+ $code = <<<'PHP'
+ $ch = curl_init($argv[1]);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_exec($ch);
+ echo "no OOM\n";
+ PHP;
+
+ $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
+ $p = proc_open(
- [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
++ [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-d', 'fatal_error_backtraces=Off', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
+ [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes
+ );
+ echo stream_get_contents($pipes[1]);
+ echo stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+ ?>
+ --EXPECTF--
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/curl/tests/oom_curl_multi_exec.phpt
index 00000000000,4a233166e70..d2e19a7e9c4
mode 000000,100644..100644
--- a/ext/curl/tests/oom_curl_multi_exec.phpt
+++ b/ext/curl/tests/oom_curl_multi_exec.phpt
@@@ -1,0 -1,43 +1,43 @@@
+ --TEST--
+ curl_multi_exec() doesn't reach hard limit
+ --EXTENSIONS--
+ curl
+ --FILE--
+ <?php
+ require __DIR__ . '/server.inc';
+ $host = curl_cli_server_start();
+
+ $extension = ini_get('extension_dir') . '/' . (PHP_OS_FAMILY === 'Windows' ? 'php_' : '') . 'curl.' . PHP_SHLIB_SUFFIX;
+ $args = [];
+ if (is_file($extension)) {
+ $args[] = '-d';
+ $args[] = "extension=$extension";
+ }
+
+ $code = <<<'PHP'
+ $mh = curl_multi_init();
+ $ch = curl_init($argv[1]);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_BUFFERSIZE, 10 * 1024 * 1024);
+ curl_multi_add_handle($mh, $ch);
+ do {
+ curl_multi_exec($mh, $running);
+ curl_multi_select($mh, 0.05);
+ } while ($running);
+ echo "no OOM\n";
+ PHP;
+
+ $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
+ $p = proc_open(
- [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
++ [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-d', 'fatal_error_backtraces=Off', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
+ [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes
+ );
+ echo stream_get_contents($pipes[1]);
+ echo stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+ ?>
+ --EXPECTF--
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/date/lib/parse_date.c
index ac03ebbe39d,31103a81fa0..9a9ae5523eb
--- a/ext/date/lib/parse_date.c
+++ b/ext/date/lib/parse_date.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Generated by re2c 1.0.3 on Sun Oct 4 18:31:01 2026 */
-/* Generated by re2c 1.0.3 on Wed Sep 30 23:15:59 2026 */
++/* Generated by re2c 1.0.3 on Thu Oct 1 14:54:40 2026 */
#line 1 "ext/date/lib/parse_date.re"
/*
* The MIT License (MIT)
diff --cc ext/date/lib/parse_iso_intervals.c
index 5dcbfe35063,bbc9cd2337f..aef0838909b
--- a/ext/date/lib/parse_iso_intervals.c
+++ b/ext/date/lib/parse_iso_intervals.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Generated by re2c 1.0.3 on Mon Sep 15 10:40:09 2025 */
-/* Generated by re2c 1.0.3 on Wed Sep 30 15:20:07 2026 */
++/* Generated by re2c 1.0.3 on Thu Oct 1 14:55:09 2026 */
#line 1 "ext/date/lib/parse_iso_intervals.re"
/*
* The MIT License (MIT)
diff --cc ext/dom/tests/oom_xpath_quote_concat.phpt
index 00000000000,00000000000..69e6f1c186a
new file mode 100644
--- /dev/null
+++ b/ext/dom/tests/oom_xpath_quote_concat.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++DOMXPath::quote() with both quote kinds doesn't reach hard memory limit
++--EXTENSIONS--
++dom
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* A string holding both quote kinds takes the concat() path */
++DOMXPath::quote(str_repeat("'\"", 2 * 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/dom/tests/oom_xpath_quote_single.phpt
index 00000000000,00000000000..9b21f00ef28
new file mode 100644
--- /dev/null
+++ b/ext/dom/tests/oom_xpath_quote_single.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++DOMXPath::quote() with one quote kind doesn't reach hard memory limit
++--EXTENSIONS--
++dom
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++DOMXPath::quote(str_repeat("a", 4 * 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/dom/xpath.c
index bb0207e526a,c3d15fe44dd..f5ed6dc4a65
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@@ -406,24 -378,27 +408,34 @@@ static void php_xpath_eval(INTERNAL_FUN
node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
} else {
- php_dom_create_object(node, &child, &intern->dom);
+ dom_object *parent = dom_xpath_intern_for_doc(intern, node->doc);
+ php_dom_create_object(node, &child, parent);
}
add_next_index_zval(&retval, &child);
+ if (ZEND_HAS_OOM()) {
+ goto oom_nodeset;
+ }
}
+ release_array = true;
} else {
ZVAL_EMPTY_ARRAY(&retval);
}
- php_dom_create_iterator(return_value, DOM_NODELIST, modern);
+
+ object_init_ex(return_value, dom_get_nodelist_ce(modern));
nodeobj = Z_DOMOBJ_P(return_value);
- dom_xpath_iter(&retval, nodeobj);
+ dom_nnodemap_object *mapptr = nodeobj->ptr;
+
+ mapptr->array = Z_ARR(retval);
+ mapptr->release_array = release_array;
+ mapptr->handler = &php_dom_obj_map_nodeset;
break;
+
+ oom_nodeset:
+ zval_ptr_dtor(&retval);
+ xmlXPathFreeObject(xpathobjp);
+ zval_ptr_dtor(return_value);
+ ZVAL_NULL(return_value);
+ RETURN_OOM();
}
case XPATH_BOOLEAN:
@@@ -542,14 -517,14 +554,20 @@@ PHP_METHOD(DOMXPath, quote)
RETURN_THROWS();
}
if (memchr(input, '\'', input_len) == NULL) {
-- zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
++ zend_string *const output = zend_string_safe_falloc(1, input_len, 2, false, /* fallible */ true);
++ if (UNEXPECTED(!output)) {
++ RETURN_OOM();
++ }
ZSTR_VAL(output)[0] = '\'';
memcpy(ZSTR_VAL(output) + 1, input, input_len);
ZSTR_VAL(output)[input_len + 1] = '\'';
ZSTR_VAL(output)[input_len + 2] = '\0';
- RETURN_STR(output);
+ RETURN_NEW_STR(output);
} else if (memchr(input, '"', input_len) == NULL) {
-- zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
++ zend_string *const output = zend_string_safe_falloc(1, input_len, 2, false, /* fallible */ true);
++ if (UNEXPECTED(!output)) {
++ RETURN_OOM();
++ }
ZSTR_VAL(output)[0] = '"';
memcpy(ZSTR_VAL(output) + 1, input, input_len);
ZSTR_VAL(output)[input_len + 1] = '"';
@@@ -573,10 -548,10 +591,14 @@@
smart_str_appendc(&output, quote_method);
ptr += bytes_until_quote;
smart_str_appendc(&output, ',');
++ if (ZEND_HAS_OOM()) {
++ smart_str_free(&output);
++ RETURN_OOM();
++ }
}
ZEND_ASSERT(ptr == end);
- ZSTR_VAL(output.s)[output.s->len - 1] = ')';
- RETURN_STR(smart_str_extract(&output));
+ ZSTR_VAL(output.s)[ZSTR_LEN(output.s) - 1] = ')';
+ RETURN_NEW_STR(smart_str_extract(&output));
}
}
/* }}} */
diff --cc ext/exif/exif.c
index a2eb8259ac7,a72acb4f2c5..b63c29751e5
--- a/ext/exif/exif.c
+++ b/ext/exif/exif.c
@@@ -2461,8 -2455,9 +2468,8 @@@ static void exif_iif_free(image_info_ty
/* {{{ add_assoc_image_info
* Add image_info to associative array value. */
- static void add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index)
+ static zend_result add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index)
{
- char buffer[64], uname[64];
int idx = 0, unknown = 0;
if (!image_info->info_list[section_index].count) {
@@@ -4211,10 -4250,13 +4260,13 @@@ static bool exif_process_IFD_in_TIFF_im
#ifdef EXIF_DEBUG
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size);
#endif
- if (!ImageInfo->Thumbnail.data) {
- ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
+ if (!ImageInfo->Thumbnail.data && exif_thumbnail_in_file(ImageInfo)) {
+ ImageInfo->Thumbnail.data = safe_femalloc(ImageInfo->Thumbnail.size, 1, 0, /* fallible */ true);
+ if (!ImageInfo->Thumbnail.data) {
+ return false;
+ }
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
- fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
+ size_t fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
if (fgot != ImageInfo->Thumbnail.size) {
EXIF_ERRLOG_THUMBEOF(ImageInfo)
efree(ImageInfo->Thumbnail.data);
@@@ -4251,10 -4293,14 +4303,14 @@@
#ifdef EXIF_DEBUG
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size);
#endif
- if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
- ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
+ if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail
+ && exif_thumbnail_in_file(ImageInfo)) {
+ ImageInfo->Thumbnail.data = safe_femalloc(ImageInfo->Thumbnail.size, 1, 0, /* fallible */ true);
+ if (!ImageInfo->Thumbnail.data) {
+ return false;
+ }
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
- fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
+ size_t fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
if (fgot != ImageInfo->Thumbnail.size) {
EXIF_ERRLOG_THUMBEOF(ImageInfo)
efree(ImageInfo->Thumbnail.data);
@@@ -4948,19 -4831,17 +5016,19 @@@ PHP_FUNCTION(exif_thumbnail
exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Returning thumbnail(%d)", ImageInfo.Thumbnail.size);
#endif
- RETVAL_STRINGL(ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size);
+ RETVAL_FSTRINGL(ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size);
- if (arg_c >= 3) {
- if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) {
- if (!exif_scan_thumbnail(&ImageInfo)) {
- ImageInfo.Thumbnail.width = ImageInfo.Thumbnail.height = 0;
- }
+ if ((z_width || z_height) && (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height)) {
+ if (!exif_scan_thumbnail(&ImageInfo)) {
+ ImageInfo.Thumbnail.width = ImageInfo.Thumbnail.height = 0;
}
+ }
+ if (z_width) {
ZEND_TRY_ASSIGN_REF_LONG(z_width, ImageInfo.Thumbnail.width);
+ }
+ if (z_height) {
ZEND_TRY_ASSIGN_REF_LONG(z_height, ImageInfo.Thumbnail.height);
}
- if (arg_c >= 4) {
+ if (z_imagetype) {
ZEND_TRY_ASSIGN_REF_LONG(z_imagetype, ImageInfo.Thumbnail.filetype);
}
diff --cc ext/fileinfo/libmagic.patch
index 4eb7ccd336f,ed95a40221c..041562d48a9
--- a/ext/fileinfo/libmagic.patch
+++ b/ext/fileinfo/libmagic.patch
@@@ -1,15 -1,6 +1,15 @@@
diff -u libmagic.orig/apprentice.c libmagic/apprentice.c
---- libmagic.orig/apprentice.c 2023-07-17 16:38:35.000000000 +0200
-+++ libmagic/apprentice.c 2026-09-30 22:55:28.668770846 +0200
+--- libmagic.orig/apprentice.c 2024-11-27 16:37:00.000000000 +0100
- +++ libmagic/apprentice.c 2025-02-09 02:25:02.364884555 +0100
+++++ libmagic/apprentice.c 2026-10-01 14:49:48.677051351 +0200
+@@ -32,7 +32,7 @@
+ #include "file.h"
+
+ #ifndef lint
+-FILE_RCSID("@(#)$File: apprentice.c,v 1.356 2024/11/27 15:37:00 christos Exp $")
++FILE_RCSID("@(#)$File: apprentice.c,v 1.355 2024/11/25 22:31:53 christos Exp $")
+ #endif /* lint */
+
+ #include "magic.h"
@@ -48,7 +48,9 @@
#ifdef QUICK
#include <sys/mman.h>
@@@ -311,29 -283,12 +311,29 @@@
fn = p;
}
-- free(mfn);
+ efree(mfn);
-
++
if (errs == -1) {
for (i = 0; i < MAGIC_SETS; i++) {
-@@ -1289,7 +1240,7 @@
+ mlist_free(ms->mlist[i]);
+@@ -1143,6 +1065,8 @@
+ mpa.lineno = mpb.lineno = 0;
+ int x = memcmp(&mpa, &mpb, sizeof(mpa));
+ if (x == 0) {
++ /* For PHP BC: don't warn */
++#if 0
+ // Don't warn for DER
+ if (mpa.type == FILE_DER)
+ return 0;
- @@ -1151,6 +1075,7 @@
++@@ -1150,6 +1074,7 @@
++ ma->mp->desc);
+ file_mdump(ma->mp);
+ file_mdump(mb->mp);
++#endif
+ return 0;
+ }
+ return x > 0 ? -1 : 1;
- }
+@@ -1303,7 +1228,7 @@
size_t incr = mset[i].max + ALLOC_INCR;
if ((mp = CAST(struct magic_entry *,
@@@ -1055,8 -976,8 +1055,8 @@@ diff -u libmagic.orig/buffer.c libmagic
goto out;
}
diff -u libmagic.orig/cdf.c libmagic/cdf.c
---- libmagic.orig/cdf.c 2022-09-24 22:56:49.000000000 +0200
-+++ libmagic/cdf.c 2026-10-02 19:35:01.765068091 +0200
+--- libmagic.orig/cdf.c 2024-11-25 22:24:59.000000000 +0100
- +++ libmagic/cdf.c 2025-02-09 01:25:00.187641434 +0100
+++++ libmagic/cdf.c 2026-10-02 19:35:44.175918710 +0200
@@ -43,7 +43,9 @@
#include <err.h>
#endif
@@@ -1067,44 -988,48 +1067,51 @@@
#include <string.h>
#include <time.h>
#include <ctype.h>
- @@ -91,44 +93,9 @@
- CDF_TOLE8(CAST(uint64_t, x))))
- #define CDF_GETUINT32(x, y) cdf_getuint32(x, y)
+ @@ -95,39 +97,39 @@
+ #define CDF_REALLOC(p, n) cdf_realloc(__FILE__, __LINE__, (p), (n))
+ #define CDF_CALLOC(n, u) cdf_calloc(__FILE__, __LINE__, (n), (u))
- -#define CDF_MALLOC(n) cdf_malloc(__FILE__, __LINE__, (n))
- -#define CDF_REALLOC(p, n) cdf_realloc(__FILE__, __LINE__, (p), (n))
- -#define CDF_CALLOC(n, u) cdf_calloc(__FILE__, __LINE__, (n), (u))
- -
-
- -/*ARGSUSED*/
+ /*ARGSUSED*/
- static void *
+-file_private void *
-cdf_malloc(const char *file __attribute__((__unused__)),
- size_t line __attribute__((__unused__)), size_t n)
- -{
- - DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
- - file, line, __func__, n));
+++static void *
+ +cdf_malloc(const char *file,
+ + size_t line, size_t n)
+ {
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+ file, line, __func__, n));
- if (n == 0)
- n++;
- return malloc(n);
- -}
- -
- -/*ARGSUSED*/
+ + return femalloc(n, /* fallible */ true);
+ }
+
+ /*ARGSUSED*/
- static void *
+-file_private void *
-cdf_realloc(const char *file __attribute__((__unused__)),
- size_t line __attribute__((__unused__)), void *p, size_t n)
- -{
- - DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
- - file, line, __func__, n));
+++static void *
+ +cdf_realloc(const char *file,
+ + size_t line, void *p, size_t n)
+ {
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+ file, line, __func__, n));
- return realloc(p, n);
- -}
- -
- -/*ARGSUSED*/
+ + return safe_ferealloc(p, 1, n, 0, /* fallible */ true);
+ }
+
+ /*ARGSUSED*/
- static void *
+-file_private void *
-cdf_calloc(const char *file __attribute__((__unused__)),
- size_t line __attribute__((__unused__)), size_t n, size_t u)
- -{
- - DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u %"
- - SIZE_T_FORMAT "u\n", file, line, __func__, n, u));
+++static void *
+ +cdf_calloc(const char *file,
+ + size_t line, size_t n, size_t u)
+ {
+ DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u %"
+ SIZE_T_FORMAT "u\n", file, line, __func__, n, u));
- if (n == 0)
- n++;
- return calloc(n, u);
@@@ -1124,8 -1051,8 +1133,8 @@@
scn->sst_tab = NULL;
return -1;
}
- @@ -342,9 +309,11 @@
+ @@ -342,9 +344,11 @@
- static size_t
+ file_private size_t
cdf_check_stream(const cdf_stream_t *sst, const cdf_header_t *h)
{
+#ifndef NDEBUG
@@@ -1136,10 -1063,10 +1145,10 @@@
return sst->sst_ss;
}
- @@ -367,11 +336,11 @@
+ @@ -367,11 +371,11 @@
}
- static ssize_t
+ file_private ssize_t
-cdf_read(const cdf_info_t *info, off_t off, void *buf, size_t len)
+cdf_read(const cdf_info_t *info, zend_off_t off, void *buf, size_t len)
{
diff --cc ext/filter/filter.c
index 608d2172a7a,7e1a0311099..c0682037c8c
--- a/ext/filter/filter.c
+++ b/ext/filter/filter.c
@@@ -365,11 -339,14 +370,14 @@@ static unsigned int php_sapi_filter(in
if (val_len) {
/* Register mangled variable */
+ if (UNEXPECTED(!ZVAL_FSTRINGL(&new_var, *val, val_len))) {
+ return 0;
+ }
if (IF_G(default_filter) != FILTER_UNSAFE_RAW) {
- ZVAL_STRINGL(&new_var, *val, val_len);
- php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL, 0);
+ php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL);
- } else {
- ZVAL_STRINGL(&new_var, *val, val_len);
+ if (ZEND_HAS_OOM()) {
+ return 0;
+ }
}
} else { /* empty string */
ZVAL_EMPTY_STRING(&new_var);
diff --cc ext/filter/sanitizing_filters.c
index 865065491d3,4e549a3d65d..de108915547
--- a/ext/filter/sanitizing_filters.c
+++ b/ext/filter/sanitizing_filters.c
@@@ -31,10 -31,9 +31,10 @@@ static bool php_filter_encode_html(zva
size_t len = Z_STRLEN_P(value);
unsigned char *s = (unsigned char *)Z_STRVAL_P(value);
unsigned char *e = s + len;
+ unsigned char *last_output = s;
if (Z_STRLEN_P(value) == 0) {
- return;
+ return true;
}
while (s < e) {
@@@ -43,15 -41,23 +43,23 @@@
smart_str_appendl(&str, "&#", 2);
smart_str_append_unsigned(&str, (zend_ulong)*s);
smart_str_appendc(&str, ';');
- } else {
- /* XXX: this needs to be optimized to work with blocks of 'safe' chars */
- smart_str_appendc(&str, *s);
+ last_output = s + 1;
}
s++;
+
+ if (ZEND_HAS_OOM()) {
+ smart_str_free(&str);
+ zval_ptr_dtor(value);
+ ZVAL_EMPTY_STRING(value);
+ return false;
+ }
}
+ smart_str_appendl(&str, (const char *) last_output, s - last_output);
+
zval_ptr_dtor(value);
- ZVAL_STR(value, smart_str_extract(&str));
+ ZVAL_NEW_STR(value, smart_str_extract(&str));
+ return true;
}
static const unsigned char hexchars[] = "0123456789ABCDEF";
@@@ -76,9 -82,14 +84,14 @@@ static void php_filter_encode_url(zval
tmp[*s++] = '\0';
}
- str = zend_string_safe_alloc(Z_STRLEN_P(value), 3, 0, 0);
+ str = zend_string_safe_falloc(Z_STRLEN_P(value), 3, 0, /* persistent */ false, /* fallible */ true);
+ if (!str) {
+ zval_ptr_dtor(value);
+ ZVAL_EMPTY_STRING(value);
+ return;
+ }
- p = (unsigned char *) ZSTR_VAL(str);
- s = (unsigned char *) Z_STRVAL_P(value);
+ p = (unsigned char*)ZSTR_VAL(str);
+ s = (const unsigned char*)Z_STRVAL_P(value);
e = s + Z_STRLEN_P(value);
while (s < e) {
@@@ -193,7 -205,9 +206,9 @@@ zend_result php_filter_string(PHP_INPUT
memset(enc + 127, 1, sizeof(enc) - 127);
}
- php_filter_encode_html(value, enc);
+ if (!php_filter_encode_html(value, enc)) {
- return;
++ return FAILURE;
+ }
/* strip tags, implicitly also removes \0 chars */
new_len = php_strip_tags_ex(Z_STRVAL_P(value), Z_STRLEN_P(value), NULL, 0, 1);
diff --cc ext/filter/tests/oom_filter_default_encoded.phpt
index 00000000000,4e81326639a..eb63678a68d
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_default_encoded.phpt
+++ b/ext/filter/tests/oom_filter_default_encoded.phpt
@@@ -1,0 -1,126 +1,127 @@@
+ --TEST--
+ filter.default=encoded doesn't reach hard limit while parsing POST data
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+ die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ echo "no OOM\n";
+ TARGET);
+
+ function oom_post_encoded(string $body, string $script): void
+ {
+ $p = proc_open(
+ [
+ getenv('TEST_PHP_CGI_EXECUTABLE'), '-n',
+ '-d', 'memory_limit=8M',
+ '-d', 'post_max_size=1G',
+ '-d', 'html_errors=0',
+ '-d', 'filter.default=encoded',
+ '-d', 'filter.default_flags=0',
++ '-d', 'fatal_error_backtraces=Off',
+ /* the conversion is what is under test, not the deprecation */
+ '-d', 'error_reporting=' . (E_ALL & ~E_DEPRECATED),
+ ],
+ [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes,
+ null,
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => (string) strlen($body),
+ 'SCRIPT_FILENAME' => $script,
+ 'REDIRECT_STATUS' => '1',
+ ]
+ );
+ fwrite($pipes[0], $body);
+ fclose($pipes[0]);
+ $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+
+ echo $out;
+ }
+
+ for ($kb = 2016; $kb <= 2064; $kb += 4) {
+ oom_post_encoded('a=' . str_repeat('x', $kb * 1024), $script);
+ }
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/filter/tests/oom_filter_input.phpt
index 00000000000,aef71dff563..68d56072f22
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_input.phpt
+++ b/ext/filter/tests/oom_filter_input.phpt
@@@ -1,0 -1,56 +1,56 @@@
+ --TEST--
+ filter_input() doesn't reach hard limit
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+ die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ filter_input(INPUT_POST, 'k', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_ENCODE_HIGH);
+ echo "no OOM\n";
+ TARGET);
+
+ $boundary = '----X';
+ $body = "--$boundary\r\nContent-Disposition: form-data; name=\"k\"\r\n\r\n"
+ . str_repeat("\xFF", 2 * 1024 * 1024)
+ . "\r\n--$boundary--\r\n";
+
+ $p = proc_open(
- [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0'],
++ [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0', '-d', 'fatal_error_backtraces=Off'],
+ [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes,
+ null,
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=$boundary",
+ 'CONTENT_LENGTH' => (string) strlen($body),
+ 'SCRIPT_FILENAME' => $script,
+ 'REDIRECT_STATUS' => '1',
+ ]
+ );
+ fwrite($pipes[0], $body);
+ fclose($pipes[0]);
+ $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+
+ echo $out;
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 12582912 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/filter/tests/oom_filter_input_array.phpt
index 00000000000,a8e2712271c..b36261f1dd5
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_input_array.phpt
+++ b/ext/filter/tests/oom_filter_input_array.phpt
@@@ -1,0 -1,56 +1,56 @@@
+ --TEST--
+ filter_input_array() doesn't reach hard limit
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+ die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ filter_input_array(INPUT_POST, ['k' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_ENCODE_HIGH]]);
+ echo "no OOM\n";
+ TARGET);
+
+ $boundary = '----X';
+ $body = "--$boundary\r\nContent-Disposition: form-data; name=\"k\"\r\n\r\n"
+ . str_repeat("\xFF", 2 * 1024 * 1024)
+ . "\r\n--$boundary--\r\n";
+
+ $p = proc_open(
- [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0'],
++ [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0', '-d', 'fatal_error_backtraces=Off'],
+ [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+ $pipes,
+ null,
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=$boundary",
+ 'CONTENT_LENGTH' => (string) strlen($body),
+ 'SCRIPT_FILENAME' => $script,
+ 'REDIRECT_STATUS' => '1',
+ ]
+ );
+ fwrite($pipes[0], $body);
+ fclose($pipes[0]);
+ $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+
+ echo $out;
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 12582912 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/gd/libgd/gd.c
index 47c4dbc0d77,6f0a7abc1e9..b088aadc840
--- a/ext/gd/libgd/gd.c
+++ b/ext/gd/libgd/gd.c
@@@ -3195,8 -3194,8 +3219,8 @@@ int gdImagePaletteToTrueColor(gdImagePt
// Note: do not revert back to gdMalloc() below ; reason here,
// due to a bug with a certain memory_limit INI value treshold,
// imagepalettetotruecolor crashes with even unrelated ZendMM allocations.
- // See GH-17772 for an use case.
- src->tpixels = (int **) gdFCalloc(sizeof(int *), sy);
+ // See GH-17772 for a use case.
- src->tpixels = (int **) gdCalloc(sy, sizeof(int *));
++ src->tpixels = (int **) gdFCalloc(sy, sizeof(int *));
if (src->tpixels == NULL) {
return 0;
}
diff --cc ext/iconv/iconv.c
index de1eb76ac52,0212d1063c7..2713e4cd70d
--- a/ext/iconv/iconv.c
+++ b/ext/iconv/iconv.c
@@@ -442,8 -445,8 +446,8 @@@ PHP_ICONV_API php_iconv_err_t php_iconv
char *out_p;
size_t bsz, result = 0;
php_iconv_err_t retval = PHP_ICONV_ERR_SUCCESS;
- zend_string *out_buf;
+ zend_string *out_buf = NULL;
- int ignore_ilseq = _php_check_ignore(out_charset);
+ bool ignore_ilseq = _php_check_ignore(out_charset);
*out = NULL;
diff --cc ext/intl/converter/converter.c
index bf89691dd6e,c9baeac9c1f..7d107c39ca1
--- a/ext/intl/converter/converter.c
+++ b/ext/intl/converter/converter.c
@@@ -639,10 -639,13 +639,13 @@@ static zend_string* php_converter_do_co
/* Get necessary buffer size first */
temp_len = 1 + ucnv_toUChars(src_cnv, NULL, 0, src, src_len, &error);
if (U_FAILURE(error) && error != U_BUFFER_OVERFLOW_ERROR) {
- THROW_UFAILURE(objval, "ucnv_toUChars", error);
+ THROW_UFAILURE(objval, error);
return NULL;
}
- temp = safe_emalloc(sizeof(UChar), temp_len, sizeof(UChar));
+ temp = safe_femalloc(sizeof(UChar), temp_len, sizeof(UChar), /* fallible */ true);
+ if (!temp) {
+ return NULL;
+ }
/* Convert to intermediate UChar* array */
error = U_ZERO_ERROR;
diff --cc ext/intl/grapheme/grapheme_string.c
index 64aa62d7458,59c3e3cddce..743939a9b66
--- a/ext/intl/grapheme/grapheme_string.c
+++ b/ext/intl/grapheme/grapheme_string.c
@@@ -919,213 -915,12 +922,232 @@@ PHP_FUNCTION(grapheme_str_split
utext_close(ut);
ubrk_close(bi);
+ return;
+
+ oom:
+ utext_close(ut);
+ ubrk_close(bi);
+ RETURN_OOM();
}
+PHP_FUNCTION(grapheme_levenshtein)
+{
+ zend_string *string1, *string2;
+ zend_long cost_ins = 1;
+ zend_long cost_rep = 1;
+ zend_long cost_del = 1;
+ char *locale = "";
+ size_t locale_len = 0;
+
+ ZEND_PARSE_PARAMETERS_START(2, 6)
+ Z_PARAM_STR(string1)
+ Z_PARAM_STR(string2)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_LONG(cost_ins)
+ Z_PARAM_LONG(cost_rep)
+ Z_PARAM_LONG(cost_del)
+ Z_PARAM_PATH(locale, locale_len)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (cost_ins <= 0 || cost_ins > UINT_MAX / 4) {
+ zend_argument_value_error(3, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
+ RETURN_THROWS();
+ }
+
+ if (cost_rep <= 0 || cost_rep > UINT_MAX / 4) {
+ zend_argument_value_error(4, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
+ RETURN_THROWS();
+ }
+
+ if (cost_del <= 0 || cost_del > UINT_MAX / 4) {
+ zend_argument_value_error(5, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
+ RETURN_THROWS();
+ }
+
+ zend_long c0, c1, c2;
+ zend_long retval;
+ size_t i2;
+ char *pstr1, *pstr2;
+
+ UChar *ustring1 = NULL;
+ UChar *ustring2 = NULL;
+
+ int32_t ustring1_len = 0;
+ int32_t ustring2_len = 0;
+
+ UErrorCode ustatus = U_ZERO_ERROR;
+
+ /* When all costs are equal, levenshtein fulfills the requirements of a metric, which means
+ * that the distance is symmetric. If string1 is shorter than string2 we can save memory (and CPU time)
+ * by having shorter rows (p1 & p2). */
+ if (ZSTR_LEN(string1) < ZSTR_LEN(string2) && cost_ins == cost_rep && cost_rep == cost_del) {
+ zend_string *tmp = string1;
+ string1 = string2;
+ string2 = tmp;
+ }
+
+ pstr1 = ZSTR_VAL(string1);
+ pstr2 = ZSTR_VAL(string2);
+
+ intl_convert_utf8_to_utf16(&ustring1, &ustring1_len, pstr1, ZSTR_LEN(string1), &ustatus);
+
++ if (ZEND_HAS_OOM()) {
++ goto out_ustring1;
++ }
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+
+ intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16");
+ RETVAL_FALSE;
+ goto out_ustring1;
+ }
+
+ intl_convert_utf8_to_utf16(&ustring2, &ustring2_len, pstr2, ZSTR_LEN(string2), &ustatus);
+
++ if (ZEND_HAS_OOM()) {
++ goto out_ustring2;
++ }
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+
+ intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16");
+ RETVAL_FALSE;
+ goto out_ustring2;
+ }
+
+ UBreakIterator *bi1, *bi2;
+
+ int32_t strlen_1, strlen_2;
+ strlen_1 = grapheme_split_string(ustring1, ustring1_len, NULL, 0);
+ strlen_2 = grapheme_split_string(ustring2, ustring2_len, NULL, 0);
+ if (UNEXPECTED(strlen_1 < 0 || strlen_2 < 0)) {
+ RETVAL_FALSE;
+ goto out_ustring2;
+ }
+
+ if (strlen_1 == 0) {
+ RETVAL_LONG(strlen_2 * cost_ins);
+ goto out_ustring2;
+ }
+ if (strlen_2 == 0) {
+ RETVAL_LONG(strlen_1 * cost_del);
+ goto out_ustring2;
+ }
+
+ unsigned char u_break_iterator_buffer1[U_BRK_SAFECLONE_BUFFERSIZE];
+ unsigned char u_break_iterator_buffer2[U_BRK_SAFECLONE_BUFFERSIZE];
+ bi1 = grapheme_get_break_iterator(u_break_iterator_buffer1, &ustatus);
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+ intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #1 ($string1)");
+ RETVAL_FALSE;
+ goto out_bi1;
+ }
+
+ bi2 = grapheme_get_break_iterator(u_break_iterator_buffer2, &ustatus);
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+ intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #2 ($string2)");
+ RETVAL_FALSE;
+ goto out_bi2;
+ }
+
+ ubrk_setText(bi1, ustring1, ustring1_len, &ustatus);
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+
+ intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #1 ($string1)");
+ RETVAL_FALSE;
+ goto out_bi2;
+ }
+
+ ubrk_setText(bi2, ustring2, ustring2_len, &ustatus);
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+
+ intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #2 ($string2)");
+ RETVAL_FALSE;
+ goto out_bi2;
+ }
+ UCollator *collator = ucol_open(locale, &ustatus);
+ if (U_FAILURE(ustatus)) {
+ intl_error_set_code(NULL, ustatus);
+
+ intl_error_set_custom_msg(NULL, "Error on ucol_open");
+ RETVAL_FALSE;
+ goto out_collator;
+ }
+
+ zend_long *p1, *p2, *tmp;
- p1 = safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0);
- p2 = safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0);
++ p1 = safe_femalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0, /* fallible */ true);
++ if (!p1) {
++ goto out_collator;
++ }
++ p2 = safe_femalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0, /* fallible */ true);
++ if (!p2) {
++ efree(p1);
++ goto out_collator;
++ }
+
+ for (i2 = 0; i2 <= strlen_2; i2++) {
+ p1[i2] = i2 * cost_ins;
+ }
+
+ int32_t current1 = 0;
+ int32_t current2 = 0;
+ int32_t pos1 = 0;
+ int32_t pos2 = 0;
+
+ while (true) {
+ current1 = ubrk_current(bi1);
+ pos1 = ubrk_next(bi1);
+ if (pos1 == UBRK_DONE) {
+ break;
+ }
+ p2[0] = p1[0] + cost_del;
+ for (i2 = 0, pos2 = 0; pos2 != UBRK_DONE; i2++) {
+ current2 = ubrk_current(bi2);
+ pos2 = ubrk_next(bi2);
+ if (pos2 == UBRK_DONE) {
+ break;
+ }
+ if (ucol_strcoll(collator, ustring1 + current1, pos1 - current1, ustring2 + current2, pos2 - current2) == UCOL_EQUAL) {
+ c0 = p1[i2];
+ } else {
+ c0 = p1[i2] + cost_rep;
+ }
+ c1 = p1[i2 + 1] + cost_del;
+ if (c1 < c0) {
+ c0 = c1;
+ }
+ c2 = p2[i2] + cost_ins;
+ if (c2 < c0) {
+ c0 = c2;
+ }
+ p2[i2 + 1] = c0;
+ }
+ ubrk_first(bi2);
+ tmp = p1;
+ p1 = p2;
+ p2 = tmp;
+ }
+
+ retval = p1[strlen_2];
+ RETVAL_LONG(retval);
+
+ efree(p2);
+ efree(p1);
+
+out_collator:
+ ucol_close(collator);
+out_bi2:
+ ubrk_close(bi2);
+out_bi1:
+ ubrk_close(bi1);
+out_ustring2:
+ efree(ustring2);
+out_ustring1:
+ efree(ustring1);
+}
+
/* }}} */
diff --cc ext/intl/normalizer/normalizer_normalize.cpp
index 92e8f1166ad,0e270c41f92..99877c398f8
--- a/ext/intl/normalizer/normalizer_normalize.cpp
+++ b/ext/intl/normalizer/normalizer_normalize.cpp
@@@ -147,10 -148,17 +147,13 @@@ U_CFUNC PHP_FUNCTION( normalizer_normal
/* Allocate memory for the destination buffer for normalization */
uret_len = uinput_len * expansion_factor;
- uret_buf = eumalloc( uret_len + 1 );
- uret_buf = safe_femalloc(uret_len + 1, sizeof(UChar), 0, /* fallible */ true);
++ uret_buf = (UChar*)safe_femalloc(uret_len + 1, sizeof(UChar), 0, /* fallible */ true);
+ if (!uret_buf) {
+ RETURN_OOM();
+ }
/* normalize */
-#if U_ICU_VERSION_MAJOR_NUM < 56
- size_needed = unorm_normalize( uinput, uinput_len, form, (int32_t) 0 /* options */, uret_buf, uret_len, &status);
-#else
size_needed = intl_normalize(form, uinput, uinput_len, uret_buf, uret_len, &status);
-#endif
/* Bail out if an unexpected error occurred.
* (U_BUFFER_OVERFLOW_ERROR means that *target buffer is not large enough).
@@@ -168,7 -176,11 +171,11 @@@
* uret_buf = eurealloc(uret_buf, size_needed + 1);
*/
efree( uret_buf );
- uret_buf = eumalloc( size_needed + 1 );
- uret_buf = safe_femalloc(size_needed + 1, sizeof(UChar), 0, /* fallible */ true);
++ uret_buf = (UChar*)safe_femalloc(size_needed + 1, sizeof(UChar), 0, /* fallible */ true);
+ if (!uret_buf) {
+ efree(uinput);
+ RETURN_OOM();
+ }
uret_len = size_needed;
status = U_ZERO_ERROR;
diff --cc ext/intl/tests/oom_grapheme_levenshtein_rows.phpt
index 00000000000,00000000000..2bed9ff5e08
new file mode 100644
--- /dev/null
+++ b/ext/intl/tests/oom_grapheme_levenshtein_rows.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++grapheme_levenshtein() doesn't reach hard limit - cost rows
++--EXTENSIONS--
++intl
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++grapheme_levenshtein("a", str_repeat("b", 1024 * 1024), 1, 2, 3);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/intl/tests/oom_grapheme_levenshtein_utf16.phpt
index 00000000000,00000000000..e6309ee288b
new file mode 100644
--- /dev/null
+++ b/ext/intl/tests/oom_grapheme_levenshtein_utf16.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++grapheme_levenshtein() doesn't reach hard limit - UTF-16 conversion
++--EXTENSIONS--
++intl
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$size = (int) (2.5 * 1024 * 1024);
++grapheme_levenshtein(str_repeat("a", $size), str_repeat("b", $size), 1, 2, 3);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/lexbor/php_lexbor.c
index f18a09a5c4f,00000000000..aa69f684e49
mode 100644,000000..100644
--- a/ext/lexbor/php_lexbor.c
+++ b/ext/lexbor/php_lexbor.c
@@@ -1,80 -1,0 +1,80 @@@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Nora Dossche <ndossche@php.net> |
+ | Mate Kocsis <kocsismate@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "php.h"
+#include "zend_globals.h"
+#include "ext/standard/info.h"
+#include "lexbor/core/base.h"
+#include "lexbor/core/types.h"
+#include "lexbor/core/lexbor.h"
+
+#ifdef HAVE_LEXBOR
+
+#include "php_lexbor.h"
+
+static void *php_lexbor_malloc(size_t size)
+{
- return emalloc(size);
++ return femalloc(size, /* fallible */ true);
+}
+
+static void *php_lexbor_realloc(void *dst, size_t size)
+{
- return erealloc(dst, size);
++ return safe_ferealloc(dst, 1, size, 0, /* fallible */ true);
+}
+
+static void *php_lexbor_calloc(size_t num, size_t size)
+{
- return ecalloc(num, size);
++ return fecalloc(num, size, /* fallible */ true);
+}
+
+static void php_lexbor_free(void *ptr)
+{
+ efree(ptr);
+}
+
+static PHP_MINFO_FUNCTION(lexbor)
+{
+ php_info_print_table_start();
+ php_info_print_table_row(2, "Lexbor support", "active");
+ php_info_print_table_row(2, "Lexbor version", LEXBOR_VERSION_STRING);
+ php_info_print_table_end();
+}
+
+static PHP_MINIT_FUNCTION(lexbor)
+{
+ lexbor_memory_setup(php_lexbor_malloc, php_lexbor_realloc, php_lexbor_calloc, php_lexbor_free);
+ return SUCCESS;
+}
+
+zend_module_entry lexbor_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "lexbor", /* extension name */
+ NULL, /* extension function list */
+ PHP_MINIT(lexbor), /* extension-wide startup function */
+ NULL, /* extension-wide shutdown function */
+ NULL, /* per-request startup function */
+ NULL, /* per-request shutdown function */
+ PHP_MINFO(lexbor), /* information function */
+ PHP_VERSION,
+ STANDARD_MODULE_PROPERTIES
+};
+
+#endif
diff --cc ext/libxml/libxml.c
index dcbdd6bca57,cb8a1df0efd..72e93e7dce2
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@@ -1199,10 -1202,12 +1203,10 @@@ PHP_FUNCTION(libxml_get_errors
ZEND_PARSE_PARAMETERS_NONE();
if (LIBXML(error_list)) {
- array_init(return_value);
+ array_init_size(return_value, zend_llist_count(LIBXML(error_list)));
- error = zend_llist_get_first(LIBXML(error_list));
+ xmlErrorPtr error = zend_llist_get_first(LIBXML(error_list));
- while (error != NULL) {
+ while (error != NULL && !ZEND_HAS_OOM()) {
zval z_error;
php_libxml_create_error_object(&z_error, error);
add_next_index_zval(return_value, &z_error);
diff --cc ext/opcache/jit/zend_jit_ir.c
index 78e7c94cea2,830dbdf3272..c41447de2fb
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@@ -2093,10 -2083,12 +2102,12 @@@ static int zend_jit_interrupt_handler_s
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
}
+ ir_MERGE_WITH(if_delayed_oom_end);
+
- if (GCC_GLOBAL_REGS) {
- ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
+ if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) {
+ zend_jit_tailcall_handler(jit, ir_LOAD_A(jit_IP(jit)));
} else {
- ir_RETURN(ir_CONST_I32(1));
+ zend_jit_vm_enter(jit, jit_IP(jit));
}
return 1;
}
diff --cc ext/opcache/zend_persist.c
index a4aea9fae65,8b8312ff81e..866dc01d102
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@@ -335,13 -314,58 +335,58 @@@ static HashTable *zend_persist_attribut
return ptr;
}
- uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name)
+ /* We delay the publishing of CE cache slots until after ZCSG(map_ptr_last) is
+ * published.
+ * CE cache slots stored in interned strings are immediately visible to other
+ * processes, so if we fail to update ZCSG(map_ptr_last) after publishing a
+ * slot (due to a bailout/exit), that slot may be re-allocated for something
+ * else by other processes. */
+ void zend_accel_pending_ce_cache_begin(void)
+ {
+ ZEND_ASSERT(ZCG(locked));
+
+ /* A persist window left through a bailout caught by the caller (e.g.
+ * opcache_compile_file()) never published its slots. Drop them. */
+ zend_accel_pending_ce_cache_discard();
+
+ ZCG(pending_ce_cache) = emalloc(sizeof(HashTable));
+ zend_hash_init(ZCG(pending_ce_cache), 8, NULL, NULL, 0);
+ }
+
+ void zend_accel_pending_ce_cache_commit(void)
+ {
+ ZEND_ASSERT(ZCG(pending_ce_cache));
+ /* Every pending slot must be covered by ZCSG(map_ptr_last) */
+ ZEND_ASSERT(ZCSG(map_ptr_last) == CG(map_ptr_last));
+
+ ZEND_HASH_FOREACH_NUM_KEY_VAL(ZCG(pending_ce_cache), zend_ulong h, zval *zv) {
+ zend_string *type_name = (zend_string*)(uintptr_t)h;
+
+ ZEND_ASSERT(!ZSTR_HAS_CE_CACHE(type_name));
+ GC_SET_REFCOUNT(type_name, (uint32_t)Z_LVAL_P(zv));
+ GC_ADD_FLAGS(type_name, IS_STR_CLASS_NAME_MAP_PTR);
+ } ZEND_HASH_FOREACH_END();
+
+ zend_accel_pending_ce_cache_discard();
+ }
+
+ void zend_accel_pending_ce_cache_discard(void)
+ {
+ if (ZCG(pending_ce_cache)) {
+ zend_hash_destroy(ZCG(pending_ce_cache));
+ efree(ZCG(pending_ce_cache));
+ ZCG(pending_ce_cache) = NULL;
+ }
+ }
+
+ void zend_accel_get_class_name_map_ptr(zend_string *type_name)
{
uint32_t ret;
+ zval *slot;
- if (zend_string_equals_literal_ci(type_name, "self") ||
- zend_string_equals_literal_ci(type_name, "parent")) {
+ if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_SELF)) ||
+ zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_PARENT))) {
- return 0;
+ return;
}
/* We use type.name.gc.refcount to keep map_ptr of corresponding type */
diff --cc ext/pcre/php_pcre.c
index 50e2e8a93f3,5146260b323..090cfd8da9a
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@@ -1031,8 -1040,8 +1031,8 @@@ static inline void add_offset_pair
}
/* }}} */
- static void populate_subpat_array(
+ static zend_result populate_subpat_array(
- zval *subpats, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
+ HashTable *subpats_ht, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags) {
zend_long offset_capture = flags & PREG_OFFSET_CAPTURE;
zend_long unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
@@@ -1085,20 -1113,25 +1103,27 @@@
populate_match_value(
&val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
zend_hash_next_index_insert_new(subpats_ht, &val);
+ if (ZEND_HAS_OOM()) {
+ return FAILURE;
+ }
}
if (unmatched_as_null) {
+ ZVAL_NULL(&val);
for (i = count; i < num_subpats; i++) {
- add_next_index_null(subpats);
+ zend_hash_next_index_insert_new(subpats_ht, &val);
+ if (ZEND_HAS_OOM()) {
+ return FAILURE;
+ }
}
}
}
}
/* Add MARK, if available */
if (mark) {
- add_assoc_string_ex(subpats, "MARK", sizeof("MARK") - 1, (char *)mark);
+ ZVAL_STRING(&val, (char *)mark);
+ zend_hash_str_add_new(subpats_ht, ZEND_STRL("MARK"), &val);
}
+ return ZEND_HAS_OOM() ? FAILURE : SUCCESS;
}
static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{ */
@@@ -1367,17 -1413,24 +1405,24 @@@ matched
/* Allocate and populate the result set array */
mark = pcre2_get_mark(match_data);
array_init_size(&result_set, count + (mark ? 1 : 0));
- populate_subpat_array(
+ zend_result result = populate_subpat_array(
- &result_set, subject, offsets, subpat_names,
+ Z_ARRVAL(result_set), subject, offsets, subpat_names,
num_subpats, count, mark, flags);
+ if (UNEXPECTED(result == FAILURE)) {
+ zval_ptr_dtor(&result_set);
+ goto oom;
+ }
/* And add it to the output array */
zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &result_set);
}
} else { /* single pattern matching */
/* For each subpattern, insert it into the subpatterns array. */
mark = pcre2_get_mark(match_data);
- populate_subpat_array(
+ zend_result result = populate_subpat_array(
- subpats, subject, offsets, subpat_names, num_subpats, count, mark, flags);
+ Z_ARRVAL_P(subpats), subject, offsets, subpat_names, num_subpats, count, mark, flags);
+ if (UNEXPECTED(result == FAILURE)) {
+ goto oom;
+ }
break;
}
}
@@@ -1572,7 -1644,11 +1636,11 @@@ static zend_string *preg_do_repl_func(z
zval arg; /* Argument to pass to function */
array_init_size(&arg, count + (mark ? 1 : 0));
- populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
- zend_result result = populate_subpat_array(&arg, subject, offsets, subpat_names, num_subpats, count, mark, flags);
++ zend_result result = populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
+ if (UNEXPECTED(result == FAILURE)) {
+ zval_ptr_dtor(&arg);
+ return NULL;
+ }
fci->retval = &retval;
fci->param_count = 1;
@@@ -1956,17 -2050,16 +2033,17 @@@ matched
}
/* Set the match location in subject */
- match = subject + offsets[0];
+ match = ZSTR_VAL(subject_str) + offsets[0];
- new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
+ /* Length of needed storage */
+ size_t new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
/* Use custom function to get replacement string and its length. */
- eval_result = preg_do_repl_func(
- fci, fcc, subject, offsets, subpat_names, num_subpats, count,
+ zend_string *eval_result = preg_do_repl_func(
+ fci, fcc, ZSTR_VAL(subject_str), offsets, subpat_names, num_subpats, count,
pcre2_get_mark(match_data), flags);
- if (UNEXPECTED(eval_result == NULL)) {
+ if (UNEXPECTED(!eval_result)) {
goto error;
}
new_len = zend_safe_address_guarded(1, ZSTR_LEN(eval_result) + ZSTR_MAX_OVERHEAD, new_len) -ZSTR_MAX_OVERHEAD;
diff --cc ext/session/session.c
index cf42be929c4,7a57652f55d..dd2c22e5d6d
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@@ -108,10 -105,9 +108,9 @@@ static void proposed_session_id_to_sess
static int my_module_number = 0;
/* Dispatched by RINIT and by php_session_destroy */
-static inline void php_rinit_session_globals(void) /* {{{ */
+static inline void php_rinit_session_globals(void)
{
/* Do NOT init PS(mod_user_names) here! */
- /* TODO: These could be moved to MINIT and removed. These should be initialized by php_rshutdown_session_globals() always when execution is finished. */
PS(id) = NULL;
PS(session_status) = php_session_none;
PS(in_save_handler) = 0;
@@@ -121,31 -117,12 +120,32 @@@
PS(define_sid) = 1;
PS(session_vars) = NULL;
PS(module_number) = my_module_number;
+ PS(in_rfc1867_update) = false;
ZVAL_UNDEF(&PS(http_session_vars));
}
-/* }}} */
-static inline void php_session_cleanup_filename(void) /* {{{ */
+static inline void php_session_headers_already_sent_error(int severity, const char *message) {
+ const char *output_start_filename = php_output_get_start_filename();
+ int output_start_lineno = php_output_get_start_lineno();
+ if (output_start_filename != NULL) {
+ php_error_docref(NULL, severity, "%s (sent from %s on line %d)", message, output_start_filename, output_start_lineno);
+ } else {
+ php_error_docref(NULL, severity, "%s", message);
+ }
+}
+
+static inline void php_session_session_already_started_error(int severity, const char *message) {
+ if (PS(session_started_filename) != NULL) {
+ php_error_docref(NULL, severity, "%s (started from %s on line %"PRIu32")", message, ZSTR_VAL(PS(session_started_filename)), PS(session_started_lineno));
+ } else if (PS(auto_start)) {
+ /* This option can't be changed at runtime, so we can assume it's because of this */
+ php_error_docref(NULL, severity, "%s (session started automatically)", message);
+ } else {
+ php_error_docref(NULL, severity, "%s", message);
+ }
+}
+
+static inline void php_session_cleanup_filename(void)
{
if (PS(session_started_filename)) {
zend_string_release(PS(session_started_filename));
@@@ -954,7 -961,29 +954,29 @@@ PHP_INI_END(
/* ***************
* Serializers *
*************** */
+
+ /* The upload progress handler (php_session_rfc1867_update()) decodes and
+ * re-encodes the session during request startup, before RINIT, when no user
+ * code may run. Objects are then kept as __PHP_Incomplete_Class: no
+ * autoloading, no unserialize_callback_func, no __wakeup() or __unserialize(),
+ * and they are written back unchanged. */
+ static void php_session_unserialize_init(php_unserialize_data_t *var_hash)
+ {
+ PHP_VAR_UNSERIALIZE_INIT(*var_hash);
+ if (PS(in_rfc1867_update)) {
+ php_var_unserialize_set_incomplete_classes_only(*var_hash, true);
+ }
+ }
+
+ static void php_session_serialize_init(php_serialize_data_t *var_hash)
+ {
+ PHP_VAR_SERIALIZE_INIT(*var_hash);
+ if (PS(in_rfc1867_update)) {
+ php_var_serialize_set_incomplete_classes_only(*var_hash, true);
+ }
+ }
+
-PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */
+PS_SERIALIZER_ENCODE_FUNC(php_serialize)
{
smart_str buf = {0};
php_serialize_data_t var_hash;
@@@ -1072,10 -1105,10 +1094,10 @@@ PS_SERIALIZER_ENCODE_FUNC(php
bool fail = false;
PS_ENCODE_VARS;
- PHP_VAR_SERIALIZE_INIT(var_hash);
+ php_session_serialize_init(&var_hash);
PS_ENCODE_LOOP(
- smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
+ smart_str_append(&buf, key);
if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) {
PHP_VAR_SERIALIZE_DESTROY(var_hash);
smart_str_free(&buf);
@@@ -1485,108 -1559,14 +1507,108 @@@ PHPAPI const ps_serializer *_php_find_p
break;
}
}
- return ret;
+ return found_serializer;
+}
+
+static bool should_invalidate_session_for_external_referer(void)
+{
+ zval *referer_data;
+
+ /* No external referer check configured */
+ if (!PS(id) || PS(extern_referer_chk)[0] == '\0') {
+ return false;
+ }
+
+ /* No SERVER globals available */
+ if (Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER])) {
+ return false;
+ }
+
+ /* Get HTTP_REFERER header */
+ referer_data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRL("HTTP_REFERER"));
+ if (!referer_data || Z_TYPE_P(referer_data) != IS_STRING || Z_STRLEN_P(referer_data) == 0) {
+ return false;
+ }
+
+ /* Check if referer contains expected string */
+ return strstr(Z_STRVAL_P(referer_data), PS(extern_referer_chk)) == NULL;
+}
+
+static void try_find_session_id_in_global(const char *global_name, size_t global_name_len)
+{
+ zval *global_data, *potential_session_id;
+
+ if (PS(id)) {
+ return;
+ }
+
+ global_data = zend_hash_str_find(&EG(symbol_table), global_name, global_name_len);
+ if (!global_data) {
+ return;
+ }
+
+ ZVAL_DEREF(global_data);
+ if (Z_TYPE_P(global_data) != IS_ARRAY) {
+ return;
+ }
+
+ potential_session_id = zend_hash_find(Z_ARRVAL_P(global_data), PS(session_name));
+ if (potential_session_id) {
+ proposed_session_id_to_session_id(potential_session_id);
+ }
+}
+
+static bool php_can_change_session_setting(const char *setting_name, bool check_cookies)
+{
+ if (PS(session_status) == php_session_active) {
+ char error_msg[256];
+ snprintf(error_msg, sizeof(error_msg), "Session %s cannot be changed when a session is active", setting_name);
+ php_session_session_already_started_error(E_WARNING, error_msg);
+
+ return false;
+ }
-
++
+ if (SG(headers_sent) && (!check_cookies || PS(use_cookies))) {
+ char error_msg[256];
+ snprintf(error_msg, sizeof(error_msg), "Session %s cannot be changed after headers have already been sent", setting_name);
+ php_session_headers_already_sent_error(E_WARNING, error_msg);
+
+ return false;
+ }
-
++
+ return true;
+}
+
+static void try_find_session_id_in_cookies(void)
+{
+ zval *cookie_data, *potential_session_id;
+
+ if (!PS(use_cookies) || PS(id)) {
+ return;
+ }
+
+ cookie_data = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_COOKIE"));
+ if (!cookie_data) {
+ return;
+ }
+
+ ZVAL_DEREF(cookie_data);
+ if (Z_TYPE_P(cookie_data) != IS_ARRAY) {
+ return;
+ }
+
+ potential_session_id = zend_hash_find(Z_ARRVAL_P(cookie_data), PS(session_name));
+ if (potential_session_id) {
+ proposed_session_id_to_session_id(potential_session_id);
+ PS(send_cookie) = 0;
+ PS(define_sid) = 0;
+ }
}
-/* }}} */
-static void ppid2sid(zval *ppid) {
- ZVAL_DEREF(ppid);
- if (Z_TYPE_P(ppid) == IS_STRING) {
- PS(id) = zend_string_init(Z_STRVAL_P(ppid), Z_STRLEN_P(ppid), 0);
+static void proposed_session_id_to_session_id(zval *proposed_session_id) {
+ ZVAL_DEREF(proposed_session_id);
+ if (Z_TYPE_P(proposed_session_id) == IS_STRING) {
+ PS(id) = zend_string_copy(Z_STR_P(proposed_session_id));
PS(send_cookie) = 0;
} else {
PS(id) = NULL;
@@@ -3171,21 -3241,26 +3198,26 @@@ static void php_session_rfc1867_update(
zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data);
}
php_session_flush(1);
+ PS(in_rfc1867_update) = false;
-} /* }}} */
+}
-static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress) /* {{{ */
+static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress)
{
- php_session_initialize();
- PS(session_status) = php_session_active;
+ PS(in_rfc1867_update) = true;
+ if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) {
+ PS(in_rfc1867_update) = false;
+ return;
+ }
IF_SESSION_VARS() {
zval *sess_var = Z_REFVAL(PS(http_session_vars));
SEPARATE_ARRAY(sess_var);
zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s);
}
php_session_flush(1);
+ PS(in_rfc1867_update) = false;
-} /* }}} */
+}
-static zend_result php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) /* {{{ */
+static zend_result php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra)
{
php_session_rfc1867_progress *progress;
zend_result retval = SUCCESS;
@@@ -3202,8 -3281,13 +3238,12 @@@
switch(event) {
case MULTIPART_EVENT_START: {
multipart_event_start *data = (multipart_event_start *) event_data;
+
+ /* This runs before RINIT, so we need to clear these early */
+ php_rinit_session_globals();
+
progress = ecalloc(1, sizeof(php_session_rfc1867_progress));
progress->content_length = data->content_length;
- progress->sname_len = strlen(PS(session_name));
PS(rfc1867_progress) = progress;
}
break;
diff --cc ext/simplexml/simplexml.c
index 42944d25844,7d4337ae384..9610b3a0bf1
--- a/ext/simplexml/simplexml.c
+++ b/ext/simplexml/simplexml.c
@@@ -946,7 -961,9 +945,8 @@@ static bool get_base_node_value(php_sxe
php_libxml_increment_node_ptr((php_libxml_node_object *)subnode, node, NULL);
ZVAL_OBJ(value, &subnode->zo);
- /*zval_add_ref(value);*/
}
+ return true;
}
/* }}} */
@@@ -2039,8 -2081,8 +2085,8 @@@ PHP_METHOD(SimpleXMLElement, key
RETURN_THROWS();
}
- curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->node)->node;
+ curnode = intern->node->node;
- RETURN_STRINGL((char*)curnode->name, xmlStrlen(curnode->name));
+ RETURN_FSTRINGL((const char *)curnode->name, xmlStrlen(curnode->name));
}
/* }}} */
diff --cc ext/soap/tests/oom_soap_response.inc
index 00000000000,ab7bea9b8ca..1f6f2f055ef
mode 000000,100644..100644
--- a/ext/soap/tests/oom_soap_response.inc
+++ b/ext/soap/tests/oom_soap_response.inc
@@@ -1,0 -1,33 +1,33 @@@
+ <?php
+
+ /* Builds a compact SOAP response for OOM tests */
+ function oom_soap_response(int $dimensions, int $items): string
+ {
+ $arrayType = 'xsd:string[' . implode(',', array_fill(0, $dimensions, 1)) . ']';
+ $items = str_repeat('<i/>', $items);
+
+ return <<<XML
+ <?xml version="1.0"?>
+ <SOAP-ENV:Envelope
+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:ns1="http://test-uri/">
+ <SOAP-ENV:Body>
+ <ns1:fResponse><p SOAP-ENC:arrayType="$arrayType">$items</p></ns1:fResponse>
+ </SOAP-ENV:Body>
+ </SOAP-ENV:Envelope>
+ XML;
+ }
+
+ /* A SoapClient whose transport is replaced by a canned response, so that the
+ * reproducer exercises only the response decoder. */
+ class OomSoapClient extends SoapClient
+ {
+ public string $response = '';
+
- public function __doRequest($request, $location, $action, $version, $oneWay = false): ?string
++ public function __doRequest($request, $location, $action, $version, $oneWay = false, $uriParserClass = null): ?string
+ {
+ return $this->response;
+ }
+ }
diff --cc ext/spl/spl_fixedarray.c
index 61f6da4cff2,16126ff31ba..71f5c9598c8
--- a/ext/spl/spl_fixedarray.c
+++ b/ext/spl/spl_fixedarray.c
@@@ -296,9 -314,21 +316,12 @@@ static zend_object *spl_fixedarray_obje
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
- while (parent) {
- if (parent == spl_ce_SplFixedArray) {
- break;
- }
-
- parent = parent->parent;
- inherited = true;
- }
-
if (orig && clone_orig) {
spl_fixedarray_object *other = spl_fixed_array_from_obj(orig);
- spl_fixedarray_copy_ctor(&intern->array, &other->array);
+ if (UNEXPECTED(spl_fixedarray_copy_ctor(&intern->array, &other->array) == FAILURE)) {
+ ZEND_ASSERT(intern->std.handlers);
+ return &intern->std;
+ }
} else {
/* The zeroed struct would mean "resizing"; set the sentinel. */
spl_fixedarray_default_ctor(&intern->array);
@@@ -735,30 -765,25 +764,32 @@@ PHP_METHOD(SplFixedArray, fromArray
zend_ulong num_index, max_index = 0;
zend_long tmp;
- ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(data), num_index, str_index) {
- if (str_index != NULL || (zend_long)num_index < 0) {
- zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "array must contain only positive integer keys");
+ if (HT_IS_PACKED(Z_ARRVAL_P(data))) {
+ /* If there are no holes, then nNumUsed is the number of elements.
+ * If there are holes, then nNumUsed is the index of the last element. */
+ tmp = Z_ARRVAL_P(data)->nNumUsed;
+ } else {
+ ZEND_HASH_MAP_FOREACH_KEY(Z_ARRVAL_P(data), num_index, str_index) {
+ if (str_index != NULL || (zend_long)num_index < 0) {
+ zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "array must contain only positive integer keys");
+ RETURN_THROWS();
+ }
+
+ if (num_index > max_index) {
+ max_index = num_index;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ tmp = max_index + 1;
+ if (tmp <= 0) {
+ zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "integer overflow detected");
RETURN_THROWS();
}
-
- if (num_index > max_index) {
- max_index = num_index;
- }
- } ZEND_HASH_FOREACH_END();
-
- tmp = max_index + 1;
- if (tmp <= 0) {
- zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "integer overflow detected");
- RETURN_THROWS();
}
+
- spl_fixedarray_init(&array, tmp);
+ if (UNEXPECTED(spl_fixedarray_init(&array, tmp) == FAILURE)) {
+ RETURN_OOM();
+ }
ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(data), num_index, element) {
ZVAL_COPY_DEREF(&array.elements[num_index], element);
diff --cc ext/standard/image.c
index 7ea8f6010b3,f58569495d3..041442baba5
--- a/ext/standard/image.c
+++ b/ext/standard/image.c
@@@ -201,14 -203,16 +201,21 @@@ static struct php_gfxinfo *php_handle_s
*/
do {
- szlength = ZSTR_LEN(bufz) * (1<<factor++);
+ factor <<= 1;
+ if (ZSTR_LEN(bufz) > ULONG_MAX / factor) {
+ status = Z_MEM_ERROR;
+ break;
+ }
+ szlength = (unsigned long) (ZSTR_LEN(bufz) * factor);
- buf = erealloc(buf, szlength);
+ unsigned char *new_buf = safe_ferealloc(buf, 1, szlength, 0, /* fallible */ true);
+ if (UNEXPECTED(!new_buf)) {
+ efree(b);
+ zend_string_release_ex(bufz, 0);
+ efree(buf);
+ return NULL;
+ }
+ buf = new_buf;
- status = uncompress(buf, &szlength, (unsigned char *) ZSTR_VAL(bufz), ZSTR_LEN(bufz));
+ status = uncompress(buf, &szlength, (unsigned char *) ZSTR_VAL(bufz), (unsigned long) ZSTR_LEN(bufz));
} while ((status==Z_BUF_ERROR)&&(factor<maxfactor));
if (bufz) {
diff --cc ext/tokenizer/tokenizer.c
index 203a9b9c363,2b61b4e3d4d..adc6ed957c8
--- a/ext/tokenizer/tokenizer.c
+++ b/ext/tokenizer/tokenizer.c
@@@ -279,8 -282,8 +282,8 @@@ static zend_string *make_str(unsigned c
}
}
- static void add_token(
+ static bool add_token(
- zval *return_value, int token_type, unsigned char *text, size_t leng, int lineno,
+ HashTable *return_value_ht, int token_type, unsigned char *text, size_t leng, int lineno,
zend_class_entry *token_class, HashTable *interned_strings) {
zval token;
if (token_class) {
@@@ -313,9 -326,14 +326,14 @@@
ZEND_HASH_FILL_NEXT();
} ZEND_HASH_FILL_END();
} else {
- ZVAL_STR(&token, make_str(text, leng, interned_strings));
+ zend_string *value = make_str(text, leng, interned_strings);
+ if (UNEXPECTED(!value)) {
+ return false;
+ }
+ ZVAL_STR(&token, value);
}
- zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &token);
+ zend_hash_next_index_insert_new(return_value_ht, &token);
+ return true;
}
static bool tokenize(zval *return_value, zend_string *source, zend_class_entry *token_class)
@@@ -342,9 -359,12 +361,12 @@@
while ((token_type = lex_scan(&token, NULL))) {
ZEND_ASSERT(token_type != T_ERROR);
- add_token(
+ if (!add_token(
- return_value, token_type, zendtext, zendleng, token_line,
+ return_value_ht, token_type, zendtext, zendleng, token_line,
- token_class, &interned_strings);
+ token_class, &interned_strings)) {
+ success = false;
+ break;
+ }
if (Z_TYPE(token) != IS_UNDEF) {
zval_ptr_dtor_nogc(&token);
@@@ -359,9 -379,12 +381,12 @@@
) {
/* fetch the rest into a T_INLINE_HTML */
if (zendcursor < zendlimit) {
- add_token(
+ if (!add_token(
- return_value, T_INLINE_HTML, zendcursor, zendlimit - zendcursor,
+ return_value_ht, T_INLINE_HTML, zendcursor, zendlimit - zendcursor,
- token_line, token_class, &interned_strings);
+ token_line, token_class, &interned_strings)) {
+ success = false;
+ break;
+ }
}
break;
}
@@@ -385,8 -408,9 +410,9 @@@
}
struct event_context {
- zval *tokens;
+ HashTable *tokens;
zend_class_entry *token_class;
+ bool oom;
};
static zval *extract_token_id_to_replace(zval *token_zv, const char *text, size_t length) {
@@@ -426,11 -454,14 +456,14 @@@ static void on_event
} else if (token == T_ECHO && LANG_SCNG(yy_leng) == sizeof("<?=") - 1) {
token = T_OPEN_TAG_WITH_ECHO;
}
- add_token(
- ctx->tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL);
+ if (UNEXPECTED(!add_token(
+ ctx->tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL))) {
+ ZEND_ASSERT(ZEND_HAS_OOM());
+ ctx->oom = true;
+ }
break;
case ON_FEEDBACK: {
- HashTable *tokens_ht = Z_ARRVAL_P(ctx->tokens);
+ HashTable *tokens_ht = ctx->tokens;
zval *token_zv, *id_zv = NULL;
ZEND_HASH_REVERSE_FOREACH_VAL(tokens_ht, token_zv) {
id_zv = extract_token_id_to_replace(token_zv, text, length);
@@@ -471,8 -502,9 +504,9 @@@ static bool tokenize_parse
zend_prepare_string_for_scanning(&source_zval, ZSTR_EMPTY_ALLOC());
array_init(&token_stream);
- ctx.tokens = &token_stream;
+ ctx.tokens = Z_ARRVAL(token_stream);
ctx.token_class = token_class;
+ ctx.oom = false;
CG(ast) = NULL;
CG(ast_arena) = zend_arena_create(1024 * 32);
diff --cc ext/uri/php_uri.c
index 77fc627b6a9,00000000000..076c9fef88a
mode 100644,000000..100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@@ -1,1172 -1,0 +1,1197 @@@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Máté Kocsis <kocsismate@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "php.h"
+#include "Zend/zend_interfaces.h"
+#include "Zend/zend_exceptions.h"
+#include "Zend/zend_attributes.h"
+#include "Zend/zend_enum.h"
+#include "ext/standard/info.h"
+
+#include "php_uri.h"
+#include "uri_parser_whatwg.h"
+#include "uri_parser_rfc3986.h"
+#include "uri_parser_php_parse_url.h"
+#include "php_uri_arginfo.h"
+#include "uriparser/Uri.h"
+
+zend_class_entry *php_uri_ce_rfc3986_uri;
+zend_class_entry *php_uri_ce_whatwg_url;
+zend_class_entry *php_uri_ce_comparison_mode;
+zend_class_entry *php_uri_ce_exception;
+zend_class_entry *php_uri_ce_error;
+zend_class_entry *php_uri_ce_invalid_uri_exception;
+zend_class_entry *php_uri_ce_whatwg_invalid_url_exception;
+zend_class_entry *php_uri_ce_whatwg_url_validation_error_type;
+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;
+
+static const zend_module_dep uri_deps[] = {
+ ZEND_MOD_REQUIRED("lexbor")
+ ZEND_MOD_END
+};
+
+static zend_array uri_parsers;
+
+static HashTable *uri_get_debug_properties(php_uri_object *object)
+{
+ const HashTable *std_properties = zend_std_get_properties(&object->std);
+ HashTable *result = zend_array_dup(std_properties);
+
+ const php_uri_parser * const parser = object->parser;
+ void * const uri = object->uri;
+
+ if (UNEXPECTED(uri == NULL)) {
+ return result;
+ }
+
+ zval tmp;
+ if (parser->property_handler.scheme.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_SCHEME), &tmp);
+ }
+
+ if (parser->property_handler.username.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_USERNAME), &tmp);
+ }
+
+ if (parser->property_handler.password.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PASSWORD), &tmp);
+ }
+
+ if (parser->property_handler.host.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_HOST), &tmp);
+ }
+
+ if (parser->property_handler.port.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PORT), &tmp);
+ }
+
+ if (parser->property_handler.path.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PATH), &tmp);
+ }
+
+ if (parser->property_handler.query.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_QUERY), &tmp);
+ }
+
+ if (parser->property_handler.fragment.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
+ zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &tmp);
+ }
+
+ return result;
+}
+
+PHPAPI const php_uri_parser *php_uri_get_parser(zend_string *uri_parser_name)
+{
+ if (uri_parser_name == NULL) {
+ return zend_hash_str_find_ptr(&uri_parsers, PHP_URI_PARSER_PHP_PARSE_URL, sizeof(PHP_URI_PARSER_PHP_PARSE_URL) - 1);
+ }
+
+ return zend_hash_find_ptr(&uri_parsers, uri_parser_name);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI php_uri_internal *php_uri_parse(const php_uri_parser *uri_parser, const char *uri_str, size_t uri_str_len, bool silent)
+{
+ void *uri = uri_parser->parse(uri_str, uri_str_len, NULL, NULL, silent);
+
+ if (uri == NULL) {
+ return NULL;
+ }
+
+ php_uri_internal *internal_uri = emalloc(sizeof(*internal_uri));
+ internal_uri->parser = uri_parser;
+ internal_uri->uri = uri;
+
+ return internal_uri;
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_scheme(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.scheme.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_username(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.username.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_password(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.password.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_host(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.host.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_port(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.port.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_path(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.path.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_query(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.query.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_fragment(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
+{
+ return internal_uri->parser->property_handler.fragment.read(internal_uri->uri, read_mode, zv);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_free(php_uri_internal *internal_uri)
+{
+ internal_uri->parser->destroy(internal_uri->uri);
+ internal_uri->uri = NULL;
+ internal_uri->parser = NULL;
+ efree(internal_uri);
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI php_uri *php_uri_parse_to_struct(
+ const php_uri_parser *uri_parser, const char *uri_str, size_t uri_str_len, php_uri_component_read_mode read_mode, bool silent
+) {
+ php_uri_internal *uri_internal = php_uri_parse(uri_parser, uri_str, uri_str_len, silent);
+ if (uri_internal == NULL) {
+ return NULL;
+ }
+
+ php_uri *uri = ecalloc(1, sizeof(*uri));
+ zval tmp;
+ zend_result result;
+
+ result = php_uri_get_scheme(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->scheme = Z_STR(tmp);
+ }
+
+ result = php_uri_get_username(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->user = Z_STR(tmp);
+ }
+
+ result = php_uri_get_password(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->password = Z_STR(tmp);
+ }
+
+ result = php_uri_get_host(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->host = Z_STR(tmp);
+ }
+
+ result = php_uri_get_port(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_LONG) {
+ uri->port = Z_LVAL(tmp);
+ }
+
+ result = php_uri_get_path(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->path = Z_STR(tmp);
+ }
+
+ result = php_uri_get_query(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->query = Z_STR(tmp);
+ }
+
+ result = php_uri_get_fragment(uri_internal, read_mode, &tmp);
+ if (result == FAILURE) {
+ goto error;
+ }
+ if (Z_TYPE(tmp) == IS_STRING) {
+ uri->fragment = Z_STR(tmp);
+ }
+
+ php_uri_free(uri_internal);
+
+ return uri;
+
+error:
+ php_uri_free(uri_internal);
+ php_uri_struct_free(uri);
+
+ return NULL;
+}
+
+ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_struct_free(php_uri *uri)
+{
+ if (uri->scheme) {
+ zend_string_release(uri->scheme);
+ }
+ if (uri->user) {
+ zend_string_release(uri->user);
+ }
+ if (uri->password) {
+ zend_string_release(uri->password);
+ }
+ if (uri->host) {
+ zend_string_release(uri->host);
+ }
+ if (uri->path) {
+ zend_string_release(uri->path);
+ }
+ if (uri->query) {
+ zend_string_release(uri->query);
+ }
+ if (uri->fragment) {
+ zend_string_release(uri->fragment);
+ }
+
+ efree(uri);
+}
+
+/**
+ * Pass the errors parameter by ref to errors_zv for userland, and frees it if
+ * it is not not needed anymore.
+ */
+static zend_result pass_errors_by_ref_and_free(zval *errors_zv, zval *errors)
+{
+ ZEND_ASSERT(Z_TYPE_P(errors) == IS_UNDEF || Z_TYPE_P(errors) == IS_ARRAY);
+
+ /* There was no error during parsing */
+ if (Z_ISUNDEF_P(errors)) {
+ return SUCCESS;
+ }
+
+ /* The errors parameter is an array, but the pass-by ref argument stored by
+ * errors_zv was not passed - the URI implementation either doesn't support
+ * returning additional error information, or the caller is not interested in it */
+ if (errors_zv == NULL) {
+ zval_ptr_dtor(errors);
+ return SUCCESS;
+ }
+
+ ZEND_TRY_ASSIGN_REF_TMP(errors_zv, errors);
+ if (EG(exception)) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri(
+ INTERNAL_FUNCTION_PARAMETERS, const zend_string *uri_str, const php_uri_object *base_url_object,
+ bool should_throw, bool should_update_this_object, zval *errors_zv
+) {
+
+ php_uri_object *uri_object;
+ if (should_update_this_object) {
+ uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ if (uri_object->uri != NULL) {
+ zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
+ RETURN_THROWS();
+ }
+ } else {
+ if (EX(func)->common.fn_flags & ZEND_ACC_STATIC) {
+ object_init_ex(return_value, Z_CE_P(ZEND_THIS));
+ } else {
+ object_init_ex(return_value, Z_OBJCE_P(ZEND_THIS));
+ }
+ uri_object = Z_URI_OBJECT_P(return_value);
+ }
+
+ const php_uri_parser *uri_parser = uri_object->parser;
+
+ zval errors;
+ ZVAL_UNDEF(&errors);
+
+ void *base_url = NULL;
+ if (base_url_object != NULL) {
+ ZEND_ASSERT(base_url_object->std.ce == uri_object->std.ce);
+ ZEND_ASSERT(base_url_object->uri != NULL);
+ ZEND_ASSERT(base_url_object->parser == uri_parser);
+ base_url = base_url_object->uri;
+ }
+
+ void *uri = uri_parser->parse(ZSTR_VAL(uri_str), ZSTR_LEN(uri_str), base_url, errors_zv != NULL ? &errors : NULL, !should_throw);
+ if (UNEXPECTED(uri == NULL)) {
+ if (should_throw) {
+ zval_ptr_dtor(&errors);
+ RETURN_THROWS();
+ } else {
+ if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
+ RETURN_THROWS();
+ }
+ zval_ptr_dtor(return_value);
+ RETURN_NULL();
+ }
+ }
+
+ if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
+ uri_parser->destroy(uri);
+ RETURN_THROWS();
+ }
+
+ uri_object->uri = uri;
+}
+
+static void create_rfc3986_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
+{
+ zend_string *uri_str;
+ zend_object *base_url_object = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_STR(uri_str)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, php_uri_ce_rfc3986_uri)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ uri_str, base_url_object ? php_uri_object_from_obj(base_url_object) : NULL, is_constructor, is_constructor, NULL);
+}
+
+static bool is_list_of_whatwg_validation_errors(const HashTable *array)
+{
+ if (!zend_array_is_list(array)) {
+ return false;
+ }
+
+ ZEND_HASH_FOREACH_VAL(array, zval *val) {
+ /* Do not allow references as they may change types after checking. */
+
+ if (Z_TYPE_P(val) != IS_OBJECT) {
+ return false;
+ }
+
+ if (!instanceof_function(Z_OBJCE_P(val), php_uri_ce_whatwg_url_validation_error)) {
+ return false;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ return true;
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, parse)
+{
+ create_rfc3986_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, __construct)
+{
+ create_rfc3986_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
+}
+
+PHP_METHOD(Uri_WhatWg_InvalidUrlException, __construct)
+{
+ zend_string *message = NULL;
+ zval *errors = NULL;
+ zend_long code = 0;
+ zval *previous = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(0, 4)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_STR(message)
+ Z_PARAM_ARRAY(errors)
+ Z_PARAM_LONG(code)
+ Z_PARAM_OBJECT_OF_CLASS_OR_NULL(previous, zend_ce_throwable)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (zend_update_exception_properties(INTERNAL_FUNCTION_PARAM_PASSTHRU, message, code, previous) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ if (errors == NULL) {
+ zval tmp;
+ ZVAL_EMPTY_ARRAY(&tmp);
+ zend_update_property(php_uri_ce_whatwg_invalid_url_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), &tmp);
+ } else {
+ if (!is_list_of_whatwg_validation_errors(Z_ARR_P(errors))) {
+ zend_argument_value_error(2, "must be a list of %s", ZSTR_VAL(php_uri_ce_whatwg_url_validation_error->name));
+ RETURN_THROWS();
+ }
+
+ zend_update_property(php_uri_ce_whatwg_invalid_url_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), errors);
+ }
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Uri_WhatWg_UrlValidationError, __construct)
+{
+ zend_string *context;
+ zval *type;
+ bool failure;
+
+ ZEND_PARSE_PARAMETERS_START(3, 3)
+ Z_PARAM_STR(context)
+ Z_PARAM_OBJECT_OF_CLASS(type, php_uri_ce_whatwg_url_validation_error_type)
+ Z_PARAM_BOOL(failure)
+ ZEND_PARSE_PARAMETERS_END();
+
+ zend_update_property_str(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZEND_STRL("context"), context);
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
+
+ zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_TYPE), type);
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
+
+ zval failure_zv;
+ ZVAL_BOOL(&failure_zv, failure);
+ zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZEND_STRL("failure"), &failure_zv);
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
+}
+
+static void create_whatwg_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
+{
+ zend_string *uri_str;
+ zend_object *base_url_object = NULL;
+ zval *errors = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 3)
+ Z_PARAM_STR(uri_str)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, php_uri_ce_whatwg_url)
+ Z_PARAM_ZVAL(errors)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ uri_str, base_url_object ? php_uri_object_from_obj(base_url_object) : NULL, is_constructor, is_constructor, errors);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, parse)
+{
+ create_whatwg_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, __construct)
+{
+ create_whatwg_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getScheme)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawScheme)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withScheme)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME);
+}
+
+static void rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAMETERS, php_uri_component_read_mode read_mode)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_read(uri_object->uri, read_mode, return_value) == FAILURE)) {
+ zend_throw_error(NULL, "The userinfo component cannot be retrieved");
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getUserInfo)
+{
+ rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawUserInfo)
+{
+ rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withUserInfo)
+{
+ zend_string *value;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR_OR_NULL(value)
+ ZEND_PARSE_PARAMETERS_END();
+
+ zval zv;
+ if (value == NULL) {
+ ZVAL_NULL(&zv);
+ } else {
+ ZVAL_STR(&zv, value);
+ }
+
+ php_uri_object *old_uri_object = php_uri_object_from_obj(Z_OBJ_P(ZEND_THIS));
+ ZEND_ASSERT(old_uri_object->uri != NULL);
+
+ zend_object *new_object = old_uri_object->std.handlers->clone_obj(&old_uri_object->std);
+ if (new_object == NULL) {
+ RETURN_THROWS();
+ }
+
+ /* Assign the object early. The engine will take care of destruction in
+ * case of an exception being thrown. */
+ RETVAL_OBJ(new_object);
+
+ php_uri_object *new_uri_object = php_uri_object_from_obj(new_object);
+ ZEND_ASSERT(new_uri_object->uri != NULL);
+
+ if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_write(new_uri_object->uri, &zv, NULL) == FAILURE)) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getUsername)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawUsername)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getPassword)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawPassword)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getHost)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawHost)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withHost)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getPort)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PORT, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withPort)
+{
+ php_uri_property_write_long_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PORT);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getPath)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawPath)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withPath)
+{
+ php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getQuery)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawQuery)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withQuery)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getFragment)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, getRawFragment)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_RAW);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, withFragment)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT);
+}
+
+static void throw_cannot_recompose_uri_to_string(php_uri_object *object)
+{
++ if (ZEND_HAS_OOM()) {
++ zend_handle_delayed_oom();
++ return;
++ }
++
+ zend_throw_exception_ex(php_uri_ce_error, 0, "Cannot recompose %s to a string", ZSTR_VAL(object->std.ce->name));
+}
+
+static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object, zend_object *comparison_mode)
+{
+ php_uri_object *this_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(this_object->uri != NULL);
+ ZEND_ASSERT(that_object->uri != NULL);
+
+ if (this_object->std.ce != that_object->std.ce &&
+ !instanceof_function(this_object->std.ce, that_object->std.ce) &&
+ !instanceof_function(that_object->std.ce, this_object->std.ce)
+ ) {
+ RETURN_FALSE;
+ }
+
+ bool exclude_fragment = true;
+ if (comparison_mode) {
+ zval *case_name = zend_enum_fetch_case_name(comparison_mode);
+ exclude_fragment = zend_string_equals_literal(Z_STR_P(case_name), "ExcludeFragment");
+ }
+
+ zend_string *this_str = this_object->parser->to_string(
+ this_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, exclude_fragment);
+ if (this_str == NULL) {
+ throw_cannot_recompose_uri_to_string(this_object);
+ RETURN_THROWS();
+ }
+
+ zend_string *that_str = that_object->parser->to_string(
+ that_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, exclude_fragment);
+ if (that_str == NULL) {
+ zend_string_release(this_str);
+ throw_cannot_recompose_uri_to_string(that_object);
+ RETURN_THROWS();
+ }
+
+ RETVAL_BOOL(zend_string_equals(this_str, that_str));
+
+ zend_string_release(this_str);
+ zend_string_release(that_str);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, equals)
+{
+ zend_object *that_object;
+ zend_object *comparison_mode = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_rfc3986_uri)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
+ ZEND_PARSE_PARAMETERS_END();
+
+ uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, toRawString)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
+ if (uri_str == NULL) {
+ throw_cannot_recompose_uri_to_string(uri_object);
+ RETURN_THROWS();
+ }
+
+ RETURN_STR(uri_str);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, toString)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, false);
+ if (uri_str == NULL) {
+ throw_cannot_recompose_uri_to_string(uri_object);
+ RETURN_THROWS();
+ }
+
+ RETURN_STR(uri_str);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, resolve)
+{
+ zend_string *uri_str;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(uri_str)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ uri_str, Z_URI_OBJECT_P(ZEND_THIS), true, false, NULL);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, __serialize)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ /* Serialize state: "uri" key in the first array */
+ zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
+ if (uri_str == NULL) {
+ throw_cannot_recompose_uri_to_string(uri_object);
+ RETURN_THROWS();
+ }
+ zval tmp;
+ ZVAL_STR(&tmp, uri_str);
+
+ array_init(return_value);
+
+ zval arr;
+ array_init(&arr);
+ zend_hash_str_add_new(Z_ARRVAL(arr), PHP_URI_SERIALIZE_URI_FIELD_NAME, sizeof(PHP_URI_SERIALIZE_URI_FIELD_NAME) - 1, &tmp);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
+
+ /* Serialize regular properties: second array */
+ ZVAL_EMPTY_ARRAY(&arr);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
+}
+
+static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS)
+{
+ HashTable *data;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ARRAY_HT(data)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_uri_object *uri_object = php_uri_object_from_obj(Z_OBJ_P(ZEND_THIS));
+ if (uri_object->uri != NULL) {
+ /* Intentionally throw two exceptions for proper chaining. */
+ zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(uri_object->std.ce->name));
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
+ if (zend_hash_num_elements(data) != 2) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ /* Unserialize state: "uri" key in the first array */
+ zval *arr = zend_hash_index_find(data, 0);
+ if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ /* Verify the expected number of elements inside the first array, this implicitly ensures that no additional elements are present. */
+ if (zend_hash_num_elements(Z_ARRVAL_P(arr)) != 1) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ zval *uri_zv = zend_hash_str_find(Z_ARRVAL_P(arr), ZEND_STRL(PHP_URI_SERIALIZE_URI_FIELD_NAME));
+ if (uri_zv == NULL || Z_TYPE_P(uri_zv) != IS_STRING) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ uri_object->uri = uri_object->parser->parse(Z_STRVAL_P(uri_zv), Z_STRLEN_P(uri_zv), NULL, NULL, true);
+ if (uri_object->uri == NULL) {
++ if (ZEND_HAS_OOM()) {
++ RETURN_THROWS();
++ }
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ /* Unserialize regular properties: second array */
+ arr = zend_hash_index_find(data, 1);
+ if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+
+ /* Verify that there is no regular property in the second array, because the URI classes have no properties and they are final. */
+ if (zend_hash_num_elements(Z_ARRVAL_P(arr)) > 0) {
+ zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, __unserialize)
+{
+ uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+PHP_METHOD(Uri_Rfc3986_Uri, __debugInfo)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+
+ RETURN_ARR(uri_get_debug_properties(uri_object));
+}
+
+PHP_METHOD(Uri_WhatWg_Url, getScheme)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, withScheme)
+{
+ php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, withUsername)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, withPassword)
+{
+ php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, getAsciiHost)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, getUnicodeHost)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, getFragment)
+{
+ php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, equals)
+{
+ zend_object *that_object;
+ zend_object *comparison_mode = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_whatwg_url)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
+ ZEND_PARSE_PARAMETERS_END();
+
+ uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, toUnicodeString)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ zend_object *this_object = Z_OBJ_P(ZEND_THIS);
+ php_uri_object *uri_object = php_uri_object_from_obj(this_object);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
- RETURN_STR(uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE, false));
++ zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE, false);
++ if (uri_str == NULL) {
++ throw_cannot_recompose_uri_to_string(uri_object);
++ RETURN_THROWS();
++ }
++
++ RETURN_STR(uri_str);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, toAsciiString)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ zend_object *this_object = Z_OBJ_P(ZEND_THIS);
+ php_uri_object *uri_object = php_uri_object_from_obj(this_object);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
- RETURN_STR(uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false));
++ zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
++ if (uri_str == NULL) {
++ throw_cannot_recompose_uri_to_string(uri_object);
++ RETURN_THROWS();
++ }
++
++ RETURN_STR(uri_str);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, resolve)
+{
+ zend_string *uri_str;
+ zval *errors = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_STR(uri_str)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ZVAL(errors)
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ uri_str, Z_URI_OBJECT_P(ZEND_THIS), true, false, errors);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, __serialize)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *this_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(this_object->uri != NULL);
+
+ /* Serialize state: "uri" key in the first array */
+ zend_string *uri_str = this_object->parser->to_string(this_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
+ if (uri_str == NULL) {
+ throw_cannot_recompose_uri_to_string(this_object);
+ RETURN_THROWS();
+ }
+ zval tmp;
+ ZVAL_STR(&tmp, uri_str);
+
+ array_init(return_value);
+
+ zval arr;
+ array_init(&arr);
+ zend_hash_str_add_new(Z_ARRVAL(arr), ZEND_STRL(PHP_URI_SERIALIZE_URI_FIELD_NAME), &tmp);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
+
+ /* Serialize regular properties: second array */
+ ZVAL_EMPTY_ARRAY(&arr);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, __unserialize)
+{
+ uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+
+ RETURN_ARR(uri_get_debug_properties(uri_object));
+}
+
+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);
+
+ zend_object_std_init(&uri_object->std, class_type);
+ object_properties_init(&uri_object->std, class_type);
+
+ uri_object->parser = parser;
+ uri_object->uri = NULL;
+
+ return uri_object;
+}
+
+static zend_object *php_uri_object_create_rfc3986(zend_class_entry *ce)
+{
+ return &php_uri_object_create(ce, &php_uri_parser_rfc3986)->std;
+}
+
+static zend_object *php_uri_object_create_whatwg(zend_class_entry *ce)
+{
+ return &php_uri_object_create(ce, &php_uri_parser_whatwg)->std;
+}
+
+PHPAPI void php_uri_object_handler_free(zend_object *object)
+{
+ php_uri_object *uri_object = php_uri_object_from_obj(object);
+
+ uri_object->parser->destroy(uri_object->uri);
+ zend_object_std_dtor(&uri_object->std);
+}
+
+PHPAPI zend_object *php_uri_object_handler_clone(zend_object *object)
+{
+ php_uri_object *uri_object = php_uri_object_from_obj(object);
+
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ php_uri_object *new_uri_object = php_uri_object_from_obj(object->ce->create_object(object->ce));
+ ZEND_ASSERT(new_uri_object->parser == uri_object->parser);
+
+ void *uri = uri_object->parser->clone(uri_object->uri);
- ZEND_ASSERT(uri != NULL);
++ if (UNEXPECTED(uri == NULL)) {
++ ZEND_ASSERT(ZEND_HAS_OOM());
++ zend_object_release(&new_uri_object->std);
++ zend_handle_delayed_oom();
++ return NULL;
++ }
+
+ new_uri_object->uri = uri;
+
+ zend_objects_clone_members(&new_uri_object->std, &uri_object->std);
+
+ return &new_uri_object->std;
+}
+
+PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser)
+{
+ zend_string *key = zend_string_init_interned(uri_parser->name, strlen(uri_parser->name), true);
+
+ ZEND_ASSERT(uri_parser->name != NULL);
+ ZEND_ASSERT(uri_parser->parse != NULL);
+ ZEND_ASSERT(uri_parser->clone != NULL || strcmp(uri_parser->name, PHP_URI_PARSER_PHP_PARSE_URL) == 0);
+ ZEND_ASSERT(uri_parser->to_string != NULL || strcmp(uri_parser->name, PHP_URI_PARSER_PHP_PARSE_URL) == 0);
+ ZEND_ASSERT(uri_parser->destroy != NULL);
+
+ zend_result result = zend_hash_add_ptr(&uri_parsers, key, (void *) uri_parser) != NULL ? SUCCESS : FAILURE;
+
+ zend_string_release_ex(key, true);
+
+ return result;
+}
+
+static PHP_MINIT_FUNCTION(uri)
+{
+ 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;
+ memcpy(&object_handlers_rfc3986_uri, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+ object_handlers_rfc3986_uri.offset = XtOffsetOf(php_uri_object, std);
+ object_handlers_rfc3986_uri.free_obj = php_uri_object_handler_free;
+ object_handlers_rfc3986_uri.clone_obj = php_uri_object_handler_clone;
+
+ php_uri_ce_whatwg_url = register_class_Uri_WhatWg_Url();
+ php_uri_ce_whatwg_url->create_object = php_uri_object_create_whatwg;
+ php_uri_ce_whatwg_url->default_object_handlers = &object_handlers_whatwg_uri;
+ memcpy(&object_handlers_whatwg_uri, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+ object_handlers_whatwg_uri.offset = XtOffsetOf(php_uri_object, std);
+ object_handlers_whatwg_uri.free_obj = php_uri_object_handler_free;
+ object_handlers_whatwg_uri.clone_obj = php_uri_object_handler_clone;
+
+ php_uri_ce_comparison_mode = register_class_Uri_UriComparisonMode();
+ php_uri_ce_exception = register_class_Uri_UriException(zend_ce_exception);
+ php_uri_ce_error = register_class_Uri_UriError(zend_ce_error);
+ php_uri_ce_invalid_uri_exception = register_class_Uri_InvalidUriException(php_uri_ce_exception);
+ php_uri_ce_whatwg_invalid_url_exception = register_class_Uri_WhatWg_InvalidUrlException(php_uri_ce_invalid_uri_exception);
+ php_uri_ce_whatwg_url_validation_error = register_class_Uri_WhatWg_UrlValidationError();
+ php_uri_ce_whatwg_url_validation_error_type = register_class_Uri_WhatWg_UrlValidationErrorType();
+
+ zend_hash_init(&uri_parsers, 4, NULL, NULL, true);
+
+ if (php_uri_parser_register(&php_uri_parser_rfc3986) == FAILURE) {
+ return FAILURE;
+ }
+
+ if (php_uri_parser_register(&php_uri_parser_whatwg) == FAILURE) {
+ return FAILURE;
+ }
+
+ if (php_uri_parser_register(&php_uri_parser_php_parse_url) == FAILURE) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static PHP_MINFO_FUNCTION(uri)
+{
+ php_info_print_table_start();
+ php_info_print_table_row(2, "URI support", "active");
+#ifdef URI_STATIC_BUILD
+ php_info_print_table_row(2, "uriparser bundled version", URI_VER_ANSI);
+#else
+ php_info_print_table_row(2, "uriparser compiled version", URI_VER_ANSI);
+ php_info_print_table_row(2, "uriparser loaded version", uriBaseRuntimeVersionA());
+#endif
+ php_info_print_table_end();
+}
+
+static PHP_MSHUTDOWN_FUNCTION(uri)
+{
+ zend_hash_destroy(&uri_parsers);
+
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(uri)
+{
+ if (PHP_RINIT(uri_parser_whatwg)(INIT_FUNC_ARGS_PASSTHRU) == FAILURE) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri)
+{
+ if (ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri_parser_whatwg)() == FAILURE) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+zend_module_entry uri_module_entry = {
+ STANDARD_MODULE_HEADER_EX, NULL,
+ uri_deps,
+ "uri", /* Extension name */
+ NULL, /* zend_function_entry */
+ PHP_MINIT(uri), /* PHP_MINIT - Module initialization */
+ PHP_MSHUTDOWN(uri), /* PHP_MSHUTDOWN - Module shutdown */
+ PHP_RINIT(uri), /* PHP_RINIT - Request initialization */
+ NULL, /* PHP_RSHUTDOWN - Request shutdown */
+ PHP_MINFO(uri), /* PHP_MINFO - Module info */
+ PHP_VERSION, /* Version */
+ NO_MODULE_GLOBALS,
+ ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri),
+ STANDARD_MODULE_PROPERTIES_EX
+};
diff --cc ext/uri/php_uri_common.c
index da73bc59bf9,00000000000..5b2b708710f
mode 100644,000000..100644
--- a/ext/uri/php_uri_common.c
+++ b/ext/uri/php_uri_common.c
@@@ -1,143 -1,0 +1,147 @@@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Máté Kocsis <kocsismate@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Zend/zend_interfaces.h"
+#include "Zend/zend_exceptions.h"
+#include "php_uri_common.h"
+
+static zend_string *get_known_string_by_property_name(php_uri_property_name property_name)
+{
+ switch (property_name) {
+ case PHP_URI_PROPERTY_NAME_SCHEME:
+ return ZSTR_KNOWN(ZEND_STR_SCHEME);
+ case PHP_URI_PROPERTY_NAME_USERNAME:
+ return ZSTR_KNOWN(ZEND_STR_USERNAME);
+ case PHP_URI_PROPERTY_NAME_PASSWORD:
+ return ZSTR_KNOWN(ZEND_STR_PASSWORD);
+ case PHP_URI_PROPERTY_NAME_HOST:
+ return ZSTR_KNOWN(ZEND_STR_HOST);
+ case PHP_URI_PROPERTY_NAME_PORT:
+ return ZSTR_KNOWN(ZEND_STR_PORT);
+ case PHP_URI_PROPERTY_NAME_PATH:
+ return ZSTR_KNOWN(ZEND_STR_PATH);
+ case PHP_URI_PROPERTY_NAME_QUERY:
+ return ZSTR_KNOWN(ZEND_STR_QUERY);
+ case PHP_URI_PROPERTY_NAME_FRAGMENT:
+ return ZSTR_KNOWN(ZEND_STR_FRAGMENT);
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+}
+
+void php_uri_property_read_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, php_uri_component_read_mode component_read_mode)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(uri_object->uri != NULL);
+
+ const php_uri_property_handler *property_handler = php_uri_parser_property_handler_by_name(uri_object->parser, property_name);
+
+ if (UNEXPECTED(property_handler->read(uri_object->uri, component_read_mode, return_value) == FAILURE)) {
++ if (ZEND_HAS_OOM()) {
++ zend_handle_delayed_oom();
++ RETURN_THROWS();
++ }
+ zend_throw_exception_ex(php_uri_ce_error, 0, "The %s component cannot be retrieved", ZSTR_VAL(get_known_string_by_property_name(property_name)));
+ RETURN_THROWS();
+ }
+}
+
+static void php_uri_property_write_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, zval *property_zv)
+{
+ php_uri_object *old_uri_object = Z_URI_OBJECT_P(ZEND_THIS);
+ ZEND_ASSERT(old_uri_object->uri != NULL);
+
+ zend_object *new_object = old_uri_object->std.handlers->clone_obj(&old_uri_object->std);
+ if (new_object == NULL) {
+ RETURN_THROWS();
+ }
+
+ /* Assign the object early. The engine will take care of destruction in
+ * case of an exception being thrown. */
+ RETVAL_OBJ(new_object);
+
+ const php_uri_property_handler *property_handler = php_uri_parser_property_handler_by_name(old_uri_object->parser, property_name);
+
+ php_uri_object *new_uri_object = php_uri_object_from_obj(new_object);
+ ZEND_ASSERT(new_uri_object->uri != NULL);
+ if (UNEXPECTED(property_handler->write == NULL)) {
+ zend_readonly_property_modification_error_ex(ZSTR_VAL(old_uri_object->std.ce->name),
+ ZSTR_VAL(get_known_string_by_property_name(property_name)));
+ RETURN_THROWS();
+ }
+
+ zval errors;
+ ZVAL_UNDEF(&errors);
+ if (UNEXPECTED(property_handler->write(new_uri_object->uri, property_zv, &errors) == FAILURE)) {
+ zval_ptr_dtor(&errors);
+ RETURN_THROWS();
+ }
+
+ ZEND_ASSERT(Z_ISUNDEF(errors));
+}
+
+void php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
+{
+ zend_string *value;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(value)
+ ZEND_PARSE_PARAMETERS_END();
+
+ zval zv;
+ ZVAL_STR(&zv, value);
+
+ php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
+}
+
+void php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
+{
+ zend_string *value;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR_OR_NULL(value)
+ ZEND_PARSE_PARAMETERS_END();
+
+ zval zv;
+ if (value == NULL) {
+ ZVAL_NULL(&zv);
+ } else {
+ ZVAL_STR(&zv, value);
+ }
+
+ php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
+}
+
+void php_uri_property_write_long_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
+{
+ zend_long value;
+ bool value_is_null;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_LONG_OR_NULL(value, value_is_null)
+ ZEND_PARSE_PARAMETERS_END();
+
+ zval zv;
+ if (value_is_null) {
+ ZVAL_NULL(&zv);
+ } else {
+ ZVAL_LONG(&zv, value);
+ }
+
+ php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
+}
diff --cc ext/uri/tests/oom_rfc3986_component_read.phpt
index 00000000000,00000000000..97fff11c5eb
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_component_read.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\Rfc3986\Uri::getPath() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 100000));
++
++$uri->getPath();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_equals.phpt
index 00000000000,00000000000..c4d0e98223a
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_equals.phpt
@@@ -1,0 -1,0 +1,18 @@@
++--TEST--
++Uri\Rfc3986\Uri::equals() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$a = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 60000));
++$b = clone $a;
++
++$a->equals($b);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_parse.phpt
index 00000000000,00000000000..0388b6e8832
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_parse.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++Uri\Rfc3986\Uri::parse() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++Uri\Rfc3986\Uri::parse(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_path_segments.phpt
index 00000000000,00000000000..4fb2c6088ec
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_path_segments.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++Uri\Rfc3986\Uri with many path segments doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++new Uri\Rfc3986\Uri(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_resolve.phpt
index 00000000000,00000000000..3116bcb4660
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_resolve.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\Rfc3986\Uri::resolve() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$base = new Uri\Rfc3986\Uri("http://example.com/a/b");
++
++$base->resolve(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_to_string.phpt
index 00000000000,00000000000..ffc62b6b9a1
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_to_string.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\Rfc3986\Uri::toString() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 100000));
++
++$uri->toString();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_with_path.phpt
index 00000000000,00000000000..f4a35d29f92
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_with_path.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\Rfc3986\Uri::withPath() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://example.com/");
++
++$uri->withPath("/" . str_repeat("a/", 300000));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_idna_host.phpt
index 00000000000,00000000000..321ed6091a7
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_idna_host.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\WhatWg\Url with a long non-ASCII host doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* A non-ASCII host goes through IDNA */
++$host = str_repeat("\u{e4}", (int) (1.5 * 1024 * 1024));
++
++Uri\WhatWg\Url::parse("http://" . $host . "/");
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_idna_host_write.phpt
index 00000000000,00000000000..a1e2fa11a12
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_idna_host_write.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\WhatWg\Url::withHost() with a long non-ASCII host reports the memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$host = str_repeat("\u{e4}", (int) (1.5 * 1024 * 1024));
++
++$url = Uri\WhatWg\Url::parse("http://example.com/");
++$url->withHost($host);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_resolve.phpt
index 00000000000,00000000000..7c2b5e65113
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_resolve.phpt
@@@ -1,0 -1,0 +1,18 @@@
++--TEST--
++Uri\WhatWg\Url::resolve() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* The relative reference carries the host, so resolving it runs IDNA while
++ * the base URL is still held. */
++$base = Uri\WhatWg\Url::parse("http://example.com/");
++
++$base->resolve("//" . str_repeat("\u{e4}", 1200 * 1024) . "/");
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_to_unicode_string.phpt
index 00000000000,00000000000..bb9317acafa
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_to_unicode_string.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\WhatWg\Url::toUnicodeString() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$url = Uri\WhatWg\Url::parse("http://" . str_repeat("\u{e4}", 1200 * 1024) . "/");
++
++$url->toUnicodeString();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_validation_errors.phpt
index 00000000000,00000000000..0624b0bb7e8
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_validation_errors.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\WhatWg\Url validation errors don't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=16M
++--FILE--
++<?php
++$errors = null;
++
++new Uri\WhatWg\Url("https://example.com/" . str_repeat("%", 100 * 1024), null, $errors);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 16777216 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/uri_parser_rfc3986.c
index 6c0bdec4b11,00000000000..d56d053aae5
mode 100644,000000..100644
--- a/ext/uri/uri_parser_rfc3986.c
+++ b/ext/uri/uri_parser_rfc3986.c
@@@ -1,635 -1,0 +1,725 @@@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Máté Kocsis <kocsismate@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "uri_parser_rfc3986.h"
+#include "php_uri_common.h"
+#include "Zend/zend_smart_str.h"
+#include "Zend/zend_exceptions.h"
+
+#include <uriparser/Uri.h>
+
+struct php_uri_parser_rfc3986_uris {
+ UriUriA uri;
+ UriUriA normalized_uri;
+ bool normalized_uri_initialized;
+};
+
+static void *php_uri_parser_rfc3986_memory_manager_malloc(UriMemoryManager *memory_manager, size_t size)
+{
- return emalloc(size);
++ return femalloc(size, /* fallible */ true);
+}
+
+static void *php_uri_parser_rfc3986_memory_manager_calloc(UriMemoryManager *memory_manager, size_t nmemb, size_t size)
+{
- return ecalloc(nmemb, size);
++ return fecalloc(nmemb, size, /* fallible */ true);
+}
+
+static void *php_uri_parser_rfc3986_memory_manager_realloc(UriMemoryManager *memory_manager, void *ptr, size_t size)
+{
- return erealloc(ptr, size);
++ return safe_ferealloc(ptr, 1, size, 0, /* fallible */ true);
+}
+
+static void *php_uri_parser_rfc3986_memory_manager_reallocarray(UriMemoryManager *memory_manager, void *ptr, size_t nmemb, size_t size)
+{
- return safe_erealloc(ptr, nmemb, size, 0);
++ return safe_ferealloc(ptr, nmemb, size, 0, /* fallible */ true);
+}
+
+static void php_uri_parser_rfc3986_memory_manager_destroy(UriMemoryManager *memory_manager, void *ptr)
+{
+ efree(ptr);
+}
+
+static const UriMemoryManager php_uri_parser_rfc3986_memory_manager = {
+ .malloc = php_uri_parser_rfc3986_memory_manager_malloc,
+ .calloc = php_uri_parser_rfc3986_memory_manager_calloc,
+ .realloc = php_uri_parser_rfc3986_memory_manager_realloc,
+ .reallocarray = php_uri_parser_rfc3986_memory_manager_reallocarray,
+ .free = php_uri_parser_rfc3986_memory_manager_destroy,
+ .userData = NULL,
+};
+
+/* The library expects a pointer to a non-const UriMemoryManager, but does
+ * not actually modify it (and neither does our implementation). Use a
+ * const struct with a non-const pointer for convenience. */
+static UriMemoryManager* const mm = (UriMemoryManager*)&php_uri_parser_rfc3986_memory_manager;
+
++static bool handle_pending_oom(void)
++{
++ if (ZEND_HAS_OOM()) {
++ zend_handle_delayed_oom();
++ return true;
++ }
++
++ return false;
++}
++
+static inline size_t get_text_range_length(const UriTextRangeA *range)
+{
+ return range->afterLast - range->first;
+}
+
+static inline bool has_text_range(const UriTextRangeA *range)
+{
+ return range->first != NULL && range->afterLast != NULL;
+}
+
- ZEND_ATTRIBUTE_NONNULL static void copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
++/* Returns false when the copy could not be allocated. */
++ZEND_ATTRIBUTE_NONNULL static bool copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
+{
+ int result = uriCopyUriMmA(new_uriparser_uri, uriparser_uri, mm);
- ZEND_ASSERT(result == URI_SUCCESS);
++ ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
++
++ return result == URI_SUCCESS;
+}
+
++/* Returns NULL when the normalized form could not be allocated. */
+ZEND_ATTRIBUTE_NONNULL static UriUriA *get_normalized_uri(php_uri_parser_rfc3986_uris *uriparser_uris) {
+ if (!uriparser_uris->normalized_uri_initialized) {
- copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri);
++ if (UNEXPECTED(!copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri))) {
++ return NULL;
++ }
+ int result = uriNormalizeSyntaxExMmA(&uriparser_uris->normalized_uri, (unsigned int)-1, mm);
- ZEND_ASSERT(result == URI_SUCCESS);
++ ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
++ if (UNEXPECTED(result != URI_SUCCESS)) {
++ uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
++ memset(&uriparser_uris->normalized_uri, 0, sizeof(uriparser_uris->normalized_uri));
++ return NULL;
++ }
+ uriparser_uris->normalized_uri_initialized = true;
+ }
+
+ return &uriparser_uris->normalized_uri;
+}
+
+ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, php_uri_component_read_mode read_mode)
+{
+ switch (read_mode) {
+ case PHP_URI_COMPONENT_READ_MODE_RAW:
+ return &uriparser_uris->uri;
+ case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
+ ZEND_FALLTHROUGH;
+ case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE:
+ return get_normalized_uri(uriparser_uris);
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(php_uri_parser_rfc3986_uris *uriparser_uris)
+{
+ return &uriparser_uris->uri;
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->scheme)) {
+ ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme));
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified scheme is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the scheme", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->userInfo)) {
+ ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, get_text_range_length(&uriparser_uri->userInfo));
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+zend_result php_uri_parser_rfc3986_userinfo_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SETUSERINFO_HOST_NOT_SET:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a userinfo without having a host", 0);
+ return FAILURE;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified userinfo is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the userinfo", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->userInfo)) {
+ size_t length = get_text_range_length(&uriparser_uri->userInfo);
+ const char *c = memchr(uriparser_uri->userInfo.first, ':', length);
+
+ if (c == NULL && length > 0) {
+ ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, length);
+ } else if (c != NULL && c - uriparser_uri->userInfo.first > 0) {
+ ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, c - uriparser_uri->userInfo.first);
+ } else {
+ ZVAL_EMPTY_STRING(retval);
+ }
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->userInfo)) {
+ const char *c = memchr(uriparser_uri->userInfo.first, ':', get_text_range_length(&uriparser_uri->userInfo));
+
+ if (c != NULL && uriparser_uri->userInfo.afterLast - c - 1 > 0) {
+ ZVAL_STRINGL(retval, c + 1, uriparser_uri->userInfo.afterLast - c - 1);
+ } else {
+ ZVAL_EMPTY_STRING(retval);
+ }
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->hostText)) {
+ if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) {
+ /* the textual representation of the host is always accessible in the .hostText field no matter what the host is */
+ smart_str host_str = {0};
+
+ smart_str_appendc(&host_str, '[');
+ smart_str_appendl(&host_str, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
+ smart_str_appendc(&host_str, ']');
+
+ ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
+ } else {
+ ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
+ }
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_host_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SETHOST_PORT_SET:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
+ return FAILURE;
+ case URI_ERROR_SETHOST_USERINFO_SET:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
+ return FAILURE;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len)
+{
+ if (len > MAX_LENGTH_OF_LONG) {
+ return -1;
+ }
+
+ char buf[MAX_LENGTH_OF_LONG + 1];
+ *(char*)zend_mempcpy(buf, str, len) = 0;
+
+ zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
+
+ if (result > ZEND_LONG_MAX) {
+ return -1;
+ }
+
+ return (zend_long)result;
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
+ ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_port_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
+ result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
+ zend_string_release_ex(tmp, false);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SETPORT_HOST_NOT_SET:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
+ return FAILURE;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (uriparser_uri->pathHead != NULL) {
+ smart_str str = {0};
+
+ if (uriparser_uri->absolutePath || uriHasHostA(uriparser_uri)) {
+ smart_str_appendc(&str, '/');
+ }
+
+ for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
+ smart_str_appendl(&str, p->text.first, get_text_range_length(&p->text));
+ if (p->next) {
+ smart_str_appendc(&str, '/');
+ }
+ }
+
+ ZVAL_NEW_STR(retval, smart_str_extract(&str));
+ } else if (uriparser_uri->absolutePath) {
+ ZVAL_CHAR(retval, '/');
+ } else {
+ ZVAL_EMPTY_STRING(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_path_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_STRLEN_P(value) == 0) {
+ result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->query)) {
+ ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_query_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
+ return FAILURE;
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return FAILURE;
++ }
+
+ if (has_text_range(&uriparser_uri->fragment)) {
+ ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, zval *value, zval *errors)
+{
+ UriUriA *uriparser_uri = get_uri_for_writing(uri);
+ int result;
+
+ if (Z_TYPE_P(value) == IS_NULL) {
+ result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
+ } else {
+ result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
+ }
+
+ switch (result) {
+ case URI_SUCCESS:
+ return SUCCESS;
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
+ return FAILURE;
+ default:
++ if (handle_pending_oom()) {
++ return FAILURE;
++ }
++
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
+ return FAILURE;
+ }
+}
+
+static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
+{
+ php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
+ uriparser_uris->normalized_uri_initialized = false;
+
+ 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)
+{
+ UriUriA uri = {0};
+
+ /* Parse the URI. */
+ int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
+ if (result != URI_SUCCESS) {
- if (!silent) {
++ if (!handle_pending_oom() && !silent) {
+ switch (result) {
+ case URI_ERROR_SYNTAX:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
+ break;
+ default:
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
+ break;
+ }
+ }
+
+ goto fail;
+ }
+
+ if (uriparser_base_urls != NULL) {
+ UriUriA tmp = {0};
+
+ /* 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) {
++ if (!handle_pending_oom() && !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;
+ }
+ }
+
+ goto fail;
+ }
+
+ /* Store the combined URI back into 'uri'. */
+ uriFreeUriMembersMmA(&uri, mm);
+ uri = tmp;
+ }
+
+ /* Make the resulting URI independent of the 'uri_str'. */
- uriMakeOwnerMmA(&uri, mm);
++ if (UNEXPECTED(uriMakeOwnerMmA(&uri, mm) != URI_SUCCESS)) {
++ /* The only documented failure is a failed allocation, and the caller
++ * expects either a URI or a reported error. */
++ if (!handle_pending_oom() && !silent) {
++ zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
++ }
++
++ goto fail;
++ }
+
+ if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
+ if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
+ if (!silent) {
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
+ }
+
+ goto fail;
+ }
+ }
+
+ php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
+ uriparser_uris->uri = uri;
+
+ return uriparser_uris;
+
+ fail:
+
+ uriFreeUriMembersMmA(&uri, mm);
+
+ return NULL;
+}
+
+void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
+{
+ return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
+}
+
+ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
+{
+ const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
+
+ php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
- copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri);
++ if (UNEXPECTED(!copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri))) {
++ efree(new_uriparser_uris);
++ return NULL;
++ }
+ /* Do not copy the normalized URI: The expected action after cloning is
+ * modifying the cloned URI (which will invalidate the cached normalized
+ * URI). */
+
+ return new_uriparser_uris;
+}
+
+ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
+{
+ php_uri_parser_rfc3986_uris *uriparser_uris = uri;
+ const UriUriA *uriparser_uri;
+
+ if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
+ uriparser_uri = &uriparser_uris->uri;
+ } else {
+ uriparser_uri = get_normalized_uri(uriparser_uris);
++ if (UNEXPECTED(uriparser_uri == NULL)) {
++ return NULL;
++ }
+ }
+
+ int charsRequired = 0;
+ int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
+ ZEND_ASSERT(result == URI_SUCCESS);
+
+ charsRequired++;
+
+ zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
+ result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
+ ZEND_ASSERT(result == URI_SUCCESS);
+
+ if (exclude_fragment) {
+ const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
+ if (pos != NULL) {
+ uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
+ ZSTR_VAL(uri_string)[ZSTR_LEN(uri_string)] = '\0';
+ }
+ }
+
+ return uri_string;
+}
+
+static void php_uri_parser_rfc3986_destroy(void *uri)
+{
+ php_uri_parser_rfc3986_uris *uriparser_uris = uri;
+
+ if (UNEXPECTED(uriparser_uris == NULL)) {
+ return;
+ }
+
+ uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
+ uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
+
+ efree(uriparser_uris);
+}
+
+PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
+ .name = PHP_URI_PARSER_RFC3986,
+ .parse = php_uri_parser_rfc3986_parse,
+ .clone = php_uri_parser_rfc3986_clone,
+ .to_string = php_uri_parser_rfc3986_to_string,
+ .destroy = php_uri_parser_rfc3986_destroy,
+ {
+ .scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
+ .username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
+ .password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
+ .host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
+ .port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
+ .path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
+ .query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
+ .fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
+ }
+};
diff --cc ext/uri/uri_parser_whatwg.c
index 9364fd5d3d8,00000000000..32988d5929b
mode 100644,000000..100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@@ -1,662 -1,0 +1,715 @@@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Máté Kocsis <kocsismate@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "uri_parser_whatwg.h"
+#include "php_uri_common.h"
+#include "Zend/zend_enum.h"
+#include "Zend/zend_smart_str.h"
+#include "Zend/zend_exceptions.h"
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+ZEND_TLS lexbor_mraw_t lexbor_mraw = {0};
+ZEND_TLS lxb_url_parser_t lexbor_parser = {0};
+ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0};
+
+static const size_t lexbor_mraw_byte_size = 8192;
+
+static zend_always_inline void zval_string_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
+{
+ if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) > 0) {
+ lexbor_str->data = (lxb_char_t *) Z_STRVAL_P(value);
+ lexbor_str->length = Z_STRLEN_P(value);
+ } else {
+ ZEND_ASSERT(Z_ISNULL_P(value) || (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) == 0));
+ lexbor_str->data = (lxb_char_t *) "";
+ lexbor_str->length = 0;
+ }
+}
+
- static zend_always_inline void zval_long_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
++static zend_always_inline bool zval_long_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
+{
+ if (Z_TYPE_P(value) == IS_LONG) {
+ ZVAL_STR(value, zend_long_to_str(Z_LVAL_P(value)));
- lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) Z_STRVAL_P(value), Z_STRLEN_P(value));
++ const lxb_char_t *appended = lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) Z_STRVAL_P(value), Z_STRLEN_P(value));
+ zval_ptr_dtor_str(value);
++ if (UNEXPECTED(appended == NULL)) {
++ return false;
++ }
+ } else {
+ ZEND_ASSERT(Z_ISNULL_P(value));
+ lexbor_str->data = (lxb_char_t *) "";
+ lexbor_str->length = 0;
+ }
++
++ return true;
+}
+
+/**
+ * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in
+ * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum.
+ * The result is passed by reference to the errors parameter.
+ */
+static const char *fill_errors(zval *errors)
+{
+ if (lexbor_parser.log == NULL || lexbor_plog_length(lexbor_parser.log) == 0) {
+ ZVAL_EMPTY_ARRAY(errors);
+ return NULL;
+ }
+
+ array_init(errors);
+ const char *result = NULL;
+
+ lexbor_plog_entry_t *lxb_error;
+ while ((lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) {
+ zval error;
+ object_init_ex(&error, php_uri_ce_whatwg_url_validation_error);
+ zend_update_property_string(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("context"), (const char *) lxb_error->data);
+
+ const char *error_str;
+ zval failure;
+ switch (lxb_error->id) {
+ case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII:
+ error_str = "DomainToAscii";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE:
+ error_str = "DomainToUnicode";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT:
+ error_str = "DomainInvalidCodePoint";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT:
+ error_str = "HostInvalidCodePoint";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART:
+ error_str = "Ipv4EmptyPart";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS:
+ error_str = "Ipv4TooManyParts";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART:
+ error_str = "Ipv4NonNumericPart";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART:
+ error_str = "Ipv4NonDecimalPart";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART:
+ error_str = "Ipv4OutOfRangePart";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED:
+ error_str = "Ipv6Unclosed";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION:
+ error_str = "Ipv6InvalidCompression";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES:
+ error_str = "Ipv6TooManyPieces";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION:
+ error_str = "Ipv6MultipleCompression";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT:
+ error_str = "Ipv6InvalidCodePoint";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES:
+ error_str = "Ipv6TooFewPieces";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES:
+ error_str = "Ipv4InIpv6TooManyPieces";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT:
+ error_str = "Ipv4InIpv6InvalidCodePoint";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART:
+ error_str = "Ipv4InIpv6OutOfRangePart";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS:
+ error_str = "Ipv4InIpv6TooFewParts";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT:
+ error_str = "InvalidUrlUnit";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS:
+ error_str = "SpecialSchemeMissingFollowingSolidus";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL:
+ error_str = "MissingSchemeNonRelativeUrl";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS:
+ error_str = "InvalidReverseSoldius";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS:
+ error_str = "InvalidCredentials";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_HOST_MISSING:
+ error_str = "HostMissing";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE:
+ error_str = "PortOutOfRange";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_PORT_INVALID:
+ error_str = "PortInvalid";
+ ZVAL_TRUE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER:
+ error_str = "FileInvalidWindowsDriveLetter";
+ ZVAL_FALSE(&failure);
+ break;
+ case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST:
+ error_str = "FileInvalidWindowsDriveLetterHost";
+ ZVAL_FALSE(&failure);
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+
+ zval error_type;
+ ZVAL_OBJ(&error_type, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_validation_error_type, error_str));
+ zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZSTR_KNOWN(ZEND_STR_TYPE), &error_type);
+
+ zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("failure"), &failure);
+
+ if (Z_TYPE(failure) == IS_TRUE) {
+ result = error_str;
+ }
+
+ add_next_index_zval(errors, &error);
++
++ if (ZEND_HAS_OOM()) {
++ break;
++ }
+ }
+
+ return result;
+}
+
+static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
+{
++ if (ZEND_HAS_OOM()) {
++ zend_handle_delayed_oom();
++ return;
++ }
++
+ zval err;
+ const char *reason = fill_errors(&err);
++ if (ZEND_HAS_OOM()) {
++ zval_ptr_dtor(&err);
++ zend_handle_delayed_oom();
++ return;
++ }
++
+ zend_object *exception = zend_throw_exception_ex(
+ php_uri_ce_whatwg_invalid_url_exception,
+ 0,
+ "The specified %s is malformed%s%s%s",
+ component,
+ reason ? " (" : "",
+ reason ? reason : "",
+ reason ? ")" : ""
+ );
+ zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
+ if (errors) {
+ zval_ptr_dtor(errors);
+ ZVAL_COPY_VALUE(errors, &err);
+ } else {
+ zval_ptr_dtor(&err);
+ }
+}
+
+static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, size_t length, void *ctx)
+{
+ smart_str *uri_str = ctx;
+
+ if (data != NULL && length > 0) {
+ smart_str_appendl(uri_str, (const char *) data, length);
+ }
+
+ return LXB_STATUS_OK;
+}
+
+static zend_result php_uri_parser_whatwg_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ ZEND_ASSERT(lexbor_uri->scheme.type != LXB_URL_SCHEMEL_TYPE__UNDEF);
+
+ ZVAL_STRINGL(retval, (const char *) lexbor_uri->scheme.name.data, lexbor_uri->scheme.name.length);
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_scheme_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_protocol_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "scheme");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+/* 4.2. URL miscellaneous: A URL includes credentials if its username or password is not the empty string. */
+static bool includes_credentials(const lxb_url_t *lexbor_uri)
+{
+ return lexbor_uri->username.length > 0 || lexbor_uri->password.length > 0;
+}
+
+static zend_result php_uri_parser_whatwg_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (includes_credentials(lexbor_uri)) {
+ ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->username.data, lexbor_uri->username.length);
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_username_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_username_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "username");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (includes_credentials(lexbor_uri)) {
+ ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->password.data, lexbor_uri->password.length);
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_password_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_password_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "password");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV4) {
+ smart_str host_str = {0};
+
+ lxb_url_serialize_host_ipv4(lexbor_uri->host.u.ipv4, serialize_to_smart_str_callback, &host_str);
++ if (ZEND_HAS_OOM()) {
++ smart_str_free(&host_str);
++ return FAILURE;
++ }
+
+ ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
+ } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV6) {
+ smart_str host_str = {0};
+
+ smart_str_appendc(&host_str, '[');
+ lxb_url_serialize_host_ipv6(lexbor_uri->host.u.ipv6, serialize_to_smart_str_callback, &host_str);
+ smart_str_appendc(&host_str, ']');
++ if (ZEND_HAS_OOM()) {
++ smart_str_free(&host_str);
++ return FAILURE;
++ }
+
+ ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
+ } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_EMPTY) {
+ ZVAL_EMPTY_STRING(retval);
+ } else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) {
+ switch (read_mode) {
+ case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE: {
+ smart_str host_str = {0};
+ lxb_url_serialize_host_unicode(&lexbor_idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
+ lxb_unicode_idna_clean(&lexbor_idna);
++ if (ZEND_HAS_OOM()) {
++ smart_str_free(&host_str);
++ return FAILURE;
++ }
+
+ ZVAL_STR(retval, smart_str_extract(&host_str));
+ break;
+ }
+ case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
+ ZEND_FALLTHROUGH;
+ case PHP_URI_COMPONENT_READ_MODE_RAW:
+ ZVAL_STRINGL(retval, (const char *) lexbor_uri->host.u.domain.data, lexbor_uri->host.u.domain.length);
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_host_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_hostname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "host");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (lexbor_uri->has_port) {
+ ZVAL_LONG(retval, lexbor_uri->port);
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_port_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
- zval_long_or_null_to_lexbor_str(value, &str);
++ if (UNEXPECTED(!zval_long_or_null_to_lexbor_str(value, &str))) {
++ ZEND_ASSERT(ZEND_HAS_OOM());
++ zend_handle_delayed_oom();
++ return FAILURE;
++ }
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_port_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "port");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (lexbor_uri->path.str.length > 0) {
+ ZVAL_STRINGL(retval, (const char *) lexbor_uri->path.str.data, lexbor_uri->path.str.length);
+ } else {
+ ZVAL_EMPTY_STRING(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_path_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_pathname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "path");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (lexbor_uri->query.data != NULL) {
+ ZVAL_STRINGL(retval, (const char *) lexbor_uri->query.data, lexbor_uri->query.length);
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_query_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_search_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "query string");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ if (lexbor_uri->fragment.data != NULL) {
+ ZVAL_STRINGL(retval, (const char *) lexbor_uri->fragment.data, lexbor_uri->fragment.length);
+ } else {
+ ZVAL_NULL(retval);
+ }
+
+ return SUCCESS;
+}
+
+static zend_result php_uri_parser_whatwg_fragment_write(void *uri, zval *value, zval *errors)
+{
+ lxb_url_t *lexbor_uri = uri;
+ lexbor_str_t str = {0};
+
+ zval_string_or_null_to_lexbor_str(value, &str);
+
+ lxb_url_parser_clean(&lexbor_parser);
+
+ if (lxb_url_api_hash_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(errors, "fragment");
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(uri_parser_whatwg)
+{
+ lxb_status_t status;
+
+ status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
+ if (status != LXB_STATUS_OK) {
+ goto fail;
+ }
+
+ status = lxb_url_parser_init(&lexbor_parser, &lexbor_mraw);
+ if (status != LXB_STATUS_OK) {
+ goto fail;
+ }
+
+ status = lxb_unicode_idna_init(&lexbor_idna);
+ if (status != LXB_STATUS_OK) {
+ goto fail;
+ }
+
+ return SUCCESS;
+
+ fail:
+
+ /* Unconditionally calling the _destroy() functions is
+ * safe on a zeroed structure. */
+ lxb_unicode_idna_destroy(&lexbor_idna, false);
+ memset(&lexbor_idna, 0, sizeof(lexbor_idna));
+ lxb_url_parser_destroy(&lexbor_parser, false);
+ memset(&lexbor_parser, 0, sizeof(lexbor_parser));
+ lexbor_mraw_destroy(&lexbor_mraw, false);
+ memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
+
+ return FAILURE;
+}
+
+ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg)
+{
+ lxb_unicode_idna_destroy(&lexbor_idna, false);
+ memset(&lexbor_idna, 0, sizeof(lexbor_idna));
+ lxb_url_parser_destroy(&lexbor_parser, false);
+ memset(&lexbor_parser, 0, sizeof(lexbor_parser));
+ lexbor_mraw_destroy(&lexbor_mraw, false);
+ memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
+
+ return SUCCESS;
+}
+
+lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent)
+{
+ lxb_url_parser_clean(&lexbor_parser);
+
+ lxb_url_t *url = lxb_url_parse(&lexbor_parser, lexbor_base_url, (unsigned char *) uri_str, uri_str_len);
+
++ if (ZEND_HAS_OOM()) {
++ zend_handle_delayed_oom();
++ return url;
++ }
++
+ if ((url == NULL && !silent) || errors != NULL) {
+ zval err;
+ const char *reason = fill_errors(&err);
++ if (ZEND_HAS_OOM()) {
++ zval_ptr_dtor(&err);
++ zend_handle_delayed_oom();
++
++ return url;
++ }
++
+ if (url == NULL && !silent) {
+ zend_object *exception = zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URI is malformed%s%s%s", reason ? " (" : "", reason ? reason : "", reason ? ")" : "");
+ zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
+ }
+ if (errors != NULL) {
+ zval_ptr_dtor(errors);
+ ZVAL_COPY_VALUE(errors, &err);
+ } else {
+ zval_ptr_dtor(&err);
+ }
+ }
+
+ return url;
+}
+
+static void *php_uri_parser_whatwg_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
+{
+ return php_uri_parser_whatwg_parse_ex(uri_str, uri_str_len, base_url, errors, silent);
+}
+
+static void *php_uri_parser_whatwg_clone(void *uri)
+{
+ const lxb_url_t *lexbor_uri = uri;
+
+ return lxb_url_clone(lexbor_parser.mraw, lexbor_uri);
+}
+
+static zend_string *php_uri_parser_whatwg_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
+{
+ const lxb_url_t *lexbor_uri = uri;
+ smart_str uri_str = {0};
+
+ switch (recomposition_mode) {
+ case PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE:
+ ZEND_FALLTHROUGH;
+ case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_UNICODE:
+ lxb_url_serialize_idna(&lexbor_idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
+ lxb_unicode_idna_clean(&lexbor_idna);
+ break;
+ case PHP_URI_RECOMPOSITION_MODE_RAW_ASCII:
+ ZEND_FALLTHROUGH;
+ case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII:
+ lxb_url_serialize(lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+
++ if (ZEND_HAS_OOM()) {
++ smart_str_free(&uri_str);
++ return NULL;
++ }
++
+ return smart_str_extract(&uri_str);
+}
+
+static void php_uri_parser_whatwg_destroy(void *uri)
+{
+ lxb_url_t *lexbor_uri = uri;
+
+ lxb_url_destroy(lexbor_uri);
+}
+
+PHPAPI const php_uri_parser php_uri_parser_whatwg = {
+ .name = PHP_URI_PARSER_WHATWG,
+ .parse = php_uri_parser_whatwg_parse,
+ .clone = php_uri_parser_whatwg_clone,
+ .to_string = php_uri_parser_whatwg_to_string,
+ .destroy = php_uri_parser_whatwg_destroy,
+ {
+ .scheme = {.read = php_uri_parser_whatwg_scheme_read, .write = php_uri_parser_whatwg_scheme_write},
+ .username = {.read = php_uri_parser_whatwg_username_read, .write = php_uri_parser_whatwg_username_write},
+ .password = {.read = php_uri_parser_whatwg_password_read, .write = php_uri_parser_whatwg_password_write},
+ .host = {.read = php_uri_parser_whatwg_host_read, .write = php_uri_parser_whatwg_host_write},
+ .port = {.read = php_uri_parser_whatwg_port_read, .write = php_uri_parser_whatwg_port_write},
+ .path = {.read = php_uri_parser_whatwg_path_read, .write = php_uri_parser_whatwg_path_write},
+ .query = {.read = php_uri_parser_whatwg_query_read, .write = php_uri_parser_whatwg_query_write},
+ .fragment = {.read = php_uri_parser_whatwg_fragment_read, .write = php_uri_parser_whatwg_fragment_write},
+ }
+};
diff --cc ext/xmlwriter/php_xmlwriter.c
index f9dd7f48c86,61927e81e2a..b30411bdbf0
--- a/ext/xmlwriter/php_xmlwriter.c
+++ b/ext/xmlwriter/php_xmlwriter.c
@@@ -1020,13 -1007,15 +1020,17 @@@ static void php_xmlwriter_flush(INTERNA
RETURN_EMPTY_STRING();
}
output_bytes = xmlTextWriterFlush(ptr);
- if (buffer) {
- const xmlChar *content = xmlBufferContent(buffer);
- zend_string *ret = zend_string_finit((const char *) content, strlen((const char *) content), /* persistent */ false, /* fallible */ true);
- if (UNEXPECTED(!ret)) {
- RETURN_OOM();
- }
- RETVAL_NEW_STR(ret);
+ if (output) {
if (empty) {
- xmlBufferEmpty(buffer);
+ RETURN_STR(smart_str_extract(output));
+ } else if (smart_str_get_len(output) > 0) {
- RETURN_NEW_STR(zend_string_dup(output->s, false));
++ zend_string *ret = zend_string_finit(ZSTR_VAL(output->s), ZSTR_LEN(output->s), /* persistent */ false, /* fallible */ true);
++ if (UNEXPECTED(!ret)) {
++ RETURN_OOM();
++ }
++ RETURN_NEW_STR(ret);
+ } else {
+ RETURN_EMPTY_STRING();
}
} else {
RETVAL_LONG(output_bytes);
diff --cc ext/zend_test/test.stub.php
index 1e693707e3f,caf3fbac08e..754ac9a693a
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@@ -262,9 -246,11 +262,13 @@@ namespace
function zend_leak_bytes(int $bytes = 3): void {}
- function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
+ function zend_delref(mixed $variable): void {}
+ function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
++
+ function zend_test_emalloc(int $size): void {}
+
+ function zend_test_callback(callable $callback): void {}
function zend_string_or_object(object|string $param): object|string {}
@@@ -346,16 -330,15 +350,21 @@@ function zend_test_override_libxml_glob
function zend_test_log_err_debug(string $str): void {}
+ function zend_test_compile_to_ast(string $str): string {}
+
function zend_test_gh18756(): void {}
+ function zend_test_opcache_preloading(): bool {}
+
+ function zend_test_uri_parser(string $uri, string $parser): array { }
+
/** @compile-time-eval */
function zend_test_gh19792(): void {}
+
+ /* @param resource $stream */
+ function zend_test_handle_post_data(mixed $stream, string $content_type, int $content_length): void {}
+
+ function zend_new_singleton_array(): array {}
}
namespace ZendTestNS {
diff --cc ext/zend_test/test_arginfo.h
index f4bd4c9ee7d,e87fd554522..820ac82fb01
Binary files differ
diff --cc ext/zlib/zlib.c
index 003ffa80111,e165d50adb3..8db7d5a1871
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@@ -935,6 -942,22 +946,22 @@@ PHP_FUNCTION(inflate_init
}
/* }}} */
+ /* Releases *out and returns false on failure. */
+ static bool php_zlib_inflate_grow(zend_string **out, size_t buffer_used, size_t chunk_size, z_stream *Z)
+ {
- zend_string *grown = zend_string_frealloc(*out, ZSTR_LEN(*out) + chunk_size, 0, /* fallible */ true);
++ zend_string *grown = zend_string_fextend(*out, ZSTR_LEN(*out) + chunk_size, 0, /* fallible */ true);
+
+ if (UNEXPECTED(grown == NULL)) {
+ zend_string_release_ex(*out, 0);
+ return false;
+ }
+
+ *out = grown;
+ Z->avail_out = chunk_size;
+ Z->next_out = (Bytef *) ZSTR_VAL(grown) + buffer_used;
+ return true;
+ }
+
/* {{{ Incrementally inflate encoded data in the specified context */
PHP_FUNCTION(inflate_add)
{
diff --cc main/main.c
index ad7ae8a5177,bc1725fa9bf..0db01692170
--- a/main/main.c
+++ b/main/main.c
@@@ -368,22 -335,26 +368,46 @@@ static PHP_INI_MH(OnChangeMemoryLimit
}
/* }}} */
+static PHP_INI_MH(OnChangeMaxMemoryLimit)
+{
+ size_t value;
+ if (new_value) {
+ value = zend_ini_parse_uquantity_warn(new_value, entry->name);
+ } else {
+ value = Z_L(1) << 30; /* effectively, no limit */
+ }
+
+ if (zend_set_memory_limit(value) == FAILURE) {
+ zend_error(E_ERROR, "Failed to set memory limit to %zd bytes (Current memory usage is %zd bytes)", value, zend_memory_usage(true));
+ return FAILURE;
+ }
+
+ PG(memory_limit) = value;
+ PG(max_memory_limit) = value;
++
++ return SUCCESS;
++}
++
+ static PHP_INI_MH(OnChangeMemoryHardLimitPercent)
+ {
+ zend_long value;
+ if (new_value) {
+ value = zend_ini_parse_quantity_warn(new_value, entry->name);
+ } else {
+ value = -1; /* default: 200% if memory limit < 1G, fixed 1G buffer otherwise */
+ }
+
+ if (value < 100 && value != -1) {
+ zend_error(E_WARNING, "memory_hard_limit_percent must be >= 100, or -1");
+ return FAILURE;
+ }
+
+ if (zend_set_memory_hard_limit_percent(value) == FAILURE) {
+ if (stage != ZEND_INI_STAGE_DEACTIVATE) {
+ zend_error(E_WARNING, "Failed to set memory_hard_limit_percent to " ZEND_LONG_FMT, value);
+ return FAILURE;
+ }
+ }
return SUCCESS;
}
@@@ -858,13 -822,10 +882,14 @@@ PHP_INI_BEGIN(
PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL)
STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_mixed_lf_and_crlf, php_core_globals, core_globals)
+ STD_PHP_INI_ENTRY("mail.cr_lf_mode", "crlf", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailCrLfMode, mail_cr_lf_mode, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals)
PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap)
- PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit)
- PHP_INI_ENTRY("memory_hard_limit_percent", NULL, PHP_INI_ALL, OnChangeMemoryHardLimitPercent)
+
+ PHP_INI_ENTRY("max_memory_limit", "-1", PHP_INI_SYSTEM, OnChangeMaxMemoryLimit)
+ PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit)
++ PHP_INI_ENTRY("memory_hard_limit_percent", NULL, PHP_INI_ALL, OnChangeMemoryHardLimitPercent)
+
PHP_INI_ENTRY("precision", "14", PHP_INI_ALL, OnSetPrecision)
PHP_INI_ENTRY("sendmail_from", NULL, PHP_INI_ALL, NULL)
PHP_INI_ENTRY("sendmail_path", DEFAULT_SENDMAIL_PATH, PHP_INI_SYSTEM, NULL)
diff --cc main/streams/streams.c
index 123c74222e9,b76c2531d94..9b3c51149a5
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@@ -1618,13 -1656,15 +1655,16 @@@ PHPAPI zend_string *_php_stream_copy_to
buflen = maxlen;
}
} else {
- buflen = step;
+ buflen = CHUNK_SIZE;
}
- result = zend_string_alloc(buflen, persistent);
+ result = zend_string_falloc(buflen, persistent, /* fallible */ true);
+ if (UNEXPECTED(!result)) {
+ return NULL;
+ }
ptr = ZSTR_VAL(result);
+ const int min_room = CHUNK_SIZE / 4;
// TODO: Propagate error?
while ((ret = php_stream_read(src, ptr, buflen - len)) > 0) {
len += ret;
@@@ -1632,12 -1672,17 +1672,17 @@@
if (maxlen == len) {
break;
}
- if (maxlen > 0 && buflen + step > maxlen) {
+ if (maxlen > 0 && buflen + CHUNK_SIZE > maxlen) {
buflen = maxlen;
} else {
- buflen += step;
+ buflen += CHUNK_SIZE;
}
- result = zend_string_extend(result, buflen, persistent);
+ zend_string *grown = zend_string_fextend(result, buflen, persistent, /* fallible */ true);
+ if (UNEXPECTED(!grown)) {
+ zend_string_free(result);
+ return NULL;
+ }
+ result = grown;
ptr = ZSTR_VAL(result) + len;
} else {
ptr += ret;
diff --cc tests/basic/oom_rfc1867.inc
index 00000000000,5750745975c..145fefec3a4
mode 000000,100644..100644
--- a/tests/basic/oom_rfc1867.inc
+++ b/tests/basic/oom_rfc1867.inc
@@@ -1,0 -1,88 +1,89 @@@
+ <?php
+
+ /* Helpers for the oom_rfc1867_*.phpt tests.
+ *
+ * Each test posts a multipart/form-data body to php-cgi with a small
+ * memory_limit and every input limit raised, so that only the memory limit
+ * bounds what the parser accumulates. */
+
+ const OOM_RFC1867_BOUNDARY = '---------------------------12345';
+
+ function oom_rfc1867_part($fh, string $headers, string $body): void
+ {
+ fwrite($fh, '--' . OOM_RFC1867_BOUNDARY . "\r\n" . $headers . "\r\n\r\n" . $body . "\r\n");
+ }
+
+ /* Writes $bytes bytes of $char, a chunk at a time, so the test itself doesn't
+ * need a multi-megabyte string in memory. */
+ function oom_rfc1867_fill($fh, int $bytes, string $char = 'A'): void
+ {
+ while ($bytes > 0) {
+ $chunk = min($bytes, 1024 * 1024);
+ fwrite($fh, str_repeat($char, $chunk));
+ $bytes -= $chunk;
+ }
+ }
+
+ /* Calls $writeBody() to write the body parts, appends the closing boundary,
+ * posts the result and prints what php-cgi reported.
+ *
+ * $extraIni is passed as additional -d options, for the paths that only run
+ * with a given extension configured (mbstring translation, upload progress). */
+ function oom_rfc1867_request(callable $writeBody, string $memoryLimit = '8M', array $extraIni = [], ?string $target = null): void
+ {
+ $bodyFile = tempnam(sys_get_temp_dir(), 'oom_rfc1867');
+ $fh = fopen($bodyFile, 'wb');
+ $writeBody($fh);
+ fwrite($fh, '--' . OOM_RFC1867_BOUNDARY . "--\r\n");
+ fclose($fh);
+
+ $cmd = [
+ getenv('TEST_PHP_CGI_EXECUTABLE'),
+ '-C',
+ '-n',
+ '-d', 'memory_limit=' . $memoryLimit,
+ '-d', 'post_max_size=1G',
+ '-d', 'upload_max_filesize=1G',
+ '-d', 'max_input_vars=10000000',
+ '-d', 'max_file_uploads=1000000',
+ '-d', 'max_multipart_body_parts=10000000',
+ '-d', 'max_input_time=-1',
+ '-d', 'html_errors=0',
++ '-d', 'fatal_error_backtraces=Off',
+ ];
+
+ foreach ($extraIni as $ini) {
+ $cmd[] = '-d';
+ $cmd[] = $ini;
+ }
+
+ $env = array_merge($_ENV, [
+ 'REDIRECT_STATUS' => '1',
+ 'CONTENT_TYPE' => 'multipart/form-data; boundary=' . OOM_RFC1867_BOUNDARY,
+ 'CONTENT_LENGTH' => filesize($bodyFile),
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_FILENAME' => $target ?? __DIR__ . '/oom_rfc1867_target.inc',
+ ]);
+
+ $spec = [
+ 0 => ['file', $bodyFile, 'r'],
+ 1 => ['pipe', 'w'],
+ 2 => ['pipe', 'w'],
+ ];
+
+ $pipes = [];
+ $handle = proc_open($cmd, $spec, $pipes, getcwd(), $env);
+ $out = stream_get_contents($pipes[1]);
+ $err = stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($handle);
+ unlink($bodyFile);
+
+ /* Drop the CGI headers, keep the body. */
+ $out = preg_replace("/^.*?\r\n\r\n/s", '', $out);
+ echo trim(str_replace("\r\n", "\n", $out)), "\n";
+ if ($err !== '') {
+ echo 'stderr: ', trim($err), "\n";
+ }
+ }