eeprom/public/index.php

472 lines
13 KiB
PHP
Raw Normal View History

2020-05-08 22:53:21 +00:00
<?php
namespace EEPROM;
require_once __DIR__ . '/../startup.php';
$reqMethod = $_SERVER['REQUEST_METHOD'];
$reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
header('X-Powered-By: EEPROM');
function eepromOriginAllowed(string $origin): bool {
$origin = mb_strtolower(parse_url($origin, PHP_URL_HOST));
if($origin === $_SERVER['HTTP_HOST'])
return true;
$allowed = Config::get('CORS', 'origins', []);
if(empty($allowed))
return true;
return in_array($origin, $allowed);
}
function eepromByteSymbol(int $bytes, bool $decimal = true, array $symbols = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']): string {
if($bytes < 1) {
return '0 B';
}
$divider = $decimal ? 1000 : 1024;
$exp = floor(log($bytes) / log($divider));
$bytes = $bytes / pow($divider, floor($exp));
$symbol = $symbols[$exp];
return sprintf("%.2f %s%sB", $bytes, $symbol, $symbol !== '' && !$decimal ? 'i' : '');
}
if($_SERVER['HTTP_HOST'] === Config::get('Uploads', 'short_domain')) {
$reqMethod = 'GET'; // short domain is read only, prevent deleting
$reqPath = '/uploads/' . trim($reqPath, '/');
$isShortDomain = true;
}
if(!empty($_SERVER['HTTP_ORIGIN'])) {
if(!eepromOriginAllowed($_SERVER['HTTP_ORIGIN'])) {
http_response_code(403);
return;
}
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
header('Vary: Origin');
}
if($reqMethod === 'OPTIONS') {
http_response_code(204);
if(isset($isShortDomain))
header('Access-Control-Allow-Methods: OPTIONS, GET');
else {
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Authorization');
header('Access-Control-Allow-Methods: OPTIONS, GET, POST, DELETE');
}
return;
}
function connectMszDatabase(): \PDO {
global $mszPdo;
if($mszPdo)
return $mszPdo;
$configPath = Config::get('Misuzu', 'config', '');
if(!is_file($configPath))
throw new \Exception('Cannot find Misuzu configuration.');
$config = parse_ini_file($configPath, true)['Database'];
$dsn = ($config['driver'] ?? 'mysql') . ':';
foreach($config as $key => $value) {
if($key === 'driver' || $key === 'username' || $key === 'password')
continue;
if($key === 'database')
$key = 'dbname';
$dsn .= $key . '=' . $value . ';';
}
try {
$mszPdo = new \PDO($dsn, $config['username'], $config['password'], DB::FLAGS);
} catch(\PDOException $ex) {
throw new \Exception('Unable to connect to Misuzu database.');
}
return $mszPdo;
}
function checkSockChatAuth(string $token): int {
if(strpos($token, '_') === false)
return -1;
$mszPdo = connectMszDatabase();
$tokenParts = explode('_', $token, 2);
$userId = intval($tokenParts[0] ?? 0);
$chatToken = strval($tokenParts[1] ?? '');
$getUserId = $mszPdo->prepare('
SELECT `user_id`
FROM `msz_user_chat_tokens`
WHERE `user_id` = :user
AND `token_string` = :token
AND `token_created` > NOW() - INTERVAL 1 WEEK
');
$getUserId->bindValue('user', $userId);
$getUserId->bindValue('token', $chatToken);
$getUserId->execute();
return (int)$getUserId->fetchColumn();
}
function checkMszAuth(string $token): int {
$packed = Base64::decode($token, true);
$packed = str_pad($packed, 37, "\x00");
$unpacked = unpack('Cversion/Nuser/H64token', $packed);
if($unpacked['version'] !== 1)
return -1;
$getUserId = connectMszDatabase()->prepare('
SELECT `user_id`
FROM `msz_sessions`
WHERE `user_id` = :user
AND `session_key` = :token
AND `session_expires` > NOW()
');
$getUserId->bindValue('user', $unpacked['user']);
$getUserId->bindValue('token', $unpacked['token']);
$getUserId->execute();
return (int)$getUserId->fetchColumn();
}
if(!isset($isShortDomain) && !empty($_SERVER['HTTP_AUTHORIZATION'])) {
$authParts = explode(' ', $_SERVER['HTTP_AUTHORIZATION'], 2);
$authMethod = strval($authParts[0] ?? '');
$authToken = strval($authParts[1] ?? '');
switch($authMethod) {
case 'SockChat':
$authUserId = checkSockChatAuth($authToken);
break;
case 'Misuzu':
$authUserId = checkMszAuth($authToken);
break;
}
if(isset($authUserId) && $authUserId > 0)
User::byId($authUserId)->setActive();
}
if(preg_match('#^/uploads/([a-zA-Z0-9-_]{32})(\.t)?/?$#', $reqPath, $matches)) {
$getNormal = empty($matches[2]);
$getThumbnail = isset($matches[2]) && $matches[2] === '.t';
try {
$uploadInfo = Upload::byId($matches[1]);
} catch(UploadNotFoundException $ex) {
http_response_code(404);
echo 'File not found.';
return;
}
if($uploadInfo->isDMCA()) {
http_response_code(451);
echo 'File is unavailable for copyright reasons.';
return;
}
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
http_response_code(404);
echo 'File not found.';
return;
}
if($reqMethod === 'DELETE') {
if(!User::hasActive()) {
http_response_code(401);
return;
}
if(User::active()->isRestricted()
|| User::active()->getId() !== $uploadInfo->getUserId()) {
http_response_code(403);
return;
}
http_response_code(204);
$uploadInfo->delete(false);
return;
}
if(!is_file($uploadInfo->getPath())) {
http_response_code(404);
echo 'Data is missing.';
return;
}
if($getNormal) {
$uploadInfo->bumpAccess();
$uploadInfo->bumpExpiry();
}
$contentType = $uploadInfo->getType();
if($contentType === 'application/octet-stream' || substr($contentType, 0, 5) === 'text/')
$contentType = 'text/plain';
$sourceDir = basename($getThumbnail ? PRM_THUMBS : PRM_UPLOADS);
if($getThumbnail) {
if(substr($contentType, 0, 6) !== 'image/') {
http_response_code(404);
echo 'Thumbnails are not supported for this filetype.';
return;
}
try {
$imagick = new \Imagick($uploadInfo->getPath());
$imagick->setImageFormat('jpg');
$imagick->setImageCompressionQuality(40);
$thumbRes = 100;
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
if ($width > $height) {
$resizeWidth = $width * $thumbRes / $height;
$resizeHeight = $thumbRes;
} else {
$resizeWidth = $thumbRes;
$resizeHeight = $height * $thumbRes / $width;
}
$imagick->resizeImage(
$resizeWidth, $resizeHeight,
\Imagick::FILTER_GAUSSIAN, 0.7
);
$imagick->cropImage(
$thumbRes,
$thumbRes,
($resizeWidth - $thumbRes) / 2,
($resizeHeight - $thumbRes) / 2
);
$imagick->writeImage($uploadInfo->getThumbPath());
} catch(\Exception $ex) {}
}
header(sprintf('X-Accel-Redirect: /%s/%s', $sourceDir, $uploadInfo->getId()));
header(sprintf('Content-Type: %s', $contentType));
header(sprintf('Content-Disposition: inline; filename="%s"', addslashes($uploadInfo->getName())));
return;
}
if(preg_match('#^/uploads/([a-zA-Z0-9-_]{32})\.json/?$#', $reqPath, $matches)) {
if(isset($isShortDomain)) {
http_response_code(404);
return;
}
try {
$uploadInfo = Upload::byId($matches[1]);
} catch(UploadNotFoundException $ex) {
http_response_code(404);
return;
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode($uploadInfo);
return;
}
2020-05-12 15:19:18 +00:00
if($reqPath === '/eeprom.js' && is_file(PRM_ROOT . '/eeprom.js')) {
header('Content-Type: application/javascript; charset=utf-8');
echo file_get_contents(PRM_ROOT . '/eeprom.js');
return;
}
2020-05-08 22:53:21 +00:00
header('Content-Type: text/plain; charset=us-ascii');
if($reqPath === '/' || $reqPath === '/stats' || $reqPath === '/html') {
$fileCount = 0;
$userCount = 0;
$totalSize = 0;
$uniqueTypes = 0;
$getUploadStats = DB::prepare('
SELECT
COUNT(`upload_id`) AS `amount`,
SUM(`upload_size`) AS `size`,
COUNT(DISTINCT `upload_type`) AS `types`
FROM `prm_uploads`
WHERE `upload_deleted` IS NULL
AND `upload_dmca` IS NULL
');
$getUploadStats->execute();
$uploadStats = $getUploadStats->execute() ? $getUploadStats->fetchObject() : null;
if(!empty($uploadStats)) {
$fileCount = intval($uploadStats->amount);
$totalSize = intval($uploadStats->size ?? 0);
$uniqueTypes = intval($uploadStats->types ?? 0);
}
$getUserStats = DB::prepare('
SELECT COUNT(`user_id`) AS `amount`
FROM `prm_users`
WHERE `user_restricted` IS NULL
');
$getUserStats->execute();
$userStats = $getUserStats->execute() ? $getUserStats->fetchObject() : null;
if(!empty($userStats)) {
$userCount = intval($userStats->amount);
}
if($reqPath === '/stats') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'files_size' => $totalSize,
'files_count' => $fileCount,
'files_types' => $uniqueTypes,
'users_count' => $userCount,
]);
return;
}
$totalSizeFmt = eepromByteSymbol($totalSize);
if($reqPath === '/html') {
header('Content-Type: text/html; charset=utf-8');
echo <<<HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Flashii EEPROM</title>
<style type="text/css">
</style>
</head>
<body>
<pre>
________________ ____ ____ __ ___
/ ____/ ____/ __ \/ __ \/ __ \/ |/ /
/ __/ / __/ / /_/ / /_/ / / / / /|_/ /
/ /___/ /___/ ____/ _, _/ /_/ / / / /
/_____/_____/_/ /_/ |_|\____/_/ /_/
Currently serving {$totalSizeFmt} ({$totalSize} bytes) of {$fileCount} files in {$uniqueTypes} unique file types from {$userCount} users.
</pre>
</body>
</html>
HTML;
return;
}
echo <<<ASCII
________________ ____ ____ __ ___
/ ____/ ____/ __ \/ __ \/ __ \/ |/ /
/ __/ / __/ / /_/ / /_/ / / / / /|_/ /
/ /___/ /___/ ____/ _, _/ /_/ / / / /
/_____/_____/_/ /_/ |_|\____/_/ /_/
Currently serving {$totalSizeFmt} ({$totalSize} bytes) of {$fileCount} files in {$uniqueTypes} unique file types from {$userCount} users.
\r\n
ASCII;
return;
}
if($reqPath === '/uploads') {
if($reqMethod !== 'POST') {
http_response_code(405);
return;
}
try {
$appInfo = Application::byId(
filter_input(INPUT_POST, 'src', FILTER_VALIDATE_INT)
);
} catch(ApplicationNotFoundException $ex) {
http_response_code(404);
return;
}
if(!User::hasActive()) {
http_response_code(401);
return;
}
$userInfo = User::active();
if($userInfo->isRestricted()) {
http_response_code(403);
return;
}
if(empty($_FILES['file']['tmp_name']) || !is_file($_FILES['file']['tmp_name'])) {
http_response_code(400);
return;
}
$maxFileSize = $appInfo->getSizeLimit();
if($appInfo->allowSizeMultiplier())
$maxFileSize *= $userInfo->getSizeMultiplier();
$fileSize = filesize($_FILES['file']['tmp_name']);
if($_FILES['file']['size'] !== $fileSize || $fileSize > $maxFileSize) {
http_response_code(413);
header('Access-Control-Expose-Headers: X-EEPROM-Max-Size');
header('X-EEPROM-Max-Size: ' . $maxFileSize);
return;
}
$hash = hash_file('sha256', $_FILES['file']['tmp_name']);
$fileInfo = Upload::byHash($hash);
if($fileInfo !== null) {
if($fileInfo->isDMCA()) {
http_response_code(451);
return;
}
if($fileInfo->getUserId() !== $userInfo->getId()
|| $fileInfo->getApplicationId() !== $appInfo->getId())
unset($fileInfo);
}
if(!empty($fileInfo)) {
if($fileInfo->isDeleted())
$fileInfo->restore();
} else {
try {
$fileInfo = Upload::create(
$appInfo, $userInfo,
$_FILES['file']['name'],
mime_content_type($_FILES['file']['tmp_name']),
$fileSize, $hash,
$appInfo->getExpiry(), true
);
} catch(UploadCreationFailedException $ex) {
http_response_code(500);
return;
}
if(!move_uploaded_file($_FILES['file']['tmp_name'], $fileInfo->getPath())) {
http_response_code(500);
return;
}
}
http_response_code(201);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($fileInfo);
return;
}
if(is_file('../_manage.php') && include_once '../_manage.php')
return;
http_response_code(404);