getArray('cors:origins'); if(empty($allowed)) return true; return in_array($origin, $allowed); } function eepromUploadInfo(Uploads\UploadInfo $uploadInfo): array { global $eeprom; $uploadsCtx = $eeprom->getUploadsContext(); return [ 'id' => $uploadInfo->getId(), 'url' => $uploadsCtx->getFileUrlV1($uploadInfo), 'urlf' => $uploadsCtx->getFileUrlV1($uploadInfo, true), 'thumb' => $uploadsCtx->getThumbnailUrlV1($uploadInfo), 'name' => $uploadInfo->getName(), 'type' => $uploadInfo->getMediaTypeString(), 'size' => $uploadInfo->getDataSize(), 'user' => (int)$uploadInfo->getUserId(), 'appl' => (int)$uploadInfo->getAppId(), 'hash' => $uploadInfo->getHashString(), 'created' => str_replace('+00:00', 'Z', $uploadInfo->getCreatedAt()->format(\DateTime::ATOM)), 'accessed' => $uploadInfo->hasBeenAccessed() ? str_replace('+00:00', 'Z', $uploadInfo->getAccessedAt()->format(\DateTime::ATOM)) : null, 'expires' => $uploadInfo->hasExpired() ? str_replace('+00:00', 'Z', $uploadInfo->getExpiredAt()->format(\DateTime::ATOM)) : null, // These can never be reached, and in situation where they technically could it's because of an outdated local record 'deleted' => null, 'dmca' => 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) { // this is illegal, don't do this $userInfo = null; $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) { global $userInfo, $eeprom; $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) $userInfo = $eeprom->getUsersContext()->getUser($authUserId); } }); $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) { global $userInfo, $eeprom; if(!$request->isFormContent()) return 400; $content = $request->getContent(); try { $appInfo = $eeprom->getAppsContext()->getApp($content->getParam('src', FILTER_VALIDATE_INT)); } catch(RuntimeException $ex) { return 404; } if($userInfo === null) return 401; 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; } $uploadsCtx = $eeprom->getUploadsContext(); $uploadsData = $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()) unset($uploadInfo); } if(empty($uploadInfo)) { $uploadInfo = $uploadsData->createUpload( $appInfo, $userInfo, $_SERVER['REMOTE_ADDR'], $file->getSuggestedFileName(), mime_content_type($localFile), $fileSize, $hash, $appInfo->getBumpAmount(), true ); $filePath = $uploadsCtx->getFileDataPath($uploadInfo); $file->moveTo($filePath); } else { $filePath = $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 eepromUploadInfo($uploadInfo); }); $router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) { global $userInfo, $eeprom; if($userInfo === null) return 401; $uploadsData = $eeprom->getUploadsContext()->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($userInfo->isRestricted() || $userInfo->getId() !== $uploadInfo->getUserId()) return 403; $uploadsData->deleteUpload($uploadInfo); return 204; }); $router->get('/uploads/:filename', function($response, $request, $fileName) use ($db) { global $eeprom; $pathInfo = pathinfo($fileName); $fileId = $pathInfo['filename']; $fileExt = $pathInfo['extension'] ?? ''; $isThumbnail = $fileExt === 't'; $isJson = $fileExt === 'json'; if($fileExt !== '' && $fileExt !== 't' && $fileExt !== 'json') return 404; $uploadsCtx = $eeprom->getUploadsContext(); $uploadsData = $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 eepromUploadInfo($uploadInfo); $filePath = $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(!$uploadsCtx->supportsThumbnailing($uploadInfo)) return 404; $contentType = 'image/jpeg'; $accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo); $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg'; } else { $accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo); } $response->accelRedirect($accelRedirectPath); $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) { global $eeprom; $pathInfo = pathinfo($fileName); $fileId = $pathInfo['filename']; $fileExt = $pathInfo['extension'] ?? ''; $isThumbnail = $fileExt === 't'; if($fileExt !== '' && $fileExt !== 't') return 404; $uploadsCtx = $eeprom->getUploadsContext(); $uploadsData = $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; } $filePath = $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(!$uploadsCtx->supportsThumbnailing($uploadInfo)) return 404; $contentType = 'image/jpeg'; $accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo); $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg'; } else { $accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo); } $response->accelRedirect($accelRedirectPath); $response->setContentType($contentType); $response->setFileName(addslashes($fileName)); }); } $router->dispatch();