138 lines
6.4 KiB
PHP
138 lines
6.4 KiB
PHP
<?php
|
|
/**
|
|
* media_upload.php — Authenticated media upload endpoint
|
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
* Called by post.php via multipart/form-data POST.
|
|
* Returns JSON: { ok: true, url, filename, type, mime }
|
|
*
|
|
* Auth: same X-API-Key header / ?api_key= param as api.php.
|
|
*
|
|
* Place this file alongside api.php and post.php (webroot).
|
|
* Files are stored in /media/ (absolute server path configured below).
|
|
*/
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
// CONFIG
|
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
// Absolute server path where uploads are saved.
|
|
// Must be writable by the web server (chmod 755 or 775).
|
|
define('UPLOAD_DIR', $_SERVER['DOCUMENT_ROOT'] . '/media/');
|
|
|
|
// Public URL prefix for uploaded files (no trailing slash).
|
|
define('UPLOAD_URL', '/media');
|
|
|
|
// Same key file used by api.php.
|
|
define('UPLOAD_KEY_FILE', __DIR__ . '/index_config/api_keys.txt');
|
|
|
|
// Max upload size in bytes (default 200 MB).
|
|
define('UPLOAD_MAX_BYTES', 200 * 1024 * 1024);
|
|
|
|
// Allowed MIME types → media type label.
|
|
define('UPLOAD_ALLOWED', serialize([
|
|
'image/jpeg' => 'image',
|
|
'image/jpg' => 'image',
|
|
'image/png' => 'image',
|
|
'image/gif' => 'image',
|
|
'image/webp' => 'image',
|
|
'image/avif' => 'image',
|
|
'image/svg+xml' => 'image',
|
|
'video/mp4' => 'video',
|
|
'video/quicktime' => 'video', // .mov
|
|
'video/webm' => 'video',
|
|
'video/ogg' => 'video',
|
|
'video/x-msvideo' => 'video', // .avi
|
|
'video/x-matroska'=> 'video', // .mkv
|
|
]));
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('X-Content-Type-Options: nosniff');
|
|
|
|
function upload_ok(array $data): never {
|
|
echo json_encode(['ok' => true] + $data, JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
function upload_err(string $msg, int $code = 400): never {
|
|
http_response_code($code);
|
|
echo json_encode(['ok' => false, 'error' => $msg]);
|
|
exit;
|
|
}
|
|
|
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
if (!file_exists(UPLOAD_KEY_FILE)) upload_err('API key file not configured.', 503);
|
|
|
|
$supplied = trim(
|
|
$_SERVER['HTTP_X_API_KEY'] ?? $_SERVER['HTTP_X_Api_Key'] ?? ($_GET['api_key'] ?? '')
|
|
);
|
|
if ($supplied === '') upload_err('Missing API key.', 401);
|
|
|
|
$valid_keys = array_filter(
|
|
array_map('trim', file(UPLOAD_KEY_FILE)),
|
|
fn($l) => $l !== '' && !str_starts_with($l, '#')
|
|
);
|
|
$authed = false;
|
|
foreach ($valid_keys as $k) { if (hash_equals($k, $supplied)) { $authed = true; break; } }
|
|
if (!$authed) upload_err('Invalid API key.', 403);
|
|
|
|
// ── Request validation ────────────────────────────────────────────────────────
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') upload_err('POST required.', 405);
|
|
if (empty($_FILES['file'])) upload_err('No file received.');
|
|
|
|
$file = $_FILES['file'];
|
|
$error = $file['error'] ?? UPLOAD_ERR_NO_FILE;
|
|
|
|
if ($error === UPLOAD_ERR_INI_SIZE || $error === UPLOAD_ERR_FORM_SIZE)
|
|
upload_err('File exceeds size limit.');
|
|
if ($error !== UPLOAD_ERR_OK)
|
|
upload_err('Upload error code: ' . $error);
|
|
|
|
if ($file['size'] > UPLOAD_MAX_BYTES)
|
|
upload_err('File too large (max ' . round(UPLOAD_MAX_BYTES / 1048576) . ' MB).');
|
|
|
|
// ── MIME detection (finfo, not extension trust) ───────────────────────────────
|
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
$mime = $finfo->file($file['tmp_name']);
|
|
|
|
$allowed = unserialize(UPLOAD_ALLOWED);
|
|
if (!isset($allowed[$mime])) {
|
|
upload_err('File type not allowed: ' . $mime . '. Allowed: JPEG, PNG, GIF, WEBP, AVIF, SVG, MP4, MOV, WEBM, OGG, AVI, MKV.');
|
|
}
|
|
$media_type = $allowed[$mime];
|
|
|
|
// ── Generate unique filename preserving original extension ────────────────────
|
|
$orig_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
|
// Normalise common aliases
|
|
$ext_map = ['jpg' => 'jpg', 'jpeg' => 'jpg', 'jpe' => 'jpg',
|
|
'png' => 'png', 'gif' => 'gif', 'webp' => 'webp',
|
|
'avif' => 'avif', 'svg' => 'svg',
|
|
'mp4' => 'mp4', 'mov' => 'mov', 'webm' => 'webm',
|
|
'ogg' => 'ogg', 'ogv' => 'ogg', 'avi' => 'avi', 'mkv' => 'mkv'];
|
|
$safe_ext = $ext_map[$orig_ext] ?? $orig_ext;
|
|
$filename = bin2hex(random_bytes(12)) . '.' . $safe_ext;
|
|
$dest = UPLOAD_DIR . $filename;
|
|
|
|
// ── Create upload directory if it doesn't exist ───────────────────────────────
|
|
if (!is_dir(UPLOAD_DIR)) {
|
|
if (!mkdir(UPLOAD_DIR, 0755, true)) {
|
|
upload_err('Cannot create upload directory.', 500);
|
|
}
|
|
// Drop an index.html to prevent directory listing
|
|
file_put_contents(UPLOAD_DIR . 'index.html', '');
|
|
}
|
|
|
|
// ── Move file ─────────────────────────────────────────────────────────────────
|
|
if (!move_uploaded_file($file['tmp_name'], $dest)) {
|
|
upload_err('Failed to save file.', 500);
|
|
}
|
|
|
|
// ── Respond ───────────────────────────────────────────────────────────────────
|
|
upload_ok([
|
|
'url' => UPLOAD_URL . '/' . $filename,
|
|
'filename' => $filename,
|
|
'type' => $media_type, // 'image' | 'video'
|
|
'mime' => $mime,
|
|
'size' => $file['size'],
|
|
]);
|