* * 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\Upload; use Chevereto\Legacy\Classes\User; use Chevereto\Legacy\G\Handler; use function Chevereto\Legacy\decodeID; use function Chevereto\Legacy\encodeID; use function Chevereto\Legacy\G\get_mimetype; use function Chevereto\Legacy\G\getQsParams; use function Chevereto\Legacy\G\is_image_url; use function Chevereto\Legacy\G\is_url; use function Chevereto\Legacy\G\json_document_output; use function Chevereto\Legacy\G\json_error; use function Chevereto\Legacy\G\mime_to_extension; use function Chevereto\Legacy\G\random_string; use function Chevereto\Legacy\getSetting; use function Chevereto\Legacy\getVariable; 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 = strval($handler->request()[0] ?? ''); $action = strval($handler->request()[1] ?? ''); $key = strval($SERVER['HTTP_X_API_KEY'] ?? $REQUEST['key'] ?? ''); foreach (['version', 'action', 'key'] as $var) { if (${$var} === '') { throw new Exception("No {$var} provided", 100); } } if (! in_array($version, ['1'], true)) { throw new Exception('Invalid API version.', 110); } $verify = ApiKey::verify($key); if ($verify === []) { if (! (bool) env()['CHEVERETO_ENABLE_API_GUEST']) { throw new Exception('Invalid API key.', 100); } $apiV1Key = (string) (getSetting('api_v1_key') ?? ''); if ($apiV1Key === '') { throw new Exception("API V1 public key can't be null. Go to /dashboard and set the Guest API key.", 0); } if (! hash_equals($apiV1Key, $key)) { throw new Exception('Invalid API key.', 100); } } else { $user = User::getSingle($verify['user_id']); } $isAdmin = boolval(($user['is_admin'] ?? false)); $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 (! $isAdmin && getSetting('website_mode') === 'personal' && getSetting('website_mode_personal_uid') !== ($user['id'] ?? 0) ) { $upload_allowed = false; } if ((! (bool) env()['CHEVERETO_ENABLE_LOCAL_STORAGE']) && getVariable('storages_active')->nullInt() === 0 ) { $upload_enabled = false; $upload_allowed = false; } if ($user === [] && $upload_allowed && getSetting('upload_max_filesize_mb_guest') ) { Settings::setValue('upload_max_filesize_mb_bak', getSetting('upload_max_filesize_mb')); Settings::setValue('upload_max_filesize_mb', getSetting('upload_max_filesize_mb_guest')); } $handler::setCond('upload_enabled', $upload_enabled); $handler::setCond('upload_allowed', $upload_allowed); if (Settings::get('enable_uploads_url') && ! $isAdmin) { Settings::setValue('enable_uploads_url', 0); } if (! $handler::cond('upload_allowed')) { throw new Exception(_s('Request denied'), 401); } $version_to_actions = [ '1' => ['upload'], ]; if (! in_array($action, $version_to_actions[$version], true)) { throw new Exception('Invalid API action', 120); } $source = $FILES['source'] ?? $REQUEST['source'] ?? $REQUEST['image'] ?? null; if ($source === null) { 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, true)); if (! hash_equals($base64source, $source)) { throw new Exception('Invalid base64 string', 120); } if ($source === '') { throw new Exception('Empty source', 130); } try { $api_temp_file = Upload::getTempNam(); } catch (Exception $e) { 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); $mimetype = get_mimetype($api_temp_file); $source = [ 'name' => random_string(12) . '.' . mime_to_extension($mimetype), 'type' => $mimetype, 'tmp_name' => $api_temp_file, 'error' => 'UPLOAD_ERR_OK', 'size' => filesize($api_temp_file), ]; break; } $isImgBBSpec = array_key_exists('image', $REQUEST); $albumId = $REQUEST['album_id'] ?? null; if ($albumId !== null) { $albumId = decodeID($albumId); } $expiration = $REQUEST['expiration'] ?? null; if ($expiration !== null && ctype_digit($expiration)) { $expiration = (int) $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, 'tags' => $REQUEST['tags'] ?? null, 'width' => $REQUEST['width'] ?? null, 'expiration' => $expiration, 'use_file_date' => $isAdmin ? ($REQUEST['use_file_date'] ?? false) : false, ]; $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( title: $params['title'] ?? null, description: $params['description'] ?? null, tags: $params['tags'] ?? null, source_db: $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( type: $image['type'], idEncoded: encodeID($uploaded_id), password: $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_document_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 { exit($json_array['status_code']); } break; } exit(); } json_document_output($json_array); } catch (Exception $e) { $json_array = json_error($e); if ($version === '1') { switch ($format) { default: case 'json': json_document_output($json_array); break; case 'txt': case 'redirect': exit($json_array['error']['message']); } } else { json_document_output($json_array); } } };