diff --git a/debug-pages/test-v1.0.html b/debug-pages/test-v1.0.html new file mode 100644 index 0000000..6d4b9d1 --- /dev/null +++ b/debug-pages/test-v1.0.html @@ -0,0 +1,112 @@ + +
+
+ + diff --git a/eeprom.php b/eeprom.php index 19964c6..3dbcf5b 100644 --- a/eeprom.php +++ b/eeprom.php @@ -13,6 +13,7 @@ define('PRM_DEBUG', is_file(PRM_ROOT . '/.debug')); define('PRM_PUBLIC', PRM_ROOT . '/public'); define('PRM_SOURCE', PRM_ROOT . '/src'); define('PRM_LIBRARY', PRM_ROOT . '/lib'); +define('PRM_DEBUG_PAGES', PRM_ROOT . '/debug-pages'); define('PRM_UPLOADS', PRM_PUBLIC . '/data'); define('PRM_THUMBS', PRM_PUBLIC . '/thumb'); diff --git a/lib/index b/lib/index index 8a5423f..9d99e10 160000 --- a/lib/index +++ b/lib/index @@ -1 +1 @@ -Subproject commit 8a5423fea397e2f2adca0b9f46d1e5c21fd13c44 +Subproject commit 9d99e10541cf8ba7407be0b53323cfa3ebfc37f5 diff --git a/public/index.php b/public/index.php index e8e2eda..88eba66 100644 --- a/public/index.php +++ b/public/index.php @@ -1,13 +1,10 @@ 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'); } +}); - 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; -} - -if(!isset($isShortDomain) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { - $authParts = explode(' ', $_SERVER['HTTP_AUTHORIZATION'], 2); - $authMethod = strval($authParts[0] ?? ''); - $authToken = strval($authParts[1] ?? ''); - - $authClients = Config::get('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(); -} - -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($db, $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($isApiDomain) { + $router->use('/', function($response, $request) { + if($request->getMethod() === 'OPTIONS') { + $response->setHeader('Access-Control-Allow-Credentials', 'true'); + $response->setHeader('Access-Control-Allow-Headers', 'Authorization'); + $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, DELETE'); + return 204; } + }); - if(User::active()->isRestricted() - || User::active()->getId() !== $uploadInfo->getUserId()) { - http_response_code(403); - return; - } + $router->use('/', function($response, $request) use ($db) { + $auth = $request->getHeaderLine('Authorization'); + if(!empty($auth)) { + $authParts = explode(' ', $auth, 2); + $authMethod = strval($authParts[0] ?? ''); + $authToken = strval($authParts[1] ?? ''); - http_response_code(204); - $uploadInfo->delete($db, false); - return; - } + $authClients = Config::get('Auth', 'clients', []); - if(!is_file($uploadInfo->getPath())) { - http_response_code(404); - echo 'Data is missing.'; - return; - } - - if($getNormal) { - $uploadInfo->bumpAccess($db); - $uploadInfo->bumpExpiry($db); - } - - $fileName = $uploadInfo->getName(); - $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 && $uploadInfo->supportsThumbnail()) { - if(!is_file($uploadInfo->getThumbPath())) - $uploadInfo->createThumbnail(); - $contentType = 'image/jpeg'; - $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg'; - } - - header(sprintf('X-Accel-Redirect: /%s/%s', $sourceDir, $uploadInfo->getId())); - header(sprintf('Content-Type: %s', $contentType)); - header(sprintf('Content-Disposition: inline; filename="%s"', addslashes($fileName))); - 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($db, $matches[1]); - } catch(UploadNotFoundException $ex) { - http_response_code(404); - return; - } - - header('Content-Type: application/json; charset=utf-8'); - echo json_encode($uploadInfo); - return; -} - -if($reqPath === '/eeprom.js') { - header('Content-Type: application/javascript; charset=utf-8'); - echo file_get_contents(PRM_ROOT . '/js/eeprom-v1.0.js'); - return; -} - -if($reqPath === '/test-v1.0.html' && PRM_DEBUG) { - header('Content-Type: text/html; charset=utf-8'); - - $cookie = htmlspecialchars((string)filter_input(INPUT_COOKIE, 'msz_auth')); - - echo << -
-
- - -TEST; - return; -} - -header('Content-Type: text/plain; charset=us-ascii'); - -if($reqPath === '/' || $reqPath === '/stats.json') { - $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); - - if($reqPath === '/stats.json') { - header('Content-Type: application/json; charset=utf-8'); - echo json_encode([ + return [ 'size' => $totalSize, 'files' => $fileCount, 'types' => $uniqueTypes, 'members' => $userCount, - ]); - return; - } + ]; + }); - header('Content-Type: text/html; charset=utf-8'); - header('X-Accel-Redirect: /index.html'); - return; -} + $router->get('/', function($response) { + $response->accelRedirect('/index.html'); + $response->setContentType('text/html; charset=utf-8'); + }); -if($reqPath === '/uploads') { - if($reqMethod !== 'POST') { - http_response_code(405); - return; - } + $router->post('/uploads', function($response, $request) use ($db) { + if(!$request->isFormContent()) + return 400; - try { - $appInfo = Application::byId($db, filter_input(INPUT_POST, 'src', FILTER_VALIDATE_INT)); - } catch(ApplicationNotFoundException $ex) { - http_response_code(404); - return; - } + $content = $request->getContent(); - 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($db, $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($db); - } else { try { - $fileInfo = Upload::create( - $db, $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; + $appInfo = Application::byId($db, (int)$content->getParam('src', FILTER_VALIDATE_INT)); + } catch(ApplicationNotFoundException $ex) { + return 404; } - if(!move_uploaded_file($_FILES['file']['tmp_name'], $fileInfo->getPath())) { - http_response_code(500); - return; + 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); + $fileInfo = Upload::byHash($db, $hash); + + if($fileInfo !== null) { + if($fileInfo->isDMCA()) + return 451; + + if($fileInfo->getUserId() !== $userInfo->getId() + || $fileInfo->getApplicationId() !== $appInfo->getId()) + unset($fileInfo); + } + + if(!empty($fileInfo)) { + if($fileInfo->isDeleted()) + $fileInfo->restore($db); + } else { + try { + $fileInfo = Upload::create( + $db, $appInfo, $userInfo, + $file->getSuggestedFileName(), + mime_content_type($localFile), + $fileSize, $hash, + $appInfo->getExpiry(), true + ); + } catch(UploadCreationFailedException $ex) { + return 500; + } + + try { + $file->moveTo($fileInfo->getPath()); + } catch(\RuntimeException $ex) { + return 500; + } + } + + $response->setStatusCode(201); + $response->setHeader('Content-Type', 'application/json; charset=utf-8'); + return $fileInfo; + }); + + $router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) { + try { + $uploadInfo = Upload::byId($db, $fileId); + } catch(UploadNotFoundException $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(UploadNotFoundException $ex) { + $response->setContent('File not found.'); + return 404; + } + + if($isJson) + return $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)); + }); + + if(PRM_DEBUG) { + $router->get('/test-v1.0.html', function($response) { + $response->setContentType('text/html; charset=utf-8'); + return strtr(file_get_contents(PRM_DEBUG_PAGES . '/test-v1.0.html'), [ + ':cookie' => htmlspecialchars((string)filter_input(INPUT_COOKIE, 'msz_auth')), + ]); + }); } +} else { + $router->use('/', function($response, $request) { + if($request->getMethod() === 'OPTIONS') { + $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET'); + return 204; + } + }); - http_response_code(201); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode($fileInfo); - return; + $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(UploadNotFoundException $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)); + }); } -http_response_code(404); +$router->dispatch(); diff --git a/js/eeprom-v1.0.js b/public/js/eeprom-v1.0.js similarity index 100% rename from js/eeprom-v1.0.js rename to public/js/eeprom-v1.0.js