From ead0bbd0758f5bf01194846d8c548186195a0817 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 13 Nov 2025 13:55:33 -0500 Subject: [PATCH 1/5] setup error handler to make (undefined array key) fatal --- defaults/config.ini.default | 1 + deployment/overrides/phpunit/config/config.ini | 1 + resources/init.php | 4 ++++ resources/lib/UnityHTTPD.php | 8 ++++++++ 4 files changed, 14 insertions(+) diff --git a/defaults/config.ini.default b/defaults/config.ini.default index 70301033..f38dc18b 100644 --- a/defaults/config.ini.default +++ b/defaults/config.ini.default @@ -17,6 +17,7 @@ allow_die = true ; internal use only enable_verbose_error_log = true ; internal use only enable_error_to_user = true ; internal use only enable_exception_handler = true ; internal use only +enable_error_handler = true ; internal use only [ldap] uri = "ldap://identity" ; URI of remote LDAP server diff --git a/deployment/overrides/phpunit/config/config.ini b/deployment/overrides/phpunit/config/config.ini index 05aaf472..86de5039 100644 --- a/deployment/overrides/phpunit/config/config.ini +++ b/deployment/overrides/phpunit/config/config.ini @@ -6,3 +6,4 @@ allow_die = false enable_verbose_error_log = false enable_error_to_user = false enable_exception_handler = false +enable_error_handler = false diff --git a/resources/init.php b/resources/init.php index 3a62a7ed..614bd458 100644 --- a/resources/init.php +++ b/resources/init.php @@ -17,6 +17,10 @@ set_exception_handler(["UnityWebPortal\lib\UnityHTTPD", "exceptionHandler"]); } +if (CONFIG["site"]["enable_error_handler"]) { + set_error_handler(["UnityWebPortal\lib\UnityHTTPD", "errorHandler"]); +} + session_start(); if (isset($GLOBALS["ldapconn"])) { diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index bf5abbf3..7820b5bb 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -143,6 +143,14 @@ public static function exceptionHandler(\Throwable $e): void ini_set("log_errors", false); // error logged successfully } + public static function errorHandler(int $severity, string $message, string $file, int $line) + { + if (str_contains($message, "Undefined array key")) { + throw new ArrayKeyException($message); + } + return false; + } + public static function getPostData(...$keys): mixed { try { From 0e7dde3afa423d0c5d49d83a1bc86fc566be4d5c Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Mon, 1 Dec 2025 17:18:52 -0500 Subject: [PATCH 2/5] tweaks --- resources/lib/UnityHTTPD.php | 6 +++++- tools/docker-dev/web/Dockerfile | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 7820b5bb..c16baca1 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -132,6 +132,11 @@ public static function internalServerError( $errorid = uniqid(); self::errorToUser("An internal server error has occurred.", 500, $errorid); self::errorLog("internal server error", $message, $errorid, $error, $data); + if (ini_get("display_errors") && ini_get("html_errors")) { + echo ""; + echo $error->xdebug_message; + echo "
"; + } self::die($message); } @@ -140,7 +145,6 @@ public static function exceptionHandler(\Throwable $e): void { ini_set("log_errors", true); // in case something goes wrong and error is not logged self::internalServerError("An internal server error has occurred.", error: $e); - ini_set("log_errors", false); // error logged successfully } public static function errorHandler(int $severity, string $message, string $file, int $line) diff --git a/tools/docker-dev/web/Dockerfile b/tools/docker-dev/web/Dockerfile index 2aebf47f..7d3b8de5 100644 --- a/tools/docker-dev/web/Dockerfile +++ b/tools/docker-dev/web/Dockerfile @@ -29,7 +29,8 @@ RUN sed -i '/zend.assertions/c\zend.assertions = 1' /etc/php/8.3/cli/php.ini RUN sed -i '/memory_limit/c\memory_limit = -1' /etc/php/8.3/apache2/php.ini RUN sed -i '/zend.exception_ignore_args/c\zend.exception_ignore_args = 0' /etc/php/8.3/apache2/php.ini RUN sed -i '/zend.exception_ignore_args/c\zend.exception_ignore_args = 0' /etc/php/8.3/cli/php.ini -RUN echo 'xdebug.mode=coverage' >> /etc/php/8.3/cli/php.ini +RUN echo 'xdebug.mode=develop,coverage,debug,gcstats,profile,trace' >> /etc/php/8.3/cli/php.ini +RUN echo 'xdebug.mode=develop,coverage,debug,gcstats,profile,trace' >> /etc/php/8.3/apache2/php.ini # Start apache2 server EXPOSE 80 From 32ef0105e21f703bc2b0a0214c26bdef5b029de4 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Mon, 1 Dec 2025 17:53:41 -0500 Subject: [PATCH 3/5] move remote_user, remote_addr, errorid to end of log message --- resources/lib/UnityHTTPD.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index c16baca1..722e8e3c 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -52,17 +52,17 @@ public static function errorLog( $output["data"] = "data could not be JSON encoded: " . $e->getMessage(); } } - $output["REMOTE_USER"] = $_SERVER["REMOTE_USER"] ?? null; - $output["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"] ?? null; - if (!is_null($errorid)) { - $output["errorid"] = $errorid; - } if (!is_null($error)) { $output["error"] = self::throwableToArray($error); } else { // newlines are bad for error log, but getTrace() is too verbose $output["trace"] = explode("\n", (new \Exception())->getTraceAsString()); } + $output["REMOTE_USER"] = $_SERVER["REMOTE_USER"] ?? null; + $output["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"] ?? null; + if (!is_null($errorid)) { + $output["errorid"] = $errorid; + } error_log("$title: " . \jsonEncode($output)); } From 0f77568d8a31ffa07bb620cab1947b806069af52 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Tue, 2 Dec 2025 09:11:46 -0500 Subject: [PATCH 4/5] check if error is null --- resources/lib/UnityHTTPD.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 722e8e3c..3185b6d2 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -132,7 +132,7 @@ public static function internalServerError( $errorid = uniqid(); self::errorToUser("An internal server error has occurred.", 500, $errorid); self::errorLog("internal server error", $message, $errorid, $error, $data); - if (ini_get("display_errors") && ini_get("html_errors")) { + if (!is_null($error) && ini_get("display_errors") && ini_get("html_errors")) { echo ""; echo $error->xdebug_message; echo "
"; From 115c2942d55e7131e47c033da54fa76109587e7b Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Tue, 9 Dec 2025 16:10:44 -0500 Subject: [PATCH 5/5] remove arrayGet function --- resources/lib/UnityHTTPD.php | 6 ++-- resources/lib/utils.php | 18 ------------ test/unit/UtilsTest.php | 56 ------------------------------------ 3 files changed, 3 insertions(+), 77 deletions(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 3185b6d2..c38a4e7b 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -155,10 +155,10 @@ public static function errorHandler(int $severity, string $message, string $file return false; } - public static function getPostData(...$keys): mixed + public static function getPostData(string $key): mixed { try { - return \arrayGet($_POST, ...$keys); + return $_POST[$key]; } catch (ArrayKeyException $e) { self::badRequest('failed to get $_POST data', $e, [ '$_POST' => $_POST, @@ -172,7 +172,7 @@ public static function getUploadedFileContents( string $encoding = "UTF-8", ): string { try { - $tmpfile_path = \arrayGet($_FILES, $filename, "tmp_name"); + $tmpfile_path = $_FILES[$filename]["tmp_name"]; } catch (ArrayKeyException $e) { self::badRequest("no such uploaded file", $e, [ '$_FILES' => $_FILES, diff --git a/resources/lib/utils.php b/resources/lib/utils.php index f9f23c8b..6b925b36 100644 --- a/resources/lib/utils.php +++ b/resources/lib/utils.php @@ -6,24 +6,6 @@ use UnityWebPortal\lib\exceptions\EncodingConversionException; use phpseclib3\Crypt\PublicKeyLoader; -function arrayGet(array $array, mixed ...$keys): mixed -{ - $cursor = $array; - $keysTraversed = []; - foreach ($keys as $key) { - array_push($keysTraversed, $key); - if (!isset($cursor[$key])) { - throw new ArrayKeyException( - "key not found: \$array" . - // [1, 2, "foo"] => [1][2]["foo"] - implode("", array_map(fn($x) => jsonEncode([$x]), $keysTraversed)), - ); - } - $cursor = $cursor[$key]; - } - return $cursor; -} - // like assert() but not subject to zend.assertions config function ensure(bool $condition, ?string $message = null): void { diff --git a/test/unit/UtilsTest.php b/test/unit/UtilsTest.php index f024e0ef..3644afd3 100644 --- a/test/unit/UtilsTest.php +++ b/test/unit/UtilsTest.php @@ -6,62 +6,6 @@ class UtilsTest extends TestCase { - public function testArrayGetReturnsValueWhenKeyExists() - { - $array = [ - "a" => [ - "b" => [ - "c" => 123, - ], - ], - ]; - $result = \arrayGet($array, "a", "b", "c"); - $this->assertSame(123, $result); - } - - public function testArrayGetReturnsArrayWhenTraversingPartially() - { - $array = [ - "foo" => [ - "bar" => "baz", - ], - ]; - $result = \arrayGet($array, "foo"); - $this->assertSame(["bar" => "baz"], $result); - } - - public function testArrayGetThrowsOnMissingKeyFirstLevel() - { - $array = ["x" => 1]; - $this->expectException(ArrayKeyException::class); - $this->expectExceptionMessage('$array["y"]'); - \arrayGet($array, "y"); - } - - public function testArrayGetThrowsOnMissingKeyNested() - { - $array = ["a" => []]; - $this->expectException(ArrayKeyException::class); - // Should include both levels - $this->expectExceptionMessage('$array["a"]["b"]'); - \arrayGet($array, "a", "b"); - } - - public function testArrayGetThrowsWhenValueIsNullButKeyNotSet() - { - $array = ["a" => null]; - $this->expectException(ArrayKeyException::class); - $this->expectExceptionMessage('$array["a"]'); - \arrayGet($array, "a"); - } - - public function testArrayGetReturnsValueWhenValueIsFalsyButSet() - { - $array = ["a" => 0]; - $result = \arrayGet($array, "a"); - $this->assertSame(0, $result); - } - public static function SSHKeyProvider() { global $HTTP_HEADER_TEST_INPUTS;