382 lines
12 KiB
PHP
382 lines
12 KiB
PHP
<?php
|
|
namespace EEPROM;
|
|
|
|
use RuntimeException;
|
|
use Index\Http\HttpFx;
|
|
|
|
require_once __DIR__ . '/../eeprom.php';
|
|
|
|
set_exception_handler(function(\Throwable $ex) {
|
|
\Sentry\captureException($ex);
|
|
|
|
ob_clean();
|
|
http_response_code(500);
|
|
|
|
if(PRM_DEBUG) {
|
|
header('Content-Type: text/plain; charset=utf-8');
|
|
echo (string)$ex;
|
|
} else echo '500';
|
|
exit;
|
|
});
|
|
|
|
function eepromOriginAllowed(string $origin): bool {
|
|
global $cfg;
|
|
|
|
$origin = mb_strtolower(parse_url($origin, PHP_URL_HOST));
|
|
|
|
if($origin === $_SERVER['HTTP_HOST'])
|
|
return true;
|
|
|
|
$allowed = $cfg->getArray('cors:origins');
|
|
if(empty($allowed))
|
|
return true;
|
|
|
|
return in_array($origin, $allowed);
|
|
}
|
|
|
|
function eepromUploadInfo(Upload $uploadInfo): array {
|
|
return [
|
|
'id' => $uploadInfo->getId(),
|
|
'url' => $uploadInfo->getPublicUrl(),
|
|
'urlf' => $uploadInfo->getPublicUrl(true),
|
|
'thumb' => $uploadInfo->getPublicThumbUrl(),
|
|
'name' => $uploadInfo->getName(),
|
|
'type' => $uploadInfo->getType(),
|
|
'size' => $uploadInfo->getSize(),
|
|
'user' => $uploadInfo->getUserId(),
|
|
'appl' => $uploadInfo->getApplicationId(),
|
|
'hash' => $uploadInfo->getHash(),
|
|
'created' => date('c', $uploadInfo->getCreated()),
|
|
'accessed' => $uploadInfo->hasBeenAccessed() ? date('c', $uploadInfo->getLastAccessed()) : null,
|
|
'expires' => $uploadInfo->hasExpired() ? date('c', $uploadInfo->getExpires()) : null,
|
|
'deleted' => $uploadInfo->isDeleted() ? date('c', $uploadInfo->getDeleted()) : null,
|
|
'dmca' => $uploadInfo->isDMCA() ? date('c', $uploadInfo->getDMCA()) : null,
|
|
];
|
|
}
|
|
|
|
$isApiDomain = $_SERVER['HTTP_HOST'] === $cfg->getString('domain:api');
|
|
$router = new HttpFx;
|
|
|
|
$router->use('/', function($response) {
|
|
$response->setPoweredBy('EEPROM');
|
|
});
|
|
|
|
$router->use('/', function($response, $request) {
|
|
$origin = $request->getHeaderLine('Origin');
|
|
|
|
if(!empty($origin)) {
|
|
if(!eepromOriginAllowed($origin))
|
|
return 403;
|
|
|
|
$response->setHeader('Access-Control-Allow-Origin', $origin);
|
|
$response->setHeader('Vary', 'Origin');
|
|
}
|
|
});
|
|
|
|
if($isApiDomain) {
|
|
$router->use('/', function($response, $request) {
|
|
if($request->hasHeader('Origin'))
|
|
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
|
});
|
|
|
|
$router->use('/', function($response, $request) {
|
|
if($request->getMethod() === 'OPTIONS') {
|
|
$response->setHeader('Access-Control-Allow-Headers', 'Authorization');
|
|
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, DELETE');
|
|
return 204;
|
|
}
|
|
});
|
|
|
|
$router->use('/', function($response, $request) use ($db, $cfg) {
|
|
$auth = $request->getHeaderLine('Authorization');
|
|
if(empty($auth)) {
|
|
$mszAuth = (string)$request->getCookie('msz_auth');
|
|
if(!empty($mszAuth))
|
|
$auth = 'Misuzu ' . $mszAuth;
|
|
}
|
|
|
|
if(!empty($auth)) {
|
|
$authParts = explode(' ', $auth, 2);
|
|
$authMethod = strval($authParts[0] ?? '');
|
|
$authToken = strval($authParts[1] ?? '');
|
|
|
|
$authClients = $cfg->getArray('auth:clients');
|
|
|
|
foreach($authClients as $client) {
|
|
$client = new $client;
|
|
if($client->getName() !== $authMethod)
|
|
continue;
|
|
$authUserId = $client->verifyToken($authToken);
|
|
break;
|
|
}
|
|
|
|
if(isset($authUserId) && $authUserId > 0)
|
|
User::byId($db, $authUserId)->setActive();
|
|
}
|
|
});
|
|
|
|
$router->get('/eeprom.js', function($response) {
|
|
$response->accelRedirect('/js/eeprom-v1.0.js');
|
|
$response->setContentType('application/javascript; charset=utf-8');
|
|
});
|
|
|
|
$router->get('/stats.json', function() use ($db) {
|
|
$fileCount = 0;
|
|
$userCount = 0;
|
|
$totalSize = 0;
|
|
$uniqueTypes = 0;
|
|
|
|
$uploadStats = $db->query('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');
|
|
|
|
if($uploadStats->next()) {
|
|
$fileCount = $uploadStats->getInteger(0);
|
|
$totalSize = $uploadStats->getInteger(1);
|
|
$uniqueTypes = $uploadStats->getInteger(2);
|
|
}
|
|
|
|
$userStats = $db->query('SELECT COUNT(`user_id`) AS `amount` FROM `prm_users` WHERE `user_restricted` IS NULL');
|
|
|
|
if($userStats->next())
|
|
$userCount = $userStats->getInteger(0);
|
|
|
|
return [
|
|
'size' => $totalSize,
|
|
'files' => $fileCount,
|
|
'types' => $uniqueTypes,
|
|
'members' => $userCount,
|
|
];
|
|
});
|
|
|
|
$router->get('/', function($response) {
|
|
$response->accelRedirect('/index.html');
|
|
$response->setContentType('text/html; charset=utf-8');
|
|
});
|
|
|
|
$router->post('/uploads', function($response, $request) use ($db) {
|
|
if(!$request->isFormContent())
|
|
return 400;
|
|
|
|
$content = $request->getContent();
|
|
|
|
try {
|
|
$appInfo = Application::byId($db, (int)$content->getParam('src', FILTER_VALIDATE_INT));
|
|
} catch(RuntimeException $ex) {
|
|
return 404;
|
|
}
|
|
|
|
if(!User::hasActive())
|
|
return 401;
|
|
|
|
$userInfo = User::active();
|
|
|
|
if($userInfo->isRestricted())
|
|
return 403;
|
|
|
|
try {
|
|
$file = $content->getUploadedFile('file');
|
|
} catch(RuntimeException $ex) {
|
|
return 400;
|
|
}
|
|
|
|
$maxFileSize = $appInfo->getSizeLimit();
|
|
if($appInfo->allowSizeMultiplier())
|
|
$maxFileSize *= $userInfo->getSizeMultiplier();
|
|
|
|
$localFile = $file->getLocalFileName();
|
|
$fileSize = filesize($localFile);
|
|
|
|
if($file->getSize() !== $fileSize || $fileSize > $maxFileSize) {
|
|
$response->setHeader('Access-Control-Expose-Headers', 'X-EEPROM-Max-Size');
|
|
$response->setHeader('X-EEPROM-Max-Size', $maxFileSize);
|
|
return 413;
|
|
}
|
|
|
|
$hash = hash_file('sha256', $localFile);
|
|
|
|
// this is stupid: dmca status is stored as a file record rather than in a separate table requiring this hack ass garbage
|
|
$uploadInfo = Upload::byAppUserHash($db, $appInfo, $userInfo, $hash) ?? Upload::byHash($db, $hash);
|
|
|
|
if($uploadInfo !== null) {
|
|
if($uploadInfo->isDMCA())
|
|
return 451;
|
|
|
|
if($uploadInfo->getUserId() !== $userInfo->getId()
|
|
|| $uploadInfo->getApplicationId() !== $appInfo->getId())
|
|
unset($uploadInfo);
|
|
}
|
|
|
|
if(!empty($uploadInfo)) {
|
|
if($uploadInfo->isDeleted())
|
|
$uploadInfo->restore($db);
|
|
$uploadInfo->bumpExpiry($db);
|
|
} else {
|
|
$uploadInfo = Upload::create(
|
|
$db, $appInfo, $userInfo,
|
|
$file->getSuggestedFileName(),
|
|
mime_content_type($localFile),
|
|
$fileSize, $hash,
|
|
$appInfo->getExpiry(), true
|
|
);
|
|
|
|
$file->moveTo($uploadInfo->getPath());
|
|
}
|
|
|
|
$response->setStatusCode(201);
|
|
$response->setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
|
|
return eepromUploadInfo($uploadInfo);
|
|
});
|
|
|
|
$router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) {
|
|
try {
|
|
$uploadInfo = Upload::byId($db, $fileId);
|
|
} catch(RuntimeException $ex) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($uploadInfo->isDMCA()) {
|
|
$response->setContent('File is unavailable for copyright reasons.');
|
|
return 451;
|
|
}
|
|
|
|
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if(!User::hasActive())
|
|
return 401;
|
|
|
|
if(User::active()->isRestricted() || User::active()->getId() !== $uploadInfo->getUserId())
|
|
return 403;
|
|
|
|
$uploadInfo->delete($db, false);
|
|
return 204;
|
|
});
|
|
|
|
$router->get('/uploads/:filename', function($response, $request, $fileName) use ($db) {
|
|
$pathInfo = pathinfo($fileName);
|
|
$fileId = $pathInfo['filename'];
|
|
$fileExt = $pathInfo['extension'] ?? '';
|
|
$isThumbnail = $fileExt === 't';
|
|
$isJson = $fileExt === 'json';
|
|
|
|
if($fileExt !== '' && $fileExt !== 't' && $fileExt !== 'json')
|
|
return 404;
|
|
|
|
try {
|
|
$uploadInfo = Upload::byId($db, $fileId);
|
|
} catch(RuntimeException $ex) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($isJson)
|
|
return eepromUploadInfo($uploadInfo);
|
|
|
|
if($uploadInfo->isDMCA()) {
|
|
$response->setContent('File is unavailable for copyright reasons.');
|
|
return 451;
|
|
}
|
|
|
|
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if(!is_file($uploadInfo->getPath())) {
|
|
$response->setContent('Data is missing.');
|
|
return 404;
|
|
}
|
|
|
|
if(!$isThumbnail) {
|
|
$uploadInfo->bumpAccess($db);
|
|
$uploadInfo->bumpExpiry($db);
|
|
}
|
|
|
|
$fileName = $uploadInfo->getName();
|
|
$contentType = $uploadInfo->getType();
|
|
|
|
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
|
$contentType = 'text/plain';
|
|
|
|
$sourceDir = basename($isThumbnail ? PRM_THUMBS : PRM_UPLOADS);
|
|
|
|
if($isThumbnail && $uploadInfo->supportsThumbnail()) {
|
|
if(!is_file($uploadInfo->getThumbPath()))
|
|
$uploadInfo->createThumbnail();
|
|
$contentType = 'image/jpeg';
|
|
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
|
}
|
|
|
|
$response->accelRedirect(sprintf('/%s/%s', $sourceDir, $uploadInfo->getId()));
|
|
$response->setContentType($contentType);
|
|
$response->setFileName(addslashes($fileName));
|
|
});
|
|
} else {
|
|
$router->use('/', function($response, $request) {
|
|
if($request->getMethod() === 'OPTIONS') {
|
|
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
|
|
return 204;
|
|
}
|
|
});
|
|
|
|
$router->get('/:filename', function($response, $request, $fileName) use ($db) {
|
|
$pathInfo = pathinfo($fileName);
|
|
$fileId = $pathInfo['filename'];
|
|
$fileExt = $pathInfo['extension'] ?? '';
|
|
$isThumbnail = $fileExt === 't';
|
|
|
|
if($fileExt !== '' && $fileExt !== 't')
|
|
return 404;
|
|
|
|
try {
|
|
$uploadInfo = Upload::byId($db, $fileId);
|
|
} catch(RuntimeException $ex) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($uploadInfo->isDMCA()) {
|
|
$response->setContent('File is unavailable for copyright reasons.');
|
|
return 451;
|
|
}
|
|
|
|
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if(!is_file($uploadInfo->getPath())) {
|
|
$response->setContent('Data is missing.');
|
|
return 404;
|
|
}
|
|
|
|
if(!$isThumbnail) {
|
|
$uploadInfo->bumpAccess($db);
|
|
$uploadInfo->bumpExpiry($db);
|
|
}
|
|
|
|
$fileName = $uploadInfo->getName();
|
|
$contentType = $uploadInfo->getType();
|
|
|
|
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
|
$contentType = 'text/plain';
|
|
|
|
$sourceDir = basename($isThumbnail ? PRM_THUMBS : PRM_UPLOADS);
|
|
|
|
if($isThumbnail && $uploadInfo->supportsThumbnail()) {
|
|
if(!is_file($uploadInfo->getThumbPath()))
|
|
$uploadInfo->createThumbnail();
|
|
$contentType = 'image/jpeg';
|
|
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
|
}
|
|
|
|
$response->accelRedirect(sprintf('/%s/%s', $sourceDir, $uploadInfo->getId()));
|
|
$response->setContentType($contentType);
|
|
$response->setFileName(addslashes($fileName));
|
|
});
|
|
}
|
|
|
|
$router->dispatch();
|