161 lines
5.4 KiB
PHP
161 lines
5.4 KiB
PHP
|
<?php
|
||
|
namespace Misuzu\Http\Handlers;
|
||
|
|
||
|
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 = [
|
||
|
'js' => [
|
||
|
'root' => MSZ_ROOT . '/assets/js',
|
||
|
'mime' => 'application/javascript; charset=utf-8',
|
||
|
],
|
||
|
'css' => [
|
||
|
'root' => MSZ_ROOT . '/assets/css',
|
||
|
'mime' => 'text/css; charset=utf-8',
|
||
|
],
|
||
|
];
|
||
|
|
||
|
public function __construct() {
|
||
|
$GLOBALS['misuzuBypassLockdown'] = true;
|
||
|
parent::__construct();
|
||
|
}
|
||
|
|
||
|
private static function recurse(string $dir): string {
|
||
|
$str = '';
|
||
|
$dir = rtrim(realpath($dir), '/') . '/*';
|
||
|
$dirs = [];
|
||
|
|
||
|
foreach(glob($dir) as $path) {
|
||
|
if(is_dir($path)) {
|
||
|
$dirs[] = $path;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(MSZ_DEBUG)
|
||
|
$str .= "/*** {$path} ***/\n";
|
||
|
$str .= trim(file_get_contents($path));
|
||
|
$str .= "\n\n";
|
||
|
}
|
||
|
|
||
|
foreach($dirs as $path)
|
||
|
$str .= self::recurse($path);
|
||
|
|
||
|
return $str;
|
||
|
}
|
||
|
|
||
|
public function serveComponent($response, $request, string $fileName) {
|
||
|
$name = pathinfo($fileName, PATHINFO_FILENAME);
|
||
|
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||
|
|
||
|
$entityTag = sprintf('%s.%s/%s', $name, $type, GitInfo::hash());
|
||
|
|
||
|
if(!MSZ_DEBUG && $name === 'debug')
|
||
|
return 404;
|
||
|
if(!MSZ_DEBUG && $request->getHeaderFirstLine('If-None-Match') === '"' . $entityTag . '"')
|
||
|
return 304;
|
||
|
|
||
|
if(array_key_exists($type, self::TYPES)) {
|
||
|
$type = self::TYPES[$type];
|
||
|
$path = ($type['root'] ?? '') . '/' . $name;
|
||
|
|
||
|
if(is_dir($path)) {
|
||
|
$response->setContentType($type['mime'] ?? 'application/octet-stream');
|
||
|
$response->setCacheControl(MSZ_DEBUG ? 'no-cache' : 'must-revalidate');
|
||
|
$response->setEntityTag($entityTag);
|
||
|
return self::recurse($path);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function canViewAsset($request, User $assetUser): bool {
|
||
|
return !$assetUser->isBanned() || (
|
||
|
User::hasCurrent()
|
||
|
&& parse_url($request->getHeaderFirstLine('Referer'), PHP_URL_PATH) === url('user-profile')
|
||
|
&& perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function serveUserAsset($response, $request, UserImageAssetInterface $assetInfo): void {
|
||
|
$contentType = $assetInfo->getMimeType();
|
||
|
$publicPath = $assetInfo->getPublicPath();
|
||
|
$fileName = $assetInfo->getFileName();
|
||
|
|
||
|
if($assetInfo instanceof UserAssetScalableInterface) {
|
||
|
$dimensions = (int)($request->getParam('res', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('r', FILTER_SANITIZE_NUMBER_INT));
|
||
|
|
||
|
if($dimensions > 0) {
|
||
|
$assetInfo->ensureScaledExists($dimensions);
|
||
|
$contentType = $assetInfo->getScaledMimeType($dimensions);
|
||
|
$publicPath = $assetInfo->getPublicScaledPath($dimensions);
|
||
|
$fileName = $assetInfo->getScaledFileName($dimensions);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$response->accelRedirect($publicPath);
|
||
|
$response->setContentType($contentType);
|
||
|
$response->setFileName($fileName, false);
|
||
|
}
|
||
|
|
||
|
public function serveAvatar($response, $request, string $fileName) {
|
||
|
$userId = intval(pathinfo($fileName, PATHINFO_FILENAME));
|
||
|
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||
|
|
||
|
if($type !== '' && $type !== 'png')
|
||
|
return 404;
|
||
|
|
||
|
$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($response, $request, string $fileName) {
|
||
|
$userId = intval(pathinfo($fileName, PATHINFO_FILENAME));
|
||
|
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||
|
|
||
|
if($type !== '' && $type !== 'png')
|
||
|
return 404;
|
||
|
|
||
|
try {
|
||
|
$userInfo = User::byId($userId);
|
||
|
} catch(UserNotFoundException $ex) {}
|
||
|
|
||
|
if(empty($userInfo) || !$userInfo->hasBackground() || !$this->canViewAsset($request, $userInfo)) {
|
||
|
$response->setContent('');
|
||
|
return 404;
|
||
|
}
|
||
|
|
||
|
$this->serveUserAsset($response, $request, $userInfo->getBackgroundInfo());
|
||
|
}
|
||
|
|
||
|
public function serveLegacy($response, $request) {
|
||
|
$assetUserId = (int)$request->getParam('u', FILTER_SANITIZE_NUMBER_INT);
|
||
|
|
||
|
switch($request->getParam('m')) {
|
||
|
case 'avatar':
|
||
|
$this->serveAvatar($response, $request, $assetUserId);
|
||
|
return;
|
||
|
case 'background':
|
||
|
$this->serveProfileBackground($response, $request, $assetUserId);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$response->setContent('');
|
||
|
return 404;
|
||
|
}
|
||
|
}
|