diff --git a/public/index.php b/public/index.php index 6a556483..96cf3d7c 100644 --- a/public/index.php +++ b/public/index.php @@ -16,7 +16,11 @@ Router::addRoutes( Route::get('/', 'index', 'Home'), // Assets - Route::get('/assets/([a-zA-Z0-9\-]+)\.(css|js)', 'view', 'Assets'), + Route::group('/assets', 'Assets')->addChildren( + Route::get('/([a-zA-Z0-9\-]+)\.(css|js)', 'serveComponent'), + Route::get('/avatar/([0-9]+)(?:\.png)?', 'serveAvatar'), + Route::get('/profile-background/([0-9]+)(?:\.png)?', 'serveProfileBackground'), + ), // Info Route::get('/info', 'index', 'Info'), @@ -71,6 +75,7 @@ Router::addRoutes( Route::get('/news/feed.php', 'legacy', 'News'), Route::get('/news/feed.php/rss', 'legacy', 'News'), Route::get('/news/feed.php/atom', 'legacy', 'News'), + Route::get('/user-assets.php', 'serveLegacy', 'Assets'), ); $response = Router::handle($request); @@ -90,7 +95,7 @@ foreach($response->getHeaders() as $name => $lines) { $responseBody = $response->getBody(); -if($responseStatus >= 400 && $responseStatus <= 599 && ($responseBody === null || $responseBody->getSize() < 1)) +if($responseStatus >= 400 && $responseStatus <= 599 && $responseBody === null) echo render_error($responseStatus); else echo (string)$responseBody; diff --git a/public/user-assets.php b/public/user-assets.php index 328c845c..41cc616f 100644 --- a/public/user-assets.php +++ b/public/user-assets.php @@ -1,66 +1,2 @@ isBanned() || ( - parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_PATH) === url('user-profile') - && User::hasCurrent() && perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS) -); - -switch($assetMode) { - case 'avatar': - if(!$assetVisible) { - $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/banned-avatar.png', MSZ_PUBLIC); - break; - } - - $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC); - - if(!isset($assetUser) || !$assetUser->hasAvatar()) - break; - - $assetInfo = $assetUser->getAvatarInfo(); - break; - - case 'background': - if(!$assetVisible || !isset($assetUser) || !$assetUser->hasBackground()) - break; - $assetInfo = $assetUser->getBackgroundInfo(); - break; -} - -if(!isset($assetInfo) || !$assetInfo->isPresent()) { - http_response_code(404); - return; -} - -$contentType = $assetInfo->getMimeType(); -$publicPath = $assetInfo->getPublicPath(); -$fileName = $assetInfo->getFileName(); - -if($assetDims > 0 && $assetInfo instanceof UserAssetScalableInterface) { - $assetInfo->ensureScaledExists($assetDims); - - $publicPath = $assetInfo->getPublicScaledPath($assetDims); - $fileName = $assetInfo->getScaledFileName($assetDims); -} - -header(sprintf('X-Accel-Redirect: %s', $publicPath)); -header(sprintf('Content-Type: %s', $contentType)); -header(sprintf('Content-Disposition: inline; filename="%s"', $fileName)); +require_once __DIR__ . '/index.php'; diff --git a/src/Http/Handlers/AssetsHandler.php b/src/Http/Handlers/AssetsHandler.php index 9f18da38..8baa9f52 100644 --- a/src/Http/Handlers/AssetsHandler.php +++ b/src/Http/Handlers/AssetsHandler.php @@ -4,6 +4,11 @@ namespace Misuzu\Http\Handlers; use HttpResponse; use HttpRequest; use Misuzu\GitInfo; +use Misuzu\Users\User; +use Misuzu\Users\UserNotFoundException; +use Misuzu\Users\Assets\StaticUserImageAsset; +use Misuzu\Users\Assets\UserImageAssetInterface; +use Misuzu\Users\Assets\UserAssetScalableInterface; final class AssetsHandler extends Handler { private const TYPES = [ @@ -44,7 +49,7 @@ final class AssetsHandler extends Handler { return $str; } - public static function view(HttpResponse $response, HttpRequest $request, string $name, string $type) { + public function serveComponent(HttpResponse $response, HttpRequest $request, string $name, string $type) { $entityTag = sprintf('W/"%s.%s/%s"', $name, $type, GitInfo::hash()); if(!MSZ_DEBUG && $name === 'debug') @@ -64,4 +69,78 @@ final class AssetsHandler extends Handler { } } } + + private function canViewAsset(HttpRequest $request, User $assetUser): bool { + return !$assetUser->isBanned() || ( + User::hasCurrent() + && parse_url($request->getHeaderLine('Referer'), PHP_URL_PATH) === url('user-profile') + && perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS) + ); + } + + private function serveUserAsset(HttpResponse $response, HttpRequest $request, UserImageAssetInterface $assetInfo): void { + $contentType = $assetInfo->getMimeType(); + $publicPath = $assetInfo->getPublicPath(); + $fileName = $assetInfo->getFileName(); + + if($assetInfo instanceof UserAssetScalableInterface) { + $dimensions = (int)($request->getQueryParam('res', FILTER_SANITIZE_NUMBER_INT) ?? $request->getQueryParam('r', FILTER_SANITIZE_NUMBER_INT)); + + if($dimensions > 0) { + $assetInfo->ensureScaledExists($dimensions); + $contentType = $assetInfo->getScaledMimeType($dimensions); + $publicPath = $assetInfo->getPublicScaledPath($dimensions); + $fileName = $assetInfo->getScaledFileName($dimensions); + } + } + + $response->setHeader('X-Accel-Redirect', $publicPath); + $response->setHeader('Content-Type', $contentType); + $response->setHeader('Content-Disposition', sprintf('inline; filename="%s"', $fileName)); + } + + public function serveAvatar(HttpResponse $response, HttpRequest $request, int $userId) { + $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC); + + try { + $userInfo = User::byId($userId); + + if(!$this->canViewAsset($request, $userInfo)) { + $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/banned-avatar.png', MSZ_PUBLIC); + } elseif($userInfo->hasAvatar()) { + $assetInfo = $userInfo->getAvatarInfo(); + } + } catch(UserNotFoundException $ex) {} + + $this->serveUserAsset($response, $request, $assetInfo); + } + + public function serveProfileBackground(HttpResponse $response, HttpRequest $request, int $userId) { + try { + $userInfo = User::byId($userId); + } catch(UserNotFoundException $ex) {} + + if(empty($userInfo) || !$userInfo->hasBackground() || !$this->canViewAsset($request, $userInfo)) { + $response->setText(''); + return 404; + } + + $this->serveUserAsset($response, $request, $userInfo->getBackgroundInfo()); + } + + public function serveLegacy(HttpResponse $response, HttpRequest $request) { + $assetUserId = (int)$request->getQueryParam('u', FILTER_SANITIZE_NUMBER_INT); + + switch($request->getQueryParam('m')) { + case 'avatar': + $this->serveAvatar($response, $request, $assetUserId); + return; + case 'background': + $this->serveProfileBackground($response, $request, $assetUserId); + return; + } + + $response->setText(''); + return 404; + } } diff --git a/src/Users/Assets/UserAssetScalableInterface.php b/src/Users/Assets/UserAssetScalableInterface.php index 194a8551..f4dbb8f9 100644 --- a/src/Users/Assets/UserAssetScalableInterface.php +++ b/src/Users/Assets/UserAssetScalableInterface.php @@ -9,4 +9,6 @@ interface UserAssetScalableInterface { public function ensureScaledExists(int $dims): void; public function getPublicScaledPath(int $dims): string; public function getScaledFileName(int $dims): string; + public function getScaledFileExtension(int $dims): string; + public function getScaledMimeType(int $dims): string; } diff --git a/src/Users/Assets/UserAvatarAsset.php b/src/Users/Assets/UserAvatarAsset.php index c114b1f7..d9c59d53 100644 --- a/src/Users/Assets/UserAvatarAsset.php +++ b/src/Users/Assets/UserAvatarAsset.php @@ -47,7 +47,19 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa return sprintf('avatar-%1$d.%2$s', $this->getUser()->getId(), $this->getFileExtension()); } public function getScaledFileName(int $dims): string { - return sprintf('avatar-%1$d-%3$dx%3$d.%2$s', $this->getUser()->getId(), $this->getFileExtension(), self::clampDimensions($dims)); + return sprintf('avatar-%1$d-%3$dx%3$d.%2$s', $this->getUser()->getId(), $this->getScaledFileExtension($dims), self::clampDimensions($dims)); + } + + public function getScaledMimeType(int $dims): string { + if(!$this->isScaledPresent($dims)) + return ''; + return mime_content_type($this->getScaledPath($dims)); + } + public function getScaledFileExtension(int $dims): string { + $imageSize = getimagesize($this->getScaledPath($dims)); + if($imageSize === null) + return 'img'; + return self::TYPES_EXT[$imageSize[2]] ?? 'img'; } public function getRelativePath(): string { diff --git a/src/url.php b/src/url.php index 0dfec48f..7a2fdfaf 100644 --- a/src/url.php +++ b/src/url.php @@ -74,8 +74,8 @@ define('MSZ_URLS', [ 'user-profile-edit' => ['/profile.php', ['u' => '', 'edit' => '1']], 'user-account-standing' => ['/profile.php', ['u' => ''], 'account-standing'], - 'user-avatar' => ['/user-assets.php', ['u' => '', 'm' => 'avatar', 'r' => '']], - 'user-background' => ['/user-assets.php', ['u' => '', 'm' => 'background']], + 'user-avatar' => ['/assets/avatar/', ['res' => '']], + 'user-background' => ['/assets/profile-background/'], 'user-relation-create' => ['/relations.php', ['u' => '', 'm' => '', 'csrf' => '{csrf}']], 'user-relation-none' => ['/relations.php', ['u' => '', 'm' => '[\Misuzu\Users\UserRelation::TYPE_NONE]', 'csrf' => '{csrf}']],