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; } }