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..c38a4e7b 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));
}
@@ -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 (!is_null($error) && ini_get("display_errors") && ini_get("html_errors")) {
+ echo "
";
+ echo $error->xdebug_message;
+ echo "
";
+ }
self::die($message);
}
@@ -140,13 +145,20 @@ 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 getPostData(...$keys): mixed
+ 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(string $key): mixed
{
try {
- return \arrayGet($_POST, ...$keys);
+ return $_POST[$key];
} catch (ArrayKeyException $e) {
self::badRequest('failed to get $_POST data', $e, [
'$_POST' => $_POST,
@@ -160,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;
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