Made the router code less janky.
This commit is contained in:
parent
3fead75834
commit
ca0f1ecb39
26 changed files with 453 additions and 609 deletions
|
@ -6,9 +6,7 @@
|
||||||
"geoip2/geoip2": "~2.0",
|
"geoip2/geoip2": "~2.0",
|
||||||
"twig/extensions": "^1.5",
|
"twig/extensions": "^1.5",
|
||||||
"jublonet/codebird-php": "^3.1",
|
"jublonet/codebird-php": "^3.1",
|
||||||
"chillerlan/php-qrcode": "^3.0",
|
"chillerlan/php-qrcode": "^3.0"
|
||||||
"psr/http-message": "^1.0",
|
|
||||||
"psr/http-server-handler": "^1.0"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
"classmap": [
|
||||||
|
|
105
composer.lock
generated
105
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "bfa28642db2d2fcc44e6d1e872948d44",
|
"content-hash": "4e5f02da04a08b7717736b58d033f8d3",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "chillerlan/php-qrcode",
|
"name": "chillerlan/php-qrcode",
|
||||||
|
@ -684,109 +684,6 @@
|
||||||
"homepage": "https://github.com/maxmind/web-service-common-php",
|
"homepage": "https://github.com/maxmind/web-service-common-php",
|
||||||
"time": "2020-05-06T14:07:26+00:00"
|
"time": "2020-05-06T14:07:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "psr/http-message",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/php-fig/http-message.git",
|
|
||||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
|
|
||||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.0.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Psr\\Http\\Message\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "PHP-FIG",
|
|
||||||
"homepage": "http://www.php-fig.org/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Common interface for HTTP messages",
|
|
||||||
"homepage": "https://github.com/php-fig/http-message",
|
|
||||||
"keywords": [
|
|
||||||
"http",
|
|
||||||
"http-message",
|
|
||||||
"psr",
|
|
||||||
"psr-7",
|
|
||||||
"request",
|
|
||||||
"response"
|
|
||||||
],
|
|
||||||
"time": "2016-08-06T14:39:51+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "psr/http-server-handler",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/php-fig/http-server-handler.git",
|
|
||||||
"reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
|
|
||||||
"reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.0",
|
|
||||||
"psr/http-message": "^1.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": "http://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"
|
|
||||||
],
|
|
||||||
"time": "2018-10-30T16:46:14+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "swiftmailer/swiftmailer",
|
"name": "swiftmailer/swiftmailer",
|
||||||
"version": "v6.2.3",
|
"version": "v6.2.3",
|
||||||
|
|
|
@ -57,8 +57,8 @@ spl_autoload_register(function(string $className) {
|
||||||
require_once $classPath;
|
require_once $classPath;
|
||||||
});
|
});
|
||||||
|
|
||||||
class_alias(\Misuzu\Http\HttpServerRequestMessage::class, '\Misuzu\Http\Handlers\Request');
|
class_alias(\Misuzu\Http\HttpResponseMessage::class, '\HttpResponse');
|
||||||
class_alias(\Misuzu\Http\Routing\RouterResponseMessage::class, '\Misuzu\Http\Handlers\Response');
|
class_alias(\Misuzu\Http\HttpRequestMessage::class, '\HttpRequest');
|
||||||
|
|
||||||
require_once 'utility.php';
|
require_once 'utility.php';
|
||||||
require_once 'src/audit_log.php';
|
require_once 'src/audit_log.php';
|
||||||
|
|
|
@ -1,49 +1,51 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Http\HttpServerRequestMessage;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Misuzu\Http\Filters\Filter;
|
|
||||||
use Misuzu\Http\Handlers\Handler;
|
|
||||||
use Misuzu\Http\Routing\Router;
|
use Misuzu\Http\Routing\Router;
|
||||||
use Misuzu\Http\Routing\Route;
|
use Misuzu\Http\Routing\Route;
|
||||||
|
|
||||||
require_once '../misuzu.php';
|
require_once '../misuzu.php';
|
||||||
|
|
||||||
$request = HttpServerRequestMessage::fromGlobals();
|
$request = HttpRequestMessage::fromGlobals();
|
||||||
|
|
||||||
$router = new Router;
|
Router::setHandlerFormat('\Misuzu\Http\Handlers\%sHandler');
|
||||||
$router->setInstance();
|
Router::setFilterFormat('\Misuzu\Http\Filters\%sFilter');
|
||||||
$router->addRoutes(
|
Router::addRoutes(
|
||||||
// Home
|
// Home
|
||||||
Route::get('/', Handler::call('index@Home')),
|
Route::get('/', 'index', 'Home'),
|
||||||
|
|
||||||
// Assets
|
// Assets
|
||||||
Route::get('/assets/([a-zA-Z0-9\-]+)\.(css|js)', true, Handler::call('view@Assets')),
|
Route::get('/assets/([a-zA-Z0-9\-]+)\.(css|js)', 'view', 'Assets'),
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
Route::get('/info', Handler::call('index@Info')),
|
Route::get('/info', 'index', 'Info'),
|
||||||
Route::get('/info/([A-Za-z0-9_/]+)', true, Handler::call('page@Info')),
|
Route::get('/info/([A-Za-z0-9_/]+)', 'page', 'Info'),
|
||||||
|
|
||||||
// Forum
|
// Forum
|
||||||
Route::get('/forum/mark-as-read', Handler::call('markAsReadGET@Forum'))->addFilters(Filter::call('EnforceLogIn')),
|
Route::group('/forum', 'Forum')->addChildren(
|
||||||
Route::post('/forum/mark-as-read', Handler::call('markAsReadPOST@Forum'))->addFilters(Filter::call('EnforceLogIn'), Filter::call('ValidateCsrf')),
|
Route::get('/mark-as-read', 'markAsReadGET')->addFilters('EnforceLogIn'),
|
||||||
|
Route::post('/mark-as-read', 'markAsReadPOST')->addFilters('EnforceLogIn', 'ValidateCsrf'),
|
||||||
|
),
|
||||||
|
|
||||||
// Sock Chat
|
// Sock Chat
|
||||||
Route::create(['GET', 'POST'], '/_sockchat.php', Handler::call('phpFile@SockChat')),
|
Route::create(['GET', 'POST'], '/_sockchat.php', 'phpFile', 'SockChat'),
|
||||||
Route::get('/_sockchat/emotes', Handler::call('emotes@SockChat')),
|
Route::group('/_sockchat', 'SockChat')->addChildren(
|
||||||
Route::get('/_sockchat/bans', Handler::call('bans@SockChat')),
|
Route::get('/emotes', 'emotes'),
|
||||||
Route::get('/_sockchat/login', Handler::call('login@SockChat')),
|
Route::get('/bans', 'bans'),
|
||||||
Route::post('/_sockchat/bump', Handler::call('bump@SockChat')),
|
Route::get('/login', 'login'),
|
||||||
Route::post('/_sockchat/verify', Handler::call('verify@SockChat')),
|
Route::post('/bump', 'bump'),
|
||||||
|
Route::post('/verify', 'verify'),
|
||||||
|
),
|
||||||
|
|
||||||
// Redirects
|
// Redirects
|
||||||
Route::get('/index.php', Handler::redirect(url('index'), true)),
|
Route::get('/index.php', url('index')),
|
||||||
Route::get('/info.php', Handler::redirect(url('info'), true)),
|
Route::get('/info.php', url('info')),
|
||||||
Route::get('/info.php/([A-Za-z0-9_/]+)', true, Handler::call('redir@Info')),
|
Route::get('/info.php/([A-Za-z0-9_/]+)', 'redir', 'Info'),
|
||||||
Route::get('/auth.php', Handler::call('legacy@Auth'))
|
Route::get('/auth.php', 'legacy', 'Auth'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$response = $router->handle($request);
|
$response = Router::handle($request);
|
||||||
$response->setHeader('X-Powered-By', 'Misuzu');
|
$response->setHeader('X-Powered-By', 'Misuzu');
|
||||||
|
|
||||||
$responseStatus = $response->getStatusCode();
|
$responseStatus = $response->getStatusCode();
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
namespace Misuzu\Http\Filters;
|
namespace Misuzu\Http\Filters;
|
||||||
|
|
||||||
use Misuzu\Http\HttpResponseMessage;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
class EnforceLogInFilter implements FilterInterface {
|
class EnforceLogInFilter implements FilterInterface {
|
||||||
public function process(ServerRequestInterface $request): ?ResponseInterface {
|
public function process(HttpRequestMessage $request): ?HttpResponseMessage {
|
||||||
if(!user_session_active())
|
if(!user_session_active())
|
||||||
return new HttpResponseMessage(403);
|
return new HttpResponseMessage(403);
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
namespace Misuzu\Http\Filters;
|
namespace Misuzu\Http\Filters;
|
||||||
|
|
||||||
use Misuzu\Http\HttpResponseMessage;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
class EnforceLogOutFilter implements FilterInterface {
|
class EnforceLogOutFilter implements FilterInterface {
|
||||||
public function process(ServerRequestInterface $request): ?ResponseInterface {
|
public function process(HttpRequestMessage $request): ?HttpResponseMessage {
|
||||||
if(user_session_active())
|
if(user_session_active())
|
||||||
return new HttpResponseMessage(404);
|
return new HttpResponseMessage(404);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu\Http\Filters;
|
|
||||||
|
|
||||||
final class Filter {
|
|
||||||
public static function call(string $className): string {
|
|
||||||
return __NAMESPACE__ . '\\' . $className . 'Filter';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Filters;
|
namespace Misuzu\Http\Filters;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
|
|
||||||
interface FilterInterface {
|
interface FilterInterface {
|
||||||
public function process(ServerRequestInterface $request): ?ResponseInterface;
|
public function process(HttpRequestMessage $request): ?HttpResponseMessage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,15 @@ namespace Misuzu\Http\Filters;
|
||||||
|
|
||||||
use Misuzu\CSRF;
|
use Misuzu\CSRF;
|
||||||
use Misuzu\Http\HttpResponseMessage;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
class ValidateCsrfFilter implements FilterInterface {
|
class ValidateCsrfFilter implements FilterInterface {
|
||||||
public function process(ServerRequestInterface $request): ?ResponseInterface {
|
public function process(HttpRequestMessage $request): ?HttpResponseMessage {
|
||||||
if($request->getMethod() !== 'GET' && $request->getMethod() !== 'DELETE') {
|
if($request->getMethod() !== 'GET' && $request->getMethod() !== 'DELETE') {
|
||||||
$token = $request->getHeaderLine('X-Misuzu-CSRF');
|
$token = $request->getHeaderLine('X-Misuzu-CSRF');
|
||||||
|
|
||||||
if(empty($token)) {
|
if(empty($token))
|
||||||
$body = $request->getParsedBody();
|
$token = $request->getBodyParam('_csrf', FILTER_SANITIZE_STRING);
|
||||||
$token = isset($body['_csrf']) && is_string($body['_csrf']) ? $body['_csrf'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(empty($token) || !CSRF::validate($token))
|
if(empty($token) || !CSRF::validate($token))
|
||||||
return new HttpResponseMessage(400);
|
return new HttpResponseMessage(400);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
use Misuzu\GitInfo;
|
use Misuzu\GitInfo;
|
||||||
|
|
||||||
final class AssetsHandler extends Handler {
|
final class AssetsHandler extends Handler {
|
||||||
|
@ -38,12 +40,12 @@ final class AssetsHandler extends Handler {
|
||||||
return $str;
|
return $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function view(Response $response, Request $request, string $name, string $type) {
|
public static function view(HttpResponse $response, HttpRequest $request, string $name, string $type) {
|
||||||
$entityTag = sprintf('W/"%s.%s/%s"', $name, $type, GitInfo::hash());
|
$entityTag = sprintf('W/"%s.%s/%s"', $name, $type, GitInfo::hash());
|
||||||
|
|
||||||
if(!MSZ_DEBUG && $name === 'debug')
|
if(!MSZ_DEBUG && $name === 'debug')
|
||||||
return 404;
|
return 404;
|
||||||
if(!MSZ_DEBUG && filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH') === $entityTag)
|
if(!MSZ_DEBUG && $request->getHeaderLine('If-None-Match') === $entityTag)
|
||||||
return 304;
|
return 304;
|
||||||
|
|
||||||
if(array_key_exists($type, self::TYPES)) {
|
if(array_key_exists($type, self::TYPES)) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
|
|
||||||
final class AuthHandler extends Handler {
|
final class AuthHandler extends Handler {
|
||||||
public static function legacy(Response $response, Request $request): void {
|
public static function legacy(HttpResponse $response, HttpRequest $request): void {
|
||||||
$query = $request->getQueryParams();
|
$mode = $request->getQueryParam('m', FILTER_SANITIZE_STRING);
|
||||||
$mode = isset($query['m']) && is_string($query['m']) ? $query['m'] : '';
|
|
||||||
$destination = [
|
$destination = [
|
||||||
'logout' => 'auth-logout',
|
'logout' => 'auth-logout',
|
||||||
'reset' => 'auth-reset',
|
'reset' => 'auth-reset',
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
use Misuzu\CSRF;
|
use Misuzu\CSRF;
|
||||||
|
|
||||||
final class ForumHandler extends Handler {
|
final class ForumHandler extends Handler {
|
||||||
public function markAsReadGET(Response $response, Request $request): void {
|
public function markAsReadGET(HttpResponse $response, HttpRequest $request): void {
|
||||||
$query = $request->getQueryParams();
|
$forumId = (int)$request->getQueryParam('forum', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$forumId = isset($query['forum']) && is_string($query['forum']) ? (int)$query['forum'] : null;
|
|
||||||
$response->setTemplate('confirm', [
|
$response->setTemplate('confirm', [
|
||||||
'title' => 'Mark forum as read',
|
'title' => 'Mark forum as read',
|
||||||
'message' => 'Are you sure you want to mark ' . ($forumId === null ? 'the entire' : 'this') . ' forum as read?',
|
'message' => 'Are you sure you want to mark ' . ($forumId === null ? 'the entire' : 'this') . ' forum as read?',
|
||||||
|
@ -17,9 +18,8 @@ final class ForumHandler extends Handler {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markAsReadPOST(Response $response, Request $request) {
|
public function markAsReadPOST(HttpResponse $response, HttpRequest $request) {
|
||||||
$body = $request->getParsedBody();
|
$forumId = (int)$request->getBodyParam('forum', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$forumId = isset($body['forum']) && is_string($body['forum']) ? (int)$body['forum'] : null;
|
|
||||||
forum_mark_read($forumId, user_session_current('user_id'));
|
forum_mark_read($forumId, user_session_current('user_id'));
|
||||||
|
|
||||||
$response->redirect(
|
$response->redirect(
|
||||||
|
|
|
@ -1,15 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
abstract class Handler {
|
abstract class Handler {}
|
||||||
public static function call(string $name): array {
|
|
||||||
[$funcName, $className] = explode('@', $name, 2);
|
|
||||||
return [__NAMESPACE__ . '\\' . $className . 'Handler', $funcName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function redirect(string $location, bool $permanent = false): callable {
|
|
||||||
return function (Response $resp) use ($location, $permanent): void {
|
|
||||||
$resp->redirect($location, $permanent);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
use Misuzu\Config;
|
use Misuzu\Config;
|
||||||
use Misuzu\DB;
|
use Misuzu\DB;
|
||||||
|
|
||||||
final class HomeHandler extends Handler {
|
final class HomeHandler extends Handler {
|
||||||
public function index(Response $response, Request $request): void {
|
public function index(HttpResponse $response, HttpRequest $request): void {
|
||||||
if(Config::get('social.embed_linked', Config::TYPE_BOOL)) {
|
if(Config::get('social.embed_linked', Config::TYPE_BOOL)) {
|
||||||
$linkedData = [
|
$linkedData = [
|
||||||
'name' => Config::get('site.name', Config::TYPE_STR, 'Misuzu'),
|
'name' => Config::get('site.name', Config::TYPE_STR, 'Misuzu'),
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
|
|
||||||
final class InfoHandler extends Handler {
|
final class InfoHandler extends Handler {
|
||||||
public function index(Response $response): void {
|
public function index(HttpResponse $response): void {
|
||||||
$response->setTemplate('info.index');
|
$response->setTemplate('info.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function redir(Response $response, Request $request, string $name = ''): void {
|
public function redir(HttpResponse $response, HttpRequest $request, string $name = ''): void {
|
||||||
$response->redirect(url('info', ['title' => $name]), true);
|
$response->redirect(url('info', ['title' => $name]), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function page(Response $response, Request $request, string $name) {
|
public function page(HttpResponse $response, HttpRequest $request, string $name) {
|
||||||
$document = [
|
$document = [
|
||||||
'content' => '',
|
'content' => '',
|
||||||
'title' => '',
|
'title' => '',
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
namespace Misuzu\Http\Handlers;
|
namespace Misuzu\Http\Handlers;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use HttpResponse;
|
||||||
|
use HttpRequest;
|
||||||
use Misuzu\Base64;
|
use Misuzu\Base64;
|
||||||
use Misuzu\Config;
|
use Misuzu\Config;
|
||||||
use Misuzu\DB;
|
use Misuzu\DB;
|
||||||
use Misuzu\Emoticon;
|
use Misuzu\Emoticon;
|
||||||
use Misuzu\Http\Stream;
|
use Misuzu\Stream;
|
||||||
use Misuzu\Users\ChatToken;
|
use Misuzu\Users\ChatToken;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
|
@ -47,22 +49,22 @@ final class SockChatHandler extends Handler {
|
||||||
$this->hashKey = file_get_contents($hashKeyPath);
|
$this->hashKey = file_get_contents($hashKeyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function phpFile(Response $response, Request $request) {
|
public function phpFile(HttpResponse $response, HttpRequest $request) {
|
||||||
$query = $request->getQueryParams();
|
$query = $request->getQueryParams();
|
||||||
|
|
||||||
if(isset($query['emotes']))
|
if(isset($query['emotes']))
|
||||||
return $this->emotes($response, $request);
|
return $this->emotes($response, $request);
|
||||||
|
|
||||||
if(isset($query['bans']) && is_string($query['bans']))
|
if(isset($query['bans']) && is_string($query['bans']))
|
||||||
return $this->bans($response, $request->withHeader('X-SharpChat-Signature', $query['bans']));
|
return $this->bans($response, $request->setHeader('X-SharpChat-Signature', $query['bans']));
|
||||||
|
|
||||||
$body = $request->getParsedBody();
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
if(isset($body['bump'], $body['hash']) && is_string($body['bump']) && is_string($body['hash']))
|
if(isset($body['bump'], $body['hash']) && is_string($body['bump']) && is_string($body['hash']))
|
||||||
return $this->bump(
|
return $this->bump(
|
||||||
$response,
|
$response,
|
||||||
$request->withHeader('X-SharpChat-Signature', $body['hash'])
|
$request->setHeader('X-SharpChat-Signature', $body['hash'])
|
||||||
->withBody(Stream::create($body['bump']))
|
->setBody(Stream::create($body['bump']))
|
||||||
);
|
);
|
||||||
|
|
||||||
$source = isset($body['user_id']) ? $body : $query;
|
$source = isset($body['user_id']) ? $body : $query;
|
||||||
|
@ -72,8 +74,8 @@ final class SockChatHandler extends Handler {
|
||||||
&& is_string($source['ip']) && is_string($source['hash']))
|
&& is_string($source['ip']) && is_string($source['hash']))
|
||||||
return $this->verify(
|
return $this->verify(
|
||||||
$response,
|
$response,
|
||||||
$request->withHeader('X-SharpChat-Signature', $source['hash'])
|
$request->setHeader('X-SharpChat-Signature', $source['hash'])
|
||||||
->withBody(Stream::create(json_encode([
|
->setBody(Stream::create(json_encode([
|
||||||
'user_id' => $source['user_id'],
|
'user_id' => $source['user_id'],
|
||||||
'token' => $source['token'],
|
'token' => $source['token'],
|
||||||
'ip' => $source['ip'],
|
'ip' => $source['ip'],
|
||||||
|
@ -83,7 +85,7 @@ final class SockChatHandler extends Handler {
|
||||||
return $this->login($response, $request);
|
return $this->login($response, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function emotes(Response $response, Request $request): array {
|
public function emotes(HttpResponse $response, HttpRequest $request): array {
|
||||||
$response->setHeader('Access-Control-Allow-Origin', '*')
|
$response->setHeader('Access-Control-Allow-Origin', '*')
|
||||||
->setHeader('Access-Control-Allow-Methods', 'GET');
|
->setHeader('Access-Control-Allow-Methods', 'GET');
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ final class SockChatHandler extends Handler {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bans(Response $response, Request $request): array {
|
public function bans(HttpResponse $response, HttpRequest $request): array {
|
||||||
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
||||||
$realHash = hash_hmac('sha256', 'givemethebeans', $this->hashKey);
|
$realHash = hash_hmac('sha256', 'givemethebeans', $this->hashKey);
|
||||||
|
|
||||||
|
@ -124,7 +126,7 @@ final class SockChatHandler extends Handler {
|
||||||
')->fetchAll();
|
')->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login(Response $response, Request $request) {
|
public function login(HttpResponse $response, HttpRequest $request) {
|
||||||
if(!user_session_active()) {
|
if(!user_session_active()) {
|
||||||
$response->redirect(url('auth-login'));
|
$response->redirect(url('auth-login'));
|
||||||
return;
|
return;
|
||||||
|
@ -140,7 +142,7 @@ final class SockChatHandler extends Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(MSZ_DEBUG && isset($params['dump'])) {
|
if(MSZ_DEBUG && isset($params['dump'])) {
|
||||||
$ipAddr = $request->getServerParams()['REMOTE_ADDR'];
|
$ipAddr = $request->getRemoteAddress();
|
||||||
$hash = hash_hmac('sha256', implode('#', [$token->getUserId(), $token->getToken(), $ipAddr]), $this->hashKey);
|
$hash = hash_hmac('sha256', implode('#', [$token->getUserId(), $token->getToken(), $ipAddr]), $this->hashKey);
|
||||||
|
|
||||||
$response->setText(sprintf(
|
$response->setText(sprintf(
|
||||||
|
@ -168,7 +170,7 @@ final class SockChatHandler extends Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bump(Response $response, Request $request): void {
|
public function bump(HttpResponse $response, HttpRequest $request): void {
|
||||||
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
||||||
$bumpString = (string)$request->getBody();
|
$bumpString = (string)$request->getBody();
|
||||||
$realHash = hash_hmac('sha256', $bumpString, $this->hashKey);
|
$realHash = hash_hmac('sha256', $bumpString, $this->hashKey);
|
||||||
|
@ -185,7 +187,7 @@ final class SockChatHandler extends Handler {
|
||||||
user_bump_last_active($bumpUser->id, $bumpUser->ip);
|
user_bump_last_active($bumpUser->id, $bumpUser->ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify(Response $response, Request $request): array {
|
public function verify(HttpResponse $response, HttpRequest $request): array {
|
||||||
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
$userHash = $request->getHeaderLine('X-SharpChat-Signature');
|
||||||
|
|
||||||
if(strlen($userHash) !== 64)
|
if(strlen($userHash) !== 64)
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
namespace Misuzu\Http;
|
namespace Misuzu\Http;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\MessageInterface;
|
use Misuzu\Stream;
|
||||||
use Psr\Http\Message\StreamInterface;
|
|
||||||
|
|
||||||
abstract class HttpMessage implements MessageInterface {
|
abstract class HttpMessage {
|
||||||
protected $version = '';
|
protected $version = '';
|
||||||
protected $headers = [];
|
protected $headers = [];
|
||||||
protected $body = null;
|
protected $body = null;
|
||||||
|
@ -28,11 +27,11 @@ abstract class HttpMessage implements MessageInterface {
|
||||||
public function getBody() {
|
public function getBody() {
|
||||||
return $this->body;
|
return $this->body;
|
||||||
}
|
}
|
||||||
public function setBody(StreamInterface $body): self {
|
public function setBody(Stream $body): self {
|
||||||
$this->body = $body;
|
$this->body = $body;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withBody(StreamInterface $stream) {
|
public function withBody(Stream $stream) {
|
||||||
return (clone $this)->setBody($stream);
|
return (clone $this)->setBody($stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,37 +2,54 @@
|
||||||
namespace Misuzu\Http;
|
namespace Misuzu\Http;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Misuzu\Stream;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Misuzu\Uri;
|
||||||
|
|
||||||
class HttpRequestMessage extends HttpMessage implements RequestInterface {
|
class HttpRequestMessage extends HttpMessage {
|
||||||
private $method = null;
|
private $method = null;
|
||||||
private $uri = null;
|
private $uri = null;
|
||||||
private $requestTarget = null;
|
private $requestTarget = null;
|
||||||
|
private $server = [];
|
||||||
|
private $cookies = [];
|
||||||
|
private $query = [];
|
||||||
|
private $files = [];
|
||||||
|
private $parsedBody = null;
|
||||||
|
private $bodyStream = null;
|
||||||
|
|
||||||
public function __construct(string $method, UriInterface $uri, array $headers = []) {
|
public function __construct(
|
||||||
|
string $method,
|
||||||
|
Uri $uri,
|
||||||
|
array $serverParams = [],
|
||||||
|
array $headers = [],
|
||||||
|
array $queryParams = [],
|
||||||
|
array $uploadedFiles = [],
|
||||||
|
$parsedBody = null,
|
||||||
|
Stream $rawBody = null
|
||||||
|
) {
|
||||||
parent::__construct($headers);
|
parent::__construct($headers);
|
||||||
$this->setMethod($method);
|
$this->setMethod($method);
|
||||||
$this->setUri($uri);
|
$this->setUri($uri);
|
||||||
|
$this->setServerParams($serverParams);
|
||||||
|
$this->setQueryParams($queryParams);
|
||||||
|
$this->setUploadedFiles($uploadedFiles);
|
||||||
|
$this->setParsedBody($parsedBody);
|
||||||
|
$this->setBody($rawBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMethod() {
|
public function getMethod() {
|
||||||
return $this->method;
|
return $this->method;
|
||||||
}
|
}
|
||||||
public function setMethod(string $method): self {
|
private function setMethod(string $method): self {
|
||||||
if(empty($method))
|
if(empty($method))
|
||||||
throw new InvalidArgumentException('Invalid method name.');
|
throw new InvalidArgumentException('Invalid method name.');
|
||||||
$this->method = $method;
|
$this->method = $method;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withMethod($method) {
|
|
||||||
return (clone $this)->setMethod($method);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUri() {
|
public function getUri() {
|
||||||
return $this->uri;
|
return $this->uri;
|
||||||
}
|
}
|
||||||
public function setUri(UriInterface $uri, bool $preserveHost = false): self {
|
private function setUri(Uri $uri, bool $preserveHost = false): self {
|
||||||
$this->uri = $uri;
|
$this->uri = $uri;
|
||||||
|
|
||||||
if(!$preserveHost || !$this->hasHeader('Host'))
|
if(!$preserveHost || !$this->hasHeader('Host'))
|
||||||
|
@ -40,9 +57,6 @@ class HttpRequestMessage extends HttpMessage implements RequestInterface {
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withUri($uri, $preserveHost = false) {
|
|
||||||
return (clone $this)->setUri($uri, $preserveHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function applyHostHeader(): void {
|
private function applyHostHeader(): void {
|
||||||
$uri = $this->getUri();
|
$uri = $this->getUri();
|
||||||
|
@ -73,14 +87,130 @@ class HttpRequestMessage extends HttpMessage implements RequestInterface {
|
||||||
|
|
||||||
return $target;
|
return $target;
|
||||||
}
|
}
|
||||||
public function setRequestTarget(?string $requestTarget): self {
|
private function setRequestTarget(?string $requestTarget): self {
|
||||||
if(preg_match('#\s#', $requestTarget))
|
if(preg_match('#\s#', $requestTarget))
|
||||||
throw new InvalidArgumentException('Request target may not contain spaces.');
|
throw new InvalidArgumentException('Request target may not contain spaces.');
|
||||||
|
|
||||||
$this->requestTarget = $requestTarget;
|
$this->requestTarget = $requestTarget;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withRequestTarget($requestTarget) {
|
|
||||||
return (clone $this)->setRequestTarget($requestTarget);
|
public function getServerParams() {
|
||||||
|
return $this->server;
|
||||||
|
}
|
||||||
|
private function setServerParams(array $serverParams): self {
|
||||||
|
$this->server = $serverParams;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function getServerParam(string $name, int $filter = FILTER_DEFAULT, $options = null) {
|
||||||
|
if(!isset($this->server[$name]))
|
||||||
|
return null;
|
||||||
|
return filter_var($this->server[$name], $filter, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemoteAddress(): string {
|
||||||
|
return $this->server['REMOTE_ADDR'] ?? '::1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCookieParams() {
|
||||||
|
return $this->cookies;
|
||||||
|
}
|
||||||
|
private function setCookieParams(array $cookies): self {
|
||||||
|
$this->cookies = $cookies;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function getCookieParam(string $name, int $filter = FILTER_DEFAULT, $options = null) {
|
||||||
|
if(!isset($this->cookies[$name]))
|
||||||
|
return null;
|
||||||
|
return filter_var($this->cookies[$name], $filter, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryParams() {
|
||||||
|
return $this->query;
|
||||||
|
}
|
||||||
|
private function setQueryParams(array $query): self {
|
||||||
|
$this->query = $query;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function getQueryParam(string $name, int $filter = FILTER_DEFAULT, $options = null) {
|
||||||
|
if(!isset($this->query[$name]))
|
||||||
|
return null;
|
||||||
|
return filter_var($this->query[$name], $filter, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploadedFiles() {
|
||||||
|
return $this->files;
|
||||||
|
}
|
||||||
|
private function setUploadedFiles(array $uploadedFiles): self {
|
||||||
|
$this->files = $uploadedFiles;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParsedBody() {
|
||||||
|
return $this->parsedBody;
|
||||||
|
}
|
||||||
|
private function setParsedBody($data): self {
|
||||||
|
if(!is_array($data) && !is_object($data) && $data !== null)
|
||||||
|
throw new InvalidArgumentException('Parsed body must by of type array, object or null.');
|
||||||
|
$this->parsedBody = $data;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function getBodyParam(string $name, int $filter = FILTER_DEFAULT, $options = null) {
|
||||||
|
if($this->parsedBody === null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$value = null;
|
||||||
|
|
||||||
|
if(is_object($this->parsedBody) && isset($this->parsedBody->{$name})) {
|
||||||
|
$value = $this->parsedBody->{$name};
|
||||||
|
} elseif(is_array($this->parsedBody) && isset($this->parsedBody[$name])) {
|
||||||
|
$value = $this->parsedBody[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter_var($value, $filter, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getRequestHeaders(): array {
|
||||||
|
if(function_exists('getallheaders'))
|
||||||
|
return getallheaders();
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
|
||||||
|
foreach($_SERVER as $key => $value) {
|
||||||
|
if(substr($key, 0, 5) === 'HTTP_') {
|
||||||
|
$key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
|
||||||
|
$reqHeaders[$key] = $value;
|
||||||
|
} elseif($key === 'CONTENT_TYPE') {
|
||||||
|
$reqHeaders['Content-Type'] = $value;
|
||||||
|
} elseif($key === 'CONTENT_LENGTH') {
|
||||||
|
$reqHeaders['Content-Length'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isset($headers['Authorization'])) {
|
||||||
|
if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||||
|
$headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
||||||
|
} elseif(isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
|
$password = $_SERVER['PHP_AUTH_PW'] ?? '';
|
||||||
|
$headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $password);
|
||||||
|
} elseif(isset($_SERVER['PHP_AUTH_DIGEST'])) {
|
||||||
|
$headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromGlobals(): HttpRequestMessage {
|
||||||
|
return new static(
|
||||||
|
$_SERVER['REQUEST_METHOD'],
|
||||||
|
new Uri('/' . trim($_SERVER['REQUEST_URI'] ?? '', '/')),
|
||||||
|
$_SERVER,
|
||||||
|
self::getRequestHeaders(),
|
||||||
|
$_GET,
|
||||||
|
UploadedFile::createFromFILES($_FILES),
|
||||||
|
$_POST,
|
||||||
|
Stream::createFromFile('php://input')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
namespace Misuzu\Http;
|
namespace Misuzu\Http;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Template;
|
||||||
use Psr\Http\Message\StreamInterface;
|
use Misuzu\Stream;
|
||||||
|
|
||||||
class HttpResponseMessage extends HttpMessage implements ResponseInterface {
|
class HttpResponseMessage extends HttpMessage {
|
||||||
private $statusCode = 0;
|
private $statusCode = 0;
|
||||||
private $reasonPhrase = '';
|
private $reasonPhrase = '';
|
||||||
|
|
||||||
|
@ -32,8 +32,56 @@ class HttpResponseMessage extends HttpMessage implements ResponseInterface {
|
||||||
$this->reasonPhrase = $reasonPhrase;
|
$this->reasonPhrase = $reasonPhrase;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withStatus($code, $reasonPhrase = '') {
|
|
||||||
return (clone $this)->setStatusCode($code)->setReasonPhrase($reasonPhrase);
|
public function getContentType(): string {
|
||||||
|
return $this->getHeaderLine('Content-Type');
|
||||||
|
}
|
||||||
|
public function setContentType(string $type): self {
|
||||||
|
$this->setHeader('Content-Type', $type);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setText(string $text): self {
|
||||||
|
$body = $this->getBody();
|
||||||
|
|
||||||
|
if($body !== null)
|
||||||
|
$body->close();
|
||||||
|
|
||||||
|
$this->setBody(Stream::create($text));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function appendText(string $text): self {
|
||||||
|
$body = $this->getBody();
|
||||||
|
|
||||||
|
if($body === null)
|
||||||
|
$this->setBody(Stream::create($text));
|
||||||
|
else
|
||||||
|
$body->write($text);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function setHtml(string $content, bool $html = true): self {
|
||||||
|
if(empty($this->getContentType()))
|
||||||
|
$this->setContentType('text/html; charset=utf-8');
|
||||||
|
$this->setText($content);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
public function setTemplate(string $file, array $vars = []): self {
|
||||||
|
return $this->setHtml(Template::renderRaw($file, $vars));
|
||||||
|
}
|
||||||
|
public function setJson($content, int $options = 0): self {
|
||||||
|
$this->setContentType('application/json; charset=utf-8');
|
||||||
|
$this->setText(json_encode($content, $options));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function redirect(string $path, bool $permanent = false, bool $xhr = false): self {
|
||||||
|
$this->setStatusCode($permanent ? 301 : 302);
|
||||||
|
$this->setHeader($xhr ? 'X-Misuzu-Location' : 'Location', $path);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): string {
|
||||||
|
return (string)($this->getBody() ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu\Http;
|
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Message\StreamInterface;
|
|
||||||
use Psr\Http\Message\UriInterface;
|
|
||||||
|
|
||||||
class HttpServerRequestMessage extends HttpRequestMessage implements ServerRequestInterface {
|
|
||||||
private $server = [];
|
|
||||||
private $cookies = [];
|
|
||||||
private $query = [];
|
|
||||||
private $files = [];
|
|
||||||
private $attributes = [];
|
|
||||||
private $parsedBody = null;
|
|
||||||
private $bodyStream = null;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
string $method,
|
|
||||||
UriInterface $uri,
|
|
||||||
array $serverParams = [],
|
|
||||||
array $headers = [],
|
|
||||||
array $queryParams = [],
|
|
||||||
array $uploadedFiles = [],
|
|
||||||
$parsedBody = null,
|
|
||||||
StreamInterface $rawBody = null
|
|
||||||
) {
|
|
||||||
parent::__construct($method, $uri, $headers);
|
|
||||||
$this->setServerParams($serverParams);
|
|
||||||
$this->setQueryParams($queryParams);
|
|
||||||
$this->setUploadedFiles($uploadedFiles);
|
|
||||||
$this->setParsedBody($parsedBody);
|
|
||||||
$this->setBody($rawBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getServerParams() {
|
|
||||||
return $this->server;
|
|
||||||
}
|
|
||||||
public function setServerParams(array $serverParams): self {
|
|
||||||
$this->server = $serverParams;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCookieParams() {
|
|
||||||
return $this->cookies;
|
|
||||||
}
|
|
||||||
public function setCookieParams(array $cookies): self {
|
|
||||||
$this->cookies = $cookies;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withCookieParams(array $cookies) {
|
|
||||||
return (clone $this)->setCookieParams($cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryParams() {
|
|
||||||
return $this->query;
|
|
||||||
}
|
|
||||||
public function setQueryParams(array $query): self {
|
|
||||||
$this->query = $query;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withQueryParams(array $query) {
|
|
||||||
return (clone $this)->setQueryParams($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUploadedFiles() {
|
|
||||||
return $this->files;
|
|
||||||
}
|
|
||||||
public function setUploadedFiles(array $uploadedFiles): self {
|
|
||||||
$this->files = $uploadedFiles;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withUploadedFiles(array $uploadedFiles): self {
|
|
||||||
return (clone $this)->setUploadedFiles($uploadedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getParsedBody() {
|
|
||||||
return $this->parsedBody;
|
|
||||||
}
|
|
||||||
public function setParsedBody($data): self {
|
|
||||||
if(!is_array($data) && !is_object($data) && $data !== null)
|
|
||||||
throw new InvalidArgumentException('Parsed body must by of type array, object or null.');
|
|
||||||
$this->parsedBody = $data;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withParsedBody($data) {
|
|
||||||
return (clone $this)->setParsedBody($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAttributes() {
|
|
||||||
return $this->attributes;
|
|
||||||
}
|
|
||||||
public function getAttribute($name, $default = null) {
|
|
||||||
return $this->attributes[$name] ?? $default;
|
|
||||||
}
|
|
||||||
public function setAttribute(string $name, $value): self {
|
|
||||||
$this->attributes[$name] = $value;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withAttribute($name, $value) {
|
|
||||||
return (clone $this)->setAttribute($name, $value);
|
|
||||||
}
|
|
||||||
public function removeAttribute(string $name): self {
|
|
||||||
unset($this->attributes[$name]);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function withoutAttribute($name) {
|
|
||||||
return (clone $this)->removeAttribute($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getRequestHeaders(): array {
|
|
||||||
if(function_exists('getallheaders'))
|
|
||||||
return getallheaders();
|
|
||||||
|
|
||||||
$headers = [];
|
|
||||||
|
|
||||||
foreach($_SERVER as $key => $value) {
|
|
||||||
if(substr($key, 0, 5) === 'HTTP_') {
|
|
||||||
$key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
|
|
||||||
$reqHeaders[$key] = $value;
|
|
||||||
} elseif($key === 'CONTENT_TYPE') {
|
|
||||||
$reqHeaders['Content-Type'] = $value;
|
|
||||||
} elseif($key === 'CONTENT_LENGTH') {
|
|
||||||
$reqHeaders['Content-Length'] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!isset($headers['Authorization'])) {
|
|
||||||
if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
|
||||||
$headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
|
||||||
} elseif(isset($_SERVER['PHP_AUTH_USER'])) {
|
|
||||||
$password = $_SERVER['PHP_AUTH_PW'] ?? '';
|
|
||||||
$headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $password);
|
|
||||||
} elseif(isset($_SERVER['PHP_AUTH_DIGEST'])) {
|
|
||||||
$headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromGlobals(): HttpServerRequestMessage {
|
|
||||||
return new static(
|
|
||||||
$_SERVER['REQUEST_METHOD'],
|
|
||||||
new Uri('/' . trim($_SERVER['REQUEST_URI'] ?? '', '/')),
|
|
||||||
$_SERVER,
|
|
||||||
self::getRequestHeaders(),
|
|
||||||
$_GET,
|
|
||||||
UploadedFile::createFromFILES($_FILES),
|
|
||||||
$_POST,
|
|
||||||
Stream::createFromFile('php://input')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,47 +2,65 @@
|
||||||
namespace Misuzu\Http\Routing;
|
namespace Misuzu\Http\Routing;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Serializable;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
|
|
||||||
class Route {
|
class Route implements Serializable {
|
||||||
private $methods = [];
|
private $methods = [];
|
||||||
private $path = '';
|
private $path = '';
|
||||||
private $regex = false;
|
|
||||||
private $handler;
|
|
||||||
private $children = [];
|
private $children = [];
|
||||||
private $filters = [];
|
private $filters = [];
|
||||||
|
private $parentRoute = null;
|
||||||
|
|
||||||
public function __construct(array $methods, string $path, bool $regex, $handler) {
|
private $handlerClass = null;
|
||||||
|
private $handlerMethod = null;
|
||||||
|
|
||||||
|
public function __construct(array $methods, string $path, ?string $method = null, ?string $class = null) {
|
||||||
$this->methods = array_map('strtoupper', $methods);
|
$this->methods = array_map('strtoupper', $methods);
|
||||||
$this->regex = $regex;
|
|
||||||
$this->handler = $handler;
|
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
|
$this->handlerClass = $class;
|
||||||
|
$this->handlerMethod = $method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function create(array $methods, string $path, $regex = null, $callable = null): self {
|
public static function create(array $methods, string $path, ?string $method = null, ?string $class = null): self {
|
||||||
return new static($methods, $path, is_bool($regex) ? $regex : false, is_bool($regex) ? $callable : $regex);
|
return new static($methods, $path, $method, $class);
|
||||||
}
|
}
|
||||||
public static function get(string $path, $regex = null, $callable = null): self {
|
public static function get(string $path, ?string $method = null, ?string $class = null): self {
|
||||||
return self::create(['GET'], $path, $regex, $callable);
|
return self::create(['GET'], $path, $method, $class);
|
||||||
}
|
}
|
||||||
public static function post(string $path, $regex = null, $callable = null): self {
|
public static function post(string $path, ?string $method = null, ?string $class = null): self {
|
||||||
return self::create(['POST'], $path, $regex, $callable);
|
return self::create(['POST'], $path, $method, $class);
|
||||||
|
}
|
||||||
|
public static function group(string $path, ?string $class = null): self {
|
||||||
|
return self::create([''], $path, null, $class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isRegex(): bool {
|
public function getHandlerClass(): string {
|
||||||
return $this->regex;
|
return $this->handlerClass ?? ($this->parentRoute === null ? '' : $this->parentRoute->getHandlerClass());
|
||||||
}
|
}
|
||||||
public function setRegex(): self {
|
public function setHandlerClass(string $class): self {
|
||||||
$this->regex = true;
|
$this->handlerClass = $class;
|
||||||
foreach($this->children as $child)
|
return $this;
|
||||||
$child->setRegex();
|
}
|
||||||
|
|
||||||
|
public function getHandlerMethod() {
|
||||||
|
return $this->handlerMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): ?self {
|
||||||
|
return $this->parentRoute;
|
||||||
|
}
|
||||||
|
public function setParent(self $route): self {
|
||||||
|
$this->parentRoute = $route;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath(): string {
|
public function getPath(): string {
|
||||||
return $this->path;
|
$path = $this->path;
|
||||||
|
if($this->parentRoute !== null)
|
||||||
|
$path = $this->parentRoute->getPath() . '/' . trim($path, '/');
|
||||||
|
return $path;
|
||||||
}
|
}
|
||||||
public function setPath(string $path): self {
|
public function setPath(string $path): self {
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
|
@ -51,80 +69,42 @@ class Route {
|
||||||
|
|
||||||
public function addFilters(string ...$filters): self {
|
public function addFilters(string ...$filters): self {
|
||||||
$this->filters = array_merge($this->filters, $filters);
|
$this->filters = array_merge($this->filters, $filters);
|
||||||
foreach($this->children as $child)
|
|
||||||
$child->addFilters(...$filters);
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function getFilters(): array {
|
public function getFilters(): array {
|
||||||
return $this->filters;
|
$filters = $this->filters;
|
||||||
|
if($this->parentRoute !== null)
|
||||||
|
$filters += $this->parentRoute->getFilters();
|
||||||
|
return $filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getChildren(): array {
|
public function getChildren(): array {
|
||||||
return $this->children;
|
return $this->children;
|
||||||
}
|
}
|
||||||
public function addChildren(Route ...$routes): self {
|
public function addChildren(Route ...$routes): self {
|
||||||
foreach($routes as $route) {
|
foreach($routes as $route)
|
||||||
$route->setPrefix($this->getPath())->addFilters(...$this->getFilters());
|
$this->children[] = $route->setParent($this);
|
||||||
|
|
||||||
if($this->isRegex())
|
|
||||||
$route->setRegex();
|
|
||||||
|
|
||||||
$this->children[] = $route;
|
|
||||||
}
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPrefix(string $prefix): self {
|
public function match(HttpRequestMessage $request, array &$matches): bool {
|
||||||
foreach($this->children as $child)
|
$matches = [];
|
||||||
$child->setPrefix($prefix);
|
|
||||||
return $this->setPath($prefix . '/' . trim($this->getPath(), '/'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function match(RequestInterface $request, array &$matches): bool {
|
|
||||||
$matches = [$this->getPath()];
|
|
||||||
|
|
||||||
if(!in_array($request->getMethod(), $this->methods))
|
if(!in_array($request->getMethod(), $this->methods))
|
||||||
return false;
|
return false;
|
||||||
|
return preg_match('#^' . $this->getPath() . '$#', $request->getUri()->getPath(), $matches) === 1;
|
||||||
$requestPath = $request->getUri()->getPath();
|
|
||||||
|
|
||||||
return $this->isRegex()
|
|
||||||
? preg_match('#^' . $this->getPath() . '$#', $requestPath, $matches)
|
|
||||||
: $this->getPath() === $requestPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch(ServerRequestInterface $request, ...$args): ResponseInterface {
|
public function serialize() {
|
||||||
$response = new RouterResponseMessage(200);
|
return serialize([
|
||||||
$result = null;
|
$this->methods,
|
||||||
|
$this->getPath(),
|
||||||
|
$this->getFilters(),
|
||||||
|
$this->getHandlerClass(),
|
||||||
|
$this->getHandlerMethod(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
array_unshift($args, $response, $request);
|
public function unserialize($data) {
|
||||||
|
[$this->methods, $this->path, $this->filters, $this->handlerClass, $this->handlerMethod] = unserialize($data);
|
||||||
if(is_array($this->handler)) {
|
|
||||||
if(method_exists($this->handler[0] ?? '', $this->handler[1] ?? '')) {
|
|
||||||
$handlerClass = new $this->handler[0]($response, $request);
|
|
||||||
$result = $handlerClass->{$this->handler[1]}(...$args);
|
|
||||||
}
|
|
||||||
} elseif(is_callable($this->handler)) {
|
|
||||||
$result = call_user_func_array($this->handler, $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($result !== null) {
|
|
||||||
$resultType = gettype($result);
|
|
||||||
|
|
||||||
switch($resultType) {
|
|
||||||
case 'array':
|
|
||||||
case 'object':
|
|
||||||
$response->setJson($result);
|
|
||||||
break;
|
|
||||||
case 'integer':
|
|
||||||
$response->setStatusCode($result);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$response->setHtml($result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +1,126 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http\Routing;
|
namespace Misuzu\Http\Routing;
|
||||||
|
|
||||||
|
use Misuzu\Http\HttpRequestMessage;
|
||||||
use Misuzu\Http\HttpResponseMessage;
|
use Misuzu\Http\HttpResponseMessage;
|
||||||
use Psr\Http\Message\RequestInterface;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
|
|
||||||
class Router implements RequestHandlerInterface {
|
class Router {
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
private $routesExact = [];
|
private $handlerFormat = '';
|
||||||
private $routesRegex = [];
|
private $filterFormat = '';
|
||||||
|
private $routes = [];
|
||||||
|
|
||||||
public static function __callStatic(string $name, array $args) {
|
public function __call(string $name, array $args) {
|
||||||
return is_null(self::$instance) ? null : self::$instance->{$name}(...$args);
|
if($name[0] === '_')
|
||||||
|
return null;
|
||||||
|
return $this->{'_' . $name}(...$args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct() {}
|
public static function __callStatic(string $name, array $args) {
|
||||||
|
if($name[0] === '_')
|
||||||
|
return null;
|
||||||
|
if(self::$instance === null)
|
||||||
|
(new static)->setInstance();
|
||||||
|
return self::$instance->{'_' . $name}(...$args);
|
||||||
|
}
|
||||||
|
|
||||||
public function setInstance(): self {
|
public function setInstance(): self {
|
||||||
return self::$instance = $this;
|
return self::$instance = $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addRoutes(Route ...$routes): self {
|
public function _getHandlerFormat(): string {
|
||||||
foreach($routes as $route) {
|
return $this->handlerFormat;
|
||||||
if($route->isRegex()) {
|
}
|
||||||
$this->routesRegex[] = $route;
|
public function _setHandlerFormat(string $format): self {
|
||||||
} else {
|
$this->handlerFormat = $format;
|
||||||
$this->routesExact[] = $route;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addRoutes(...$route->getChildren());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchExact(RequestInterface $request, array &$matches): ?Route {
|
public function _getFilterFormat(): string {
|
||||||
foreach($this->routesExact as $route)
|
return $this->filterFormat;
|
||||||
|
}
|
||||||
|
public function _setFilterFormat(string $format): self {
|
||||||
|
$this->filterFormat = $format;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _addRoutes(Route ...$routes): self {
|
||||||
|
foreach($routes as $route) {
|
||||||
|
$this->routes[] = $route;
|
||||||
|
$this->addRoutes(...$route->getChildren());
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unused, might be useful for the future to immediately smash the routes list together
|
||||||
|
// without having to propagate children.
|
||||||
|
// Should obviously not be in debug mode and should also be nuked after a pull on stable.
|
||||||
|
public function _getData(): string {
|
||||||
|
return serialize([$this->routes]);
|
||||||
|
}
|
||||||
|
public function _setData(string $data): void {
|
||||||
|
[$this->routes] = unserialize($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function match(HttpRequestMessage $request, array &$matches): ?Route {
|
||||||
|
foreach($this->routes as $route)
|
||||||
if($route->match($request, $matches))
|
if($route->match($request, $matches))
|
||||||
return $route;
|
return $route;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchRegex(RequestInterface $request, array &$matches): ?Route {
|
public function _handle(HttpRequestMessage $request): HttpResponseMessage {
|
||||||
foreach($this->routesRegex as $route)
|
|
||||||
if($route->match($request, $matches))
|
|
||||||
return $route;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function match(RequestInterface $request, array &$matches): ?Route {
|
|
||||||
return $this->matchExact($request, $matches) ?? $this->matchRegex($request, $matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(ServerRequestInterface $request): ResponseInterface {
|
|
||||||
$matches = [];
|
$matches = [];
|
||||||
$route = $this->match($request, $matches);
|
$route = $this->match($request, $matches);
|
||||||
|
array_shift($matches);
|
||||||
|
|
||||||
if($route === null)
|
if($route === null)
|
||||||
return new HttpResponseMessage(404);
|
return new HttpResponseMessage(404);
|
||||||
|
|
||||||
foreach($route->getFilters() as $filter) {
|
foreach($route->getFilters() as $filter) {
|
||||||
|
if(!class_exists($filter))
|
||||||
|
$filter = sprintf($this->_getFilterFormat(), $filter);
|
||||||
$response = (new $filter)->process($request, $this);
|
$response = (new $filter)->process($request, $this);
|
||||||
|
|
||||||
if($response !== null)
|
if($response !== null)
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $route->dispatch($request, ...array_slice($matches, 1));
|
$response = new HttpResponseMessage(200);
|
||||||
|
$result = null;
|
||||||
|
array_unshift($matches, $response, $request);
|
||||||
|
|
||||||
|
$handlerMethod = $route->getHandlerMethod();
|
||||||
|
if(is_callable($handlerMethod)) {
|
||||||
|
$result = call_user_func_array($handlerMethod, $args);
|
||||||
|
} elseif($handlerMethod[0] === '/') {
|
||||||
|
$response->redirect($handlerMethod);
|
||||||
|
} else {
|
||||||
|
$handlerClass = $route->getHandlerClass();
|
||||||
|
if(!empty($handlerClass)) {
|
||||||
|
if(!class_exists($handlerClass))
|
||||||
|
$handlerClass = sprintf($this->_getHandlerFormat(), $handlerClass);
|
||||||
|
$handlerClass = new $handlerClass($response, $request);
|
||||||
|
$result = $handlerClass->{$handlerMethod}(...$matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($result !== null) {
|
||||||
|
$resultType = gettype($result);
|
||||||
|
|
||||||
|
switch($resultType) {
|
||||||
|
case 'array':
|
||||||
|
case 'object':
|
||||||
|
$response->setJson($result);
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
$response->setStatusCode($result);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$response->setHtml($result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu\Http\Routing;
|
|
||||||
|
|
||||||
use Misuzu\Template;
|
|
||||||
use Misuzu\Http\HttpResponseMessage;
|
|
||||||
use Misuzu\Http\Stream;
|
|
||||||
|
|
||||||
class RouterResponseMessage extends HttpResponseMessage {
|
|
||||||
public function getContentType(): string {
|
|
||||||
return $this->getHeaderLine('Content-Type');
|
|
||||||
}
|
|
||||||
public function setContentType(string $type): self {
|
|
||||||
$this->setHeader('Content-Type', $type);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setText(string $text): self {
|
|
||||||
$body = $this->getBody();
|
|
||||||
|
|
||||||
if($body !== null)
|
|
||||||
$body->close();
|
|
||||||
|
|
||||||
$this->setBody(Stream::create($text));
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function appendText(string $text): self {
|
|
||||||
$body = $this->getBody();
|
|
||||||
|
|
||||||
if($body === null)
|
|
||||||
$this->setBody(Stream::create($text));
|
|
||||||
else
|
|
||||||
$body->write($text);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function setHtml(string $content, bool $html = true): self {
|
|
||||||
if(empty($this->getContentType()))
|
|
||||||
$this->setContentType('text/html; charset=utf-8');
|
|
||||||
$this->setText($content);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function setTemplate(string $file, array $vars = []): self {
|
|
||||||
return $this->setHtml(Template::renderRaw($file, $vars));
|
|
||||||
}
|
|
||||||
public function setJson($content, int $options = 0): self {
|
|
||||||
$this->setContentType('application/json; charset=utf-8');
|
|
||||||
$this->setText(json_encode($content, $options));
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function redirect(string $path, bool $permanent = false, bool $xhr = false): self {
|
|
||||||
$this->setStatusCode($permanent ? 301 : 302);
|
|
||||||
$this->setHeader($xhr ? 'X-Misuzu-Location' : 'Location', $path);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getContents(): string {
|
|
||||||
return (string)($this->getBody() ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,9 @@ namespace Misuzu\Http;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
use Misuzu\Stream;
|
||||||
|
|
||||||
class UploadedFile implements UploadedFileInterface {
|
class UploadedFile {
|
||||||
private $stream = null;
|
private $stream = null;
|
||||||
private $fileName = null;
|
private $fileName = null;
|
||||||
private $size = null;
|
private $size = null;
|
||||||
|
@ -34,7 +34,7 @@ class UploadedFile implements UploadedFileInterface {
|
||||||
$this->fileName = $fileNameOrStream;
|
$this->fileName = $fileNameOrStream;
|
||||||
elseif(is_resource($fileNameOrStream))
|
elseif(is_resource($fileNameOrStream))
|
||||||
$this->stream = Stream::create($fileNameOrStream);
|
$this->stream = Stream::create($fileNameOrStream);
|
||||||
elseif($fileNameOrStream instanceof StreamInterface)
|
elseif($fileNameOrStream instanceof Stream)
|
||||||
$this->stream = $fileNameOrStream;
|
$this->stream = $fileNameOrStream;
|
||||||
|
|
||||||
if($size === null && $this->stream !== null)
|
if($size === null && $this->stream !== null)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Psr\Http\Message\StreamInterface;
|
|
||||||
|
|
||||||
class Stream implements StreamInterface {
|
class Stream {
|
||||||
private $stream = null;
|
private $stream = null;
|
||||||
private $metaData = [];
|
private $metaData = [];
|
||||||
private $seekable = false;
|
private $seekable = false;
|
||||||
|
@ -44,13 +43,13 @@ class Stream implements StreamInterface {
|
||||||
$this->seekable = $metaData['seekable'] && fseek($this->stream, 0, SEEK_CUR) === 0;
|
$this->seekable = $metaData['seekable'] && fseek($this->stream, 0, SEEK_CUR) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function create($contents = ''): StreamInterface {
|
public static function create($contents = ''): Stream {
|
||||||
if($contents instanceof StreamInterface)
|
if($contents instanceof Stream)
|
||||||
return $contents;
|
return $contents;
|
||||||
|
|
||||||
return new static($contents);
|
return new static($contents);
|
||||||
}
|
}
|
||||||
public static function createFromFile(string $filename, string $mode = 'rb'): StreamInterface {
|
public static function createFromFile(string $filename, string $mode = 'rb'): Stream {
|
||||||
if(!in_array($mode[0], ['r', 'w', 'a', 'x', 'c']))
|
if(!in_array($mode[0], ['r', 'w', 'a', 'x', 'c']))
|
||||||
throw new InvalidArgumentException("Provided mode ({$mode}) is invalid.");
|
throw new InvalidArgumentException("Provided mode ({$mode}) is invalid.");
|
||||||
|
|
||||||
|
@ -76,18 +75,18 @@ class Stream implements StreamInterface {
|
||||||
return $metaData[$key] ?? null;
|
return $metaData[$key] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isReadable() {
|
public function isReadable(): bool {
|
||||||
return $this->readable;
|
return $this->readable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function read($length) {
|
public function read($length): int {
|
||||||
if(!$this->isReadable())
|
if(!$this->isReadable())
|
||||||
throw RuntimeException('Can\'t read from this stream.');
|
throw RuntimeException('Can\'t read from this stream.');
|
||||||
|
|
||||||
return fread($this->stream, $length);
|
return fread($this->stream, $length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContents() {
|
public function getContents(): string {
|
||||||
if(!isset($this->stream))
|
if(!isset($this->stream))
|
||||||
throw new RuntimeException('Can\'t read contents of stream.');
|
throw new RuntimeException('Can\'t read contents of stream.');
|
||||||
|
|
||||||
|
@ -97,11 +96,11 @@ class Stream implements StreamInterface {
|
||||||
return $contents;
|
return $contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isWritable() {
|
public function isWritable(): bool {
|
||||||
return $this->writable;
|
return $this->writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function write($string) {
|
public function write($string): int {
|
||||||
if(!$this->isWritable())
|
if(!$this->isWritable())
|
||||||
throw new RuntimeException('Can\'t write to this stream.');
|
throw new RuntimeException('Can\'t write to this stream.');
|
||||||
|
|
||||||
|
@ -113,11 +112,11 @@ class Stream implements StreamInterface {
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isSeekable() {
|
public function isSeekable(): bool {
|
||||||
return $this->seekable;
|
return $this->seekable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function seek($offset, $whence = SEEK_SET) {
|
public function seek($offset, $whence = SEEK_SET): void {
|
||||||
if(!$this->isSeekable())
|
if(!$this->isSeekable())
|
||||||
throw new RuntimeException('Can\'t seek in this stream.');
|
throw new RuntimeException('Can\'t seek in this stream.');
|
||||||
|
|
||||||
|
@ -125,11 +124,11 @@ class Stream implements StreamInterface {
|
||||||
throw new RuntimeException("Failed to seek to position {$offset} ({$whence}).");
|
throw new RuntimeException("Failed to seek to position {$offset} ({$whence}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rewind() {
|
public function rewind(): void {
|
||||||
$this->seek(0);
|
$this->seek(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tell() {
|
public function tell(): int {
|
||||||
if(!isset($this->stream))
|
if(!isset($this->stream))
|
||||||
throw new RuntimeException('Can\'t determine the position of a detached stream.');
|
throw new RuntimeException('Can\'t determine the position of a detached stream.');
|
||||||
if(($pos = ftell($this->stream)) === false)
|
if(($pos = ftell($this->stream)) === false)
|
||||||
|
@ -138,11 +137,11 @@ class Stream implements StreamInterface {
|
||||||
return $pos;
|
return $pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function eof() {
|
public function eof(): bool {
|
||||||
return !isset($this->stream) || feof($this->stream);
|
return !isset($this->stream) || feof($this->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSize() {
|
public function getSize(): ?int {
|
||||||
if($this->size !== null)
|
if($this->size !== null)
|
||||||
return $this->size;
|
return $this->size;
|
||||||
|
|
||||||
|
@ -170,7 +169,7 @@ class Stream implements StreamInterface {
|
||||||
return $stream;
|
return $stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close() {
|
public function close(): void {
|
||||||
$stream = $this->detach();
|
$stream = $this->detach();
|
||||||
|
|
||||||
if(is_resource($stream))
|
if(is_resource($stream))
|
|
@ -1,10 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\Http;
|
namespace Misuzu;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\UriInterface;
|
|
||||||
|
|
||||||
class Uri implements UriInterface {
|
class Uri {
|
||||||
private $scheme = '';
|
private $scheme = '';
|
||||||
private $user = '';
|
private $user = '';
|
||||||
private $password = '';
|
private $password = '';
|
||||||
|
@ -45,12 +44,6 @@ class Uri implements UriInterface {
|
||||||
$this->scheme = $scheme;
|
$this->scheme = $scheme;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withScheme($scheme) {
|
|
||||||
if(!is_string($scheme))
|
|
||||||
throw new InvalidArgumentException('Scheme must be a string.');
|
|
||||||
|
|
||||||
return (clone $this)->setScheme($scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAuthority() {
|
public function getAuthority() {
|
||||||
$authority = '';
|
$authority = '';
|
||||||
|
@ -79,9 +72,6 @@ class Uri implements UriInterface {
|
||||||
$this->password = $password;
|
$this->password = $password;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withUserInfo($user, $password = null) {
|
|
||||||
return (clone $this)->setUserInfo($user, $password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHost() {
|
public function getHost() {
|
||||||
return $this->host;
|
return $this->host;
|
||||||
|
@ -90,12 +80,6 @@ class Uri implements UriInterface {
|
||||||
$this->host = $host;
|
$this->host = $host;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withHost($host) {
|
|
||||||
if(!is_string($host))
|
|
||||||
throw new InvalidArgumentException('Hostname must be a string.');
|
|
||||||
|
|
||||||
return (clone $this)->setHost($host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPort() {
|
public function getPort() {
|
||||||
return $this->port;
|
return $this->port;
|
||||||
|
@ -107,9 +91,6 @@ class Uri implements UriInterface {
|
||||||
$this->port = $port;
|
$this->port = $port;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withPort($port) {
|
|
||||||
return (clone $this)->setPort($port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPath() {
|
public function getPath() {
|
||||||
return $this->path;
|
return $this->path;
|
||||||
|
@ -118,12 +99,6 @@ class Uri implements UriInterface {
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withPath($path) {
|
|
||||||
if(!is_string($path))
|
|
||||||
throw new InvalidArgumentException('Path must be a string.');
|
|
||||||
|
|
||||||
return (clone $this)->setPath($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQuery() {
|
public function getQuery() {
|
||||||
return $this->query;
|
return $this->query;
|
||||||
|
@ -132,12 +107,6 @@ class Uri implements UriInterface {
|
||||||
$this->query = $query;
|
$this->query = $query;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withQuery($query) {
|
|
||||||
if(!is_string($query))
|
|
||||||
throw new InvalidArgumentException('Query string must be a string.');
|
|
||||||
|
|
||||||
return (clone $this)->setQuery($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFragment() {
|
public function getFragment() {
|
||||||
return $this->fragment;
|
return $this->fragment;
|
||||||
|
@ -146,9 +115,6 @@ class Uri implements UriInterface {
|
||||||
$this->fragment = $fragment;
|
$this->fragment = $fragment;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
public function withFragment($fragment) {
|
|
||||||
return (clone $this)->setFragment($fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString() {
|
public function __toString() {
|
||||||
$string = '';
|
$string = '';
|
Loading…
Add table
Reference in a new issue