Updated to latest Index as well as some minor bug fixes.

This commit is contained in:
flash 2025-03-24 00:20:41 +00:00 committed by flash
parent ad89d45cf0
commit 5ba8b30047
77 changed files with 900 additions and 937 deletions

View file

@ -1,4 +1,12 @@
const MszCommentsApi = (() => {
const argsToFormData = args => {
const formData = new FormData;
for(const name in args)
formData.append(name, args[name]);
return formData;
};
return {
getCategory: async name => {
if(typeof name !== 'string' || name.trim() === '')
@ -62,7 +70,7 @@ const MszCommentsApi = (() => {
const { status, body } = await $xhr.post(
'/comments/posts',
{ csrf: true, type: 'json' },
args
argsToFormData(args)
);
if(status !== 201)
throw new Error(body.error?.text ?? 'something went wrong', { cause: body.error?.name ?? 'something' });
@ -75,10 +83,10 @@ const MszCommentsApi = (() => {
if(typeof args !== 'object' || args === null)
throw new Error('args must be a non-null object');
const { status, body } = await $xhr.post(
const { status, body } = await $xhr.patch(
`/comments/posts/${post}`,
{ csrf: true, type: 'json' },
args
argsToFormData(args)
);
if(status !== 200)
throw new Error(body.error?.text ?? 'something went wrong', { cause: body.error?.name ?? 'something' });

View file

@ -52,7 +52,7 @@ const MszMessages = () => {
formData.append('format', format);
formData.append('draft', draft);
const { body } = await $xhr.post(`/messages/${encodeURIComponent(messageId)}`, { type: 'json', csrf: true }, formData);
const { body } = await $xhr.patch(`/messages/${encodeURIComponent(messageId)}`, { type: 'json', csrf: true }, formData);
if(body.error !== undefined)
throw body.error;

View file

@ -188,7 +188,7 @@ const MszOAuth2Authorise = async () => {
params.scope = scope;
try {
const { body } = await $xhr.post('/oauth2/authorise', { authed: true, csrf: true, type: 'json' }, params);
const { body } = await $xhr.post('/oauth2/authorize', { authed: true, csrf: true, type: 'json' }, params);
if(!body)
throw 'authorisation failed';
if(typeof body.error === 'string')

View file

@ -2,8 +2,8 @@
"require": {
"php": ">=8.4",
"ext-mbstring": "*",
"flashwave/index": "^0.2501",
"flashii/rpcii": "~4.0",
"flashwave/index": "^0.2503",
"flashii/rpcii": "~5.0",
"erusev/parsedown": "~1.7",
"chillerlan/php-qrcode": "~5.0",
"symfony/mailer": "~7.2",
@ -11,9 +11,9 @@
"sentry/sdk": "~4.0",
"nesbot/carbon": "~3.8",
"vlucas/phpdotenv": "~5.6",
"filp/whoops": "~2.17",
"filp/whoops": "~2.18",
"phpseclib/phpseclib": "~3.0",
"guzzlehttp/guzzle": "~7.0"
"guzzlehttp/guzzle": "~7.9"
},
"autoload": {
"classmap": [

135
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1045c8f605203fae19361d659fb64c54",
"content-hash": "baa15cf491eb4500360fa9c20fd42a4d",
"packages": [
{
"name": "carbonphp/carbon-doctrine-types",
@ -313,16 +313,16 @@
},
{
"name": "egulias/email-validator",
"version": "4.0.3",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "b115554301161fa21467629f1e1391c1936de517"
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517",
"reference": "b115554301161fa21467629f1e1391c1936de517",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"shasum": ""
},
"require": {
@ -368,7 +368,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/4.0.3"
"source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
},
"funding": [
{
@ -376,7 +376,7 @@
"type": "github"
}
],
"time": "2024-12-27T00:36:43+00:00"
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "erusev/parsedown",
@ -430,16 +430,16 @@
},
{
"name": "filp/whoops",
"version": "2.17.0",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "075bc0c26631110584175de6523ab3f1652eb28e"
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e",
"reference": "075bc0c26631110584175de6523ab3f1652eb28e",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"shasum": ""
},
"require": {
@ -489,7 +489,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.17.0"
"source": "https://github.com/filp/whoops/tree/2.18.0"
},
"funding": [
{
@ -497,24 +497,26 @@
"type": "github"
}
],
"time": "2025-01-25T12:00:00+00:00"
"time": "2025-03-15T12:00:00+00:00"
},
{
"name": "flashii/rpcii",
"version": "v4.0.0",
"version": "v5.0.1",
"source": {
"type": "git",
"url": "https://patchii.net/flashii/rpcii-php.git",
"reference": "7849b2effd7ac878e7cf174eef7e27914799e60d"
"reference": "28c25e0a342173524f8894d03158663842e03252"
},
"require": {
"ext-msgpack": ">=2.2",
"flashwave/index": "^0.2501",
"php": ">=8.4"
"flashwave/index": "^0.2503",
"guzzlehttp/guzzle": "~7.0",
"php": ">=8.4",
"psr/http-client": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5"
"phpunit/phpunit": "^12.0"
},
"type": "library",
"autoload": {
@ -536,25 +538,27 @@
],
"description": "HTTP RPC client/server library.",
"homepage": "https://railgun.sh/rpcii",
"time": "2025-01-29T22:00:17+00:00"
"time": "2025-03-21T19:38:29+00:00"
},
{
"name": "flashwave/index",
"version": "v0.2501.221237",
"version": "v0.2503.230355",
"source": {
"type": "git",
"url": "https://patchii.net/flash/index.git",
"reference": "fee9a65e3bca341be7401fe2e21b795630f15f2a"
"reference": "2372a113d26380176994f64ab99c42aaf2e9d98e"
},
"require": {
"ext-mbstring": "*",
"php": ">=8.4",
"twig/html-extra": "^3.18",
"twig/twig": "^3.18"
"psr/http-message": "^2.0",
"psr/http-server-handler": "^1.0",
"twig/html-extra": "^3.20",
"twig/twig": "^3.20"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5"
"phpunit/phpunit": "^12.0"
},
"suggest": {
"ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).",
@ -591,7 +595,7 @@
],
"description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index",
"time": "2025-01-22T12:38:11+00:00"
"time": "2025-03-23T03:45:47+00:00"
},
{
"name": "graham-campbell/result-type",
@ -982,16 +986,16 @@
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10"
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
@ -1001,8 +1005,9 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
@ -1035,9 +1040,9 @@
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2024-11-18T16:19:46+00:00"
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "matomo/device-detector",
@ -1882,6 +1887,62 @@
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/http-server-handler",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
"keywords": [
"handler",
"http",
"http-interop",
"psr",
"psr-15",
"psr-7",
"request",
"response",
"server"
],
"support": {
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
},
"time": "2023-04-10T20:06:20+00:00"
},
{
"name": "psr/log",
"version": "3.0.2",
@ -3618,16 +3679,16 @@
"packages-dev": [
{
"name": "phpstan/phpstan",
"version": "2.1.6",
"version": "2.1.10",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c"
"reference": "051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f",
"reference": "051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f",
"shasum": ""
},
"require": {
@ -3672,7 +3733,7 @@
"type": "github"
}
],
"time": "2025-02-19T15:46:42+00:00"
"time": "2025-03-23T14:57:55+00:00"
}
],
"aliases": [],

View file

@ -15,10 +15,15 @@ if($msz->authInfo->loggedIn) {
if(!empty($_GET['resolve'])) {
header('Content-Type: application/json; charset=utf-8');
try {
// Only works for usernames, this is by design
$userInfo = $msz->usersCtx->users->getUser((string)filter_input(INPUT_GET, 'name'), 'name');
} catch(Exception $ex) {
if(!empty($_GET['name']) && is_scalar($_GET['name']))
try {
// Only works for usernames, this is by design
$userInfo = $msz->usersCtx->users->getUser((string)$_GET['name'], 'name');
} catch(Exception $ex) {
unset($userInfo);
}
if(empty($userInfo)) {
echo json_encode([
'id' => 0,
'name' => '',
@ -62,16 +67,16 @@ if($siteIsPrivate) {
$canResetPassword = true;
}
while(!empty($_POST['login']) && is_array($_POST['login'])) {
while($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$loginRedirect = empty($_POST['login']['redirect']) || !is_string($_POST['login']['redirect']) ? '' : $_POST['login']['redirect'];
$loginRedirect = empty($_POST['redirect']) || !is_string($_POST['redirect']) ? '' : $_POST['redirect'];
if(empty($_POST['login']['username']) || empty($_POST['login']['password'])
|| !is_string($_POST['login']['username']) || !is_string($_POST['login']['password'])) {
if(empty($_POST['username']) || empty($_POST['password'])
|| !is_string($_POST['username']) || !is_string($_POST['password'])) {
$notices[] = "You didn't fill in a username and/or password.";
break;
}
@ -91,7 +96,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
$loginFailedError = "Invalid username or password, {$attemptsRemainingError}.";
try {
$userInfo = $msz->usersCtx->users->getUser($_POST['login']['username'], 'login');
$userInfo = $msz->usersCtx->users->getUser($_POST['username'], 'login');
} catch(Exception $ex) {
$msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo);
$notices[] = $loginFailedError;
@ -110,14 +115,14 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
break;
}
if(!$pwInfo->verifyPassword($_POST['login']['password'])) {
if(!$pwInfo->verifyPassword($_POST['password'])) {
$msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
$notices[] = $loginFailedError;
break;
}
if($pwInfo->needsRehash)
$msz->usersCtx->passwords->updateUserPassword($userInfo, $_POST['login']['password']);
$msz->usersCtx->passwords->updateUserPassword($userInfo, $_POST['password']);
if(!empty($loginPermCat) && $loginPermVal > 0 && !$msz->perms->checkPermissions($loginPermCat, $loginPermVal, $userInfo)) {
$notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
@ -157,7 +162,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
$welcomeMode = !empty($_GET['welcome']);
$oauth2Mode = !empty($_GET['oauth2']);
$loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : (
$loginUsername = !empty($_POST['username']) && is_string($_POST['username']) ? $_POST['username'] : (
!empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : ''
);
$loginRedirect = $welcomeMode ? $msz->urls->format('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? $msz->urls->format('index');

View file

@ -12,11 +12,10 @@ if($msz->authInfo->loggedIn) {
return;
}
$reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : [];
$forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : [];
$userId = !empty($reset['user']) ? (int)$reset['user'] : (
!empty($_GET['user']) ? (int)$_GET['user'] : 0
);
$userId = !empty($_POST['user']) && is_scalar($_POST['user'])
? (int)$_POST['user'] : (
!empty($_GET['user']) && is_scalar($_GET['user']) ? (int)$_GET['user'] : 0
);
if($userId > 0)
try {
@ -30,17 +29,16 @@ $notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$siteIsPrivate = $msz->config->getBoolean('private.enable');
$canResetPassword = $siteIsPrivate ? $msz->config->getBoolean('private.allow_password_reset', true) : true;
$remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
while($canResetPassword) {
if(!empty($reset) && $userId > 0) {
if(!empty($_POST['verification']) && is_scalar($_POST['verification']) && !empty($userInfo)) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$verifyCode = !empty($reset['verification']) && is_string($reset['verification']) ? $reset['verification'] : '';
$verifyCode = (string)$_POST['verification'];
try {
$tokenInfo = $msz->authCtx->recoveryTokens->getToken(verifyCode: $verifyCode);
@ -53,9 +51,8 @@ while($canResetPassword) {
break;
}
$password = !empty($reset['password']) && is_array($reset['password']) ? $reset['password'] : [];
$passwordNew = !empty($password['new']) && is_string($password['new']) ? $password['new'] : '';
$passwordConfirm = !empty($password['confirm']) && is_string($password['confirm']) ? $password['confirm'] : '';
$passwordNew = !empty($_POST['password_new']) && is_scalar($_POST['password_new']) ? $_POST['password_new'] : '';
$passwordConfirm = !empty($_POST['password_confirm']) && is_scalar($_POST['password_confirm']) ? $_POST['password_confirm'] : '';
if(empty($passwordNew) || empty($passwordConfirm)
|| $passwordNew !== $passwordConfirm) {
@ -82,24 +79,19 @@ while($canResetPassword) {
return;
}
if(!empty($forgot)) {
if(!empty($_POST['email']) && is_scalar($_POST['email'])) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if(empty($forgot['email']) || !is_string($forgot['email'])) {
$notices[] = "You didn't supply an e-mail address.";
break;
}
if($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
try {
$forgotUser = $msz->usersCtx->users->getUser($forgot['email'], 'email');
$forgotUser = $msz->usersCtx->users->getUser((string)$_POST['email'], 'email');
} catch(RuntimeException $ex) {
unset($forgotUser);
}
@ -142,7 +134,7 @@ while($canResetPassword) {
Template::render(isset($userInfo) ? 'auth.password_reset' : 'auth.password_forgot', [
'password_notices' => $notices,
'password_email' => !empty($forget['email']) && is_string($forget['email']) ? $forget['email'] : '',
'password_email' => !empty($_POST['email']) && is_scalar($_POST['email']) ? (string)$_POST['email'] : '',
'password_attempts_remaining' => $remainingAttempts,
'password_user' => $userInfo ?? null,
'password_verification' => $verifyCode ?? '',

View file

@ -12,14 +12,13 @@ if($msz->authInfo->loggedIn) {
return;
}
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
while(!empty($register)) {
while($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
@ -30,13 +29,13 @@ while(!empty($register)) {
break;
}
if(empty($register['username']) || empty($register['password']) || empty($register['email']) || empty($register['question'])
|| !is_string($register['username']) || !is_string($register['password']) || !is_string($register['email']) || !is_string($register['question'])) {
if(empty($_POST['username']) || empty($_POST['password']) || empty($_POST['email']) || empty($_POST['question'])
|| !is_scalar($_POST['username']) || !is_scalar($_POST['password']) || !is_scalar($_POST['email']) || !is_scalar($_POST['question'])) {
$notices[] = "You haven't filled in all fields.";
break;
}
$checkSpamBot = mb_strtolower($register['question']);
$checkSpamBot = mb_strtolower($_POST['question']);
$spamBotValid = [
'21', 'twentyone', 'twenty-one', 'twenty one',
];
@ -52,18 +51,18 @@ while(!empty($register)) {
break;
}
$usernameValidation = $msz->usersCtx->users->validateName($register['username']);
$usernameValidation = $msz->usersCtx->users->validateName($_POST['username']);
if($usernameValidation !== '')
$notices[] = $msz->usersCtx->users->validateNameText($usernameValidation);
$emailValidation = $msz->usersCtx->users->validateEMailAddress($register['email']);
$emailValidation = $msz->usersCtx->users->validateEMailAddress($_POST['email']);
if($emailValidation !== '')
$notices[] = $msz->usersCtx->users->validateEMailAddressText($emailValidation);
if($register['password_confirm'] !== $register['password'])
if($_POST['password_confirm'] !== $_POST['password'])
$notices[] = "The given passwords don't match.";
$passwordValidation = UserPasswordsData::validateUserPassword($register['password']);
$passwordValidation = UserPasswordsData::validateUserPassword($_POST['password']);
if($passwordValidation !== '')
$notices[] = UserPasswordsData::validateUserPasswordText($passwordValidation);
@ -74,13 +73,13 @@ while(!empty($register)) {
try {
$userInfo = $msz->usersCtx->users->createUser(
$register['username'],
$register['email'],
$_POST['username'],
$_POST['email'],
$ipAddress,
$countryCode,
$defaultRoleInfo
);
$msz->usersCtx->passwords->updateUserPassword($userInfo, $register['password']);
$msz->usersCtx->passwords->updateUserPassword($userInfo, $_POST['password']);
} catch(RuntimeException $ex) {
$notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!';
break;
@ -99,7 +98,7 @@ while(!empty($register)) {
Template::render('auth.register', [
'register_notices' => $notices,
'register_username' => !empty($register['username']) && is_string($register['username']) ? $register['username'] : '',
'register_email' => !empty($register['email']) && is_string($register['email']) ? $register['email'] : '',
'register_username' => !empty($_POST['username']) && is_scalar($_POST['username']) ? (string)$_POST['username'] : '',
'register_email' => !empty($_POST['email']) && is_scalar($_POST['email']) ? (string)$_POST['email'] : '',
'register_restricted' => '',
]);

View file

@ -16,13 +16,12 @@ if($msz->authInfo->loggedIn) {
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : [];
$notices = [];
$remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
$tokenString = !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : (
!empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : ''
$tokenString = !empty($_GET['token']) && is_scalar($_GET['token']) ? (string)$_GET['token'] : (
!empty($_POST['token']) && is_scalar($_POST['token']) ? (string)$_POST['token'] : ''
);
$tokenUserId = $msz->authCtx->tfaSessions->getTokenUserId($tokenString);
@ -37,16 +36,16 @@ if($totpInfo === null) {
return;
}
while(!empty($twofactor)) {
while($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$redirect = !empty($twofactor['redirect']) && is_string($twofactor['redirect']) ? $twofactor['redirect'] : '';
$redirect = !empty($_POST['redirect']) && is_scalar($_POST['redirect']) ? (string)$_POST['redirect'] : '';
if(empty($twofactor['code']) || !is_string($twofactor['code'])) {
if(empty($_POST['code']) || !is_string($_POST['code'])) {
$notices[] = 'Code field was empty.';
break;
}
@ -59,7 +58,7 @@ while(!empty($twofactor)) {
$clientInfo = ClientInfo::fromRequest();
$generator = $totpInfo->createGenerator();
if(!in_array($twofactor['code'], $generator->generateRange())) {
if(!in_array($_POST['code'], $generator->generateRange())) {
$notices[] = sprintf(
"Invalid two factor code, %d attempt%s remaining",
$remainingAttempts - 1,

View file

@ -16,8 +16,8 @@ $config = $msz->config->getValues([
'forum_leader.unranked.topic:a',
]);
$mode = (string)filter_input(INPUT_GET, 'mode');
$yearMonth = (string)filter_input(INPUT_GET, 'id');
$mode = isset($_GET['mode']) && is_scalar($_GET['mode']) ? (string)$_GET['mode'] : '';
$yearMonth = isset($_GET['id']) && is_scalar($_GET['id']) ? (string)$_GET['id'] : '';
$year = $month = 0;
$currentYear = (int)date('Y');
@ -39,7 +39,7 @@ if(!empty($yearMonth)) {
}
}
if(filter_has_var(INPUT_GET, 'allow_unranked')) {
if(isset($_GET['allow_unranked'])) {
$unrankedForums = $unrankedTopics = [];
} else {
$unrankedForums = $config['forum_leader.unranked.forum'];

View file

@ -19,11 +19,11 @@ $currentUserId = $currentUser->id;
if($msz->usersCtx->hasActiveBan($currentUser))
Template::throwError(403);
if(filter_has_var(INPUT_POST, 'preview')) {
if(!empty($_POST['preview'])) {
header('Content-Type: text/plain; charset=utf-8');
$text = (string)filter_input(INPUT_POST, 'text');
$format = TextFormat::tryFrom((string)filter_input(INPUT_POST, 'format'));
$text = isset($_POST['text']) && is_scalar($_POST['text']) ? (string)$_POST['text'] : '';
$format = TextFormat::tryFrom(isset($_POST['format']) && is_scalar($_POST['format']) ? (string)$_POST['format'] : '');
if($format === null) {
http_response_code(400);
return;
@ -39,10 +39,10 @@ $forumPostingModes = [
];
if(!empty($_POST)) {
$mode = !empty($_POST['post']['mode']) && is_string($_POST['post']['mode']) ? $_POST['post']['mode'] : 'create';
$postId = !empty($_POST['post']['id']) && is_string($_POST['post']['id']) ? (int)$_POST['post']['id'] : 0;
$topicId = !empty($_POST['post']['topic']) && is_string($_POST['post']['topic']) ? (int)$_POST['post']['topic'] : 0;
$forumId = !empty($_POST['post']['forum']) && is_string($_POST['post']['forum']) ? (int)$_POST['post']['forum'] : 0;
$mode = !empty($_POST['mode']) && is_string($_POST['mode']) ? $_POST['mode'] : 'create';
$postId = !empty($_POST['id']) && is_string($_POST['id']) ? (int)$_POST['id'] : 0;
$topicId = !empty($_POST['topic']) && is_string($_POST['topic']) ? (int)$_POST['topic'] : 0;
$forumId = !empty($_POST['forum']) && is_string($_POST['forum']) ? (int)$_POST['forum'] : 0;
} else {
$mode = !empty($_GET['m']) && is_string($_GET['m']) ? $_GET['m'] : 'create';
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
@ -141,11 +141,11 @@ if($mode === 'edit') {
$notices = [];
if(!empty($_POST)) {
$topicTitle = $_POST['post']['title'] ?? '';
$postText = $_POST['post']['text'] ?? '';
$postParser = TextFormat::tryFrom((string)($_POST['post']['parser'] ?? '')) ?? TextFormat::BBCode;
$topicType = isset($_POST['post']['type']) ? $_POST['post']['type'] : null;
$postSignature = isset($_POST['post']['signature']);
$topicTitle = $_POST['title'] ?? '';
$postText = $_POST['text'] ?? '';
$postParser = TextFormat::tryFrom((string)($_POST['parser'] ?? '')) ?? TextFormat::BBCode;
$topicType = isset($_POST['type']) ? $_POST['type'] : null;
$postSignature = isset($_POST['signature']);
if(!CSRF::validateRequest()) {
$notices[] = 'Could not verify request.';

View file

@ -17,7 +17,7 @@ $changeActions = [];
foreach(ChangelogData::ACTIONS as $action)
$changeActions[$action] = ChangelogData::actionText($action);
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$changeId = !empty($_GET['c']) && is_scalar($_GET['c']) ? (string)$_GET['c'] : '';
$changeInfo = null;
$changeTagIds = [];
$tagInfos = $msz->changelog->getTags();
@ -45,12 +45,12 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
// make errors not echos lol
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$action = trim((string)filter_input(INPUT_POST, 'cl_action'));
$summary = trim((string)filter_input(INPUT_POST, 'cl_summary'));
$body = trim((string)filter_input(INPUT_POST, 'cl_body'));
$userId = (int)filter_input(INPUT_POST, 'cl_user', FILTER_SANITIZE_NUMBER_INT);
$createdAt = trim((string)filter_input(INPUT_POST, 'cl_created'));
$tags = filter_input(INPUT_POST, 'cl_tags', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
$action = !empty($_POST['cl_action']) && is_scalar($_POST['cl_action']) ? trim((string)$_POST['cl_action']) : '';
$summary = !empty($_POST['cl_summary']) && is_scalar($_POST['cl_summary']) ? trim((string)$_POST['cl_summary']) : '';
$body = !empty($_POST['cl_body']) && is_scalar($_POST['cl_body']) ? trim((string)$_POST['cl_body']) : '';
$userId = !empty($_POST['cl_user']) && is_scalar($_POST['cl_user']) ? (int)$_POST['cl_user'] : 0;
$createdAt = !empty($_POST['cl_created']) && is_scalar($_POST['cl_created']) ? trim((string)$_POST['cl_created']) : '';
$tags = !empty($_POST['cl_tags']) && is_array($_POST['cl_tags']) ? $_POST['cl_tags'] : [];
if($userId < 1) $userId = null;
else $userId = (string)$userId;

View file

@ -9,7 +9,7 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE))
Template::throwError(403);
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
$tagId = !empty($_GET['t']) && is_scalar($_GET['t']) ? (string)$_GET['t'] : '';
$loadTagInfo = fn() => $msz->changelog->getTag($tagId);
if(empty($tagId))
@ -33,8 +33,8 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$name = trim((string)filter_input(INPUT_POST, 'ct_name'));
$description = trim((string)filter_input(INPUT_POST, 'ct_desc'));
$name = !empty($_POST['ct_name']) && is_scalar($_POST['ct_name']) ? trim((string)$_POST['ct_name']) : '';
$description = !empty($_POST['ct_desc']) && is_scalar($_POST['ct_desc']) ? trim((string)$_POST['ct_desc']) : '';
$archive = !empty($_POST['ct_archive']);
if($isNew) {

View file

@ -12,11 +12,8 @@ if(!$msz->authInfo->getPerms('global')->check(Perm::G_FORUM_CATEGORIES_MANAGE))
$permsInfos = $msz->perms->getPermissionInfo(categoryNames: Perm::INFO_FOR_FORUM_CATEGORY);
$permsLists = Perm::createList(Perm::LISTS_FOR_FORUM_CATEGORY);
if(filter_has_var(INPUT_POST, 'perms'))
Template::set('calculated_perms', Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_FORUM_CATEGORY
));
if(!empty($_POST))
Template::set('calculated_perms', Perm::convertSubmission($_POST, Perm::INFO_FOR_FORUM_CATEGORY));
Template::render('manage.forum.listing', [
'perms_lists' => $permsLists,

View file

@ -11,8 +11,8 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
$rTopicId = (string)filter_input(INPUT_POST, 'topic_redir_id');
$rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url'));
$rTopicId = !empty($_POST['topic_redir_id']) && is_scalar($_POST['topic_redir_id']) ? trim((string)$_POST['topic_redir_id']) : '';
$rTopicURL = !empty($_POST['topic_redir_url']) && is_scalar($_POST['topic_redir_url']) ? trim((string)$_POST['topic_redir_url']) : '';
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
$msz->forumCtx->topicRedirects->createTopicRedirect($rTopicId, $msz->authInfo->userInfo, $rTopicURL);
@ -20,11 +20,11 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
return;
}
if(filter_input(INPUT_GET, 'm') === 'explode') {
if(!empty($_GET['m']) && $_GET['m'] === 'explode') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
$rTopicId = (string)filter_input(INPUT_GET, 't');
$rTopicId = !empty($_GET['t']) && is_scalar($_GET['t']) ? (string)$_GET['t'] : '';
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
$msz->forumCtx->topicRedirects->deleteTopicRedirect($rTopicId);
Tools::redirect($msz->urls->format('manage-forum-topic-redirs'));

View file

@ -10,7 +10,7 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('global')->check(Perm::G_EMOTES_MANAGE))
Template::throwError(403);
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
$emoteId = !empty($_GET['e']) && is_scalar($_GET['e']) ? (string)$_GET['e'] : '';
$emoteInfo = [];
$emoteStrings = [];
@ -27,10 +27,10 @@ else
// make errors not echos lol
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$order = (int)filter_input(INPUT_POST, 'em_order', FILTER_SANITIZE_NUMBER_INT);
$minRank = (int)filter_input(INPUT_POST, 'em_minrank', FILTER_SANITIZE_NUMBER_INT);
$url = trim((string)filter_input(INPUT_POST, 'em_url'));
$strings = explode(' ', trim((string)filter_input(INPUT_POST, 'em_strings')));
$order = !empty($_POST['em_order']) && is_scalar($_POST['em_order']) ? (int)$_POST['em_order'] : '';
$minRank = !empty($_POST['em_minrank']) && is_scalar($_POST['em_minrank']) ? (int)$_POST['em_minrank'] : '';
$url = !empty($_POST['em_url']) && is_scalar($_POST['em_url']) ? trim((string)$_POST['em_url']) : '';
$strings = explode(' ', !empty($_POST['em_strings']) && is_scalar($_POST['em_strings']) ? trim((string)$_POST['em_strings']) : '');
if($isNew || $url !== $emoteInfo->url) {
$checkUrl = $msz->emotes->checkEmoteUrl($url);

View file

@ -10,7 +10,7 @@ if(!$msz->authInfo->getPerms('global')->check(Perm::G_EMOTES_MANAGE))
Template::throwError(403);
if(CSRF::validateRequest() && !empty($_GET['emote'])) {
$emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT);
$emoteId = !empty($_GET['emote']) && is_scalar($_GET['emote']) ? (string)$_GET['emote'] : '';
try {
$emoteInfo = $msz->emotes->getEmote($emoteId);
@ -23,14 +23,14 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
$msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->id]);
} else {
if(isset($_GET['order'])) {
$order = filter_input(INPUT_GET, 'order');
$order = !empty($_GET['order']) && is_scalar($_GET['order']) ? (string)$_GET['order'] : '';
$offset = $order === 'i' ? 10 : ($order === 'd' ? -10 : 0);
$msz->emotes->updateEmoteOrderOffset($emoteInfo, $offset);
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->id]);
}
if(isset($_GET['alias'])) {
$alias = (string)filter_input(INPUT_GET, 'alias');
$alias = !empty($_GET['alias']) && is_scalar($_GET['alias']) ? (string)$_GET['alias'] : '';
if($msz->emotes->checkEmoteString($alias) === '') {
$msz->emotes->addEmoteString($emoteInfo, $alias);
$msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->id, $alias]);

View file

@ -7,7 +7,7 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
Template::throwError(403);
$valueInfo = $msz->config->getValueInfo((string)filter_input(INPUT_GET, 'name'));
$valueInfo = $msz->config->getValueInfo(!empty($_GET['name']) && is_scalar($_GET['name']) ? (string)$_GET['name'] : '');
if($valueInfo === null)
Template::throwError(404);

View file

@ -10,8 +10,8 @@ if(!$msz->authInfo->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
Template::throwError(403);
$isNew = true;
$sName = (string)filter_input(INPUT_GET, 'name');
$sType = (string)filter_input(INPUT_GET, 'type');
$sName = !empty($_GET['name']) && is_scalar($_GET['name']) ? (string)$_GET['name'] : '';
$sType = !empty($_GET['type']) && is_scalar($_GET['type']) ? (string)$_GET['type'] : '';
$sValue = null;
$loadValueInfo = fn() => $msz->config->getValueInfo($sName);
@ -27,13 +27,13 @@ if(!empty($sName)) {
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if($isNew) {
$sName = trim((string)filter_input(INPUT_POST, 'conf_name'));
$sName = !empty($_POST['conf_name']) && is_scalar($_POST['conf_name']) ? trim((string)$_POST['conf_name']) : '';
if(!DbConfig::validateName($sName)) {
echo 'Name contains invalid characters.';
break;
}
$sType = trim((string)filter_input(INPUT_POST, 'conf_type'));
$sType = !empty($_POST['conf_type']) && is_scalar($_POST['conf_type']) ? trim((string)$_POST['conf_type']) : '';
if(!in_array($sType, ['string', 'int', 'float', 'bool', 'array'])) {
echo 'Invalid type specified.';
break;
@ -43,7 +43,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if($sType === 'array') {
$applyFunc = $msz->config->setArray(...);
$sValue = [];
$sRaw = filter_input(INPUT_POST, 'conf_value', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
$sRaw = !empty($_POST['conf_value']) && is_array($_POST['conf_value']) ? $_POST['conf_value'] : [];
foreach($sRaw as $rValue) {
if(strpos($rValue, ':') === 1) {
$rType = $rValue[0];
@ -63,7 +63,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$sValue = !empty($_POST['conf_value']);
$applyFunc = $msz->config->setBoolean(...);
} else {
$sValue = filter_input(INPUT_POST, 'conf_value');
$sValue = !empty($_POST['conf_value']) && is_scalar($_POST['conf_value']) ? trim((string)$_POST['conf_value']) : '';
if($sType === 'int') {
$applyFunc = $msz->config->setInteger(...);
$sValue = (int)$sValue;

View file

@ -9,7 +9,7 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE))
Template::throwError(403);
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$categoryId = !empty($_GET['c']) && is_scalar($_GET['c']) ? (string)$_GET['c'] : '';
$loadCategoryInfo = fn() => $msz->news->getCategory(categoryId: $categoryId);
if(empty($categoryId))
@ -33,8 +33,8 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$name = trim((string)filter_input(INPUT_POST, 'nc_name'));
$description = trim((string)filter_input(INPUT_POST, 'nc_desc'));
$name = !empty($_POST['nc_name']) && is_scalar($_POST['nc_name']) ? trim((string)$_POST['nc_name']) : '';
$description = !empty($_POST['nc_desc']) && is_scalar($_POST['nc_desc']) ? trim((string)$_POST['nc_desc']) : '';
$hidden = !empty($_POST['nc_hidden']);
if($isNew) {

View file

@ -10,7 +10,7 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
Template::throwError(403);
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
$postId = !empty($_GET['p']) && is_scalar($_GET['p']) ? (string)$_GET['p'] : '';
$loadPostInfo = fn() => $msz->news->getPost($postId);
if(empty($postId))
@ -34,10 +34,10 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$title = trim((string)filter_input(INPUT_POST, 'np_title'));
$category = (string)filter_input(INPUT_POST, 'np_category', FILTER_SANITIZE_NUMBER_INT);
$title = !empty($_POST['np_title']) && is_scalar($_POST['np_title']) ? trim((string)$_POST['np_title']) : '';
$category = !empty($_POST['np_category']) && is_scalar($_POST['np_category']) ? trim((string)$_POST['np_category']) : '';
$featured = !empty($_POST['np_featured']);
$body = trim((string)filter_input(INPUT_POST, 'np_body'));
$body = !empty($_POST['np_body']) && is_scalar($_POST['np_body']) ? trim((string)$_POST['np_body']) : '';
if($isNew) {
$postInfo = $msz->news->createPost($category, $title, $body, $featured, $msz->authInfo->userInfo);

View file

@ -11,12 +11,12 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
Template::throwError(403);
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest())
Template::throwError(403);
try {
$banInfo = $msz->usersCtx->bans->getBan((string)filter_input(INPUT_GET, 'b'));
$banInfo = $msz->usersCtx->bans->getBan(!empty($_GET['b']) && is_scalar($_GET['b']) ? (string)$_GET['b'] : '');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
@ -28,7 +28,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete'))
}
try {
$userInfo = $msz->usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
$userInfo = $msz->usersCtx->getUserInfo(!empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '', 'id');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
@ -36,11 +36,11 @@ try {
$modInfo = $msz->authInfo->userInfo;
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT);
$expiresCustom = (string)filter_input(INPUT_POST, 'ub_expires_custom');
$publicReason = trim((string)filter_input(INPUT_POST, 'ub_reason_pub'));
$privateReason = trim((string)filter_input(INPUT_POST, 'ub_reason_priv'));
$severity = (int)filter_input(INPUT_POST, 'ub_severity', FILTER_SANITIZE_NUMBER_INT);
$expires = !empty($_POST['ub_expires']) && is_scalar($_POST['ub_expires']) ? (int)$_POST['ub_expires'] : 0;
$expiresCustom = !empty($_POST['ub_expires_custom']) && is_scalar($_POST['ub_expires_custom']) ? trim((string)$_POST['ub_expires_custom']) : '';
$publicReason = !empty($_POST['ub_reason_pub']) && is_scalar($_POST['ub_reason_pub']) ? trim((string)$_POST['ub_reason_pub']) : '';
$privateReason = !empty($_POST['ub_reason_priv']) && is_scalar($_POST['ub_reason_priv']) ? trim((string)$_POST['ub_reason_priv']) : '';
$severity = !empty($_POST['ub_severity']) && is_scalar($_POST['ub_severity']) ? (int)$_POST['ub_severity'] : 0;
Template::set([
'ban_value_expires' => $expires,

View file

@ -10,8 +10,8 @@ if(!$msz->authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
Template::throwError(403);
$filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
if(!empty($_GET['u'])) {
$filterUserId = !empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '';
try {
$filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) {
@ -20,7 +20,7 @@ if(filter_has_var(INPUT_GET, 'u')) {
}
$pagination = Pagination::fromInput($msz->usersCtx->bans->countBans(userInfo: $filterUser), 10);
if(!$pagination->validOffset)
if(!$pagination->validOffset && $pagination->count > 0)
Template::throwError(404);
$banList = [];

View file

@ -9,8 +9,8 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
Template::throwError(403);
$hasNoteId = filter_has_var(INPUT_GET, 'n');
$hasUserId = filter_has_var(INPUT_GET, 'u');
$hasNoteId = !empty($_GET['n']);
$hasUserId = !empty($_GET['u']);
if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId))
Template::throwError(400);
@ -19,7 +19,7 @@ if($hasUserId) {
$isNew = true;
try {
$userInfo = $msz->usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT));
$userInfo = $msz->usersCtx->getUserInfo(!empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
@ -29,12 +29,12 @@ if($hasUserId) {
$isNew = false;
try {
$noteInfo = $msz->usersCtx->modNotes->getNote((string)filter_input(INPUT_GET, 'n', FILTER_SANITIZE_NUMBER_INT));
$noteInfo = $msz->usersCtx->modNotes->getNote(!empty($_GET['n']) && is_scalar($_GET['n']) ? (string)$_GET['n'] : '');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest())
Template::throwError(403);
@ -49,8 +49,8 @@ if($hasUserId) {
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$title = trim((string)filter_input(INPUT_POST, 'mn_title'));
$body = trim((string)filter_input(INPUT_POST, 'mn_body'));
$title = trim((string)($_POST['mn_title'] ?? ''));
$body = trim((string)($_POST['mn_body'] ?? ''));
if($isNew) {
$noteInfo = $msz->usersCtx->modNotes->createNote($userInfo, $title, $body, $authorInfo);

View file

@ -10,8 +10,8 @@ if(!$msz->authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
Template::throwError(403);
$filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
if(!empty($_GET['u'])) {
$filterUserId = !empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '';
try {
$filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) {
@ -20,7 +20,7 @@ if(filter_has_var(INPUT_GET, 'u')) {
}
$pagination = Pagination::fromInput($msz->usersCtx->modNotes->countNotes(userInfo: $filterUser), 10);
if(!$pagination->validOffset)
if(!$pagination->validOffset && $pagination->count > 0)
Template::throwError(404);
$notes = [];

View file

@ -15,8 +15,8 @@ if(!$viewerPerms->check(Perm::U_ROLES_MANAGE))
$roleInfo = null;
if(filter_has_var(INPUT_GET, 'r')) {
$roleId = (string)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
if(!empty($_GET['r'])) {
$roleId = !empty($_GET['r']) && is_scalar($_GET['r']) ? (string)$_GET['r'] : '';
try {
$isNew = false;
@ -40,17 +40,17 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
break;
}
$roleString = (string)filter_input(INPUT_POST, 'ur_string');
$roleName = (string)filter_input(INPUT_POST, 'ur_name');
$roleString = !empty($_POST['ur_string']) && is_scalar($_POST['ur_string']) ? trim((string)$_POST['ur_string']) : '';
$roleName = !empty($_POST['ur_name']) && is_scalar($_POST['ur_name']) ? trim((string)$_POST['ur_name']) : '';
$roleHide = !empty($_POST['ur_hidden']);
$roleLeavable = !empty($_POST['ur_leavable']);
$roleRank = (int)filter_input(INPUT_POST, 'ur_rank', FILTER_SANITIZE_NUMBER_INT);
$roleTitle = (string)filter_input(INPUT_POST, 'ur_title');
$roleDesc = (string)filter_input(INPUT_POST, 'ur_desc');
$roleRank = !empty($_POST['ur_rank']) && is_scalar($_POST['ur_rank']) ? (int)$_POST['ur_rank'] : 0;
$roleTitle = !empty($_POST['ur_title']) && is_scalar($_POST['ur_title']) ? trim((string)$_POST['ur_title']) : '';
$roleDesc = !empty($_POST['ur_desc']) && is_scalar($_POST['ur_desc']) ? trim((string)$_POST['ur_desc']) : '';
$colourInherit = !empty($_POST['ur_col_inherit']);
$colourRed = (int)filter_input(INPUT_POST, 'ur_col_red', FILTER_SANITIZE_NUMBER_INT);
$colourGreen = (int)filter_input(INPUT_POST, 'ur_col_green', FILTER_SANITIZE_NUMBER_INT);
$colourBlue = (int)filter_input(INPUT_POST, 'ur_col_blue', FILTER_SANITIZE_NUMBER_INT);
$colourRed = !empty($_POST['ur_col_red']) && is_scalar($_POST['ur_col_red']) ? (int)$_POST['ur_col_red'] : 0;
$colourGreen = !empty($_POST['ur_col_green']) && is_scalar($_POST['ur_col_green']) ? (int)$_POST['ur_col_green'] : 0;
$colourBlue = !empty($_POST['ur_col_blue']) && is_scalar($_POST['ur_col_blue']) ? (int)$_POST['ur_col_blue'] : 0;
Template::set([
'role_ur_string' => $roleString,
@ -153,12 +153,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
[$roleInfo->id]
);
if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) {
$permsApply = Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_ROLE
);
if($canEditPerms) {
$permsApply = Perm::convertSubmission($_POST, Perm::INFO_FOR_ROLE);
foreach($permsApply as $categoryName => $values)
$msz->perms->setPermissions($categoryName, $values['allow'], $values['deny'], roleInfo: $roleInfo);

View file

@ -29,7 +29,7 @@ if(!$hasAccess)
Template::throwError(403);
$notices = [];
$userId = (string)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
$userId = !empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '';
try {
$userInfo = $msz->usersCtx->users->getUser($userId, 'id');
@ -201,12 +201,8 @@ if(CSRF::validateRequest() && $canEdit) {
}
}
if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) {
$permsApply = Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_USER
);
if($canEditPerms) {
$permsApply = Perm::convertSubmission($_POST, Perm::INFO_FOR_USER);
foreach($permsApply as $categoryName => $values)
$msz->perms->setPermissions($categoryName, $values['allow'], $values['deny'], userInfo: $userInfo);

View file

@ -9,12 +9,12 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
if(!$msz->authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
Template::throwError(403);
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest())
Template::throwError(403);
try {
$warnInfo = $msz->usersCtx->warnings->getWarning((string)filter_input(INPUT_GET, 'w'));
$warnInfo = $msz->usersCtx->warnings->getWarning(!empty($_GET['w']) && is_scalar($_GET['w']) ? (string)$_GET['w'] : '');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
@ -26,7 +26,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete'))
}
try {
$userInfo = $msz->usersCtx->users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
$userInfo = $msz->usersCtx->users->getUser(!empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '', 'id');
} catch(RuntimeException $ex) {
Template::throwError(404);
}
@ -34,7 +34,7 @@ try {
$modInfo = $msz->authInfo->userInfo;
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$body = trim((string)filter_input(INPUT_POST, 'uw_body'));
$body = trim((string)($_POST['uw_body'] ?? ''));
Template::set('warn_value_body', $body);
$warnInfo = $msz->usersCtx->warnings->createWarning(

View file

@ -10,8 +10,8 @@ if(!$msz->authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
Template::throwError(403);
$filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
if(!empty($_GET['u'])) {
$filterUserId = !empty($_GET['u']) && is_scalar($_GET['u']) ? (string)$_GET['u'] : '';
try {
$filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) {
@ -20,7 +20,7 @@ if(filter_has_var(INPUT_GET, 'u')) {
}
$pagination = Pagination::fromInput($msz->usersCtx->warnings->countWarnings(userInfo: $filterUser), 10);
if(!$pagination->validOffset)
if(!$pagination->validOffset && $pagination->count > 0)
Template::throwError(404);
$warnList = [];

View file

@ -11,9 +11,9 @@ if(!$msz->authInfo->loggedIn)
// TODO: restore forum-topics and forum-posts orderings
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
$orderDir = strtolower((string)filter_input(INPUT_GET, 'sd'));
$roleId = !empty($_GET['r']) && is_string($_GET['r']) ? $_GET['r'] : null;
$orderBy = strtolower(!empty($_GET['ss']) && is_string($_GET['ss']) ? $_GET['ss'] : '');
$orderDir = strtolower(!empty($_GET['sd']) && is_string($_GET['sd']) ? $_GET['sd'] : '');
$orderDirs = [
'asc' => 'In Order',

View file

@ -5,6 +5,8 @@ use stdClass;
use InvalidArgumentException;
use RuntimeException;
use Index\ByteFormat;
use Index\Http\Content\MultipartFormContent;
use Index\Http\Content\Multipart\FileMultipartFormData;
use Misuzu\Forum\ForumSignaturesData;
use Misuzu\Parsers\TextFormat;
use Misuzu\Profile\{ProfileAboutData,ProfileBackgroundAttach};
@ -111,52 +113,49 @@ if($isEditing) {
if(!CSRF::validateRequest()) {
$notices[] = "Couldn't verify you, please refresh the page and retry.";
} else {
$profileFieldsSubmit = filter_input(INPUT_POST, 'profile', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if(!$perms->edit_profile) {
$notices[] = "You're not allowed to edit your profile.";
} else {
$profileFieldInfos = iterator_to_array($msz->profileCtx->fields->getFields());
$profileFieldsSetInfos = [];
$profileFieldsSetValues = [];
$profileFieldsRemove = [];
if(!empty($profileFieldsSubmit)) {
if(!$perms->edit_profile) {
$notices[] = "You're not allowed to edit your profile.";
} else {
$profileFieldInfos = iterator_to_array($msz->profileCtx->fields->getFields());
$profileFieldsSetInfos = [];
$profileFieldsSetValues = [];
$profileFieldsRemove = [];
foreach($profileFieldInfos as $fieldInfo) {
$fieldName = sprintf('profile_%s', $fieldInfo->name);
$fieldValue = empty($_POST[$fieldName]) || !is_scalar($_POST[$fieldName])
? '' : (string)filter_var($_POST[$fieldName]);
foreach($profileFieldInfos as $fieldInfo) {
$fieldName = $fieldInfo->name;
$fieldValue = empty($profileFieldsSubmit[$fieldName]) ? '' : (string)filter_var($profileFieldsSubmit[$fieldName]);
if(empty($profileFieldsSubmit[$fieldName])) {
$profileFieldsRemove[] = $fieldInfo;
continue;
}
if($fieldInfo->checkValue($fieldValue)) {
$profileFieldsSetInfos[] = $fieldInfo;
$profileFieldsSetValues[] = $fieldValue;
} else
$notices[] = sprintf("%s isn't properly formatted.", $fieldInfo->title);
unset($fieldName, $fieldValue, $fieldInfo);
if(empty($_POST[$fieldName])) {
$profileFieldsRemove[] = $fieldInfo;
continue;
}
if(!empty($profileFieldsRemove))
$msz->profileCtx->fields->removeFieldValues($userInfo, $profileFieldsRemove);
if(!empty($profileFieldsSetInfos))
$msz->profileCtx->fields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues);
if($fieldInfo->checkValue($fieldValue)) {
$profileFieldsSetInfos[] = $fieldInfo;
$profileFieldsSetValues[] = $fieldValue;
} else
$notices[] = sprintf("%s isn't properly formatted.", $fieldInfo->title);
unset($fieldName, $fieldValue, $fieldInfo);
}
if(!empty($profileFieldsRemove))
$msz->profileCtx->fields->removeFieldValues($userInfo, $profileFieldsRemove);
if(!empty($profileFieldsSetInfos))
$msz->profileCtx->fields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues);
}
if(filter_has_var(INPUT_POST, 'about_body')) {
if(isset($_POST['about_body']) && is_scalar($_POST['about_body'])) {
if(!$perms->edit_about) {
$notices[] = "You're not allowed to edit your about page.";
} else {
$aboutBody = (string)filter_input(INPUT_POST, 'about_body');
$aboutBody = (string)$_POST['about_body'];
if(trim($aboutBody) === '') {
$msz->profileCtx->about->deleteProfileAbout($userInfo);
$aboutInfo = null;
} else {
$aboutFormat = TextFormat::tryFrom(filter_input(INPUT_POST, 'about_format'));
$aboutFormat = TextFormat::tryFrom(isset($_POST['about_format']) && is_scalar($_POST['about_format']) ? (string)$_POST['about_format'] : '');
$aboutValid = ProfileAboutData::validateProfileAbout($aboutFormat, $aboutBody);
if($aboutValid === '')
$aboutInfo = $msz->profileCtx->about->updateProfileAbout($userInfo, $aboutBody, $aboutFormat);
@ -166,16 +165,16 @@ if($isEditing) {
}
}
if(filter_has_var(INPUT_POST, 'sig_body')) {
if(isset($_POST['sig_body']) && is_scalar($_POST['sig_body'])) {
if(!$perms->edit_signature) {
$notices[] = "You're not allowed to edit your forum signature.";
} else {
$sigBody = (string)filter_input(INPUT_POST, 'sig_body');
$sigBody = (string)$_POST['sig_body'];
if(trim($sigBody) === '') {
$msz->forumCtx->signatures->deleteSignature($userInfo);
$sigInfo = null;
} else {
$sigFormat = TextFormat::tryFrom(filter_input(INPUT_POST, 'sig_format'));
$sigFormat = TextFormat::tryFrom(isset($_POST['sig_format']) && is_scalar($_POST['sig_format']) ? (string)$_POST['sig_format'] : '');
$sigValid = ForumSignaturesData::validateSignature($sigFormat, $sigBody);
if($sigValid === '')
$sigInfo = $msz->forumCtx->signatures->updateSignature($userInfo, $sigBody, $sigFormat);
@ -185,13 +184,13 @@ if($isEditing) {
}
}
if(!empty($_POST['birthdate']) && is_array($_POST['birthdate'])) {
if(!empty($_POST['birth_day']) && !empty($_POST['birth_month'])) {
if(!$perms->edit_birthdate) {
$notices[] = "You aren't allow to change your birthdate.";
} else {
$birthYear = (int)($_POST['birthdate']['year'] ?? 0);
$birthMonth = (int)($_POST['birthdate']['month'] ?? 0);
$birthDay = (int)($_POST['birthdate']['day'] ?? 0);
$birthYear = (int)($_POST['birth_year'] ?? 0);
$birthMonth = (int)$_POST['birth_month'];
$birthDay = (int)$_POST['birth_day'];
$birthValid = UserBirthdatesData::validateBirthdate($birthYear, $birthMonth, $birthDay);
if($birthValid === '') {
@ -204,53 +203,39 @@ if($isEditing) {
}
}
if(!empty($_FILES['avatar'])) {
if(!empty($_POST['avatar']['delete'])) {
$avatarAsset->delete();
} else {
if(!empty($_POST['avatar_delete'])) {
$avatarAsset->delete();
} elseif(isset($mszRequestContent) && $mszRequestContent instanceof MultipartFormContent) {
$avatarInfo = $mszRequestContent->getParamData('avatar_file');
if($avatarInfo instanceof FileMultipartFormData) {
if(!$perms->edit_avatar) {
$notices[] = "You aren't allow to change your avatar.";
} elseif(!empty($_FILES['avatar'])
&& is_array($_FILES['avatar'])
&& !empty($_FILES['avatar']['name']['file'])) {
if($_FILES['avatar']['error']['file'] !== UPLOAD_ERR_OK) {
switch($_FILES['avatar']['error']['file']) {
case UPLOAD_ERR_NO_FILE:
$notices[] = 'Select a file before hitting upload!';
break;
case UPLOAD_ERR_PARTIAL:
$notices[] = 'The upload was interrupted, please try again!';
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$notices[] = sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarAsset->getMaxBytes()));
break;
default:
$notices[] = 'Unable to save your avatar, contact an administator!';
break;
}
} else {
try {
$avatarAsset->setFromPath($_FILES['avatar']['tmp_name']['file']);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf("Your avatar can't be larger than %dx%d!", $avatarAsset->getMaxWidth(), $avatarAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
$notices[] = 'Unable to save your avatar, contact an administator!';
}
} elseif($avatarInfo->getSize() > 0) {
$avatarTemp = tempnam(sys_get_temp_dir(), 'msz-legacy-avatar-');
try {
$avatarInfo->moveTo($avatarTemp);
$avatarAsset->setFromPath($avatarTemp);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf("Your avatar can't be larger than %dx%d!", $avatarAsset->getMaxWidth(), $avatarAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
$notices[] = 'Unable to save your avatar, contact an administator!';
} finally {
if(is_file($avatarTemp))
unlink($avatarTemp);
}
}
}
}
if(filter_has_var(INPUT_POST, 'bg_attach')) {
$bgFormat = ProfileBackgroundAttach::tryFrom((string)filter_input(INPUT_POST, 'bg_attach'));
if(isset($_POST['bg_attach']) && is_scalar($_POST['bg_attach'])) {
$bgFormat = ProfileBackgroundAttach::tryFrom((string)$_POST['bg_attach']);
if($bgFormat === null) {
$backgroundAsset->delete();
@ -259,47 +244,35 @@ if($isEditing) {
} else {
if(!$perms->edit_background) {
$notices[] = "You aren't allow to change your background.";
} elseif(!empty($_FILES['bg_file']) && is_array($_FILES['bg_file'])) {
if(!empty($_FILES['bg_file']['name'])) {
if($_FILES['bg_file']['error'] !== UPLOAD_ERR_OK) {
switch($_FILES['bg_file']['error']) {
case UPLOAD_ERR_NO_FILE:
$notices[] = 'Select a file before hitting upload!';
break;
case UPLOAD_ERR_PARTIAL:
$notices[] = 'The upload was interrupted, please try again!';
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$notices[] = sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format(isset($backgroundProps) && is_array($backgroundProps) ? $backgroundProps['max_size'] : 0));
break;
default:
$notices[] = 'Unable to save your background, contact an administator!';
break;
}
} else {
try {
$backgroundAsset->setFromPath($_FILES['bg_file']['tmp_name']);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf("Your background can't be larger than %dx%d!", $backgroundAsset->getMaxWidth(), $backgroundAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format($backgroundAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
$notices[] = 'Unable to save your background, contact an administator!';
}
} elseif(isset($mszRequestContent) && $mszRequestContent instanceof MultipartFormContent) {
$bgInfo = $mszRequestContent->getParamData('bg_file');
if($bgInfo instanceof FileMultipartFormData && $bgInfo->getSize() > 0) {
$bgTemp = tempnam(sys_get_temp_dir(), 'msz-legacy-profile-background-');
try {
$bgInfo->moveTo($bgTemp);
$backgroundAsset->setFromPath($bgTemp);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf("Your background can't be larger than %dx%d!", $backgroundAsset->getMaxWidth(), $backgroundAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format($backgroundAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
$notices[] = 'Unable to save your background, contact an administator!';
} finally {
if(is_file($bgTemp))
unlink($bgTemp);
}
}
$backgroundInfo = $msz->profileCtx->backgrounds->updateProfileBackground(
$userInfo,
$bgFormat,
filter_has_var(INPUT_POST, 'bg_blend'),
filter_has_var(INPUT_POST, 'bg_slide')
!empty($_POST['bg_blend']),
!empty($_POST['bg_slide'])
);
}
}

View file

@ -14,7 +14,7 @@ $currentUser = $msz->authInfo->userInfo;
$activeSessionId = $msz->authInfo->sessionId;
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$sessionId = (string)filter_input(INPUT_POST, 'session');
$sessionId = !empty($_POST['session']) && is_scalar($_POST['session']) ? trim((string)$_POST['session']) : '';
$activeSessionKilled = false;
if($sessionId === 'all') {

View file

@ -2,6 +2,9 @@
namespace Misuzu;
use RuntimeException;
use Index\MediaType;
use Index\Http\Content\MultipartFormContent;
use Index\Http\Content\Multipart\ValueMultipartFormData;
use Misuzu\Auth\{AuthTokenBuilder,AuthTokenCookie,AuthTokenInfo};
require_once __DIR__ . '/../misuzu.php';
@ -25,12 +28,12 @@ $request = \Index\Http\HttpRequest::fromRequest();
$tokenPacker = $msz->authCtx->createAuthTokenPacker();
if(filter_has_var(INPUT_COOKIE, 'msz_auth'))
$tokenInfo = $tokenPacker->unpack(filter_input(INPUT_COOKIE, 'msz_auth'));
elseif(filter_has_var(INPUT_COOKIE, 'msz_uid') && filter_has_var(INPUT_COOKIE, 'msz_sid')) {
if(!empty($_COOKIE['msz_auth']) && is_string($_COOKIE['msz_auth']))
$tokenInfo = $tokenPacker->unpack($_COOKIE['msz_auth']);
elseif(!empty($_COOKIE['msz_uid']) && !empty($_COOKIE['msz_sid']) && is_string($_COOKIE['msz_uid']) && is_string($_COOKIE['msz_sid'])) {
$tokenBuilder = new AuthTokenBuilder;
$tokenBuilder->setUserId((string)filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT));
$tokenBuilder->setSessionToken((string)filter_input(INPUT_COOKIE, 'msz_sid'));
$tokenBuilder->setUserId($_COOKIE['msz_uid']);
$tokenBuilder->setSessionToken($_COOKIE['msz_sid']);
$tokenInfo = $tokenBuilder->toInfo();
$tokenBuilder = null;
} else
@ -39,7 +42,7 @@ elseif(filter_has_var(INPUT_COOKIE, 'msz_uid') && filter_has_var(INPUT_COOKIE, '
$userInfo = null;
$sessionInfo = null;
$userInfoReal = null;
$remoteAddr = (string)filter_input(INPUT_SERVER, 'REMOTE_ADDR');
$remoteAddr = $_SERVER['REMOTE_ADDR'];
if($tokenInfo->hasUserId && $tokenInfo->hasSessionToken) {
$tokenBuilder = new AuthTokenBuilder($tokenInfo);
@ -112,7 +115,52 @@ $router = $msz->createRouting($request);
$msz->startTemplating();
if($msz->domainRoles->hasRole($request->getHeaderLine('Host'), 'main')) {
$mszRequestPath = substr($request->path, 1);
// Reconstruct $_POST since PHP no longer makes it for us
if($request->getBody()->isReadable() && empty($_POST)) {
$mszRequestContent = (function($contentType, $stream) {
if($contentType->equals('application/x-www-form-urlencoded')) {
parse_str((string)$stream, $postVars);
return $postVars;
}
if($contentType->equals('multipart/form-data'))
try {
return MultipartFormContent::parseStream($stream, $contentType->boundary);
} catch(RuntimeException $ex) {}
return null;
})(MediaType::parse($request->getHeaderLine('Content-Type')), $request->getBody());
$_POST = (function($requestContent) {
if(is_array($requestContent))
return $requestContent;
if($requestContent instanceof MultipartFormContent) {
$postVars = [];
foreach($requestContent->params as $name => $values) {
if(count($values) === 0)
$postVars[$name] = '';
elseif(count($values) === 1)
$postVars[$name] = $values[0] instanceof ValueMultipartFormData ? (string)$values[0] : '';
else {
$postVar = [];
foreach($values as $value)
$postVars[] = $value instanceof ValueMultipartFormData ? (string)$value : '';
$postVars[$name] = $postVar;
}
}
return $postVars;
}
return [];
})($mszRequestContent);
$_REQUEST = array_merge($_GET, $_POST);
}
$mszRequestPath = substr($request->requestTarget, 1);
$mszLegacyPathPrefix = Misuzu::PATH_PUBLIC_LEGACY . '/';
$mszLegacyPath = $mszLegacyPathPrefix . $mszRequestPath;

View file

@ -26,7 +26,7 @@ final class AuthTokenCookie {
$threeMonths->format('D, d M Y H:i:s e'),
$threeMonths->getTimestamp() - $now->getTimestamp(),
self::domain(),
filter_has_var(INPUT_SERVER, 'HTTPS') ? ' Secure' : ''
!empty($_SERVER['HTTPS']) ? ' Secure' : ''
));
}
@ -34,7 +34,7 @@ final class AuthTokenCookie {
header(sprintf(
'Set-Cookie: msz_auth=; Expires=Wed, 31 Dec 1969 21:29:59 UTC; Max-Age=-9001; Domain=%s; Path=/; SameSite=Lax; HttpOnly;%s',
self::domain(),
filter_has_var(INPUT_SERVER, 'HTTPS') ? ' Secure' : ''
!empty($_SERVER['HTTPS']) ? ' Secure' : ''
));
}
}

View file

@ -36,9 +36,9 @@ final class CSRF {
if(self::$instance === null)
return false;
$token = (string)filter_input(INPUT_POST, '_csrf');
$token = isset($_POST['_csrf']) && is_string($_POST['_csrf']) ? $_POST['_csrf'] : '';
if(empty($token))
$token = (string)filter_input(INPUT_GET, 'csrf');
$token = isset($_GET['csrf']) && is_string($_GET['csrf']) ? $_GET['csrf'] : '';
return self::$instance->verifyToken($token, $tolerance);
}

View file

@ -4,7 +4,8 @@ namespace Misuzu\Changelog;
use ErrorException;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Syndication\FeedBuilder;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{Pagination,SiteInfo,Template};
@ -22,11 +23,11 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
private CommentsContext $commentsCtx,
) {}
#[HttpGet('/changelog')]
#[ExactRoute('GET', '/changelog')]
#[UrlFormat('changelog-index', '/changelog', ['date' => '<date>', 'user' => '<user>', 'tags' => '<tags>', 'p' => '<page>'])]
public function getIndex(HttpResponseBuilder $response, HttpRequest $request): int|string {
$filterDate = (string)$request->getParam('date');
$filterUser = (string)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT);
$filterUser = (string)$request->getFilteredParam('user', FILTER_SANITIZE_NUMBER_INT);
$filterTags = (string)$request->getParam('tags');
if(empty($filterDate))
@ -96,7 +97,7 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/changelog/change/([0-9]+)')]
#[PatternRoute('GET', '/changelog/change/([0-9]+)')]
#[UrlFormat('changelog-change', '/changelog/change/<change>')]
#[UrlFormat('changelog-change-comments', '/changelog/change/<change>', fragment: 'comments')]
public function getChange(HttpResponseBuilder $response, HttpRequest $request, string $changeId): int|string {
@ -120,7 +121,7 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/changelog.(xml|rss|atom)')]
#[PatternRoute('GET', '/changelog.(xml|rss|atom)')]
#[UrlFormat('changelog-feed', '/changelog.xml')]
public function getFeed(HttpResponseBuilder $response): string {
$response->setContentType('application/rss+xml; charset=utf-8');

View file

@ -4,7 +4,11 @@ namespace Misuzu\Comments;
use RuntimeException;
use Index\XArray;
use Index\Http\{FormHttpContent,HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpDelete,HttpGet,HttpMiddleware,HttpPatch,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Filters\PrefixFilter;
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Perm};
use Misuzu\Auth\AuthInfo;
@ -186,7 +190,7 @@ class CommentsRoutes implements RouteHandler, UrlSource {
}
/** @return void|array{error: array{name: string, text: string}} */
#[HttpMiddleware('/comments')]
#[PrefixFilter('/comments')]
public function checkCsrf(HttpResponseBuilder $response, HttpRequest $request) {
if(in_array($request->method, ['DELETE', 'PATCH', 'POST'])) {
if(!$this->authInfo->loggedIn)
@ -228,8 +232,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
* posts: mixed[]
* }|array{error: array{name: string, text: string}}
*/
#[HttpGet('/comments/categories/([A-Za-z0-9-]+)')]
public function getCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
#[PatternRoute('GET', '/comments/categories/([A-Za-z0-9-]+)')]
public function getCategory(HttpResponseBuilder $response, string $categoryName): array {
try {
$catInfo = $this->commentsCtx->categories->getCategory(name: $categoryName);
} catch(RuntimeException $ex) {
@ -290,11 +294,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
* locked?: string|false,
* }|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/categories/([A-Za-z0-9-]+)')]
public function patchCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
if(!($request->content instanceof FormHttpContent))
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
#[PatternRoute('POST', '/comments/categories/([A-Za-z0-9-]+)')]
#[Before('input:urlencoded')]
public function patchCategory(HttpResponseBuilder $response, FormContent $content, string $categoryName): array {
try {
$catInfo = $this->commentsCtx->categories->getCategory(name: $categoryName);
} catch(RuntimeException $ex) {
@ -304,11 +306,11 @@ class CommentsRoutes implements RouteHandler, UrlSource {
$perms = $this->getGlobalPerms();
$locked = null;
if($request->content->hasParam('lock')) {
if($content->hasParam('lock')) {
if(!$perms->check(Perm::G_COMMENTS_LOCK))
return self::error($response, 403, 'comments:lock-not-allowed', 'You are not allowed to lock this comment section.');
$locked = !empty($request->content->getParam('lock'));
$locked = !empty($content->getParam('lock'));
}
$this->commentsCtx->categories->updateCategory(
@ -332,34 +334,32 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return mixed[]|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/posts')]
public function postPost(HttpResponseBuilder $response, HttpRequest $request): array {
if(!($request->content instanceof FormHttpContent))
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
#[ExactRoute('POST', '/comments/posts')]
#[Before('input:multipart')]
public function postPost(HttpResponseBuilder $response, FormContent $content): array {
$perms = $this->getGlobalPerms();
if(!$perms->check(Perm::G_COMMENTS_CREATE))
return self::error($response, 403, 'comments:create-not-allowed', 'You are not allowed to post comments.');
if(!$request->content->hasParam('category') || !$request->content->hasParam('body'))
if(!$content->hasParam('category') || !$content->hasParam('body'))
return self::error($response, 400, 'comments:missing-fields', 'Required fields are not specified.');
$pinned = false;
$body = preg_replace("/[\r\n]{2,}/", "\n", (string)$request->content->getParam('body'));
$body = preg_replace("/[\r\n]{2,}/", "\n", (string)$content->getParam('body'));
if(mb_strlen(mb_trim($body)) < 1)
return self::error($response, 400, 'comments:body-too-short', 'Your comment must be longer.');
if(mb_strlen($body) > 5000)
return self::error($response, 400, 'comments:body-too-long', 'Your comment is too long.');
try {
$catInfo = $this->commentsCtx->categories->getCategory(name: (string)$request->content->getParam('category'));
$catInfo = $this->commentsCtx->categories->getCategory(name: (string)$content->getParam('category'));
} catch(RuntimeException $ex) {
return self::error($response, 404, 'comments:category-not-found', 'No comment section with that name exists.');
}
if($request->content->hasParam('reply_to')) {
if($content->hasParam('reply_to')) {
try {
$replyToInfo = $this->commentsCtx->posts->getPost((string)$request->content->getParam('reply_to'));
$replyToInfo = $this->commentsCtx->posts->getPost((string)$content->getParam('reply_to'));
if($replyToInfo->deleted)
return self::error($response, 404, 'comments:parent-not-found', 'The comment you are trying to reply to does not exist.');
} catch(RuntimeException $ex) {
@ -368,13 +368,13 @@ class CommentsRoutes implements RouteHandler, UrlSource {
} else
$replyToInfo = null;
if($request->content->hasParam('pin')) {
if($content->hasParam('pin')) {
if(!$perms->check(Perm::G_COMMENTS_PIN) && $catInfo->ownerId !== $this->authInfo->userId)
return self::error($response, 403, 'comments:pin-not-allowed', 'You are not allowed to pin comments.');
if($replyToInfo !== null)
return self::error($response, 400, 'comments:post-not-root', 'Replies cannot be pinned.');
$pinned = !empty($request->content->getParam('pin'));
$pinned = !empty($content->getParam('pin'));
}
try {
@ -396,8 +396,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return mixed[]|array{error: array{name: string, text: string}}
*/
#[HttpGet('/comments/posts/([0-9]+)')]
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
#[PatternRoute('GET', '/comments/posts/([0-9]+)')]
public function getPost(HttpResponseBuilder $response, string $commentId): array {
try {
$postInfo = $this->commentsCtx->posts->getPost($commentId);
$catInfo = $this->commentsCtx->categories->getCategory(postInfo: $postInfo);
@ -421,8 +421,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return mixed[]|array{error: array{name: string, text: string}}
*/
#[HttpGet('/comments/posts/([0-9]+)/replies')]
public function getPostReplies(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
#[PatternRoute('GET', '/comments/posts/([0-9]+)/replies')]
public function getPostReplies(HttpResponseBuilder $response, string $commentId): array {
try {
$postInfo = $this->commentsCtx->posts->getPost($commentId);
$catInfo = $this->commentsCtx->categories->getCategory(postInfo: $postInfo);
@ -445,13 +445,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
* edited?: string,
* }|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/posts/([0-9]+)')]
// this should be HttpPatch but PHP doesn't parse into $_POST for PATCH...
// fix this in the v3 router for index by just ignoring PHP's parsing altogether
public function patchPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
if(!($request->content instanceof FormHttpContent))
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
#[PatternRoute('PATCH', '/comments/posts/([0-9]+)')]
#[Before('input:multipart')]
public function patchPost(HttpResponseBuilder $response, FormContent $content, string $commentId): array {
try {
$postInfo = $this->commentsCtx->posts->getPost($commentId);
$catInfo = $this->commentsCtx->categories->getCategory(postInfo: $postInfo);
@ -467,20 +463,20 @@ class CommentsRoutes implements RouteHandler, UrlSource {
$pinned = null;
$edited = false;
if($request->content->hasParam('pin')) {
if($content->hasParam('pin')) {
if(!$perms->check(Perm::G_COMMENTS_PIN) && $catInfo->ownerId !== $this->authInfo->userId)
return self::error($response, 403, 'comments:pin-not-allowed', 'You are not allowed to pin comments.');
if($postInfo->reply)
return self::error($response, 400, 'comments:post-not-root', 'Replies cannot be pinned.');
$pinned = !empty($request->content->getParam('pin'));
$pinned = !empty($content->getParam('pin'));
}
if($request->content->hasParam('body')) {
if($content->hasParam('body')) {
if(!$perms->check(Perm::G_COMMENTS_EDIT_ANY) && !($perms->check(Perm::G_COMMENTS_EDIT_OWN) && $this->authInfo->userId === $postInfo->userId))
return self::error($response, 403, 'comments:edit-not-allowed', 'You are not allowed to edit comments.');
$body = preg_replace("/[\r\n]{2,}/", "\n", (string)$request->content->getParam('body'));
$body = preg_replace("/[\r\n]{2,}/", "\n", (string)$content->getParam('body'));
if(mb_strlen(mb_trim($body)) < 1)
return self::error($response, 400, 'comments:body-too-short', 'Your comment must be longer.');
if(mb_strlen($body) > 5000)
@ -518,8 +514,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return string|array{error: array{name: string, text: string}}
*/
#[HttpDelete('/comments/posts/([0-9]+)')]
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array|string {
#[PatternRoute('DELETE', '/comments/posts/([0-9]+)')]
public function deletePost(HttpResponseBuilder $response, string $commentId): array|string {
try {
$postInfo = $this->commentsCtx->posts->getPost($commentId);
if($postInfo->deleted)
@ -547,8 +543,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return mixed[]|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/posts/([0-9]+)/restore')]
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
#[PatternRoute('POST', '/comments/posts/([0-9]+)/restore')]
public function postPostRestore(HttpResponseBuilder $response, string $commentId): array {
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
return self::error($response, 403, 'comments:restore-not-allowed', 'You are not allowed to restore comments.');
@ -572,8 +568,8 @@ class CommentsRoutes implements RouteHandler, UrlSource {
/**
* @return mixed[]|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/posts/([0-9]+)/nuke')]
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
#[PatternRoute('POST' ,'/comments/posts/([0-9]+)/nuke')]
public function postPostNuke(HttpResponseBuilder $response, string $commentId): array {
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
return self::error($response, 403, 'comments:nuke-not-allowed', 'You are not allowed to permanently delete comments.');
@ -601,12 +597,10 @@ class CommentsRoutes implements RouteHandler, UrlSource {
* negative: int,
* }|array{error: array{name: string, text: string}}
*/
#[HttpPost('/comments/posts/([0-9]+)/vote')]
public function postPostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
if(!($request->content instanceof FormHttpContent))
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
$vote = (int)$request->content->getParam('vote', FILTER_SANITIZE_NUMBER_INT);
#[PatternRoute('POST', '/comments/posts/([0-9]+)/vote')]
#[Before('input:urlencoded')]
public function postPostVote(HttpResponseBuilder $response, FormContent $content, string $commentId): array {
$vote = (int)$content->getFilteredParam('vote', FILTER_SANITIZE_NUMBER_INT);
if($vote === 0)
return self::error($response, 400, 'comments:vote', 'Could not process vote.');
@ -649,7 +643,7 @@ class CommentsRoutes implements RouteHandler, UrlSource {
* negative: int,
* }|array{error: array{name: string, text: string}}
*/
#[HttpDelete('/comments/posts/([0-9]+)/vote')]
#[PatternRoute('DELETE', '/comments/posts/([0-9]+)/vote')]
public function deletePostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_VOTE))
return self::error($response, 403, 'comments:vote-not-allowed', 'You are not allowed to vote on comments.');

View file

@ -4,7 +4,8 @@ namespace Misuzu;
use Index\Config\Config;
use Index\Db\{DbBackends,DbConnection};
use Index\Db\Migration\{DbMigrationManager,DbMigrationRepo,FsDbMigrationRepo};
use Index\Http\Routing\{HttpMiddleware,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Filters\PrefixFilter;
class DatabaseContext implements RouteHandler {
use RouteHandlerCommon;
@ -40,8 +41,8 @@ class DatabaseContext implements RouteHandler {
}
/** @return void|int */
#[HttpMiddleware('/')]
public function middleware() {
#[PrefixFilter('/')]
public function filterMigrate() {
if(is_file($this->getMigrateLockPath()))
return 503;
}

View file

@ -4,7 +4,8 @@ namespace Misuzu\Forum;
use stdClass;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Pagination,Perm,Template};
use Misuzu\Auth\AuthInfo;
@ -19,7 +20,7 @@ class ForumCategoriesRoutes implements RouteHandler, UrlSource {
private AuthInfo $authInfo,
) {}
#[HttpGet('/forum')]
#[ExactRoute('GET', '/forum')]
#[UrlFormat('forum-index', '/forum')]
#[UrlFormat('forum-category-root', '/forum', fragment: '<forum>')]
public function getIndex(): string {
@ -170,7 +171,7 @@ class ForumCategoriesRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/forum/([0-9]+)')]
#[PatternRoute('GET', '/forum/([0-9]+)')]
#[UrlFormat('forum-category', '/forum/<forum>', ['page' => '<page>'])]
public function getCategory(HttpResponseBuilder $response, HttpRequest $request, string $catId): mixed {
try {
@ -328,7 +329,7 @@ class ForumCategoriesRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpPost('/forum/mark-as-read')]
#[ExactRoute('POST', '/forum/mark-as-read')]
#[UrlFormat('forum-mark-as-read', '/forum/mark-as-read', ['cat' => '<category>', 'rec' => '<recursive>'])]
public function postMarkAsRead(HttpResponseBuilder $response, HttpRequest $request): mixed {
if(!$this->authInfo->loggedIn)
@ -338,7 +339,7 @@ class ForumCategoriesRoutes implements RouteHandler, UrlSource {
return 403;
$response->setHeader('X-CSRF-Token', CSRF::token());
$catId = (string)$request->getParam('cat', FILTER_SANITIZE_NUMBER_INT);
$catId = (string)$request->getFilteredParam('cat', FILTER_SANITIZE_NUMBER_INT);
$recursive = !empty($request->getParam('rec'));
if($catId === '') {

View file

@ -3,7 +3,8 @@ namespace Misuzu\Forum;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpDelete,HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Perm};
use Misuzu\AuditLog\AuditLogData;
@ -21,7 +22,7 @@ class ForumPostsRoutes implements RouteHandler, UrlSource {
private AuthInfo $authInfo,
) {}
#[HttpGet('/forum/posts/([0-9]+)')]
#[PatternRoute('GET', '/forum/posts/([0-9]+)')]
#[UrlFormat('forum-post', '/forum/posts/<post>')]
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
try {
@ -54,7 +55,7 @@ class ForumPostsRoutes implements RouteHandler, UrlSource {
return 302;
}
#[HttpDelete('/forum/posts/([0-9]+)')]
#[PatternRoute('DELETE', '/forum/posts/([0-9]+)')]
#[UrlFormat('forum-post-delete', '/forum/posts/<post>')]
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
if(!$this->authInfo->loggedIn)
@ -189,7 +190,7 @@ class ForumPostsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/posts/([0-9]+)/nuke')]
#[PatternRoute('POST', '/forum/posts/([0-9]+)/nuke')]
#[UrlFormat('forum-post-nuke', '/forum/posts/<post>/nuke')]
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
if(!$this->authInfo->loggedIn)
@ -269,7 +270,7 @@ class ForumPostsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/posts/([0-9]+)/restore')]
#[PatternRoute('POST', '/forum/posts/([0-9]+)/restore')]
#[UrlFormat('forum-post-restore', '/forum/posts/<post>/restore')]
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
if(!$this->authInfo->loggedIn)

View file

@ -4,7 +4,9 @@ namespace Misuzu\Forum;
use stdClass;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpDelete,HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Pagination,Perm,Template};
use Misuzu\AuditLog\AuditLogData;
@ -21,7 +23,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
private AuthInfo $authInfo,
) {}
#[HttpGet('/forum/topics/([0-9]+)')]
#[PatternRoute('GET', '/forum/topics/([0-9]+)')]
#[UrlFormat('forum-topic', '/forum/topics/<topic>', ['page' => '<page>'], '<topic_fragment>')]
public function getTopic(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
$isNuked = $deleted = $canDeleteAny = false;
@ -152,7 +154,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpDelete('/forum/topics/([0-9]+)')]
#[PatternRoute('DELETE', '/forum/topics/([0-9]+)')]
#[UrlFormat('forum-topic-delete', '/forum/topics/<topic>')]
public function deleteTopic(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)
@ -272,7 +274,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/topics/([0-9]+)/restore')]
#[PatternRoute('POST', '/forum/topics/([0-9]+)/restore')]
#[UrlFormat('forum-topic-restore', '/forum/topics/<topic>/restore')]
public function postTopicRestore(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)
@ -352,7 +354,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/topics/([0-9]+)/nuke')]
#[PatternRoute('POST', '/forum/topics/([0-9]+)/nuke')]
#[UrlFormat('forum-topic-nuke', '/forum/topics/<topic>/nuke')]
public function postTopicNuke(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)
@ -432,7 +434,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/topics/([0-9]+)/bump')]
#[PatternRoute('POST', '/forum/topics/([0-9]+)/bump')]
#[UrlFormat('forum-topic-bump', '/forum/topics/<topic>/bump')]
public function postTopicBump(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)
@ -512,7 +514,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/topics/([0-9]+)/lock')]
#[PatternRoute('POST', '/forum/topics/([0-9]+)/lock')]
#[UrlFormat('forum-topic-lock', '/forum/topics/<topic>/lock')]
public function postTopicLock(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)
@ -602,7 +604,7 @@ class ForumTopicsRoutes implements RouteHandler, UrlSource {
return 204;
}
#[HttpPost('/forum/topics/([0-9]+)/unlock')]
#[PatternRoute('POST', '/forum/topics/([0-9]+)/unlock')]
#[UrlFormat('forum-topic-unlock', '/forum/topics/<topic>/unlock')]
public function postTopicUnlock(HttpResponseBuilder $response, HttpRequest $request, string $topicId): mixed {
if(!$this->authInfo->loggedIn)

View file

@ -7,7 +7,8 @@ use Index\Config\Config;
use Index\Colour\Colour;
use Index\Db\{DbConnection,DbTools};
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\ExactRoute;
use Index\Urls\{UrlFormat,UrlSource,UrlSourceCommon};
use Misuzu\{Pagination,SiteInfo,Template};
use Misuzu\Auth\AuthInfo;
@ -198,12 +199,10 @@ class HomeRoutes implements RouteHandler, UrlSource {
return $topics;
}
#[HttpGet('/')]
#[ExactRoute('GET', '/')]
#[UrlFormat('index', '/')]
public function getIndex(HttpResponseBuilder $response, HttpRequest $request): string {
return $this->authInfo->loggedIn
? $this->getHome()
: $this->getLanding($response, $request);
public function getIndex(): string {
return $this->authInfo->loggedIn ? $this->getHome() : $this->getLanding();
}
public function getHome(): string {
@ -246,8 +245,8 @@ class HomeRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/_landing')]
public function getLanding(HttpResponseBuilder $response, HttpRequest $request): string {
#[ExactRoute('GET', '/_landing')]
public function getLanding(): string {
$config = $this->config->getValues([
['social.embed_linked:b'],
['landing.forum_categories:a'],

View file

@ -3,7 +3,8 @@ namespace Misuzu\Info;
use Index\Index;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlSource,UrlSourceCommon};
use Misuzu\{Misuzu,Template};
use Misuzu\Parsers\{Parsers,TextFormat};
@ -23,13 +24,13 @@ class InfoRoutes implements RouteHandler, UrlSource {
'rpcii' => 'RPCii » %s',
];
#[HttpGet('/info')]
#[ExactRoute('GET', '/info')]
#[UrlFormat('info-index', '/info')]
public function getIndex(): string {
return Template::renderRaw('info.index');
}
#[HttpGet('/info/([A-Za-z0-9_]+)')]
#[PatternRoute('GET', '/info/([A-Za-z0-9_]+)')]
#[UrlFormat('info', '/info/<title>')]
#[UrlFormat('info-doc', '/info/<title>')]
public function getDocsPage(HttpResponseBuilder $response, HttpRequest $request, string $name): string {
@ -76,7 +77,7 @@ class InfoRoutes implements RouteHandler, UrlSource {
return '';
}
#[HttpGet('/info/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
#[PatternRoute('GET', '/info/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
#[UrlFormat('info-project-doc', '/info/<project>/<title>')]
public function getProjectPage(HttpResponseBuilder $response, HttpRequest $request, string $project, string $name): int|string {
if(!array_key_exists($project, self::PROJECT_PATHS))

View file

@ -3,6 +3,7 @@ namespace Misuzu;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource};
class LegacyRoutes implements RouteHandler, UrlSource {
@ -110,126 +111,123 @@ class LegacyRoutes implements RouteHandler, UrlSource {
$urls->register('manage-role', '/manage/users/role.php', ['r' => '<role>']);
}
#[HttpGet('/index.php')]
#[ExactRoute('GET', '/index.php')]
public function getIndexPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('index'), true);
}
#[HttpGet('/info.php')]
#[ExactRoute('GET', '/info.php')]
public function getInfoPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('info'), true);
}
#[HttpGet('/info.php/([A-Za-z0-9_]+)')]
#[PatternRoute('GET', '/info.php/([A-Za-z0-9_]+)')]
public function getInfoDocsPHP(HttpResponseBuilder $response, HttpRequest $request, string $name): void {
$response->redirect($this->urls->format('info', ['title' => $name]), true);
}
#[HttpGet('/info.php/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
#[PatternRoute('GET', '/info.php/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
public function getInfoProjectPHP(HttpResponseBuilder $response, HttpRequest $request, string $project, string $name): void {
$response->redirect($this->urls->format('info', ['title' => $project . '/' . $name]), true);
}
#[HttpGet('/news.php')]
#[ExactRoute('GET', '/news.php')]
public function getNewsPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$postId = (int)($request->getParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT));
$postId = (int)($request->getFilteredParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT));
if($postId > 0)
$location = $this->urls->format('news-post', ['post' => $postId]);
else {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$pageId = $request->getParam('page', FILTER_SANITIZE_NUMBER_INT);
$catId = (int)$request->getFilteredParam('c', FILTER_SANITIZE_NUMBER_INT);
$pageId = $request->getFilteredParam('page', FILTER_SANITIZE_NUMBER_INT);
$location = $this->urls->format($catId > 0 ? 'news-category' : 'news-index', ['category' => $catId, 'page' => $pageId]);
}
$response->redirect($location, true);
}
#[HttpGet('/news/index.php')]
#[ExactRoute('GET', '/news/index.php')]
public function getNewsIndexPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('news-index', [
'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getFilteredParam('page', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/news/category.php')]
#[ExactRoute('GET', '/news/category.php')]
public function getNewsCategoryPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('news-category', [
'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
'category' => $request->getFilteredParam('c', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/news/post.php')]
#[ExactRoute('GET', '/news/post.php')]
public function getNewsPostPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('news-post', [
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
'post' => $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/news.php/rss')]
#[HttpGet('/news.php/atom')]
#[HttpGet('/news/feed.php')]
#[HttpGet('/news/feed.php/rss')]
#[HttpGet('/news/feed.php/atom')]
#[PatternRoute('GET', '/news.php/(?:atom|rss)')]
#[PatternRoute('GET', '/news/feed.php(?:/(?:atom|rss)?)?')]
public function getNewsFeedPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$catId = (int)$request->getFilteredParam('c', FILTER_SANITIZE_NUMBER_INT);
$response->redirect($this->urls->format(
$catId > 0 ? 'news-category-feed' : 'news-feed',
['category' => $catId]
), true);
}
#[HttpGet('/forum/index.php')]
#[ExactRoute('GET', '/forum/index.php')]
public function getForumIndexPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('forum-index'), true);
}
#[HttpGet('/forum/forum.php')]
#[ExactRoute('GET', '/forum/forum.php')]
public function getForumForumPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('forum-category', [
'forum' => $request->getParam('f', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
'forum' => $request->getFilteredParam('f', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/forum/topic.php')]
#[ExactRoute('GET', '/forum/topic.php')]
public function getForumTopicPHP(HttpResponseBuilder $response, HttpRequest $request): void {
if($request->hasParam('p'))
$response->redirect($this->urls->format('forum-post', [
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
'post' => $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
else
$response->redirect($this->urls->format('forum-topic', [
'topic' => $request->getParam('t', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
'topic' => $request->getFilteredParam('t', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getFilteredParam('page', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/forum/post.php')]
#[ExactRoute('GET', '/forum/post.php')]
public function getForumPostPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('forum-post', [
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
'post' => $request->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/changelog.php')]
#[ExactRoute('GET', '/changelog.php')]
public function getChangelogPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$changeId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$changeId = $request->getFilteredParam('c', FILTER_SANITIZE_NUMBER_INT);
if($changeId) {
$response->redirect($this->urls->format('changelog-change', ['change' => $changeId]), true);
return;
}
$response->redirect($this->urls->format('changelog-index', [
'date' => $request->getParam('d'),
'user' => $request->getParam('u', FILTER_SANITIZE_NUMBER_INT),
'date' => $request->getFilteredParam('d'),
'user' => $request->getFilteredParam('u', FILTER_SANITIZE_NUMBER_INT),
]), true);
}
#[HttpGet('/auth.php')]
#[ExactRoute('GET', '/auth.php')]
public function getAuthPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format(match($request->getParam('m')) {
$response->redirect($this->urls->format(match($request->getFilteredParam('m')) {
'logout' => 'auth-logout',
'reset' => 'auth-reset',
'forgot' => 'auth-forgot',
@ -238,44 +236,44 @@ class LegacyRoutes implements RouteHandler, UrlSource {
}), true);
}
#[HttpGet('/auth')]
#[HttpGet('/auth/index.php')]
#[ExactRoute('GET', '/auth')]
#[ExactRoute('GET', '/auth/index.php')]
public function getAuthIndexPHP(HttpResponseBuilder $response, HttpRequest $request): void {
$response->redirect($this->urls->format('auth-login'), true);
}
#[HttpGet('/settings.php')]
#[ExactRoute('GET', '/settings.php')]
public function getSettingsPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('settings-index'), true);
}
#[HttpGet('/settings')]
#[ExactRoute('GET', '/settings')]
public function getSettingsIndex(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('settings-account'));
}
#[HttpGet('/settings/index.php')]
#[ExactRoute('GET', '/settings/index.php')]
public function getSettingsIndexPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('settings-account'), true);
}
#[HttpGet('/manage')]
#[ExactRoute('GET', '/manage')]
#[UrlFormat('manage-index', '/manage')]
public function getManageIndex(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('manage-general-overview'));
}
#[HttpGet('/manage/index.php')]
#[ExactRoute('GET', '/manage/index.php')]
public function getManageIndexPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('manage-general-overview'), true);
}
#[HttpGet('/manage/news')]
#[ExactRoute('GET', '/manage/news')]
public function getManageNewsIndex(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('manage-news-categories'));
}
#[HttpGet('/manage/news/index.php')]
#[ExactRoute('GET', '/manage/news/index.php')]
public function getManageNewsIndexPHP(HttpResponseBuilder $response): void {
$response->redirect($this->urls->format('manage-news-categories'), true);
}

View file

@ -8,7 +8,11 @@ use Index\XString;
use Index\Config\Config;
use Index\Colour\Colour;
use Index\Http\{FormHttpContent,HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Filters\PrefixFilter;
use Index\Http\Routing\Processors\{After,Before};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Misuzu,Pagination,Perm,Template};
use Misuzu\Auth\AuthInfo;
@ -38,7 +42,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
private bool $canSendMessages;
/** @return void|int|array{error: array{name: string, text: string}} */
#[HttpMiddleware('/messages')]
#[PrefixFilter('/messages')]
public function checkAccess(HttpResponseBuilder $response, HttpRequest $request) {
// should probably be a permission or something too
if(!$this->authInfo->loggedIn)
@ -56,9 +60,6 @@ class MessagesRoutes implements RouteHandler, UrlSource {
&& !$this->usersCtx->hasActiveBan($this->authInfo->userInfo);
if($request->method === 'POST') {
if(!($request->content instanceof FormHttpContent))
return 400;
if(!CSRF::validate($request->getHeaderLine('x-csrf-token')))
return [
'error' => [
@ -92,9 +93,9 @@ class MessagesRoutes implements RouteHandler, UrlSource {
return $message;
}
#[HttpGet('/messages')]
#[ExactRoute('GET', '/messages')]
#[UrlFormat('messages-index', '/messages', ['folder' => '<folder>', 'page' => '<page>'])]
public function getIndex(HttpResponseBuilder $response, HttpRequest $request, string $folderName = ''): int|string {
public function getIndex(HttpRequest $request, string $folderName = ''): int|string {
$folderName = (string)$request->getParam('folder');
if($folderName === '')
$folderName = 'inbox';
@ -146,7 +147,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return array{unread: int} */
#[HttpGet('/messages/stats')]
#[ExactRoute('GET', '/messages/stats')]
#[UrlFormat('messages-stats', '/messages/stats')]
public function getStats(): array {
$selfInfo = $this->authInfo->userInfo;
@ -171,16 +172,14 @@ class MessagesRoutes implements RouteHandler, UrlSource {
* avatar: string
* }
*/
#[HttpPost('/messages/recipient')]
#[ExactRoute('POST', '/messages/recipient')]
#[Before('input:urlencoded')]
#[UrlFormat('messages-recipient', '/messages/recipient')]
public function postRecipient(HttpResponseBuilder $response, HttpRequest $request): int|array {
public function postRecipient(FormContent $content): int|array {
if(!$this->canSendMessages)
return 403;
if(!($request->content instanceof FormHttpContent))
return 400;
$name = trim((string)$request->content->getParam('name'));
$name = trim((string)$content->getParam('name'));
// flappy hacks
if(str_starts_with(mb_strtolower($name), 'flappyzor'))
@ -211,9 +210,9 @@ class MessagesRoutes implements RouteHandler, UrlSource {
];
}
#[HttpGet('/messages/compose')]
#[ExactRoute('GET', '/messages/compose')]
#[UrlFormat('messages-compose', '/messages/compose', ['recipient' => '<recipient>'])]
public function getEditor(HttpResponseBuilder $response, HttpRequest $request): int|string {
public function getEditor(HttpRequest $request): int|string {
if(!$this->canSendMessages)
return 403;
@ -222,9 +221,9 @@ class MessagesRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/messages/([A-Za-z0-9]+)')]
#[PatternRoute('GET', '/messages/([A-Za-z0-9]+)')]
#[UrlFormat('messages-view', '/messages/<message>')]
public function getView(HttpResponseBuilder $response, HttpRequest $request, string $messageId): int|string {
public function getView(string $messageId): int|string {
if(strlen($messageId) !== 8)
return 404;
@ -355,21 +354,19 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|array{id: string, url: string} */
#[HttpPost('/messages/create')]
#[ExactRoute('POST', '/messages/create')]
#[Before('input:multipart')]
#[UrlFormat('messages-create', '/messages/create')]
public function postCreate(HttpResponseBuilder $response, HttpRequest $request): int|array {
public function postCreate(FormContent $content): int|array {
if(!$this->canSendMessages)
return 403;
if(!($request->content instanceof FormHttpContent))
return 400;
$recipient = (string)$request->content->getParam('recipient');
$replyTo = (string)$request->content->getParam('reply');
$title = (string)$request->content->getParam('title');
$body = (string)$request->content->getParam('body');
$format = TextFormat::tryFrom((string)$request->content->getParam('format'));
$draft = !empty($request->content->getParam('draft'));
$recipient = (string)$content->getParam('recipient');
$replyTo = (string)$content->getParam('reply');
$title = (string)$content->getParam('title');
$body = (string)$content->getParam('body');
$format = TextFormat::tryFrom((string)$content->getParam('format'));
$draft = !empty($content->getParam('draft'));
$error = $this->checkMessageFields($title, $body, $format);
if($error !== null)
@ -459,19 +456,17 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|array{id: string, url: string} */
#[HttpPost('/messages/([A-Za-z0-9]+)')]
#[PatternRoute('PATCH', '/messages/([A-Za-z0-9]+)')]
#[Before('input:multipart')]
#[UrlFormat('messages-update', '/messages/<message>')]
public function postUpdate(HttpResponseBuilder $response, HttpRequest $request, string $messageId): int|array {
public function patchUpdate(FormContent $content, string $messageId): int|array {
if(!$this->canSendMessages)
return 403;
if(!($request->content instanceof FormHttpContent))
return 400;
$title = (string)$request->content->getParam('title');
$body = (string)$request->content->getParam('body');
$format = TextFormat::tryFrom((string)$request->content->getParam('format'));
$draft = !empty($request->content->getParam('draft'));
$title = (string)$content->getParam('title');
$body = (string)$content->getParam('body');
$format = TextFormat::tryFrom((string)$content->getParam('format'));
$draft = !empty($content->getParam('draft'));
$error = $this->checkMessageFields($title, $body, $format);
if($error !== null)
@ -551,14 +546,12 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|scalar[] */
#[HttpPost('/messages/mark')]
#[ExactRoute('POST', '/messages/mark')]
#[Before('input:urlencoded')]
#[UrlFormat('messages-mark', '/messages/mark')]
public function postMark(HttpResponseBuilder $response, HttpRequest $request): int|array {
if(!($request->content instanceof FormHttpContent))
return 400;
$type = (string)$request->content->getParam('type');
$messages = explode(',', (string)$request->content->getParam('messages'));
public function postMark(FormContent $content): int|array {
$type = (string)$content->getParam('type');
$messages = explode(',', (string)$content->getParam('messages'));
if($type !== 'read' && $type !== 'unread')
return [
@ -582,13 +575,11 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|scalar[] */
#[HttpPost('/messages/delete')]
#[ExactRoute('POST', '/messages/delete')]
#[Before('input:urlencoded')]
#[UrlFormat('messages-delete', '/messages/delete')]
public function postDelete(HttpResponseBuilder $response, HttpRequest $request): int|array {
if(!($request->content instanceof FormHttpContent))
return 400;
$messages = (string)$request->content->getParam('messages');
public function postDelete(FormContent $content): int|array {
$messages = (string)$content->getParam('messages');
if($messages === '')
return [
'error' => [
@ -608,13 +599,11 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|scalar[] */
#[HttpPost('/messages/restore')]
#[ExactRoute('POST', '/messages/restore')]
#[Before('input:urlencoded')]
#[UrlFormat('messages-restore', '/messages/restore')]
public function postRestore(HttpResponseBuilder $response, HttpRequest $request) {
if(!($request->content instanceof FormHttpContent))
return 400;
$messages = (string)$request->content->getParam('messages');
public function postRestore(FormContent $content) {
$messages = (string)$content->getParam('messages');
if($messages === '')
return [
'error' => [
@ -634,13 +623,11 @@ class MessagesRoutes implements RouteHandler, UrlSource {
}
/** @return int|array{error: array{name: string, text: string}}|scalar[] */
#[HttpPost('/messages/nuke')]
#[ExactRoute('POST', '/messages/nuke')]
#[Before('input:urlencoded')]
#[UrlFormat('messages-nuke', '/messages/nuke')]
public function postNuke(HttpResponseBuilder $response, HttpRequest $request) {
if(!($request->content instanceof FormHttpContent))
return 400;
$messages = (string)$request->content->getParam('messages');
public function postNuke(FormContent $content) {
$messages = (string)$content->getParam('messages');
if($messages === '')
return [
'error' => [

View file

@ -9,7 +9,7 @@ use Index\Db\Migration\{DbMigrationManager,DbMigrationRepo,FsDbMigrationRepo};
use Index\Http\HttpRequest;
use Index\Templating\TplEnvironment;
use Index\Urls\UrlRegistry;
use Misuzu\Routing\{BackedRoutingContext,RoutingContext};
use Misuzu\Routing\RoutingContext;
use Misuzu\Users\UserInfo;
use RPCii\HmacVerificationProvider;
use RPCii\Server\{HttpRpcServer,RpcServer};
@ -139,36 +139,30 @@ class MisuzuContext {
Template::init($this->templating);
}
public function createRouting(HttpRequest $request): BackedRoutingContext {
public function createRouting(HttpRequest $request): RoutingContext {
$host = $request->getHeaderLine('Host');
$roles = $this->domainRoles->getRoles($host);
$routingCtx = new BackedRoutingContext;
$routingCtx = new RoutingContext;
$this->deps->register($this->urls = $routingCtx->urls);
$rpcServer = new HttpRpcServer;
$routingCtx->register($rpcServer->createRouteHandler(
new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret'))
));
if(in_array('main', $roles))
$this->registerMainRoutes(
$routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'main')),
$rpcServer
);
$this->registerMainRoutes($routingCtx);
if(in_array('redirect', $roles))
$this->registerRedirectorRoutes(
$routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'redirect'))
);
$this->registerRedirectorRoutes($routingCtx);
return $routingCtx;
}
public function registerMainRoutes(
RoutingContext $routingCtx,
RpcServer $rpcServer
RoutingContext $routingCtx
): void {
$rpcServer = new HttpRpcServer;
$routingCtx->register($rpcServer->createRouteHandler(
new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret'))
));
$this->deps->register($wf = new WebFinger\WebFingerRegistry);
$wf->register($this->deps->constructLazy(
Users\UsersWebFingerResolver::class,

View file

@ -4,7 +4,8 @@ namespace Misuzu\News;
use RuntimeException;
use Index\Colour\Colour;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Syndication\FeedBuilder;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{Pagination,SiteInfo,Template};
@ -98,7 +99,7 @@ class NewsRoutes implements RouteHandler, UrlSource {
return $posts;
}
#[HttpGet('/news')]
#[ExactRoute('GET', '/news')]
#[UrlFormat('news-index', '/news', ['p' => '<page>'])]
public function getIndex(HttpResponseBuilder $response, HttpRequest $request): int|string {
$categories = $this->news->getCategories(hidden: false);
@ -116,7 +117,7 @@ class NewsRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/news/([0-9]+)(?:\.(xml|rss|atom))?')]
#[PatternRoute('GET', '/news/([0-9]+)(?:\.(xml|rss|atom))?')]
#[UrlFormat('news-category', '/news/<category>', ['p' => '<page>'])]
public function getCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryId, string $type = ''): int|string {
try {
@ -141,7 +142,7 @@ class NewsRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/news/post/([0-9]+)')]
#[PatternRoute('GET', '/news/post/([0-9]+)')]
#[UrlFormat('news-post', '/news/post/<post>')]
#[UrlFormat('news-post-comments', '/news/post/<post>', fragment: 'comments')]
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $postId): int|string {
@ -168,7 +169,7 @@ class NewsRoutes implements RouteHandler, UrlSource {
]);
}
#[HttpGet('/news.(?:xml|rss|atom)')]
#[PatternRoute('GET', '/news.(?:xml|rss|atom)')]
#[UrlFormat('news-feed', '/news.xml')]
#[UrlFormat('news-category-feed', '/news/<category>.xml')]
public function getFeed(HttpResponseBuilder $response, HttpRequest $request, ?NewsCategoryInfo $categoryInfo = null): string {

View file

@ -4,8 +4,12 @@ namespace Misuzu\OAuth2;
use RuntimeException;
use Index\XArray;
use Index\Colour\{Colour,ColourRgb};
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpOptions,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\SiteInfo;
use Misuzu\Apps\AppsContext;
@ -52,20 +56,16 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
}
/**
* @return int|array{
* @return array{
* resource: string,
* authorization_servers: string[],
* scopes_supported: string[],
* bearer_methods_supported: string[],
* }
*/
#[HttpOptions('/.well-known/oauth-protected-resource')]
#[HttpGet('/.well-known/oauth-protected-resource')]
public function getWellKnownProtectedResource(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
#[AccessControl]
#[ExactRoute('GET', '/.well-known/oauth-protected-resource')]
public function getWellKnownProtectedResource(HttpResponseBuilder $response, HttpRequest $request): array {
return [
'resource' => $this->siteInfo->url,
'authorization_servers' => [$this->siteInfo->url],
@ -75,7 +75,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
}
/**
* @return int|array{
* @return array{
* issuer: string,
* authorization_endpoint: string,
* token_endpoint: string,
@ -92,13 +92,9 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
* code_challenge_methods_supported: string[],
* }
*/
#[HttpOptions('/.well-known/oauth-authorization-server')]
#[HttpGet('/.well-known/oauth-authorization-server')]
public function getWellKnownAuthorizationServer(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
#[AccessControl]
#[ExactRoute('GET', '/.well-known/oauth-authorization-server')]
public function getWellKnownAuthorizationServer(HttpResponseBuilder $response, HttpRequest $request): array {
return [
'issuer' => $this->siteInfo->url,
'authorization_endpoint' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('oauth2-authorise')),
@ -134,7 +130,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
}
/**
* @return int|array{
* @return array{
* issuer: string,
* authorization_endpoint: string,
* token_endpoint: string,
@ -148,13 +144,9 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
* token_endpoint_auth_methods_supported: string[],
* }
*/
#[HttpOptions('/.well-known/openid-configuration')]
#[HttpGet('/.well-known/openid-configuration')]
public function getWellKnown(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
#[AccessControl]
#[ExactRoute('GET', '/.well-known/openid-configuration')]
public function getWellKnown(HttpResponseBuilder $response, HttpRequest $request): array {
$signingAlgs = array_values(array_unique(XArray::select(
$this->oauth2Ctx->keys->getPublicKeySet()->keys,
fn($key) => $key->algo
@ -185,11 +177,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
#[HttpOptions('/oauth2/jwks.json')]
#[HttpGet('/oauth2/jwks.json')]
#[UrlFormat('oauth2-jwks', '/oauth2/jwks.json')]
public function getJwks(HttpResponseBuilder $response, HttpRequest $request): int|array {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
public function getJwks(HttpResponseBuilder $response, HttpRequest $request): array {
return $this->oauth2Ctx->keys->getPublicKeysForJson();
}
@ -203,13 +191,13 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
* interval?: int
* }|array{ error: string, error_description: string }
*/
#[HttpPost('/oauth2/request-authorise')]
#[HttpPost('/oauth2/request-authorize')]
#[PatternRoute('POST', '/oauth2/request-authori[sz]e')]
#[Before('input:urlencoded', required: false)]
#[UrlFormat('oauth2-request-authorise', '/oauth2/request-authorize')]
public function postRequestAuthorise(HttpResponseBuilder $response, HttpRequest $request): array {
public function postRequestAuthorise(HttpResponseBuilder $response, HttpRequest $request, ?FormContent $content): array {
$response->setHeader('Cache-Control', 'no-store');
if(!($request->content instanceof FormHttpContent))
if($content === null)
return self::filter($response, $request, [
'error' => 'invalid_request',
'error_description' => 'Your request must use content type application/x-www-form-urlencoded.',
@ -226,7 +214,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
'error_description' => 'You must use the Basic method for Authorization parameters.',
], authzHeader: true);
} else {
$clientId = (string)$request->content->getParam('client_id');
$clientId = (string)$content->getParam('client_id');
$clientSecret = '';
}
@ -250,12 +238,12 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
return self::filter($response, $request, $this->oauth2Ctx->createDeviceAuthorisationRequest(
$appInfo,
$request->content->hasParam('scope') ? (string)$request->content->getParam('scope') : null
$content->hasParam('scope') ? (string)$content->getParam('scope') : null
));
}
/**
* @return int|array{
* @return array{
* access_token: string,
* token_type: 'Bearer',
* expires_in?: int,
@ -263,36 +251,14 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
* refresh_token?: string,
* }|array{ error: string, error_description: string }
*/
#[HttpOptions('/oauth2/token')]
#[HttpPost('/oauth2/token')]
#[AccessControl(allowHeaders: ['Authorization'], exposeHeaders: ['WWW-Authenticate'])]
#[ExactRoute('POST', '/oauth2/token')]
#[Before('input:urlencoded', required: false)]
#[UrlFormat('oauth2-token', '/oauth2/token')]
public function postToken(HttpResponseBuilder $response, HttpRequest $request): array|int {
public function postToken(HttpResponseBuilder $response, HttpRequest $request, ?FormContent $content): array {
$response->setHeader('Cache-Control', 'no-store');
$originHeaders = ['Origin', 'X-Origin', 'Referer'];
$origins = [];
foreach($originHeaders as $originHeader) {
$originHeader = $request->getHeaderFirstLine($originHeader);
if($originHeader !== '' && !in_array($originHeader, $origins))
$origins[] = $originHeader;
}
if(!empty($origins)) {
// TODO: check if none of the provided origins is on a blocklist or something
// different origins being specified for each header should probably also be considered suspect...
$response->setHeader('Access-Control-Allow-Origin', $origins[0]);
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST');
$response->setHeader('Access-Control-Allow-Headers', 'Authorization');
$response->setHeader('Access-Control-Expose-Headers', 'Vary');
foreach($originHeaders as $originHeader)
$response->setHeader('Vary', $originHeader);
}
if($request->method === 'OPTIONS')
return 204;
if(!($request->content instanceof FormHttpContent))
if($content === null)
return self::filter($response, $request, [
'error' => 'invalid_request',
'error_description' => 'Your request must use content type application/x-www-form-urlencoded.',
@ -310,8 +276,8 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
'error_description' => 'You must either use the Basic method for Authorization or use the client_id and client_secret parameters.',
], authzHeader: true);
} else {
$clientId = (string)$request->content->getParam('client_id');
$clientSecret = (string)$request->content->getParam('client_secret');
$clientId = (string)$content->getParam('client_id');
$clientSecret = (string)$content->getParam('client_secret');
}
try {
@ -334,36 +300,36 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
], authzHeader: $authzHeader[0] !== '');
}
$type = (string)$request->content->getParam('grant_type');
$type = (string)$content->getParam('grant_type');
if($type === 'authorization_code')
return self::filter($response, $request, $this->oauth2Ctx->redeemAuthorisationCode(
$appInfo,
$isAuthed,
(string)$request->content->getParam('code'),
(string)$request->content->getParam('code_verifier')
(string)$content->getParam('code'),
(string)$content->getParam('code_verifier')
));
if($type === 'refresh_token')
return self::filter($response, $request, $this->oauth2Ctx->redeemRefreshToken(
$appInfo,
$isAuthed,
(string)$request->content->getParam('refresh_token'),
$request->content->hasParam('scope') ? (string)$request->content->getParam('scope') : null
(string)$content->getParam('refresh_token'),
$content->hasParam('scope') ? (string)$content->getParam('scope') : null
));
if($type === 'client_credentials')
return self::filter($response, $request, $this->oauth2Ctx->redeemClientCredentials(
$appInfo,
$isAuthed,
$request->content->hasParam('scope') ? (string)$request->content->getParam('scope') : null
$content->hasParam('scope') ? (string)$content->getParam('scope') : null
));
if($type === 'urn:ietf:params:oauth:grant-type:device_code' || $type === 'device_code')
return self::filter($response, $request, $this->oauth2Ctx->redeemDeviceCode(
$appInfo,
$isAuthed,
(string)$request->content->getParam('device_code')
(string)$content->getParam('device_code')
));
return self::filter($response, $request, [
@ -373,7 +339,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
}
/**
* @return int|array{
* @return array{
* sub: string,
* name?: string,
* nickname?: string,

View file

@ -4,8 +4,11 @@ namespace Misuzu\OAuth2;
use InvalidArgumentException;
use RuntimeException;
use Index\XArray;
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Template};
use Misuzu\Auth\AuthInfo;
@ -24,15 +27,14 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
private AuthInfo $authInfo,
) {}
#[HttpGet('/oauth2/authorise')]
#[HttpGet('/oauth2/authorize')]
#[PatternRoute('GET', '/oauth2/authori[sz]e')]
#[UrlFormat('oauth2-authorise', '/oauth2/authorize')]
public function getAuthorise(HttpResponseBuilder $response, HttpRequest $request): string {
return Template::renderRaw('oauth2.authorise');
}
/**
* @return int|array{
* @return array{
* error: 'auth'|'csrf'|'method'|'length'|'client'|'scope'|'format'|'required'|'authorise'|'resptype',
* scope?: string,
* reason?: string,
@ -41,11 +43,9 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
* redirect: string,
* }
*/
#[HttpPost('/oauth2/authorise')]
public function postAuthorise(HttpResponseBuilder $response, HttpRequest $request): int|array {
if(!($request->content instanceof FormHttpContent))
return 400;
#[ExactRoute('POST', '/oauth2/authorize')]
#[Before('input:urlencoded')]
public function postAuthorise(HttpResponseBuilder $response, HttpRequest $request, FormContent $content): array {
// TODO: RATE LIMITING
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
@ -56,20 +56,20 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
if(!$this->authInfo->loggedIn)
return ['error' => 'auth'];
$responseTypes = explode(' ', (string)$request->content->getParam('rt'), 2);
$responseTypes = explode(' ', (string)$content->getParam('rt'), 2);
sort($responseTypes);
$responseType = implode(' ', $responseTypes);
if(!in_array($responseType, self::RESPONSE_TYPES))
return ['error' => 'resptype'];
$codeChallengeMethod = 'plain';
if($request->content->hasParam('ccm')) {
$codeChallengeMethod = $request->content->getParam('ccm');
if($content->hasParam('ccm')) {
$codeChallengeMethod = $content->getParam('ccm');
if(!in_array($codeChallengeMethod, ['plain', 'S256']))
return ['error' => 'method'];
}
$codeChallenge = $request->content->getParam('cc');
$codeChallenge = $content->getParam('cc');
$codeChallengeLength = strlen($codeChallenge);
if($codeChallengeMethod === 'S256') {
if($codeChallengeLength !== 43)
@ -81,16 +81,16 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
try {
$appInfo = $this->oauth2Ctx->appsCtx->apps->getAppInfo(
clientId: (string)$request->content->getParam('client'),
clientId: (string)$content->getParam('client'),
deleted: false
);
} catch(RuntimeException $ex) {
return ['error' => 'client'];
}
if($request->content->hasParam('scope')) {
if($content->hasParam('scope')) {
$scope = [];
$scopeInfos = $this->oauth2Ctx->appsCtx->handleScopeString($appInfo, (string)$request->content->getParam('scope'));
$scopeInfos = $this->oauth2Ctx->appsCtx->handleScopeString($appInfo, (string)$content->getParam('scope'));
foreach($scopeInfos as $scopeName => $scopeInfo) {
if(is_string($scopeInfo))
@ -102,8 +102,8 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
$scope = implode(' ', $scope);
} else $scope = '';
if($request->content->hasParam('redirect')) {
$redirectUri = (string)$request->content->getParam('redirect');
if($content->hasParam('redirect')) {
$redirectUri = (string)$content->getParam('redirect');
$redirectUriId = $this->oauth2Ctx->appsCtx->apps->getAppUriId($appInfo, $redirectUri);
if($redirectUriId === null)
return ['error' => 'format'];
@ -169,7 +169,7 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
* scope: string[],
* }
*/
#[HttpGet('/oauth2/resolve-authorise-app')]
#[ExactRoute('GET', '/oauth2/resolve-authorise-app')]
#[UrlFormat('oauth2-resolve-authorise-app', '/oauth2/resolve-authorise-app')]
public function getResolveAuthorise(HttpResponseBuilder $response, HttpRequest $request): array {
// TODO: RATE LIMITING
@ -243,14 +243,14 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
return $result;
}
#[HttpGet('/oauth2/verify')]
#[ExactRoute('GET', '/oauth2/verify')]
#[UrlFormat('oauth2-verify', '/oauth2/verify')]
public function getVerify(HttpResponseBuilder $response, HttpRequest $request): string {
return Template::renderRaw('oauth2.verify');
}
/**
* @return int|array{
* @return array{
* error: 'auth'|'csrf'|'invalid'|'code'|'expired'|'approval'|'code'|'scope',
* scope?: string,
* reason?: string,
@ -258,11 +258,9 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
* approval: 'approved'|'denied',
* }
*/
#[HttpPost('/oauth2/verify')]
public function postVerify(HttpResponseBuilder $response, HttpRequest $request): int|array {
if(!($request->content instanceof FormHttpContent))
return 400;
#[ExactRoute('POST', '/oauth2/verify')]
#[Before('input:urlencoded')]
public function postVerify(HttpResponseBuilder $response, HttpRequest $request, FormContent $content): array {
// TODO: RATE LIMITING
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
@ -273,13 +271,13 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
if(!$this->authInfo->loggedIn)
return ['error' => 'auth'];
$approve = (string)$request->content->getParam('approve');
$approve = (string)$content->getParam('approve');
if(!in_array($approve, ['yes', 'no']))
return ['error' => 'invalid'];
try {
$deviceInfo = $this->oauth2Ctx->devices->getDeviceInfo(
userCode: (string)$request->content->getParam('code')
userCode: (string)$content->getParam('code')
);
} catch(RuntimeException $ex) {
return ['error' => 'code'];
@ -357,7 +355,7 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
* scope: string[],
* }
*/
#[HttpGet('/oauth2/resolve-verify')]
#[ExactRoute('GET', '/oauth2/resolve-verify')]
#[UrlFormat('oauth2-resolve-verify', '/oauth2/resolve-verify')]
public function getResolveVerify(HttpResponseBuilder $response, HttpRequest $request) {
// TODO: RATE LIMITING

View file

@ -57,7 +57,7 @@ final class Pagination {
): self {
return self::fromPage(
$count,
(int)(filter_input(INPUT_GET, $pageParam, FILTER_SANITIZE_NUMBER_INT) ?? $firstPage),
!empty($_GET[$pageParam]) && is_scalar($_GET[$pageParam]) ? (int)$_GET[$pageParam] : $firstPage,
$range,
$firstPage
);
@ -72,7 +72,7 @@ final class Pagination {
): Pagination {
return self::fromPage(
$count,
(int)($request->getParam($pageParam, FILTER_SANITIZE_NUMBER_INT) ?? $firstPage),
(int)($request->getFilteredParam($pageParam, FILTER_SANITIZE_NUMBER_INT) ?? $firstPage),
$range,
$firstPage
);

View file

@ -423,7 +423,7 @@ final class Perm {
$item->perms[] = $permItem = new stdClass;
$permItem->category = $categoryName;
$permItem->name = sprintf('perms[%s:%d]', $categoryName, $perm);
$permItem->name = sprintf('perm_%s_%d', $categoryName, $perm);
$permItem->title = self::label($categoryName, $perm);
$permItem->value = $perm;
}
@ -437,11 +437,18 @@ final class Perm {
* @param string[] $categories
* @return array<string, array<string, int>>
*/
public static function convertSubmission(array $raw, array $categories): array {
public static function convertSubmission(array $raw, array $categories, string $prefix = 'perm_'): array {
$apply = [];
foreach($raw as $name => $mode) {
$nameParts = explode(':', $name, 2);
if($prefix !== '') {
if(!str_starts_with($name, $prefix))
continue;
$name = substr($name, strlen($prefix));
}
$nameParts = explode('_', $name, 2);
if(count($nameParts) !== 2 || !ctype_alpha($nameParts[0]) || !ctype_digit($nameParts[1]))
continue;
@ -449,12 +456,11 @@ final class Perm {
if(!in_array($category, $categories))
continue;
if($mode === 'yes' || $mode === 'never') {
if(!array_key_exists($category, $apply))
$apply[$category] = ['allow' => 0, 'deny' => 0];
if(!array_key_exists($category, $apply))
$apply[$category] = ['allow' => 0, 'deny' => 0];
if($mode === 'yes' || $mode === 'never')
$apply[$category][$mode === 'yes' ? 'allow' : 'deny'] |= (int)$value;
}
}
return $apply;

View file

@ -3,7 +3,8 @@ namespace Misuzu\Redirects;
use Index\Config\Config;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
class AliasRedirectsRoutes implements RouteHandler {
use RouteHandlerCommon;
@ -24,23 +25,23 @@ class AliasRedirectsRoutes implements RouteHandler {
$response->redirect($url, true);
}
#[HttpGet('/[up]([0-9]+)')]
#[HttpGet('/[up]/([A-Za-z0-9\-_]+)')]
#[PatternRoute('GET', '/[up]([0-9]+)')]
#[PatternRoute('GET', '/[up]/([A-Za-z0-9\-_]+)')]
public function getProfileRedirect(HttpResponseBuilder $response, HttpRequest $request, string $userId): void {
$this->redirect($response, $request, 'user_profile', $userId);
}
#[HttpGet('/fc?/?([0-9]+)')]
#[PatternRoute('GET', '/fc?/?([0-9]+)')]
public function getForumCategoryRedirect(HttpResponseBuilder $response, HttpRequest $request, string $categoryId): void {
$this->redirect($response, $request, 'forum_category', $categoryId);
}
#[HttpGet('/ft/?([0-9]+)')]
#[PatternRoute('GET', '/ft/?([0-9]+)')]
public function getForumTopicRedirect(HttpResponseBuilder $response, HttpRequest $request, string $topicId): void {
$this->redirect($response, $request, 'forum_topic', $topicId);
}
#[HttpGet('/fp/?([0-9]+)')]
#[PatternRoute('GET', '/fp/?([0-9]+)')]
public function getForumPostRedirect(HttpResponseBuilder $response, HttpRequest $request, string $postId): void {
$this->redirect($response, $request, 'forum_post', $postId);
}

View file

@ -3,8 +3,11 @@ namespace Misuzu\Redirects;
use RuntimeException;
use Index\XNumber;
use Index\Http\{FormHttpContent,HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\HttpResponseBuilder;
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
class IncrementalRedirectsRoutes implements RouteHandler {
use RouteHandlerCommon;
@ -13,8 +16,8 @@ class IncrementalRedirectsRoutes implements RouteHandler {
private RedirectsContext $redirectsCtx,
) {}
#[HttpGet('/[bg]/([A-Za-z0-9]+)')]
public function getIncrementalRedirect(HttpResponseBuilder $response, HttpRequest $request, string $linkId): int {
#[PatternRoute('GET', '/[bg]/([A-Za-z0-9]+)')]
public function getIncrementalRedirect(HttpResponseBuilder $response, string $linkId): int {
$linkId = XNumber::fromBase62($linkId);
if($linkId === false)
return 400;
@ -30,15 +33,13 @@ class IncrementalRedirectsRoutes implements RouteHandler {
}
/** @return int|array{url: string} */
#[HttpPost('/satori/create')]
public function postIncrementalRedirect(HttpResponseBuilder $response, HttpRequest $request): int|array {
if(!($request->content instanceof FormHttpContent))
return 400;
#[ExactRoute('POST', '/satori/create')]
#[Before('input:urlencoded')]
public function postIncrementalRedirect(FormContent $content): int|array {
$config = $this->redirectsCtx->config->scopeTo('incremental');
$url = (string)$request->content->getParam('u');
$time = (int)$request->content->getParam('t', FILTER_SANITIZE_NUMBER_INT);
$sign = base64_decode((string)$request->content->getParam('s'));
$url = (string)$content->getParam('u');
$time = (int)$content->getFilteredParam('t', FILTER_SANITIZE_NUMBER_INT);
$sign = base64_decode((string)$content->getParam('s'));
$hash = hash_hmac('sha256', "satori#create#{$time}#{$url}", $config->getString('secret'), true);
if(!hash_equals($hash, $sign))

View file

@ -1,13 +1,14 @@
<?php
namespace Misuzu\Redirects;
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\ExactRoute;
use Misuzu\Template;
class LandingRedirectsRoutes implements RouteHandler {
use RouteHandlerCommon;
#[HttpGet('/')]
#[ExactRoute('GET', '/')]
public function getIndex(): string {
return Template::renderRaw('redirects.landing');
}

View file

@ -2,9 +2,9 @@
namespace Misuzu\Redirects;
use RuntimeException;
use Index\Config\Config;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
class NamedRedirectsRoutes implements RouteHandler {
use RouteHandlerCommon;
@ -13,7 +13,7 @@ class NamedRedirectsRoutes implements RouteHandler {
private RedirectsContext $redirectsCtx,
) {}
#[HttpGet('/([A-Za-z0-9\-_]+)')]
#[PatternRoute('GET', '/([A-Za-z0-9\-_]+)')]
public function getNamedRedirect(HttpResponseBuilder $response, HttpRequest $request, string $name): int {
try {
$redirectInfo = $this->redirectsCtx->named->getNamedRedirect(

View file

@ -1,8 +1,9 @@
<?php
namespace Misuzu\Redirects;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\HttpResponseBuilder;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
use Misuzu\Template;
class SocialRedirectsRoutes implements RouteHandler {
@ -12,8 +13,8 @@ class SocialRedirectsRoutes implements RouteHandler {
private RedirectsContext $redirectsCtx
) {}
#[HttpGet('/bsky/((did:[a-z0-9]+:[A-Za-z0-9.\-_:%]+)|(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])))')]
public function getBlueskyRedirect(HttpResponseBuilder $response, HttpRequest $request, string $handle): int|string {
#[PatternRoute('GET', '/bsky/((did:[a-z0-9]+:[A-Za-z0-9.\-_:%]+)|(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])))')]
public function getBlueskyRedirect(HttpResponseBuilder $response, string $handle): int|string {
$did = null;
if(str_starts_with($handle, 'did:'))
@ -47,8 +48,8 @@ class SocialRedirectsRoutes implements RouteHandler {
]);
}
#[HttpGet('/fedi/([A-Za-z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})')]
public function getFediverseRedirect(HttpResponseBuilder $response, HttpRequest $request, string $userName, string $instance): string {
#[PatternRoute('GET', '/fedi/([A-Za-z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})')]
public function getFediverseRedirect(string $userName, string $instance): string {
return Template::renderRaw('redirects.fedi', [
'fedi_username' => $userName,
'fedi_instance' => $instance,

View file

@ -1,39 +0,0 @@
<?php
namespace Misuzu\Routing;
use Index\Http\HttpRequest;
use Index\Http\Routing\{HttpRouter,RouteHandler};
use Index\Urls\{ArrayUrlRegistry,UrlRegistry,UrlSource};
class BackedRoutingContext implements RoutingContext {
public private(set) HttpRouter $router;
public private(set) UrlRegistry $urls;
public function __construct(
?HttpRouter $router = null,
?UrlRegistry $urls = null
) {
$this->urls = $urls ?? new ArrayUrlRegistry;
$this->router = $router ?? new HttpRouter(errorHandler: new RoutingErrorHandler);
$this->router->use('/', fn($resp) => $resp->setPoweredBy('Misuzu'));
}
public function register(RouteHandler|UrlSource $handler): void {
if($handler instanceof RouteHandler)
$this->router->register($handler);
if($handler instanceof UrlSource)
$this->urls->register($handler);
}
public function scopeTo(string $prefix): RoutingContext {
return new ScopedRoutingContext(
$this->router->scopeTo($prefix),
$this->urls->scopeTo(pathPrefix: $prefix)
);
}
/** @param mixed[] $args */
public function dispatch(?HttpRequest $request = null, array $args = []): void {
$this->router->dispatch($request, $args);
}
}

View file

@ -1,11 +1,32 @@
<?php
namespace Misuzu\Routing;
use Index\Http\HttpRequest;
use Index\Http\Routing\RouteHandler;
use Index\Urls\UrlSource;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{Router,RouteHandler};
use Index\Http\Routing\Filters\FilterInfo;
use Index\Urls\{ArrayUrlRegistry,UrlRegistry,UrlSource};
interface RoutingContext {
public function register(RouteHandler|UrlSource $handler): void;
public function scopeTo(string $prefix): RoutingContext;
class RoutingContext {
public private(set) Router $router;
public private(set) UrlRegistry $urls;
public function __construct(
?Router $router = null,
?UrlRegistry $urls = null
) {
$this->urls = $urls ?? new ArrayUrlRegistry;
$this->router = $router ?? new Router(errorHandler: new RoutingErrorHandler);
$this->router->filter(FilterInfo::prefix('/', fn(HttpResponseBuilder $response) => $response->setPoweredBy('Misuzu')));
}
public function register(RouteHandler|UrlSource $handler): void {
if($handler instanceof RouteHandler)
$this->router->register($handler);
if($handler instanceof UrlSource)
$this->urls->register($handler);
}
public function dispatch(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
}

View file

@ -1,14 +1,19 @@
<?php
namespace Misuzu\Routing;
use Index\Http\{HtmlHttpErrorHandler,HttpResponseBuilder,HttpRequest};
use Misuzu\{Misuzu,Template};
use Index\Http\Routing\HandlerContext;
use Index\Http\Routing\ErrorHandling\HtmlErrorHandler;
use Index\Http\Streams\Stream;
use Misuzu\Misuzu;
class RoutingErrorHandler extends HtmlHttpErrorHandler {
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void {
if(str_starts_with($request->path, '/_')) {
$response->setTypePlain();
$response->content = sprintf('HTTP %03d', $code);
class RoutingErrorHandler extends HtmlErrorHandler {
public function handle(HandlerContext $context): void {
if(!$context->response->needsBody)
return;
if(str_starts_with($context->request->requestTarget, '/_')) {
$context->response->setTypePlain();
$context->response->body = Stream::createStream(sprintf('HTTP %03d', $context->response->statusCode));
return;
}
@ -23,11 +28,11 @@ class RoutingErrorHandler extends HtmlHttpErrorHandler {
$path = sprintf('%s/error-%03d.html', Misuzu::PATH_PUBLIC, $code);
if(is_file($path)) {
$response->setTypeHTML();
$response->content = file_get_contents($path);
$context->response->setTypeHTML();
$context->response->body = Stream::createStreamFromFile($path, 'rb');
return;
}
parent::handle($response, $request, $code, $message);
parent::handle($context);
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Misuzu\Routing;
use Index\Http\HttpRequest;
use Index\Http\Routing\{RouteHandler,Router};
use Index\Urls\{UrlRegistry,UrlSource};
class ScopedRoutingContext implements RoutingContext {
public function __construct(
private Router $router,
private UrlRegistry $urls
) {}
public function register(RouteHandler|UrlSource $handler): void {
if($handler instanceof RouteHandler)
$this->router->register($handler);
if($handler instanceof UrlSource)
$this->urls->register($handler);
}
public function scopeTo(string $prefix): RoutingContext {
return new ScopedRoutingContext(
$this->router->scopeTo($prefix),
$this->urls->scopeTo(pathPrefix: $prefix)
);
}
}

View file

@ -5,7 +5,12 @@ use RuntimeException;
use Index\Colour\Colour;
use Index\Config\Config;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,HttpMiddleware,RouteHandler,RouteHandlerCommon};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Filters\PrefixFilter;
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\ExactRoute;
use Misuzu\Pagination;
use Misuzu\Forum\ForumContext;
use Misuzu\Profile\ProfileContext;
@ -22,8 +27,8 @@ final class SatoriRoutes implements RouteHandler {
) {}
/** @return void|int */
#[HttpMiddleware('/_satori')]
public function verifyRequest(HttpResponseBuilder $response, HttpRequest $request) {
#[PrefixFilter('/_satori')]
public function verifyRequest(HttpRequest $request) {
$secretKey = $this->config->getString('secret');
if(!empty($secretKey)) {
@ -34,7 +39,7 @@ final class SatoriRoutes implements RouteHandler {
if(empty($userHash) || $userTime < $currentTime - 60 || $userTime > $currentTime + 60)
return 403;
$verifyText = (string)$userTime . '#' . $request->path . '?' . $request->getParamString();
$verifyText = (string)$userTime . '#' . $request->requestTarget . '?' . $request->getParamString(true);
$verifyHash = hash_hmac('sha256', $verifyText, $secretKey, true);
if(!hash_equals($verifyHash, $userHash))
@ -43,10 +48,10 @@ final class SatoriRoutes implements RouteHandler {
}
/** @return array{error: int}|array{field_value: string} */
#[HttpGet('/_satori/get-profile-field')]
public function getProfileField(HttpResponseBuilder $response, HttpRequest $request): array {
$userId = (string)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT);
$fieldId = (string)$request->getParam('field', FILTER_SANITIZE_NUMBER_INT);
#[ExactRoute('GET', '/_satori/get-profile-field')]
public function getProfileField(HttpRequest $request): array {
$userId = (string)$request->getFilteredParam('user', FILTER_SANITIZE_NUMBER_INT);
$fieldId = (string)$request->getFilteredParam('field', FILTER_SANITIZE_NUMBER_INT);
try {
$fieldValue = $this->profileCtx->fields->getFieldValue($fieldId, $userId);
@ -72,15 +77,15 @@ final class SatoriRoutes implements RouteHandler {
* is_opening_post: int
* }[]
*/
#[HttpGet('/_satori/get-recent-forum-posts')]
public function getRecentForumPosts(HttpResponseBuilder $response, HttpRequest $request): array {
#[ExactRoute('GET', '/_satori/get-recent-forum-posts')]
public function getRecentForumPosts(HttpRequest $request): array {
$categoryIds = $this->config->getArray('forum.categories');
if(empty($categoryIds))
return [];
$batchSize = $this->config->getInteger('forum.batch', 6);
$backlogDays = $this->config->getInteger('forum.backlog', 7);
$startId = (string)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT);
$startId = (string)$request->getFilteredParam('start', FILTER_SANITIZE_NUMBER_INT);
$posts = [];
$postInfos = $this->forumCtx->posts->getPosts(
@ -120,11 +125,11 @@ final class SatoriRoutes implements RouteHandler {
* username: string
* }[]
*/
#[HttpGet('/_satori/get-recent-registrations')]
public function getRecentRegistrations(HttpResponseBuilder $response, HttpRequest $request): array {
#[ExactRoute('GET', '/_satori/get-recent-registrations')]
public function getRecentRegistrations(HttpRequest $request): array {
$batchSize = $this->config->getInteger('users.batch', 10);
$backlogDays = $this->config->getInteger('users.backlog', 7);
$startId = (string)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT);
$startId = (string)$request->getFilteredParam('start', FILTER_SANITIZE_NUMBER_INT);
$userInfos = $this->usersCtx->users->getUsers(
after: $startId,

View file

@ -4,8 +4,12 @@ namespace Misuzu\SharpChat;
use RuntimeException;
use Index\Colour\Colour;
use Index\Config\Config;
use Index\Http\{FormHttpContent,HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HandlerAttribute,HttpDelete,HttpGet,HttpOptions,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Content\{FormContent,UrlEncodedFormContent};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\ExactRoute;
use Index\Urls\UrlRegistry;
use Misuzu\Auth\{AuthContext,AuthInfo,Sessions};
use Misuzu\Counters\CountersData;
@ -34,17 +38,10 @@ final class SharpChatRoutes implements RouteHandler {
$this->hashKey = $this->config->getString('hashKey', 'woomy');
}
/** @return int|array{Text: string[], Image: string, Hierarchy: int}[] */
#[HttpOptions('/_sockchat/emotes')]
#[HttpGet('/_sockchat/emotes')]
public function getEmotes(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
$response->setHeader('Access-Control-Allow-Methods', 'GET');
$response->setHeader('Access-Control-Allow-Headers', 'Cache-Control');
if($request->method === 'OPTIONS')
return 204;
/** @return array{Text: string[], Image: string, Hierarchy: int}[] */
#[AccessControl(allowHeaders: ['Cache-Control'])]
#[ExactRoute('GET', '/_sockchat/emotes')]
public function getEmotes(): array {
$this->counters->increment('dev:legacy_emotes_loads');
$emotes = $this->emotes->getEmotes(orderBy: 'order');
@ -66,7 +63,7 @@ final class SharpChatRoutes implements RouteHandler {
return $out;
}
#[HttpGet('/_sockchat/login')]
#[ExactRoute('GET', '/_sockchat/login')]
public function getLogin(HttpResponseBuilder $response, HttpRequest $request): void {
if(!$this->authInfo->loggedIn) {
$response->redirect($this->urls->format('auth-login'));
@ -87,34 +84,11 @@ final class SharpChatRoutes implements RouteHandler {
return in_array($targetId, $whitelist, true);
}
/** @return int|array{ok: false, err: string}|array{ok: true, usr: int, tkn: string} */
#[HttpOptions('/_sockchat/token')]
#[HttpGet('/_sockchat/token')]
public function getToken(HttpResponseBuilder $response, HttpRequest $request): int|array {
$host = $request->hasHeader('Host') ? $request->getHeaderFirstLine('Host') : '';
$origin = $request->hasHeader('Origin') ? $request->getHeaderFirstLine('Origin') : '';
$originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
if(!empty($originHost) && $originHost !== $host) {
$whitelist = $this->config->getArray('origins', []);
if(!in_array($originHost, $whitelist))
return 403;
$originProto = strtolower(parse_url($origin, PHP_URL_SCHEME));
$origin = $originProto . '://' . $originHost;
$response->setHeader('Access-Control-Allow-Origin', $origin);
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
$response->setHeader('Access-Control-Allow-Credentials', 'true');
$response->setHeader('Vary', 'Origin');
}
if($request->method === 'OPTIONS')
return 204;
/** @return array{ok: false, err: string}|array{ok: true, usr: int, tkn: string} */
#[AccessControl(credentials: true)]
#[ExactRoute('GET', '/_sockchat/token')]
public function getToken(HttpRequest $request): array {
$tokenInfo = $this->authInfo->tokenInfo;
if(!$tokenInfo->hasSessionToken)
return ['ok' => false, 'err' => 'token'];
@ -144,25 +118,28 @@ final class SharpChatRoutes implements RouteHandler {
}
/** @return int|void */
#[HttpPost('/_sockchat/bump')]
public function postBump(HttpResponseBuilder $response, HttpRequest $request) {
#[ExactRoute('POST', '/_sockchat/bump')]
#[Before('input:urlencoded')]
public function postBump(HttpRequest $request, UrlEncodedFormContent $content) {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
if(!($request->content instanceof FormHttpContent))
return 400;
$bumpList = [];
foreach($content->params as $name => $value) {
if(count($value) < 1)
continue;
$bumpList = $request->content->getParam('u', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if(!is_array($bumpList))
return 400;
if(str_starts_with($name, 'u[') && str_ends_with($name, ']'))
$bumpList[substr($name, 2, -1)] = $value[0];
}
$userTime = (int)$request->content->getParam('t', FILTER_SANITIZE_NUMBER_INT);
$userTime = (int)$content->getFilteredParam('t', FILTER_SANITIZE_NUMBER_INT);
$signature = "bump#{$userTime}";
foreach($bumpList as $userId => $ipAddr)
$signature .= "#{$userId}:{$ipAddr}";
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
$realHash = hash_hmac('sha256', $signature, $this->hashKey);
if(!hash_equals($realHash, $userHash))
return 403;
@ -185,19 +162,17 @@ final class SharpChatRoutes implements RouteHandler {
* super: bool
* }
*/
#[HttpPost('/_sockchat/verify')]
public function postVerify(HttpResponseBuilder $response, HttpRequest $request): int|array {
#[ExactRoute('POST', '/_sockchat/verify')]
#[Before('input:urlencoded')]
public function postVerify(HttpRequest $request, FormContent $content): int|array {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
if(!($request->content instanceof FormHttpContent))
return ['success' => false, 'reason' => 'request'];
$authMethod = (string)$content->getParam('method');
$authToken = (string)$content->getParam('token');
$ipAddress = (string)$content->getParam('ipaddr');
$authMethod = (string)$request->content->getParam('method');
$authToken = (string)$request->content->getParam('token');
$ipAddress = (string)$request->content->getParam('ipaddr');
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
if(strlen($userHash) !== 64)
return ['success' => false, 'reason' => 'length'];
@ -291,13 +266,13 @@ final class SharpChatRoutes implements RouteHandler {
* expires: string
* }[]
*/
#[HttpGet('/_sockchat/bans/list')]
public function getBanList(HttpResponseBuilder $response, HttpRequest $request): int|array {
#[ExactRoute('GET', '/_sockchat/bans/list')]
public function getBanList(HttpRequest $request): int|array {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
$userTime = (int)$request->getFilteredParam('x', FILTER_SANITIZE_NUMBER_INT);
$realHash = hash_hmac('sha256', "list#{$userTime}", $this->hashKey);
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
@ -335,16 +310,16 @@ final class SharpChatRoutes implements RouteHandler {
* expires: string
* }
*/
#[HttpGet('/_sockchat/bans/check')]
public function getBanCheck(HttpResponseBuilder $response, HttpRequest $request): int|array {
#[ExactRoute('GET', '/_sockchat/bans/check')]
public function getBanCheck(HttpRequest $request): int|array {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
$userTime = (int)$request->getFilteredParam('x', FILTER_SANITIZE_NUMBER_INT);
$ipAddress = (string)$request->getParam('a');
$userId = (string)$request->getParam('u');
$userIdIsName = (int)$request->getParam('n', FILTER_SANITIZE_NUMBER_INT);
$userIdIsName = (int)$request->getFilteredParam('n', FILTER_SANITIZE_NUMBER_INT);
$realHash = hash_hmac('sha256', "check#{$userTime}#{$userId}#{$ipAddress}#{$userIdIsName}", $this->hashKey);
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
@ -371,23 +346,21 @@ final class SharpChatRoutes implements RouteHandler {
];
}
#[HttpPost('/_sockchat/bans/create')]
public function postBanCreate(HttpResponseBuilder $response, HttpRequest $request): int {
#[ExactRoute('POST', '/_sockchat/bans/create')]
#[Before('input:urlencoded')]
public function postBanCreate(HttpRequest $request, FormContent $content): int {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
if(!($request->content instanceof FormHttpContent))
return 400;
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userTime = (int)$request->content->getParam('t', FILTER_SANITIZE_NUMBER_INT);
$userId = (string)$request->content->getParam('ui', FILTER_SANITIZE_NUMBER_INT);
$userAddr = (string)$request->content->getParam('ua');
$modId = (string)$request->content->getParam('mi', FILTER_SANITIZE_NUMBER_INT);
$modAddr = (string)$request->content->getParam('ma');
$duration = (int)$request->content->getParam('d', FILTER_SANITIZE_NUMBER_INT);
$isPermanent = (int)$request->content->getParam('p', FILTER_SANITIZE_NUMBER_INT);
$reason = (string)$request->content->getParam('r');
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
$userTime = (int)$content->getFilteredParam('t', FILTER_SANITIZE_NUMBER_INT);
$userId = (string)$content->getFilteredParam('ui', FILTER_SANITIZE_NUMBER_INT);
$userAddr = (string)$content->getParam('ua');
$modId = (string)$content->getFilteredParam('mi', FILTER_SANITIZE_NUMBER_INT);
$modAddr = (string)$content->getParam('ma');
$duration = (int)$content->getFilteredParam('d', FILTER_SANITIZE_NUMBER_INT);
$isPermanent = (int)$content->getFilteredParam('p', FILTER_SANITIZE_NUMBER_INT);
$reason = (string)$content->getParam('r');
$signature = implode('#', [
'create', $userTime, $userId, $userAddr,
@ -447,13 +420,13 @@ final class SharpChatRoutes implements RouteHandler {
return 201;
}
#[HttpDelete('/_sockchat/bans/revoke')]
public function deleteBanRevoke(HttpResponseBuilder $response, HttpRequest $request): int {
#[ExactRoute('DELETE', '/_sockchat/bans/revoke')]
public function deleteBanRevoke(HttpRequest $request): int {
if(!$request->hasHeader('X-SharpChat-Signature'))
return 400;
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
$userHash = (string)$request->getHeaderLine('X-SharpChat-Signature');
$userTime = (int)$request->getFilteredParam('x', FILTER_SANITIZE_NUMBER_INT);
$type = (string)$request->getParam('t');
$subject = (string)$request->getParam('s');

View file

@ -16,7 +16,7 @@ final class Tools {
return false;
if(isset($parsed['scheme'])) {
$isSecure ??= filter_has_var(INPUT_SERVER, 'HTTPS');
$isSecure ??= !empty($_SERVER['HTTPS']);
// only allow https when secure
if($isSecure && $parsed['scheme'] !== 'https')
@ -28,7 +28,7 @@ final class Tools {
}
if(isset($parsed['host'])) {
$host ??= filter_input(INPUT_SERVER, 'HTTP_HOST');
$host ??= $_SERVER['HTTP_HOST'];
// ensure host is identical
if($parsed['host'] !== $host)

View file

@ -5,6 +5,7 @@ use InvalidArgumentException;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{Misuzu,Perm};
use Misuzu\Auth\AuthInfo;
@ -24,14 +25,14 @@ class AssetsRoutes implements RouteHandler, UrlSource {
// allow staff viewing profile to still see banned user assets
// should change the Referer check with some query param only applied when needed
return $this->authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE)
&& parse_url($request->getHeaderFirstLine('Referer'), PHP_URL_PATH) === $this->urls->format('user-profile');
&& parse_url($request->getHeaderLine('Referer'), PHP_URL_PATH) === $this->urls->format('user-profile');
return true;
}
/** @return void */
#[HttpGet('/assets/avatar')]
#[HttpGet('/assets/avatar/([0-9]+)(?:\.[a-z]+)?')]
#[ExactRoute('GET', '/assets/avatar')]
#[PatternRoute('GET', '/assets/avatar/([0-9]+)(?:\.[a-z]+)?')]
#[UrlFormat('user-avatar', '/assets/avatar/<user>', ['res' => '<res>'])]
public function getAvatar(HttpResponseBuilder $response, HttpRequest $request, string $userId = '') {
$assetInfo = new StaticUserImageAsset(Misuzu::PATH_PUBLIC . '/images/no-avatar.png', Misuzu::PATH_PUBLIC);
@ -53,8 +54,8 @@ class AssetsRoutes implements RouteHandler, UrlSource {
}
/** @return string|void */
#[HttpGet('/assets/profile-background')]
#[HttpGet('/assets/profile-background/([0-9]+)(?:\.[a-z]+)?')]
#[ExactRoute('GET', '/assets/profile-background')]
#[PatternRoute('GET', '/assets/profile-background/([0-9]+)(?:\.[a-z]+)?')]
#[UrlFormat('user-background', '/assets/profile-background/<user>')]
public function getProfileBackground(HttpResponseBuilder $response, HttpRequest $request, string $userId = '') {
try {
@ -78,9 +79,9 @@ class AssetsRoutes implements RouteHandler, UrlSource {
}
/** @return string|void */
#[HttpGet('/user-assets.php')]
#[ExactRoute('GET', '/user-assets.php')]
public function getUserAssets(HttpResponseBuilder $response, HttpRequest $request) {
$userId = (string)$request->getParam('u', FILTER_SANITIZE_NUMBER_INT);
$userId = (string)$request->getFilteredParam('u', FILTER_SANITIZE_NUMBER_INT);
$mode = (string)$request->getParam('m');
if($mode === 'avatar') {
@ -102,8 +103,8 @@ class AssetsRoutes implements RouteHandler, UrlSource {
$fileName = $assetInfo->getFileName();
if($assetInfo instanceof UserAssetScalableInterface) {
$dimensions = (int)($request->getParam('res', FILTER_SANITIZE_NUMBER_INT)
?? $request->getParam('r', FILTER_SANITIZE_NUMBER_INT));
$dimensions = (int)($request->getFilteredParam('res', FILTER_SANITIZE_NUMBER_INT)
?? $request->getFilteredParam('r', FILTER_SANITIZE_NUMBER_INT));
if($dimensions > 0) {
$assetInfo->ensureScaledExists($dimensions);

View file

@ -3,7 +3,9 @@ namespace Misuzu\WebFinger;
use Index\XArray;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpOptions,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Routes\ExactRoute;
class WebFingerRoutes implements RouteHandler {
use RouteHandlerCommon;
@ -69,13 +71,9 @@ class WebFingerRoutes implements RouteHandler {
* }[]
* }
*/
#[HttpOptions('/.well-known/webfinger')]
#[HttpGet('/.well-known/webfinger')]
#[AccessControl]
#[ExactRoute('GET', '/.well-known/webfinger')]
public function getWebFinger(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
$resInfos = $this->registry->resolve(trim((string)$request->getParam('resource')));
if(empty($resInfos))
return 404;

View file

@ -7,7 +7,7 @@
{% block content %}
<form class="container auth__container auth__login js-login-form" method="post" action="{{ url('auth-login') }}">
{{ input_csrf() }}
{{ input_hidden('login[redirect]', login_redirect) }}
{{ input_hidden('redirect', login_redirect) }}
<div class="container__title">
<div class="container__title__background"></div>
@ -45,7 +45,7 @@
Username
</div>
<div class="auth__label__value">
{{ input_text('login[username]', 'auth__label__input js-login-username', login_username, 'text', '', true, null, 1, not login_welcome) }}
{{ input_text('username', 'auth__label__input js-login-username', login_username, 'text', '', true, null, 1, not login_welcome) }}
</div>
</label>
@ -57,7 +57,7 @@
{% endif %}
</div>
<div class="auth__label__value">
{{ input_text('login[password]', 'auth__label__input', '', 'password', '', true, null, 2, login_welcome) }}
{{ input_text('password', 'auth__label__input', '', 'password', '', true, null, 2, login_welcome) }}
</div>
</label>

View file

@ -24,7 +24,7 @@
E-mail
</div>
<div class="auth__label__value">
{{ input_text('forgot[email]', 'auth__label__input', password_email, 'text', '', true, null, 1) }}
{{ input_text('email', 'auth__label__input', password_email, 'text', '', true, null, 1) }}
</div>
</label>

View file

@ -8,7 +8,7 @@
<form class="container auth__container auth__password" method="post" action="{{ url('auth-reset') }}">
{{ container_title('<i class="fas fa-user-lock fa-fw"></i> Resetting password for ' ~ password_user.name) }}
{{ input_hidden('reset[user]', password_user.id) }}
{{ input_hidden('user', password_user.id) }}
{{ input_csrf() }}
{% if password_notices|length > 0 %}
@ -28,14 +28,14 @@
{% endif %}
{% if password_verification|length == 12 %}
{{ input_hidden('reset[verification]', password_verification) }}
{{ input_hidden('verification', password_verification) }}
{% else %}
<label class="auth__label">
<div class="auth__label__text">
Verification Code
</div>
<div class="auth__label__value">
{{ input_text('reset[verification]', 'input__text--monospace auth__label__input', '', 'text', '', true, {'maxlength':12}, 1) }}
{{ input_text('verification', 'input__text--monospace auth__label__input', '', 'text', '', true, {'maxlength':12}, 1) }}
</div>
</label>
{% endif %}
@ -45,7 +45,7 @@
New Password
</div>
<div class="auth__label__value">
{{ input_text('reset[password][new]', 'auth__label__input', '', 'password', '', true, null, 2) }}
{{ input_text('password_new', 'auth__label__input', '', 'password', '', true, null, 2) }}
</div>
</label>
@ -54,7 +54,7 @@
Confirm Password
</div>
<div class="auth__label__value">
{{ input_text('reset[password][confirm]', 'auth__label__input', '', 'password', '', true, null, 3) }}
{{ input_text('password_confirm', 'auth__label__input', '', 'password', '', true, null, 3) }}
</div>
</label>

View file

@ -55,7 +55,7 @@
Username
</div>
<div class="auth__label__value">
{{ input_text('register[username]', 'auth__label__input', register_username, 'text', '', true, null, 10, true) }}
{{ input_text('username', 'auth__label__input', register_username, 'text', '', true, null, 10, true) }}
</div>
</label>
@ -64,7 +64,7 @@
Password
</div>
<div class="auth__label__value">
{{ input_text('register[password]', 'auth__label__input', '', 'password', '', true, null, 20) }}
{{ input_text('password', 'auth__label__input', '', 'password', '', true, null, 20) }}
</div>
</label>
@ -73,7 +73,7 @@
Confirm Password
</div>
<div class="auth__label__value">
{{ input_text('register[password_confirm]', 'auth__label__input', '', 'password', '', true, null, 30) }}
{{ input_text('password_confirm', 'auth__label__input', '', 'password', '', true, null, 30) }}
</div>
</label>
@ -82,7 +82,7 @@
E-mail
</div>
<div class="auth__label__value">
{{ input_text('register[email]', 'auth__label__input', register_email, 'text', '', true, null, 40) }}
{{ input_text('email', 'auth__label__input', register_email, 'text', '', true, null, 40) }}
</div>
</label>
@ -91,7 +91,7 @@
What is the outcome of nine plus ten?
</div>
<div class="auth__label__value">
{{ input_text('register[question]', 'auth__label__input', '', 'text', '', true, null, 50) }}
{{ input_text('question', 'auth__label__input', '', 'text', '', true, null, 50) }}
</div>
</label>

View file

@ -9,8 +9,8 @@
{{ container_title('<i class="fas fa-user-shield fa-fw"></i> Two Factor Authentication') }}
{{ input_csrf() }}
{{ input_hidden('twofactor[redirect]', twofactor_redirect) }}
{{ input_hidden('twofactor[token]', twofactor_token) }}
{{ input_hidden('redirect', twofactor_redirect) }}
{{ input_hidden('token', twofactor_token) }}
{% if twofactor_notices|length > 0 %}
<div class="warning auth__warning">
@ -27,7 +27,7 @@
Code
</div>
<div class="auth__label__value">
{{ input_text('twofactor[code]', 'input__text--monospace input__text--centre auth__label__input', '', 'text', '', true, {'maxlength': 6, 'inputmode': 'numeric'}, 1) }}
{{ input_text('code', 'input__text--monospace input__text--centre auth__label__input', '', 'text', '', true, {'maxlength': 6, 'inputmode': 'numeric'}, 1) }}
</div>
</label>

View file

@ -8,15 +8,15 @@
{% set is_opening = not is_reply or posting_post.isOriginalPost|default(false) %}
{% block content %}
<form method="post" action="{{ url('forum-' ~ (is_reply ? 'post' : 'topic') ~ '-create') }}" class="js-forum-posting">
{{ input_hidden('post[' ~ (is_reply ? 'topic' : 'forum') ~ ']', is_reply ? posting_topic.id : posting_forum.id) }}
{{ input_hidden('post[mode]', posting_mode) }}
<form method="post" action="{{ url('forum-' ~ (is_reply ? 'post' : 'topic') ~ '-create') }}" class="js-forum-posting" enctype="multipart/form-data">
{{ input_hidden((is_reply ? 'topic' : 'forum'), is_reply ? posting_topic.id : posting_forum.id) }}
{{ input_hidden('mode', posting_mode) }}
{{ input_csrf() }}
{{ forum_header(
is_reply and not is_opening
? posting_topic.title
: input_text(
'post[title]',
'title',
'forum__header__input',
posting_defaults.title|default(posting_topic.title|default('')),
'text',
@ -30,7 +30,7 @@
) }}
{% if posting_post is defined %}
{{ input_hidden('post[id]', posting_post.info.id) }}
{{ input_hidden('id', posting_post.info.id) }}
{% endif %}
{% if posting_notices|length > 0 %}
@ -74,25 +74,25 @@
{% endif %}
</span>
</div>
<textarea name="post[text]" class="forum__post__text forum__post__text--edit js-forum-posting-text js-ctrl-enter-submit" placeholder="Type your post content here...">{{ posting_defaults.text|default(posting_post.info.body|default('')) }}</textarea>
<textarea name="text" class="forum__post__text forum__post__text--edit js-forum-posting-text js-ctrl-enter-submit" placeholder="Type your post content here...">{{ posting_defaults.text|default(posting_post.info.body|default('')) }}</textarea>
<div class="forum__post__text js-forum-posting-preview" hidden></div>
<div class="forum__post__actions js-forum-posting-actions"></div>
<div class="forum__post__options">
<div class="forum__post__settings">
{{ input_select(
'post[parser]', parser_options(),
'parser', parser_options(),
posting_defaults.parser|default(posting_post.info.bodyFormat|default(posting_user_preferred_parser)).value,
null, null, false, 'forum__post__dropdown js-forum-posting-parser'
) }}
{% if is_opening and posting_types|length > 1 %}
<select class="input__select forum__post__dropdown" name="post[type]">
<select class="input__select forum__post__dropdown" name="type">
{% for type_name, type_title in posting_types %}
<option value="{{ type_name }}"{% if type_name == posting_topic.typeString|default('discussion') %} selected{% endif %}>{{ type_title }}</option>
{% endfor %}
</select>
{% endif %}
{{ input_checkbox(
'post[signature]',
'signature',
'Display Signature',
posting_defaults.signature is not null
? posting_defaults.signature : (

View file

@ -6,7 +6,7 @@
<div class="container">
{{ container_title('<i class="fas fa-ban fa-fw"></i> Issuing a ban on user #' ~ ban_user.id ~ ' ' ~ ban_user.name) }}
<form method="post" enctype="multipart/form-data" action="{{ url('manage-users-ban', {'user': ban_user.id}) }}" class="manage__ban">
<form method="post" action="{{ url('manage-users-ban', {'user': ban_user.id}) }}" class="manage__ban">
{{ input_csrf() }}
<div class="manage__ban__field">

View file

@ -6,7 +6,7 @@
<div class="container">
{{ container_title('<i class="fas fa-sticky-note fa-fw"></i> ' ~ (note_new ? ('Adding mod note to ' ~ note_user.name) : ('Editing mod note #' ~ note_info.id))) }}
<form method="post" enctype="multipart/form-data" action="{{ url('manage-users-note', note_new ? {'user': note_user.id} : {'note': note_info.id}) }}" class="manage__note {{ note_new ? 'manage__note--edit' : 'manage__note--view' }}">
<form method="post" action="{{ url('manage-users-note', note_new ? {'user': note_user.id} : {'note': note_info.id}) }}" class="manage__note {{ note_new ? 'manage__note--edit' : 'manage__note--view' }}">
{{ input_csrf() }}
<div class="manage__note__header">

View file

@ -6,7 +6,7 @@
<div class="container">
{{ container_title('<i class="fas fa-exclamation-circle fa-fw"></i> Issuing a warning to user #' ~ warn_user.id ~ ' ' ~ warn_user.name) }}
<form method="post" enctype="multipart/form-data" action="{{ url('manage-users-warning', {'user': warn_user.id}) }}" class="manage__warning">
<form method="post" action="{{ url('manage-users-warning', {'user': warn_user.id}) }}" class="manage__warning">
{{ input_csrf() }}
<div class="manage__warning__field">

View file

@ -16,7 +16,7 @@
Select
</label>
{{ input_checkbox_raw('avatar[delete]', false, 'profile__header__avatar__check', '', false, {'id':'avatar-delete'}) }}
{{ input_checkbox_raw('avatar_delete', false, 'profile__header__avatar__check', null, false, {'id':'avatar-delete'}) }}
<label class="input__button profile__header__avatar__option profile__header__avatar__option--delete"
for="avatar-delete">
Remove

View file

@ -15,7 +15,7 @@
{{ input_csrf() }}
{% if perms.edit_avatar %}
{{ input_file_raw('avatar[file]', 'profile__hidden', ['image/png', 'image/jpeg', 'image/gif'], {'id':'avatar-selection'}) }}
{{ input_file_raw('avatar_file', 'profile__hidden', ['image/png', 'image/jpeg', 'image/gif'], {'id':'avatar-selection'}) }}
<script>
function updateAvatarPreview(name, url, preview) {
@ -120,7 +120,7 @@
</div>
{% if profile_is_editing %}
{{ input_text('profile[' ~ fieldInfo.name ~ ']', 'profile__accounts__input', profile_fields_raw_values[fieldInfo.name]|default('')) }}
{{ input_text('profile_' ~ fieldInfo.name, 'profile__accounts__input', profile_fields_raw_values[fieldInfo.name]|default('')) }}
{% else %}
<div class="profile__accounts__value">
{% if profile_fields_link_values[fieldInfo.name] is defined %}
@ -212,14 +212,14 @@
<div class="profile__birthdate__title">
Day
</div>
{{ input_select('birthdate[day]', ['-']|merge(range(1, 31)), birthdate_info.day|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }}
{{ input_select('birth_day', ['-']|merge(range(1, 31)), birthdate_info.day|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }}
</label>
<label class="profile__birthdate__label">
<div class="profile__birthdate__title">
Month
</div>
{{ input_select('birthdate[month]', ['-']|merge(range(1, 12)), birthdate_info.month|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--month') }}
{{ input_select('birth_month', ['-']|merge(range(1, 12)), birthdate_info.month|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--month') }}
</label>
</div>
@ -228,7 +228,7 @@
<div class="profile__birthdate__title">
Year (may be left empty)
</div>
{{ input_select('birthdate[year]', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), birthdate_info.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }}
{{ input_select('birth_year', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), birthdate_info.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }}
</label>
</div>
</div>

View file

@ -2,7 +2,8 @@
<?php
namespace Misuzu;
use Index\Http\{HttpHeader,HttpHeaders,HttpRequest};
use Index\Http\{HttpRequest,HttpUri};
use Index\Http\Streams\NullStream;
require_once __DIR__ . '/../misuzu.php';
@ -76,10 +77,7 @@ handleValue:
$hostName ??= 'localhost';
// this should really not be necessary, mostly done to make sure the url registry is available
$msz->createRouting(new HttpRequest('::1', true, 'XX', '1.1', 'GET', '/', [], [], new HttpHeaders([
new HttpHeader('Host', $hostName),
]), null));
$msz->createRouting(new HttpRequest('1.1', ['Host' => [$hostName]], NullStream::instance(), [], 'GET', HttpUri::createUri('/'), [], []));
$msz->startTemplating(false);
$ctx = $msz->templating->load(implode(' ', array_slice($argv, $pathIndex)));