Skip to content

Commit 64f6836

Browse files
use message system in exception handler (#381)
Co-authored-by: Copilot <[email protected]>
1 parent bde4033 commit 64f6836

File tree

4 files changed

+103
-46
lines changed

4 files changed

+103
-46
lines changed

defaults/config.ini.default

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ terms_of_service_url = "https://github.com" ; this can be external or a portal p
1515
account_policy_url = "https://github.com" ; this can be external or a portal page created with "content management"
1616
allow_die = true ; internal use only
1717
enable_verbose_error_log = true ; internal use only
18-
enable_error_to_user = true ; internal use only
18+
enable_redirect_message = true ; internal use only
1919
enable_exception_handler = true ; internal use only
2020
enable_error_handler = true ; internal use only
2121

deployment/overrides/phpunit/config/config.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ custom_user_mappings_dir = "test/custom_user_mappings"
44
[site]
55
allow_die = false
66
enable_verbose_error_log = false
7-
enable_error_to_user = false
7+
enable_redirect_message = false
88
enable_exception_handler = false
99
enable_error_handler = false
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[site]
2-
enable_error_to_user = false
2+
enable_redirect_message = false

resources/lib/UnityHTTPD.php

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,79 @@ public static function die(mixed $x = null, bool $show_user = false): never
3434
}
3535
}
3636

37+
/*
38+
send HTTP header, set HTTP response code,
39+
print a message just in case the browser fails to redirect if PHP is not being run from the CLI,
40+
and then die
41+
*/
3742
public static function redirect(?string $dest = null): never
3843
{
3944
$dest ??= pathJoin(CONFIG["site"]["prefix"], $_SERVER["REQUEST_URI"]);
4045
$dest = htmlspecialchars($dest);
4146
header("Location: $dest");
42-
self::errorToUser("Redirect failed, click <a href='$dest'>here</a> to continue.", 302);
47+
http_response_code(302);
48+
if (CONFIG["site"]["enable_redirect_message"]) {
49+
echo "If you're reading this message, then your browser has failed to redirect you " .
50+
"to the proper destination. click <a href='$dest'>here</a> to continue.";
51+
}
4352
self::die();
4453
}
4554

55+
/*
56+
generates a unique error ID, writes to error log, and then:
57+
if "html_errors" is disabled in the PHP config file:
58+
prints a message to stdout and dies
59+
else, if the user is doing an HTTP POST:
60+
registers a message in the user's session and issues a redirect to display that message
61+
else:
62+
prints an HTML message to stdout, sets an HTTP response code, and dies
63+
we can't always do a redirect or else we could risk an infinite loop.
64+
*/
65+
public static function gracefulDie(
66+
string $log_title,
67+
string $log_message,
68+
string $user_message_title,
69+
string $user_message_body,
70+
?\Throwable $error = null,
71+
int $http_response_code = 200,
72+
mixed $data = null,
73+
): never {
74+
$errorid = uniqid();
75+
$suffix = sprintf(
76+
"Please notify a Unity admin at %s. Error ID: %s.",
77+
CONFIG["mail"]["support"],
78+
$errorid,
79+
);
80+
$user_message_title = htmlspecialchars($user_message_title);
81+
$user_message_body = htmlspecialchars($user_message_body);
82+
if (strlen($user_message_body) === 0) {
83+
$user_message_body = $suffix;
84+
} else {
85+
$user_message_body .= " $suffix";
86+
}
87+
self::errorLog($log_title, $log_message, data: $data, error: $error, errorid: $errorid);
88+
if (ini_get("html_errors") !== "1") {
89+
self::die("$user_message_title -- $user_message_body");
90+
} elseif (($_SERVER["REQUEST_METHOD"] ?? "") == "POST") {
91+
self::messageError($user_message_title, $user_message_body);
92+
self::redirect();
93+
} else {
94+
if (!headers_sent()) {
95+
http_response_code($http_response_code);
96+
}
97+
// text may not be shown in the webpage in an obvious way, so make a popup
98+
self::alert("$user_message_title -- $user_message_body");
99+
echo "<h1>$user_message_title</h1><p>$user_message_body</p>";
100+
// display_errors should not be enabled in production
101+
if (!is_null($error) && ini_get("display_errors") === "1") {
102+
echo "<table>";
103+
echo $error->xdebug_message;
104+
echo "</table>";
105+
}
106+
self::die();
107+
}
108+
}
109+
46110
// $data must be JSON serializable
47111
public static function errorLog(
48112
string $title,
@@ -72,6 +136,7 @@ public static function errorLog(
72136
}
73137
$output["REMOTE_USER"] = $_SERVER["REMOTE_USER"] ?? null;
74138
$output["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"] ?? null;
139+
$output["_REQUEST"] = $_REQUEST;
75140
if (!is_null($errorid)) {
76141
$output["errorid"] = $errorid;
77142
}
@@ -94,69 +159,61 @@ private static function throwableToArray(\Throwable $t): array
94159
return $output;
95160
}
96161

97-
private static function errorToUser(
98-
string $msg,
99-
int $http_response_code,
100-
?string $errorid = null,
101-
): void {
102-
if (!CONFIG["site"]["enable_error_to_user"]) {
103-
return;
104-
}
105-
$notes = "Please notify a Unity admin at " . CONFIG["mail"]["support"] . ".";
106-
if (!is_null($errorid)) {
107-
$notes = $notes . " Error ID: $errorid.";
108-
}
109-
if (!headers_sent()) {
110-
http_response_code($http_response_code);
111-
}
112-
// text may not be shown in the webpage in an obvious way, so make a popup
113-
self::alert("$msg $notes");
114-
echo "<h1>$msg</h1><p>$notes</p>";
115-
}
116-
117162
public static function badRequest(
118-
string $message,
163+
string $log_message,
119164
?\Throwable $error = null,
120165
?array $data = null,
121166
): never {
122-
$errorid = uniqid();
123-
self::errorToUser("Invalid requested action or submitted data.", 400, $errorid);
124-
self::errorLog("bad request", $message, $errorid, $error, $data);
125-
self::die($message);
167+
self::gracefulDie(
168+
"bad request",
169+
$log_message,
170+
"Invalid requested action or submitted data.",
171+
"",
172+
error: $error,
173+
http_response_code: 400,
174+
data: $data,
175+
);
126176
}
127177

128178
public static function forbidden(
129-
string $message,
179+
string $log_message,
130180
?\Throwable $error = null,
131181
?array $data = null,
132182
): never {
133-
$errorid = uniqid();
134-
self::errorToUser("Permission denied.", 403, $errorid);
135-
self::errorLog("forbidden", $message, $errorid, $error, $data);
136-
self::die($message);
183+
self::gracefulDie(
184+
"forbidden",
185+
$log_message,
186+
"Permission denied.",
187+
"",
188+
error: $error,
189+
http_response_code: 403,
190+
data: $data,
191+
);
137192
}
138193

139194
public static function internalServerError(
140-
string $message,
195+
string $log_message,
141196
?\Throwable $error = null,
142197
?array $data = null,
143198
): never {
144-
$errorid = uniqid();
145-
self::errorToUser("An internal server error has occurred.", 500, $errorid);
146-
self::errorLog("internal server error", $message, $errorid, $error, $data);
147-
if (!is_null($error) && ini_get("display_errors") && ini_get("html_errors")) {
148-
echo "<table>";
149-
echo $error->xdebug_message;
150-
echo "</table>";
151-
}
152-
self::die($message);
199+
self::gracefulDie(
200+
"internal server error",
201+
$log_message,
202+
"An internal server error has occurred.",
203+
"",
204+
error: $error,
205+
http_response_code: 500,
206+
data: $data,
207+
);
153208
}
154209

155210
// https://www.php.net/manual/en/function.set-exception-handler.php
156211
public static function exceptionHandler(\Throwable $e): void
157212
{
158-
ini_set("log_errors", true); // in case something goes wrong and error is not logged
159-
self::internalServerError("An internal server error has occurred.", error: $e);
213+
// we disable log_errors before we enable this exception handler to avoid duplicate logging
214+
// if this exception handler itself fails, information will be lost unless we re-enable it
215+
ini_set("log_errors", true);
216+
self::internalServerError("", error: $e);
160217
}
161218

162219
public static function errorHandler(int $severity, string $message, string $file, int $line)

0 commit comments

Comments
 (0)