133 lines
4.0 KiB
PHP
133 lines
4.0 KiB
PHP
<?php
|
|
require_once 'config.php';
|
|
|
|
// --- CSRF Protection ---
|
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
|
$receivedToken = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
|
|
|
if ($receivedToken !== $_SESSION['csrf_token']) {
|
|
echo json_encode(["error" => "Invalid CSRF token"]);
|
|
http_response_code(403);
|
|
exit;
|
|
}
|
|
|
|
// Check if the user is authenticated.
|
|
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|
http_response_code(401);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Unauthorized"]);
|
|
exit;
|
|
}
|
|
|
|
// Read and decode the JSON input.
|
|
$rawData = file_get_contents("php://input");
|
|
$data = json_decode($rawData, true);
|
|
|
|
if (!is_array($data) || !isset($data['folder']) || !isset($data['files']) || !is_array($data['files'])) {
|
|
http_response_code(400);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Invalid input."]);
|
|
exit;
|
|
}
|
|
|
|
$folder = $data['folder'];
|
|
$files = $data['files'];
|
|
|
|
// Validate folder name to allow subfolders.
|
|
// "root" is allowed; otherwise, split by "/" and validate each segment.
|
|
if ($folder !== "root") {
|
|
$parts = explode('/', $folder);
|
|
foreach ($parts as $part) {
|
|
if (empty($part) || $part === '.' || $part === '..' || !preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $part)) {
|
|
http_response_code(400);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Invalid folder name."]);
|
|
exit;
|
|
}
|
|
}
|
|
$relativePath = implode(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR;
|
|
} else {
|
|
$relativePath = "";
|
|
}
|
|
|
|
// Use the absolute UPLOAD_DIR from config.php.
|
|
$baseDir = realpath(UPLOAD_DIR);
|
|
if ($baseDir === false) {
|
|
http_response_code(500);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Uploads directory not configured correctly."]);
|
|
exit;
|
|
}
|
|
|
|
$folderPath = $baseDir . DIRECTORY_SEPARATOR . $relativePath;
|
|
$folderPathReal = realpath($folderPath);
|
|
if ($folderPathReal === false || strpos($folderPathReal, $baseDir) !== 0) {
|
|
http_response_code(404);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Folder not found."]);
|
|
exit;
|
|
}
|
|
|
|
if (empty($files)) {
|
|
http_response_code(400);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "No files specified."]);
|
|
exit;
|
|
}
|
|
|
|
foreach ($files as $fileName) {
|
|
if (!preg_match('/^[A-Za-z0-9_\-\.\(\) ]+$/', $fileName)) {
|
|
http_response_code(400);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Invalid file name: " . $fileName]);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Build an array of files to include in the ZIP.
|
|
$filesToZip = [];
|
|
foreach ($files as $fileName) {
|
|
$filePath = $folderPathReal . DIRECTORY_SEPARATOR . $fileName;
|
|
if (file_exists($filePath)) {
|
|
$filesToZip[] = $filePath;
|
|
}
|
|
}
|
|
|
|
if (empty($filesToZip)) {
|
|
http_response_code(400);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "No valid files found to zip."]);
|
|
exit;
|
|
}
|
|
|
|
// Create a temporary file for the ZIP archive.
|
|
$tempZip = tempnam(sys_get_temp_dir(), 'zip');
|
|
unlink($tempZip); // Remove the temporary file so ZipArchive can create a new one.
|
|
$tempZip .= '.zip';
|
|
|
|
$zip = new ZipArchive();
|
|
if ($zip->open($tempZip, ZipArchive::CREATE) !== TRUE) {
|
|
http_response_code(500);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(["error" => "Could not create zip archive."]);
|
|
exit;
|
|
}
|
|
|
|
// Add each file to the archive using its base name.
|
|
foreach ($filesToZip as $filePath) {
|
|
$zip->addFile($filePath, basename($filePath));
|
|
}
|
|
$zip->close();
|
|
|
|
// Send headers to force download and disable caching.
|
|
header('Content-Type: application/zip');
|
|
header('Content-Disposition: attachment; filename="files.zip"');
|
|
header('Content-Length: ' . filesize($tempZip));
|
|
header('Cache-Control: no-store, no-cache, must-revalidate');
|
|
header('Pragma: no-cache');
|
|
|
|
// Output the file and delete it afterward.
|
|
readfile($tempZip);
|
|
unlink($tempZip);
|
|
exit;
|
|
?>
|