225 lines
7.7 KiB
PHP
225 lines
7.7 KiB
PHP
<?php
|
|
namespace EEPROM\Uploads;
|
|
|
|
use RuntimeException;
|
|
use Index\Routing\IRouter;
|
|
use Index\Routing\IRouteHandler;
|
|
use Index\Routing\Route;
|
|
use EEPROM\Apps\AppsContext;
|
|
use EEPROM\Auth\AuthInfo;
|
|
use EEPROM\Uploads\UploadsContext;
|
|
|
|
class UploadsRoutes implements IRouteHandler {
|
|
public function __construct(
|
|
private AuthInfo $authInfo,
|
|
private AppsContext $appsCtx,
|
|
private UploadsContext $uploadsCtx,
|
|
private bool $isApiDomain
|
|
) {}
|
|
|
|
public function registerRoutes(IRouter $router): void {
|
|
if($this->isApiDomain) {
|
|
Route::handleAttributes($router, $this);
|
|
} else {
|
|
$router->options('/', $this->getUpload(...));
|
|
$router->get('/:filename', $this->getUpload(...));
|
|
}
|
|
}
|
|
|
|
#[Route('OPTIONS', '/uploads/:filename')]
|
|
#[Route('GET', '/uploads/:filename')]
|
|
public function getUpload($response, $request, string $fileName) {
|
|
if($this->isApiDomain) {
|
|
if($request->hasHeader('Origin'))
|
|
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
|
|
|
$response->setHeader('Access-Control-Allow-Headers', 'Authorization');
|
|
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, DELETE');
|
|
} else {
|
|
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
|
|
}
|
|
|
|
if($request->getMethod() === 'OPTIONS')
|
|
return 204;
|
|
|
|
$pathInfo = pathinfo($fileName);
|
|
$fileId = $pathInfo['filename'];
|
|
$fileExt = $pathInfo['extension'] ?? '';
|
|
$isData = $fileExt === '';
|
|
$isThumbnail = $fileExt === 't';
|
|
$isJson = $this->isApiDomain && $fileExt === 'json';
|
|
|
|
if(!$isData && !$isThumbnail && !$isJson)
|
|
return 404;
|
|
|
|
$uploadsData = $this->uploadsCtx->getUploadsData();
|
|
$uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
|
|
if($uploadInfo === null) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($uploadInfo->isCopyrightTakedown()) {
|
|
$response->setContent('File is unavailable for copyright reasons.');
|
|
return 451;
|
|
}
|
|
|
|
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($isJson)
|
|
return $this->uploadsCtx->convertToClientJsonV1($uploadInfo);
|
|
|
|
$filePath = $this->uploadsCtx->getFileDataPath($uploadInfo);
|
|
if(!is_file($filePath)) {
|
|
$response->setContent('Data is missing.');
|
|
return 404;
|
|
}
|
|
|
|
if(!$isThumbnail) {
|
|
$uploadsData->bumpUploadAccess($uploadInfo);
|
|
$uploadsData->bumpUploadExpires($uploadInfo);
|
|
}
|
|
|
|
$fileName = $uploadInfo->getName();
|
|
$contentType = $uploadInfo->getMediaTypeString();
|
|
|
|
if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
|
|
$contentType = 'text/plain';
|
|
|
|
if($isThumbnail) {
|
|
if(!$this->uploadsCtx->supportsThumbnailing($uploadInfo))
|
|
return 404;
|
|
|
|
$contentType = 'image/jpeg';
|
|
$accelRedirectPath = $this->uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo);
|
|
$fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
|
|
} else {
|
|
$accelRedirectPath = $this->uploadsCtx->getFileDataRedirectPath($uploadInfo);
|
|
}
|
|
|
|
$response->accelRedirect($accelRedirectPath);
|
|
$response->setContentType($contentType);
|
|
$response->setFileName(addslashes($fileName));
|
|
}
|
|
|
|
#[Route('OPTIONS', '/uploads')]
|
|
#[Route('POST', '/uploads')]
|
|
public function postUpload($response, $request) {
|
|
if($request->hasHeader('Origin'))
|
|
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
|
|
|
$response->setHeader('Access-Control-Allow-Headers', 'Authorization');
|
|
$response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST');
|
|
|
|
if($request->getMethod() === 'OPTIONS')
|
|
return 204;
|
|
|
|
if(!$request->isFormContent())
|
|
return 400;
|
|
|
|
$content = $request->getContent();
|
|
|
|
try {
|
|
$appInfo = $this->appsCtx->getApp($content->getParam('src', FILTER_VALIDATE_INT));
|
|
} catch(RuntimeException $ex) {
|
|
return 404;
|
|
}
|
|
|
|
if(!$this->authInfo->isLoggedIn())
|
|
return 401;
|
|
|
|
$userInfo = $this->authInfo->getUserInfo();
|
|
if($userInfo->isRestricted())
|
|
return 403;
|
|
|
|
try {
|
|
$file = $content->getUploadedFile('file');
|
|
} catch(RuntimeException $ex) {
|
|
return 400;
|
|
}
|
|
|
|
$maxFileSize = $appInfo->getDataSizeLimit();
|
|
if($appInfo->allowSizeMultiplier())
|
|
$maxFileSize *= $userInfo->getDataSizeMultiplier();
|
|
|
|
$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;
|
|
}
|
|
|
|
$uploadsData = $this->uploadsCtx->getUploadsData();
|
|
$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 = $uploadsData->getUpload(appInfo: $appInfo, userInfo: $userInfo, hashString: $hash)
|
|
?? $uploadsData->getUpload(hashString: $hash);
|
|
|
|
if($uploadInfo !== null) {
|
|
if($uploadInfo->isCopyrightTakedown())
|
|
return 451;
|
|
|
|
if($uploadInfo->getUserId() !== $userInfo->getId()
|
|
|| $uploadInfo->getAppId() !== $appInfo->getId())
|
|
$uploadInfo = null;
|
|
}
|
|
|
|
if($uploadInfo === null) {
|
|
$uploadInfo = $uploadsData->createUpload(
|
|
$appInfo, $userInfo, $_SERVER['REMOTE_ADDR'],
|
|
$file->getSuggestedFileName(), mime_content_type($localFile),
|
|
$fileSize, $hash, $appInfo->getBumpAmount(), true
|
|
);
|
|
$filePath = $this->uploadsCtx->getFileDataPath($uploadInfo);
|
|
$file->moveTo($filePath);
|
|
} else {
|
|
$filePath = $this->uploadsCtx->getFileDataPath($uploadInfo);
|
|
if($uploadInfo->isDeleted())
|
|
$uploadsData->restoreUpload($uploadInfo);
|
|
|
|
$uploadsData->bumpUploadExpires($uploadInfo);
|
|
}
|
|
|
|
$response->setStatusCode(201);
|
|
$response->setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
|
|
return $this->uploadsCtx->convertToClientJsonV1($uploadInfo);
|
|
}
|
|
|
|
#[Route('DELETE', '/uploads/:fileid')]
|
|
public function deleteUpload($response, $request, string $fileId) {
|
|
if(!$this->authInfo->isLoggedIn())
|
|
return 401;
|
|
|
|
$uploadsData = $this->uploadsCtx->getUploadsData();
|
|
|
|
$uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
|
|
if($uploadInfo === null) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
if($uploadInfo->isCopyrightTakedown()) {
|
|
$response->setContent('File is unavailable for copyright reasons.');
|
|
return 451;
|
|
}
|
|
|
|
if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
|
|
$response->setContent('File not found.');
|
|
return 404;
|
|
}
|
|
|
|
$userInfo = $this->authInfo->getUserInfo();
|
|
if($userInfo->isRestricted() || $userInfo->getId() !== $uploadInfo->getUserId())
|
|
return 403;
|
|
|
|
$uploadsData->deleteUpload($uploadInfo);
|
|
return 204;
|
|
}
|
|
}
|