free-chevereto/app/legacy/routes/api.php
2024-05-13 19:53:04 +00:00

237 lines
8.8 KiB
PHP

<?php
/*
* This file is part of Chevereto.
*
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*
* This API V1 was introduced in Chevereto V2 and it was carried over to V3.
*
* From Chevereto V4 onwards the API versioning follow the major version:
*
* - V2 -> API V1
* - V3 -> API V1
* - V4 -> API V4 + API V1.1
*/
use Chevereto\Legacy\Classes\Akismet;
use Chevereto\Legacy\Classes\ApiKey;
use Chevereto\Legacy\Classes\Image;
use Chevereto\Legacy\Classes\Settings;
use Chevereto\Legacy\Classes\User;
use function Chevereto\Legacy\decodeID;
use function Chevereto\Legacy\encodeID;
use function Chevereto\Legacy\G\getQsParams;
use Chevereto\Legacy\G\Handler;
use function Chevereto\Legacy\G\is_image_url;
use function Chevereto\Legacy\G\is_url;
use function Chevereto\Legacy\G\json_error;
use function Chevereto\Legacy\G\json_output;
use function Chevereto\Legacy\G\random_string;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Vars\env;
use function Chevereto\Vars\files;
use function Chevereto\Vars\request;
use function Chevereto\Vars\server;
return function (Handler $handler) {
try {
$user = [];
$REQUEST = request();
$FILES = files();
$SERVER = server();
$format = $REQUEST['format'] ?? 'json';
$version = $handler->request()[0] ?? null;
$action = $handler->request()[1] ?? null;
$key = $SERVER['HTTP_X_API_KEY'] ?? $REQUEST['key'] ?? null;
foreach (['version', 'action', 'key'] as $var) {
if (${$var} === null) {
throw new Exception("No $var provided", 100);
}
}
if (!in_array($version, ['1'])) {
throw new Exception('Invalid API version.', 110);
}
$verify = ApiKey::verify($key);
if ($verify === []) {
if (!(bool) env()['CHEVERETO_ENABLE_API_GUEST']) {
throw new Exception("Guest API is disabled.", 400);
}
$apiV1Key = (string) (getSetting('api_v1_key') ?? '');
if ($apiV1Key == '') {
throw new Exception("API V1 public key can't be null. Go to your dashboard and set the Guest API key.", 0);
}
// @var string $key
if (!hash_equals($apiV1Key, $key)) {
throw new Exception("Invalid guest API key.", 100);
}
} else {
$user = User::getSingle($verify['user_id']);
}
$isAdmin = boolval(($user['is_admin'] ?? false));
if (Settings::get('enable_uploads_url') && !$isAdmin) {
Settings::setValue('enable_uploads_url', 0);
}
$upload_enabled = $isAdmin ?: getSetting('enable_uploads');
$upload_allowed = $upload_enabled;
if ($user === []) {
if (!getSetting('guest_uploads')
|| getSetting('website_privacy_mode') == 'private'
|| $handler::cond('maintenance')
) {
$upload_allowed = false;
}
} elseif (!$user['is_admin']
&& getSetting('website_mode') == 'personal'
&& getSetting('website_mode_personal_uid') !== $user['id']
) {
$upload_allowed = false;
}
if (!$upload_allowed) {
throw new Exception(_s('Request denied'), 401);
}
$version_to_actions = [
'1' => ['upload']
];
if (!in_array($action, $version_to_actions[$version])) {
throw new Exception('Invalid API action.', 120);
}
$source = $FILES['source']
?? $REQUEST['source']
?? $REQUEST['image']
?? null;
if (is_null($source)) {
throw new Exception('Empty upload source.', 130);
}
switch (true) {
case isset($FILES['source'], $FILES['source']['tmp_name']):
$source = $FILES['source'];
break;
case is_image_url($source) || is_url($source):
if (($SERVER['REQUEST_METHOD'] ?? '') === 'GET') {
$sourceQs = urldecode(getQsParams()['source']);
}
$source = $sourceQs ?? $source;
break;
default:
if (($SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
throw new Exception('Upload using base64 source must be done using POST method.', 130);
}
$source = trim(preg_replace('/\s+/', '', $source));
$base64source = base64_encode(base64_decode($source));
if (!hash_equals($base64source, $source)) {
throw new Exception('Invalid base64 string.', 120);
}
$api_temp_file = tempnam(sys_get_temp_dir(), 'chvtemp');
if (!$api_temp_file || !is_writable($api_temp_file)) {
throw new Exception("Can't get a tempnam.", 200);
}
$fh = fopen($api_temp_file, 'w');
stream_filter_append($fh, 'convert.base64-decode', STREAM_FILTER_WRITE);
fwrite($fh, $source);
fclose($fh);
$source = [
'name' => random_string(12) . '.jpg',
'type' => 'image/jpeg',
'tmp_name' => $api_temp_file,
'error' => 'UPLOAD_ERR_OK',
'size' => '1'
];
break;
}
$isImgBBSpec = array_key_exists('image', $REQUEST);
$albumId = $REQUEST['album_id'] ?? null;
if ($albumId !== null) {
$albumId = decodeID($albumId);
}
$expiration = $REQUEST['expiration'] ?? null;
if (!is_null($expiration) && ctype_digit($expiration)) {
$expiration = Image::getExpirationFromSeconds($expiration);
}
$params = [
'album_id' => $albumId,
'category_id' => $REQUEST['category_id'] ?? null,
'description' => $REQUEST['description'] ?? null,
'nsfw' => $REQUEST['nsfw'] ?? null,
'title' => $REQUEST['title'] ?? $REQUEST['name'] ?? null,
'width' => $REQUEST['width'] ?? null,
'expiration' => $expiration,
'mimetype' => $REQUEST['mimetype'] ?? 'image/jpeg',
];
$params = array_filter($params);
if (!$handler::cond('content_manager') && getSetting('akismet')) {
$user_source_db = [
'user_name' => $user['name'] ?? null,
'user_username' => $user['username'] ?? null,
'user_email' => $user['email'] ?? null,
];
Akismet::checkImage($params['title'] ?? null, $params['description'] ?? null, $user_source_db);
}
$uploadToWebsite = Image::uploadToWebsite($source, $user, $params);
$uploaded_id = intval($uploadToWebsite[0]);
$image = Image::formatArray(Image::getSingle($uploaded_id), true);
$image['delete_url'] = Image::getDeleteUrl(encodeID($uploaded_id), $uploadToWebsite[1]);
unset($image['user'], $image['album']);
if (!$image['is_approved']) {
unset($image['image']['url'], $image['thumb']['url'], $image['medium']['url'], $image['url'], $image['display_url']);
}
$json_array = [];
$json_array['status_code'] = 200;
if ($isImgBBSpec) {
$json_array['status'] = $json_array['status_code'];
$image['id'] = $image['id_encoded'];
}
$json_array['success'] = ['message' => 'file uploaded', 'code' => 200];
$json_array[$isImgBBSpec ? 'data' : 'image'] = $image;
if ($version == 1) {
switch ($format) {
default:
case 'json':
json_output($json_array);
break;
case 'txt':
echo $image['url'];
break;
case 'redirect':
if ($json_array['status_code'] === 200) {
$redirect_url = $image['path_viewer'];
header("Location: $redirect_url");
} else {
die($json_array['status_code']);
}
break;
}
die();
} else {
json_output($json_array);
}
} catch (Exception $e) {
$json_array = json_error($e);
if ($version == 1) {
switch ($format) {
default:
case 'json':
json_output($json_array);
break;
case 'txt':
case 'redirect':
die($json_array['error']['message']);
}
} else {
json_output($json_array);
}
}
};