eeprom/public/index.php

432 lines
13 KiB
PHP

<?php
namespace EEPROM;
require_once __DIR__ . '/../eeprom.php';
$reqMethod = $_SERVER['REQUEST_METHOD'];
$reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
header('X-Powered-By: EEPROM');
function eepromOriginAllowed(string $origin): bool {
$origin = mb_strtolower(parse_url($origin, PHP_URL_HOST));
if($origin === $_SERVER['HTTP_HOST'])
return true;
$allowed = Config::get('CORS', 'origins', []);
if(empty($allowed))
return true;
return in_array($origin, $allowed);
}
if($_SERVER['HTTP_HOST'] !== Config::get('Uploads', 'api_domain')) {
$reqMethod = 'GET'; // short domain is read only, prevent deleting
$reqPath = '/uploads/' . trim($reqPath, '/');
$isShortDomain = true;
}
if(!empty($_SERVER['HTTP_ORIGIN'])) {
if(!eepromOriginAllowed($_SERVER['HTTP_ORIGIN'])) {
http_response_code(403);
return;
}
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($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($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(User::active()->isRestricted()
|| User::active()->getId() !== $uploadInfo->getUserId()) {
http_response_code(403);
return;
}
http_response_code(204);
$uploadInfo->delete(false);
return;
}
if(!is_file($uploadInfo->getPath())) {
http_response_code(404);
echo 'Data is missing.';
return;
}
if($getNormal) {
$uploadInfo->bumpAccess();
$uploadInfo->bumpExpiry();
}
$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($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
<!doctype html>
<input type="file" id="-eeprom-file"/> <button id="-eeprom-abort">Abort</button> <progress id="-eeprom-progress" min="0" max="100" value="0"></progress> <span id="-eeprom-progress-done"></span> <span id="-eeprom-progress-total"></span><br/>
<div id="-eeprom-history"></div>
<script src="/eeprom.js" type="text/javascript"></script>
<script type="text/javascript">
var eFile = document.getElementById('-eeprom-file'),
eAbort = document.getElementById('-eeprom-abort'),
eProgress = document.getElementById('-eeprom-progress'),
eProgressD = document.getElementById('-eeprom-progress-done'),
eProgressT = document.getElementById('-eeprom-progress-total'),
eHistory = document.getElementById('-eeprom-history'),
eTask = undefined;
eAbort.onclick = function() {
if(eTask)
eTask.abort();
};
var eClient = new EEPROM(1, '/uploads', 'Misuzu {$cookie}');
eFile.onchange = function() {
var task = eTask = eClient.createUpload(this.files[0]);
task.onProgress = function(progressInfo) {
eProgress.value = progressInfo.progress;
eProgressD.textContent = progressInfo.loaded;
eProgressT.textContent = progressInfo.total;
};
task.onFailure = function(errorInfo) {
if(!errorInfo.userAborted) {
var errorText = 'Was unable to upload file.';
switch(errorInfo.error) {
case EEPROM.ERR_INVALID:
errorText = 'Upload request was invalid.';
break;
case EEPROM.ERR_AUTH:
errorText = 'Upload authentication failed, refresh and try again.';
break;
case EEPROM.ERR_ACCESS:
errorText = 'You\'re not allowed to upload files.';
break;
case EEPROM.ERR_GONE:
errorText = 'Upload client has a configuration error or the server is gone.';
break;
case EEPROM.ERR_DMCA:
errorText = 'This file has been uploaded before and was removed for copyright reasons, you cannot upload this file.';
break;
case EEPROM.ERR_SERVER:
errorText = 'Upload server returned a critical error, try again later.';
break;
case EEPROM.ERR_SIZE:
if(errorInfo.maxSize < 1)
errorText = 'Selected file is too large.';
else {
var _t = ['bytes', 'KB', 'MB', 'GB', 'TB'],
_i = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
_s = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
errorText = 'Upload may not be larger than %1 %2.'.replace('%1', _s).replace('%2', _t[_i]);
}
break;
}
alert(errorText);
}
};
task.onComplete = function(fileInfo) {
eTask = undefined;
var elem = document.createElement('div');
elem.id = fileInfo.id;
var link = document.createElement('a');
link.textContent = fileInfo.id;
link.href = fileInfo.url;
link.target = '_blank';
elem.appendChild(link);
elem.appendChild(document.createTextNode(' '));
var del = document.createElement('button');
del.textContent = 'Delete';
del.onclick = function() {
var dTask = eClient.deleteUpload(fileInfo);
dTask.onFailure = function(reason) {
alert('Delete failed: ' + reason);
};
dTask.onSuccess = function() {
eHistory.removeChild(elem);
};
dTask.start();
};
elem.appendChild(del);
elem.appendChild(document.createTextNode(' '));
var json = document.createElement('code');
json.textContent = JSON.stringify(fileInfo);
elem.appendChild(json);
eHistory.appendChild(elem);
};
task.start();
};
</script>
TEST;
return;
}
header('Content-Type: text/plain; charset=us-ascii');
if($reqPath === '/' || $reqPath === '/stats.json') {
$fileCount = 0;
$userCount = 0;
$totalSize = 0;
$uniqueTypes = 0;
$getUploadStats = DB::prepare('
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
');
$getUploadStats->execute();
$uploadStats = $getUploadStats->execute() ? $getUploadStats->fetchObject() : null;
if(!empty($uploadStats)) {
$fileCount = intval($uploadStats->amount);
$totalSize = intval($uploadStats->size ?? 0);
$uniqueTypes = intval($uploadStats->types ?? 0);
}
$getUserStats = DB::prepare('
SELECT COUNT(`user_id`) AS `amount`
FROM `prm_users`
WHERE `user_restricted` IS NULL
');
$getUserStats->execute();
$userStats = $getUserStats->execute() ? $getUserStats->fetchObject() : null;
if(!empty($userStats))
$userCount = intval($userStats->amount);
if($reqPath === '/stats.json') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'size' => $totalSize,
'files' => $fileCount,
'types' => $uniqueTypes,
'members' => $userCount,
]);
return;
}
header('Content-Type: text/html; charset=utf-8');
header('X-Accel-Redirect: /index.html');
return;
}
if($reqPath === '/uploads') {
if($reqMethod !== 'POST') {
http_response_code(405);
return;
}
try {
$appInfo = Application::byId(
filter_input(INPUT_POST, 'src', FILTER_VALIDATE_INT)
);
} catch(ApplicationNotFoundException $ex) {
http_response_code(404);
return;
}
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($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();
} else {
try {
$fileInfo = Upload::create(
$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;
}
if(!move_uploaded_file($_FILES['file']['tmp_name'], $fileInfo->getPath())) {
http_response_code(500);
return;
}
}
http_response_code(201);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($fileInfo);
return;
}
http_response_code(404);