<?php
// SECURE: File management with proper validation and safe execution
header('Content-Type: application/json');
class SecureFileManager {
private const ALLOWED_ACTIONS = ['backup', 'info', 'hash', 'count'];
private const WORK_DIR = '/secure/uploads';
private const BACKUP_DIR = '/secure/backup';
private const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
public function handleRequest(): void {
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new Exception('Only POST method allowed');
}
$input = $this->validateInput();
$result = $this->executeAction($input);
echo json_encode(['success' => true, 'result' => $result]);
} catch (Exception $e) {
error_log('File operation error: ' . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'Operation failed']);
}
}
private function validateInput(): array {
$action = $_POST['action'] ?? '';
$filename = $_POST['filename'] ?? '';
// Validate action
if (!in_array($action, self::ALLOWED_ACTIONS, true)) {
throw new Exception('Action not allowed');
}
// Validate filename
if (!$this->isValidFilename($filename)) {
throw new Exception('Invalid filename');
}
return ['action' => $action, 'filename' => $filename];
}
private function isValidFilename(string $filename): bool {
// Strict filename validation
return preg_match('/^[a-zA-Z0-9._-]+$/', $filename) &&
strlen($filename) > 0 &&
strlen($filename) <= 255 &&
!str_contains($filename, '..');
}
private function getSecureFilePath(string $filename): string {
$fullPath = realpath(self::WORK_DIR . '/' . $filename);
// Ensure file is within work directory
if (!$fullPath || !str_starts_with($fullPath, realpath(self::WORK_DIR))) {
throw new Exception('File not in allowed directory');
}
// Check file exists and constraints
if (!is_file($fullPath)) {
throw new Exception('File not found');
}
if (!is_readable($fullPath)) {
throw new Exception('File not readable');
}
if (filesize($fullPath) > self::MAX_FILE_SIZE) {
throw new Exception('File too large');
}
return $fullPath;
}
private function executeAction(array $input): array {
$action = $input['action'];
$filePath = $this->getSecureFilePath($input['filename']);
switch ($action) {
case 'backup':
return $this->backupFile($filePath);
case 'info':
return $this->getFileInfo($filePath);
case 'hash':
return $this->calculateFileHash($filePath);
case 'count':
return $this->countFileLines($filePath);
default:
throw new Exception('Unknown action');
}
}
// SECURE: Native PHP file operations
private function backupFile(string $filePath): array {
$filename = basename($filePath);
$timestamp = date('Y-m-d_H-i-s');
$backupName = $filename . '_' . $timestamp . '.bak';
$backupPath = self::BACKUP_DIR . '/' . $backupName;
// Ensure backup directory exists
if (!is_dir(self::BACKUP_DIR)) {
if (!mkdir(self::BACKUP_DIR, 0755, true)) {
throw new Exception('Cannot create backup directory');
}
}
// SECURE: Native PHP copy
if (!copy($filePath, $backupPath)) {
throw new Exception('Backup failed');
}
return [
'message' => 'File backed up successfully',
'backup_name' => $backupName,
'original_size' => filesize($filePath),
'backup_size' => filesize($backupPath)
];
}
private function getFileInfo(string $filePath): array {
$stat = stat($filePath);
$info = pathinfo($filePath);
return [
'name' => $info['basename'],
'extension' => $info['extension'] ?? '',
'size' => $stat['size'],
'size_human' => $this->formatBytes($stat['size']),
'modified' => date('Y-m-d H:i:s', $stat['mtime']),
'permissions' => substr(sprintf('%o', fileperms($filePath)), -4),
'mime_type' => mime_content_type($filePath) ?: 'unknown'
];
}
private function calculateFileHash(string $filePath): array {
$algorithms = ['md5', 'sha1', 'sha256'];
$hashes = [];
foreach ($algorithms as $algo) {
$hash = hash_file($algo, $filePath);
if ($hash !== false) {
$hashes[$algo] = $hash;
}
}
return [
'file' => basename($filePath),
'hashes' => $hashes,
'calculated_at' => date('Y-m-d H:i:s')
];
}
private function countFileLines(string $filePath): array {
$handle = fopen($filePath, 'r');
if (!$handle) {
throw new Exception('Cannot open file for reading');
}
$lineCount = 0;
$charCount = 0;
$wordCount = 0;
while (($line = fgets($handle)) !== false) {
$lineCount++;
$charCount += strlen($line);
$wordCount += str_word_count($line);
}
fclose($handle);
return [
'file' => basename($filePath),
'lines' => $lineCount,
'characters' => $charCount,
'words' => $wordCount
];
}
private function formatBytes(int $bytes): string {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, 2) . ' ' . $units[$pow];
}
}
// Usage
$manager = new SecureFileManager();
$manager->handleRequest();