Skip to content

Instantly share code, notes, and snippets.

@jakubboucek
Created September 13, 2022 10:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakubboucek/5ce3868f1eb8e20871edf597c44f0403 to your computer and use it in GitHub Desktop.
Save jakubboucek/5ce3868f1eb8e20871edf597c44f0403 to your computer and use it in GitHub Desktop.
Shottr upload server-side script
<?php
// CONFIGURATION
$uploadPath = 'upload'; // <-- directory to save images
$wwwPath = ''; // <-- url to uplaod directory (optional - will be filled)
$secretKey = ''; // <-- secret key - optional, fill to protect endpoint
// SECURITY CONFIG - do not modify when you not understand consequences
$allowedExts = ['png', 'jpg'];
$allowedChars = '-a-z0-9.';
function sendResponse($data, $code = 200)
{
header('Content-Type: application/json;charset=utf-8', true, $code);
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
die();
}
function sendError($message, $code = 400)
{
$data = ['error' => ['code' => $code, 'message' => $message]];
sendResponse($data, $code);
}
$validKey = true;
if (empty($secretKey) === false) {
$requestKey = $_SERVER['HTTP_X_SHOTTR_KEY'] ?? '';
$validKey = hash_equals($secretKey, $requestKey);
}
//Detect URLs
$authority = (($_SERVER['HTTPS'] ?? '') === 'on' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
$scriptFile = $_SERVER['SCRIPT_NAME'] ?? '/';
$scriptPath = dirname($scriptFile);
if (strpos($uploadPath, '/') !== 0) { // rewrite relative path to absolute
$uploadSuffix = $uploadPath;
$uploadPath = __DIR__ . '/' . $uploadSuffix;
$wwwPath = $authority . trim($scriptPath, '/') . '/' . $uploadSuffix;
}
if (!is_dir($uploadPath) && !mkdir($uploadPath, 0777, true) && !is_dir($uploadPath)) {
sendError("File upload failed because error on server", 500);
}
// Send doc
if (strcasecmp($_SERVER['REQUEST_METHOD'], 'get') === 0) {
sendResponse([
'name' => 'Shottr uploader',
'uploadUrlPrefix' => $wwwPath,
'validKey' => $validKey
]);
} elseif (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') !== 0) {
sendError("Method '{$_SERVER['REQUEST_METHOD']}' not allowed", 405);
}
if (empty($secretKey) === false && $validKey === false) {
sendError('Unauthorized, X-Shottr-Key key missing or invalid', 401);
}
if (isset($_FILES['file']) === false || is_string($_FILES['file']['name']) === false) {
sendError('Missing uploaded file in POST request', 400);
}
$file = $_FILES['file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
sendError("File upload failed because error on server (upload error: {$file['error']})", 500);
}
$filename = mb_strtolower(basename($file['name']));
// SECURITY: Prevent attack with vulnerable file type
$exp = pathinfo($filename, PATHINFO_EXTENSION);
if (in_array($exp, $allowedExts, true) === false) {
sendError("File '$filename' is unacceptable to upload", 403);
}
// SECURITY: Remove unsafe chars from filename
$filename = preg_replace("~[^{$allowedChars}]+~", '-', $filename);
if (move_uploaded_file($file['tmp_name'], $uploadPath . '/' . $filename) === false) {
sendError("File upload failed because error on server (save error)", 500);
}
sendResponse(['url' => $wwwPath . '/' . rawurlencode($filename)]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment