From f2bb296e9a5630f364389d08ccef7337924f1b1e Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 11 Dec 2025 14:41:58 -0500 Subject: [PATCH 1/8] add getMessage, deleteMessage put back test function with regex --- resources/autoload.php | 1 + resources/lib/UnityHTTPD.php | 48 +++++++++++++++++-- .../UnityHTTPDMessageNotFoundException | 5 ++ test/phpunit-bootstrap.php | 1 + 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 resources/lib/exceptions/UnityHTTPDMessageNotFoundException diff --git a/resources/autoload.php b/resources/autoload.php index 7f262cdc..158db4bc 100644 --- a/resources/autoload.php +++ b/resources/autoload.php @@ -32,6 +32,7 @@ require_once __DIR__ . "/lib/exceptions/EnsureException.php"; require_once __DIR__ . "/lib/exceptions/EncodingUnknownException.php"; require_once __DIR__ . "/lib/exceptions/EncodingConversionException.php"; +require_once __DIR__ . "/lib/exceptions/UnityHTTPDMessageNotFoundException.php"; require_once __DIR__ . "/config.php"; require __DIR__ . "/init.php"; diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index bed4bb95..e1bfc6fa 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -4,6 +4,7 @@ use UnityWebPortal\lib\exceptions\NoDieException; use UnityWebPortal\lib\exceptions\ArrayKeyException; +use UnityWebPortal\lib\exceptions\UnityHTTPDMessageNotFoundException; use RuntimeException; enum UnityHTTPDMessageLevel: string @@ -323,8 +324,49 @@ public static function getMessages() return $_SESSION["messages"]; } - public static function clearMessages() - { - $_SESSION["messages"] = []; + private static function getMessageIndex( + UnityHTTPDMessageLevel $level, + string $title_regex, + string $body_regex, + ) { + $messages = self::getMessages(); + $error_msg = sprintf( + "message(level='%s' title_regex='%s' body_regex='%s'), not found. found messages: %s", + $level->value, + $title_regex, + $body_regex, + jsonEncode($messages), + ); + foreach ($messages as $i => $message) { + if ( + preg_match($title_regex, $message[0]) && + preg_match($body_regex, $message[1]) && + $level == $message[2] + ) { + return $i; + } + } + throw new UnityHTTPDMessageNotFoundException($error_msg); + } + + /* returns the 1st message that matches criteria or throws UnityHTTPDMessageNotFoundException */ + public static function getMessage( + UnityHTTPDMessageLevel $level, + string $title_regex, + string $body_regex, + ) { + $index = self::getMessageIndex($level, $title_regex, $body_regex); + return $_SESSION["messages"][$index]; + } + + /* deletes the 1st message that matches criteria or throws UnityHTTPDMessageNotFoundException */ + public static function deleteMessage( + UnityHTTPDMessageLevel $level, + string $title_regex, + string $body_regex, + ) { + $index = self::getMessageIndex($level, $title_regex, $body_regex); + unset($_SESSION["messages"][$index]); + $_SESSION["messages"] = array_values($_SESSION["messages"]); } } diff --git a/resources/lib/exceptions/UnityHTTPDMessageNotFoundException b/resources/lib/exceptions/UnityHTTPDMessageNotFoundException new file mode 100644 index 00000000..214fc5bb --- /dev/null +++ b/resources/lib/exceptions/UnityHTTPDMessageNotFoundException @@ -0,0 +1,5 @@ + Date: Thu, 11 Dec 2025 15:18:12 -0500 Subject: [PATCH 2/8] fix filename --- ...geNotFoundException => UnityHTTPDMessageNotFoundException.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/lib/exceptions/{UnityHTTPDMessageNotFoundException => UnityHTTPDMessageNotFoundException.php} (100%) diff --git a/resources/lib/exceptions/UnityHTTPDMessageNotFoundException b/resources/lib/exceptions/UnityHTTPDMessageNotFoundException.php similarity index 100% rename from resources/lib/exceptions/UnityHTTPDMessageNotFoundException rename to resources/lib/exceptions/UnityHTTPDMessageNotFoundException.php From e50a7654b0356626a5ad3bc3ac6ecdd4da29b80d Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 11 Dec 2025 15:18:29 -0500 Subject: [PATCH 3/8] X button deletes specific messag with ajax --- resources/lib/UnityHTTPD.php | 24 ++++++++++++++++++------ resources/templates/header.php | 23 ++++++++++++++++++++--- webroot/panel/ajax/delete_message.php | 12 ++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 webroot/panel/ajax/delete_message.php diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index e1bfc6fa..0a705d8c 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -227,13 +227,25 @@ public static function errorHandler(int $severity, string $message, string $file public static function getPostData(string $key): mixed { - try { - return $_POST[$key]; - } catch (ArrayKeyException $e) { - self::badRequest('failed to get $_POST data', $e, [ - '$_POST' => $_POST, - ]); + if (!isset($_SERVER)) { + throw new RuntimeException('$_SERVER is unset'); + } + if (!array_key_exists("REQUEST_METHOD", $_SERVER)) { + throw new RuntimeException('$_SERVER has no array key "REQUEST_METHOD"'); + } + if ($_SERVER["REQUEST_METHOD"] !== "POST") { + self::badRequest('$_SERVER["REQUEST_METHOD"] != "POST"'); + } + if (!isset($_POST)) { + self::badRequest('$_POST is unset'); + } + if ($_POST === null) { + self::badRequest('$_POST is null'); + } + if (!array_key_exists($key, $_POST)) { + self::badRequest("\$_POST has no array key '$key'"); } + return $_POST[$key]; } public static function getUploadedFileContents( diff --git a/resources/templates/header.php b/resources/templates/header.php index 2e466f85..2db52606 100644 --- a/resources/templates/header.php +++ b/resources/templates/header.php @@ -147,15 +147,32 @@

%s

%s

- +
", htmlspecialchars($level->value), htmlspecialchars($title), - htmlspecialchars($body) + htmlspecialchars($body), + htmlspecialchars($level->value), + htmlspecialchars($title), + htmlspecialchars($body), ); } - UnityHTTPD::clearMessages(); if ( isset($_SESSION["is_admin"]) && $_SESSION["is_admin"] diff --git a/webroot/panel/ajax/delete_message.php b/webroot/panel/ajax/delete_message.php new file mode 100644 index 00000000..ebbd4ef4 --- /dev/null +++ b/webroot/panel/ajax/delete_message.php @@ -0,0 +1,12 @@ + Date: Thu, 11 Dec 2025 15:25:14 -0500 Subject: [PATCH 4/8] put back clearMessages --- resources/lib/UnityHTTPD.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 0a705d8c..a3ba877b 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -336,6 +336,12 @@ public static function getMessages() return $_SESSION["messages"]; } + public static function clearMessages() + { + self::ensureSessionMessagesSanity(); + $_SESSION["messages"] = []; + } + private static function getMessageIndex( UnityHTTPDMessageLevel $level, string $title_regex, From e5f537ce1470ef5a3ee2a5b4695dc3f5e93f2f9b Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 11 Dec 2025 15:32:57 -0500 Subject: [PATCH 5/8] remove check for null --- resources/lib/UnityHTTPD.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index a3ba877b..4b6dc691 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -239,9 +239,6 @@ public static function getPostData(string $key): mixed if (!isset($_POST)) { self::badRequest('$_POST is unset'); } - if ($_POST === null) { - self::badRequest('$_POST is null'); - } if (!array_key_exists($key, $_POST)) { self::badRequest("\$_POST has no array key '$key'"); } From f831c4948b157105d6581b9183af2b5fb8316f6a Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 11 Dec 2025 15:33:49 -0500 Subject: [PATCH 6/8] regex quote --- resources/templates/header.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/templates/header.php b/resources/templates/header.php index 2db52606..a61c07c3 100644 --- a/resources/templates/header.php +++ b/resources/templates/header.php @@ -169,8 +169,8 @@ htmlspecialchars($title), htmlspecialchars($body), htmlspecialchars($level->value), - htmlspecialchars($title), - htmlspecialchars($body), + preg_quote(htmlspecialchars($title), "/"), + preg_quote(htmlspecialchars($body), "/"), ); } if ( From dc5f3fbb6e1500d2e7ae32b46aeac26e359ceb3f Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Thu, 11 Dec 2025 16:03:22 -0500 Subject: [PATCH 7/8] add test --- test/functional/DeleteMessageTest.php | 40 +++++++++++++++++++++++++++ test/phpunit-bootstrap.php | 8 ++++-- 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 test/functional/DeleteMessageTest.php diff --git a/test/functional/DeleteMessageTest.php b/test/functional/DeleteMessageTest.php new file mode 100644 index 00000000..5ca6f3a0 --- /dev/null +++ b/test/functional/DeleteMessageTest.php @@ -0,0 +1,40 @@ +assertEmpty($initial); + UnityHTTPD::messageDebug("foo1", "bar1"); + UnityHTTPD::messageDebug("foo2", "bar2"); + UnityHTTPD::messageDebug("foo3", "bar3"); + UnityHTTPD::messageError("foo", "bar"); + UnityHTTPD::messageInfo("foo", "bar"); + UnityHTTPD::messageSuccess("foo", "bar"); + UnityHTTPD::messageWarning("foo", "bar"); + try { + $before = array_map("jsonEncode", UnityHTTPD::getMessages()); + http_post( + __DIR__ . "/../../webroot/panel/ajax/delete_message.php", + [ + "level" => "debug", + "title_regex" => "/^.*2$/", + "body_regex" => "/^.*2$/", + ], + enforce_PRG: false, + ); + $after = array_map("jsonEncode", UnityHTTPD::getMessages()); + $difference = array_diff($before, $after); + $message_expected_removed = ["foo2", "bar2", UnityHTTPDMessageLevel::DEBUG]; + $this->assertEqualsCanonicalizing([jsonEncode($message_expected_removed)], $difference); + } finally { + UnityHTTPD::clearMessages(); + } + } +} diff --git a/test/phpunit-bootstrap.php b/test/phpunit-bootstrap.php index 80870088..1b231a6c 100644 --- a/test/phpunit-bootstrap.php +++ b/test/phpunit-bootstrap.php @@ -97,7 +97,7 @@ function switchUser( ensure(!is_null($USER)); } -function http_post(string $phpfile, array $post_data): void +function http_post(string $phpfile, array $post_data, bool $enforce_PRG = true): void { global $LDAP, $SQL, @@ -127,8 +127,10 @@ function http_post(string $phpfile, array $post_data): void unset($_POST); $_SERVER = $_PREVIOUS_SERVER; } - // https://en.wikipedia.org/wiki/Post/Redirect/Get - ensure($post_did_redirect_or_die, "post did not redirect or die!"); + if ($enforce_PRG) { + // https://en.wikipedia.org/wiki/Post/Redirect/Get + ensure($post_did_redirect_or_die, "post did not redirect or die!"); + } } function http_get(string $phpfile, array $get_data = []): void From 3000e276c63f9e56f895bf8aadc7a43d657c7c2b Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 12 Dec 2025 09:07:30 -0500 Subject: [PATCH 8/8] no more regex --- resources/lib/UnityHTTPD.php | 38 ++++++++++----------------- resources/templates/header.php | 8 +++--- test/functional/DeleteMessageTest.php | 4 +-- webroot/panel/ajax/delete_message.php | 6 ++--- 4 files changed, 23 insertions(+), 33 deletions(-) diff --git a/resources/lib/UnityHTTPD.php b/resources/lib/UnityHTTPD.php index 4b6dc691..d1794369 100644 --- a/resources/lib/UnityHTTPD.php +++ b/resources/lib/UnityHTTPD.php @@ -341,46 +341,36 @@ public static function clearMessages() private static function getMessageIndex( UnityHTTPDMessageLevel $level, - string $title_regex, - string $body_regex, + string $title, + string $body, ) { $messages = self::getMessages(); $error_msg = sprintf( - "message(level='%s' title_regex='%s' body_regex='%s'), not found. found messages: %s", + "message(level='%s' title='%s' body='%s'), not found. found messages: %s", $level->value, - $title_regex, - $body_regex, + $title, + $body, jsonEncode($messages), ); foreach ($messages as $i => $message) { - if ( - preg_match($title_regex, $message[0]) && - preg_match($body_regex, $message[1]) && - $level == $message[2] - ) { + if ($title == $message[0] && $body == $message[1] && $level == $message[2]) { return $i; } } throw new UnityHTTPDMessageNotFoundException($error_msg); } - /* returns the 1st message that matches criteria or throws UnityHTTPDMessageNotFoundException */ - public static function getMessage( - UnityHTTPDMessageLevel $level, - string $title_regex, - string $body_regex, - ) { - $index = self::getMessageIndex($level, $title_regex, $body_regex); + /* returns the 1st message that matches or throws UnityHTTPDMessageNotFoundException */ + public static function getMessage(UnityHTTPDMessageLevel $level, string $title, string $body) + { + $index = self::getMessageIndex($level, $title, $body); return $_SESSION["messages"][$index]; } - /* deletes the 1st message that matches criteria or throws UnityHTTPDMessageNotFoundException */ - public static function deleteMessage( - UnityHTTPDMessageLevel $level, - string $title_regex, - string $body_regex, - ) { - $index = self::getMessageIndex($level, $title_regex, $body_regex); + /* deletes the 1st message that matches or throws UnityHTTPDMessageNotFoundException */ + public static function deleteMessage(UnityHTTPDMessageLevel $level, string $title, string $body) + { + $index = self::getMessageIndex($level, $title, $body); unset($_SESSION["messages"][$index]); $_SESSION["messages"] = array_values($_SESSION["messages"]); } diff --git a/resources/templates/header.php b/resources/templates/header.php index a61c07c3..bc055b1f 100644 --- a/resources/templates/header.php +++ b/resources/templates/header.php @@ -155,8 +155,8 @@ method: 'POST', data: { 'level': '%s', - 'title_regex': '/^%s$/', - 'body_regex': '/^%s$/', + 'title': '%s', + 'body': '%s', } }); \" @@ -169,8 +169,8 @@ htmlspecialchars($title), htmlspecialchars($body), htmlspecialchars($level->value), - preg_quote(htmlspecialchars($title), "/"), - preg_quote(htmlspecialchars($body), "/"), + htmlspecialchars($title), + htmlspecialchars($body), ); } if ( diff --git a/test/functional/DeleteMessageTest.php b/test/functional/DeleteMessageTest.php index 5ca6f3a0..133c19e9 100644 --- a/test/functional/DeleteMessageTest.php +++ b/test/functional/DeleteMessageTest.php @@ -24,8 +24,8 @@ public function testDeleteMessage(): void __DIR__ . "/../../webroot/panel/ajax/delete_message.php", [ "level" => "debug", - "title_regex" => "/^.*2$/", - "body_regex" => "/^.*2$/", + "title" => "foo2", + "body" => "bar2", ], enforce_PRG: false, ); diff --git a/webroot/panel/ajax/delete_message.php b/webroot/panel/ajax/delete_message.php index ebbd4ef4..23870636 100644 --- a/webroot/panel/ajax/delete_message.php +++ b/webroot/panel/ajax/delete_message.php @@ -7,6 +7,6 @@ $level_str = UnityHTTPD::getPostData("level"); $level = UnityHTTPDMessageLevel::from($level_str); -$title_regex = UnityHTTPD::getPostData("title_regex"); -$body_regex = UnityHTTPD::getPostData("body_regex"); -UnityHTTPD::deleteMessage($level, $title_regex, $body_regex); +$title = UnityHTTPD::getPostData("title"); +$body = UnityHTTPD::getPostData("body"); +UnityHTTPD::deleteMessage($level, $title, $body);