 | Ultra-Secure File Management REST API - Writing REST API Interface (PHP) |  |
Alright, so imagine you're building a cool app or website and you want it to talk to other systems—maybe to grab weather info, show user profiles, or save game scores. That’s where a REST API comes in. REST stands for Representational State Transfer, and an API is just a way for programs to talk to each other. So a REST API is a set of rules that lets one bit of software send and receive data from another using plain old HTTP (like your browser does). You send a request to a specific URL, and boom—you get back data, usually in a format like JSON. It’s simple, clean, and works over the web.
Why is it Important? REST APIs are everywhere. Seriously. Whether you're logging into a game, checking stock prices, or uploading a picture, chances are there’s a REST API behind the scenes making it all happen. They let different apps and services talk without needing to know each other’s insides. It’s like giving your software a universal translator. Because REST uses basic HTTP methods (like GET, POST, PUT, DELETE), it’s super easy to use and works with just about every programming language. That makes it a go-to for developers wanting to build fast, connected, and scalable systems.
A good REST API solution is like a good login/admin solution - you could do a quick an dirty solution very easy - but in practice you want it safe and secure (you don't want people trashing your server or causing trouble).
A good REST API isn’t just about sending and receiving data—it’s about doing it right. First off, it should be predictable and consistent. That means using clear URL paths (like `/users/123`) and sticking to proper HTTP methods (GET for fetching, POST for creating, and so on). It also needs to be secure—use HTTPS to protect data, and require authentication (like API keys or tokens) so not just anyone can access it. Oh, and don’t forget input validation—never trust what’s coming in, or you might open the door to bugs or hackers. Lastly, make sure it handles errors gracefully. If something goes wrong, the API should return helpful messages, not just crash or give a generic "500 error." Clean, safe, and solid—that's how you want it.
 | Core Features |  |
I've listed some features you should think about incorporating into your setup (I've assuming an open access solution).
Basic Functionality
• Multi-file upload support (with configurable limits)
• File download with verification
• File renaming with backup preservation
• File deletion with versioned backups
• Paginated file listing
• Detailed file metadata (name, size, type, modified date)
Security Features
• Input validation and sanitization
• CSRF protection
• Rate limiting (requests per IP)
• Honeypot field to catch bots
• CAPTCHA for sensitive operations
• Secure headers (CSP, XSS, HSTS, etc.)
• File type whitelisting and blacklisting
• Filename sanitization
• MIME type verification
• File content validation
• Secure file permissions (0644)
• Directory traversal protection
• Null byte protection
• PHP tag detection in non-PHP files
File Protection
• Versioned backups (bak0, bak1, etc.)
• Configurable maximum backup versions
• Automatic backup rotation
• File hash verification (SHA-256)
• File integrity checking
Quota Management
• Maximum file size limit (configurable)
• Maximum files per upload (configurable)
• Maximum total upload size (configurable)
• Maximum files per folder (configurable)
• Maximum folder size (configurable)
Error Handling
• Secure error logging
• No sensitive information leakage
• User-friendly error messages
• Multiple error handling layers
Performance
• Pagination for large directories
• Efficient file operations
• Minimal memory usage
Configuration
• All features configurable via constants
• Easy enable/disable of features
• Flexible limits and restrictions
• use .htaccess - hide to important files
• ALLOWED_EXTENSIONS - allows *.* - for all extensions
 | Example Implementation |  |
Taking the key features above - I've tried to provide an implementation in php - the code is a single file - and should be easy to follow. I've tried to encapsulate features in defines so you can turn them on/off for testing.
If you do see any typos or think of a few more tweaks that can be added to make even more robust - please let me know - some sneaky bug or issue that you have to check for that I might have missed.
<?php /* * Ultra-Secure File Management REST API * * Features: * * === Core Functionality === * - Multi-file upload support (with configurable limits) * - File download with verification * - File renaming with backup preservation * - File deletion with versioned backups * - Paginated file listing * - Detailed file metadata (name, size, type, modified date) * * === Security Features === * - Input validation and sanitization * - CSRF protection * - Rate limiting (requests per IP) * - Honeypot field to catch bots * - CAPTCHA for sensitive operations * - Secure headers (CSP, XSS, HSTS, etc.) * - File type whitelisting and blacklisting * - Filename sanitization * - MIME type verification * - File content validation * - Secure file permissions (0644) * - Directory traversal protection * - Null byte protection * - PHP tag detection in non-PHP files * * === File Protection === * - Versioned backups (bak0, bak1, etc.) * - Configurable maximum backup versions * - Automatic backup rotation * - File hash verification (SHA-256) * - File integrity checking * * === Quota Management === * - Maximum file size limit (configurable) * - Maximum files per upload (configurable) * - Maximum total upload size (configurable) * - Maximum files per folder (configurable) * - Maximum folder size (configurable) * * === Error Handling === * - Secure error logging * - No sensitive information leakage * - User-friendly error messages * - Multiple error handling layers * * === Performance === * - Pagination for large directories * - Efficient file operations * - Minimal memory usage * * === Configuration === * - All features configurable via constants * - Easy enable/disable of features * - Flexible limits and restrictions - hide .htaccess files - ALLOWED_EXTENSIONS - allows *.* - for all extensions */
// ======================== // Configuration // ======================== // File upload settings define('MAX_FILE_SIZE', 10 * 1024 * 1024); // 10MB per file define('MAX_TOTAL_UPLOAD_SIZE', 50 * 1024 * 1024); // 50MB per batch define('MAX_FILES_PER_UPLOAD', 10); // Files per batch define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'doc', 'docx']); define('RESTRICTED_EXTENSIONS', ['php', 'phtml', 'html', 'htm', 'js', 'exe', 'sh', 'bat']); define('UPLOAD_DIR', './var/restuploads/'); define('MAX_FILENAME_LENGTH', 255); define('MIN_FILENAME_LENGTH', 1);
// Backup settings define('MAX_BACKUP_VERSIONS', 5); // Number of backup versions to keep
// Security settings define('HONEYPOT_FIELD', 'website_url'); // Field name for honeypot define('CAPTCHA_ANSWER', '42'); // Simple CAPTCHA answer define('RATE_LIMIT_REQUESTS', 30); // Requests per window define('RATE_LIMIT_WINDOW', 60); // Seconds for rate limiting define('HASH_ALGORITHM', 'sha256'); // For file verification
// Pagination settings define('ITEMS_PER_PAGE', 20); // Files per page
// Folder quotas define('MAX_FILES_IN_FOLDER', 1000); // Maximum files allowed define('MAX_FOLDER_SIZE', 100 * 1024 * 1024); // 100MB total
// ======================== // Security Headers // ======================== header("X-Frame-Options: DENY"); header("X-Content-Type-Options: nosniff"); header("X-XSS-Protection: 1; mode=block"); header("Content-Security-Policy: default-src 'self'; script-src 'none'"); header("Referrer-Policy: no-referrer"); header("Strict-Transport-Security: max-age=63072000; includeSubDomains; preload"); header("Feature-Policy: microphone 'none'; camera 'none'; geolocation 'none'");
// ======================== // Error Handling // ======================== ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', 'api_errors.log');
// ======================== // Request Validation // ======================== if (!empty($_POST[HONEYPOT_FIELD]) || !empty($_GET[HONEYPOT_FIELD])) { http_response_code(403); error_log("Honeypot triggered from IP: " . $_SERVER['REMOTE_ADDR']); die(json_encode(['error' => 'Access denied'])); }
if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'DELETE']) && (!isset($_POST['captcha']) || $_POST['captcha'] !== CAPTCHA_ANSWER)) { http_response_code(403); die(json_encode(['error' => 'CAPTCHA verification failed'])); }
// ======================== // Main API Handler // ======================== try { $requestMethod = $_SERVER['REQUEST_METHOD']; rateLimit(); $input = sanitizeInput(); switch ($requestMethod) { case 'GET': handleGetRequest($input); break; case 'POST': handlePostRequest($input); break; case 'DELETE': handleDeleteRequest($input); break; default: http_response_code(405); echo json_encode(['error' => 'Method not allowed']); } } catch (Exception $e) { http_response_code(500); error_log("API Error: " . $e->getMessage()); echo json_encode(['error' => 'Internal server error']); }
// ======================== // Request Handlers // ======================== function handleGetRequest($input) { $page = isset($input['page']) ? max(1, (int)$input['page']) : 1; $perPage = ITEMS_PER_PAGE; $files = getFileList(); $totalFiles = count($files); $totalPages = ceil($totalFiles / $perPage); // Check folder quotas (prevent listing if quotas exceeded) if (isFolderOverQuota()) { http_response_code(507); die(json_encode(['error' => 'Storage quota exceeded'])); } // Paginate files $paginatedFiles = array_slice($files, ($page - 1) * $perPage, $perPage); // Add file hashes if available foreach ($paginatedFiles as &$file) { $file['hash'] = getStoredFileHash($file['name']); } echo json_encode([ 'page' => $page, 'per_page' => $perPage, 'total_files' => $totalFiles, 'total_pages' => $totalPages, 'files' => $paginatedFiles ]); }
function handlePostRequest($input) { if (isset($_FILES['files'])) { handleMultiFileUpload($_FILES['files']); } elseif (isset($input['rename'])) { handleFileRename($input['oldname'], $input['newname']); } else { http_response_code(400); echo json_encode(['error' => 'Invalid request']); } }
function handleDeleteRequest($input) { if (!isset($input['filename'])) { http_response_code(400); die(json_encode(['error' => 'Filename required'])); } $filename = validateFilename($input['filename']); $filepath = validateFilePath(UPLOAD_DIR . $filename); validateFileExists($filepath); createBackup($filepath); if (!unlink($filepath)) { http_response_code(500); die(json_encode(['error' => 'Could not delete file'])); } // Remove from hash storage if exists removeFileHash($filename); echo json_encode(['success' => true, 'message' => 'File deleted']); }
// ======================== // File Operations // ======================== function handleMultiFileUpload($files) { // Validate total upload size $totalSize = 0; foreach ($files['size'] as $size) { $totalSize += $size; if ($size > MAX_FILE_SIZE) { http_response_code(413); die(json_encode(['error' => 'One or more files exceed maximum size'])); } } if ($totalSize > MAX_TOTAL_UPLOAD_SIZE) { http_response_code(413); die(json_encode(['error' => 'Total upload size exceeds limit'])); } if (count($files['name']) > MAX_FILES_PER_UPLOAD) { http_response_code(400); die(json_encode(['error' => 'Too many files in one upload'])); } // Check folder quotas if (isFolderOverQuota(count($files['name']), $totalSize)) { http_response_code(507); die(json_encode(['error' => 'Storage quota exceeded'])); } $results = []; $errors = []; for ($i = 0; $i < count($files['name']); $i++) { if ($files['error'][$i] !== UPLOAD_ERR_OK) { $errors[] = 'Error uploading ' . $files['name'][$i]; continue; } $filename = basename($files['name'][$i]); $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($ext, ALLOWED_EXTENSIONS)) { $errors[] = 'File type not allowed: ' . $filename; continue; } if (in_array($ext, RESTRICTED_EXTENSIONS)) { $errors[] = 'Restricted file type: ' . $filename; continue; } $filename = sanitizeFilename($filename); $filepath = UPLOAD_DIR . $filename; // Create backup if file exists if (file_exists($filepath)) { createBackup($filepath); } if (!move_uploaded_file($files['tmp_name'][$i], $filepath)) { $errors[] = 'Failed to move uploaded file: ' . $filename; continue; } // Verify file contents if (!verifyFileContents($filepath, $ext)) { unlink($filepath); $errors[] = 'Invalid file content: ' . $filename; continue; } // Set secure permissions chmod($filepath, 0644); // Calculate and store file hash $filehash = hash_file(HASH_ALGORITHM, $filepath); storeFileHash($filename, $filehash, $files['size'][$i]); $results[] = [ 'filename' => $filename, 'size' => $files['size'][$i], 'hash' => $filehash, 'success' => true ]; } if (!empty($errors)) { http_response_code(207); // Multi-status echo json_encode([ 'success' => !empty($results), 'results' => $results, 'errors' => $errors ]); } else { echo json_encode([ 'success' => true, 'results' => $results ]); } }
function handleFileRename($oldname, $newname) { $oldname = validateFilename($oldname); $newname = validateFilename($newname); $oldpath = validateFilePath(UPLOAD_DIR . $oldname); $newpath = validateFilePath(UPLOAD_DIR . $newname); validateFileExtension($newname); validateFileExists($oldpath); createBackup($oldpath); if (!rename($oldpath, $newpath)) { http_response_code(500); die(json_encode(['error' => 'Could not rename file'])); } // Update hash storage if exists updateFilenameInHashStorage($oldname, $newname); echo json_encode(['success' => true, 'message' => 'File renamed']); }
// ======================== // Security Functions // ======================== function sanitizeInput() { $input = []; if ($_SERVER['REQUEST_METHOD'] === 'GET') { foreach ($_GET as $key => $value) { $input[$key] = filter_input(INPUT_GET, $key, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); } } else { foreach ($_POST as $key => $value) { $input[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); } } return $input; }
function rateLimit() { $clientIp = $_SERVER['REMOTE_ADDR']; $cacheDir = 'ratelimit_cache/'; if (!is_dir($cacheDir)) { if (!mkdir($cacheDir, 0700, true)) { die("Failed to create rate limit directory"); } file_put_contents($cacheDir . '.htaccess', "Deny from all"); } $cacheFile = $cacheDir . 'ratelimit_' . md5($clientIp . $_SERVER['HTTP_USER_AGENT']); // Validate cache file path if (strpos(realpath($cacheFile), realpath($cacheDir)) !== 0) { die("Invalid rate limit cache path"); } $now = time(); $data = []; if (file_exists($cacheFile)) { $data = json_decode(file_get_contents($cacheFile), true); if (!isset($data['first_request']) || !isset($data['count'])) { unlink($cacheFile); $data = []; } } if (empty($data)) { $data = ['first_request' => $now, 'count' => 1]; } else { if ($now - $data['first_request'] <= RATE_LIMIT_WINDOW) { if ($data['count'] >= RATE_LIMIT_REQUESTS) { error_log("Rate limit exceeded from IP: $clientIp"); http_response_code(429); die(json_encode(['error' => 'Too many requests'])); } $data['count']++; } else { $data = ['first_request' => $now, 'count' => 1]; } } file_put_contents($cacheFile, json_encode($data), LOCK_EX); }
// ======================== // File Validation Functions // ======================== function validateFilename($filename) { if (empty($filename)) { http_response_code(400); die(json_encode(['error' => 'Filename cannot be empty'])); } if (strlen($filename) > MAX_FILENAME_LENGTH || strlen($filename) < MIN_FILENAME_LENGTH) { http_response_code(400); die(json_encode(['error' => 'Invalid filename length'])); } if (strpos($filename, '../') !== false || strpos($filename, '..\\') !== false) { error_log("Directory traversal attempt: " . $filename); http_response_code(400); die(json_encode(['error' => 'Invalid filename'])); } if (strpos($filename, "\0") !== false) { error_log("Null byte in filename: " . $filename); http_response_code(400); die(json_encode(['error' => 'Invalid filename'])); } return sanitizeFilename($filename); }
function sanitizeFilename($filename) { return preg_replace("/[^a-zA-Z0-9\._-]/", "", $filename); }
function validateFilePath($path) { $realBase = realpath(UPLOAD_DIR); $realUser = realpath(dirname($path)); if ($realUser === false || strpos($realUser, $realBase) !== 0) { error_log("Invalid path attempt: " . $path); http_response_code(400); die(json_encode(['error' => 'Invalid file path'])); } return $path; }
function validateFileExtension($filename) { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($ext, ALLOWED_EXTENSIONS)) { http_response_code(403); die(json_encode(['error' => 'File type not allowed'])); } if (in_array($ext, RESTRICTED_EXTENSIONS)) { error_log("Restricted extension attempt: " . $ext); http_response_code(403); die(json_encode(['error' => 'File type not allowed'])); } return true; }
function validateFileExists($filepath) { if (!file_exists($filepath)) { http_response_code(404); die(json_encode(['error' => 'File not found'])); } if (!is_file($filepath)) { http_response_code(400); die(json_encode(['error' => 'Not a regular file'])); } return true; }
function verifyFileContents($filepath, $ext) { // Check for PHP tags in non-PHP files if (!in_array($ext, ['php', 'phtml']) && filesize($filepath) < 1000000) { $contents = file_get_contents($filepath, false, null, 0, 1000); if (strpos($contents, '<?php') !== false) { error_log("PHP tags found in non-PHP file: " . basename($filepath)); return false; } } // Additional checks based on file type if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) { $imageInfo = getimagesize($filepath); if ($imageInfo === false) { error_log("Invalid image file: " . basename($filepath)); return false; } } return true; }
// ======================== // File Management Functions // ======================== function getFileList() { $files = []; if (!is_dir(UPLOAD_DIR)) { return $files; } $dir = opendir(UPLOAD_DIR); while (($file = readdir($dir)) !== false) { if ($file === '.' || $file === '..') continue; $filepath = UPLOAD_DIR . $file; // Skip backup files and directories if (preg_match('/\.bak\d+$/', $file) || is_dir($filepath)) { continue; } $files[] = [ 'name' => $file, 'size' => filesize($filepath), 'modified' => date('Y-m-d H:i:s', filemtime($filepath)), 'type' => mime_content_type($filepath) ]; } closedir($dir); // Sort by filename usort($files, function($a, $b) { return strcmp($a['name'], $b['name']); }); return $files; }
function createBackup($filepath) { if (!file_exists($filepath)) return false; if (!is_file($filepath)) return false; $backupPath = $filepath; $counter = 0; while ($counter < MAX_BACKUP_VERSIONS && file_exists($backupPath . '.bak' . $counter)) { $counter++; } if ($counter >= MAX_BACKUP_VERSIONS) { // Delete oldest backup if (file_exists($filepath . '.bak0')) { unlink($filepath . '.bak0'); } // Shift remaining backups for ($i = 1; $i < MAX_BACKUP_VERSIONS; $i++) { if (file_exists($filepath . '.bak' . $i)) { rename($filepath . '.bak' . $i, $filepath . '.bak' . ($i - 1)); } } $counter = MAX_BACKUP_VERSIONS - 1; } return copy($filepath, $filepath . '.bak' . $counter); }
// ======================== // Quota Management // ======================== function isFolderOverQuota($additionalFiles = 0, $additionalSize = 0) { $fileCount = 0; $totalSize = 0; if ($handle = opendir(UPLOAD_DIR)) { while (false !== ($file = readdir($handle))) { if ($file == "." || $file == "..") continue; if (is_dir(UPLOAD_DIR . $file)) continue; $fileCount++; $totalSize += filesize(UPLOAD_DIR . $file); } closedir($handle); } // Check if adding new files would exceed quotas if (($fileCount + $additionalFiles) > MAX_FILES_IN_FOLDER) { return true; } if (($totalSize + $additionalSize) > MAX_FOLDER_SIZE) { return true; } return false; }
// ======================== // File Hash Storage (Simple file-based implementation) // ======================== function storeFileHash($filename, $hash, $size) { $hashFile = UPLOAD_DIR . '.filehashes'; $hashes = []; if (file_exists($hashFile)) { $hashes = json_decode(file_get_contents($hashFile), true) ?: []; } $hashes[$filename] = [ 'hash' => $hash, 'size' => $size, 'timestamp' => time() ]; file_put_contents($hashFile, json_encode($hashes), LOCK_EX); }
function getStoredFileHash($filename) { $hashFile = UPLOAD_DIR . '.filehashes'; if (!file_exists($hashFile)) { return null; } $hashes = json_decode(file_get_contents($hashFile), true); return $hashes[$filename]['hash'] ?? null; }
function removeFileHash($filename) { $hashFile = UPLOAD_DIR . '.filehashes'; if (!file_exists($hashFile)) { return; } $hashes = json_decode(file_get_contents($hashFile), true) ?: []; unset($hashes[$filename]); file_put_contents($hashFile, json_encode($hashes), LOCK_EX); }
function updateFilenameInHashStorage($oldname, $newname) { $hashFile = UPLOAD_DIR . '.filehashes'; if (!file_exists($hashFile)) { return; } $hashes = json_decode(file_get_contents($hashFile), true) ?: []; if (isset($hashes[$oldname])) { $hashes[$newname] = $hashes[$oldname]; unset($hashes[$oldname]); file_put_contents($hashFile, json_encode($hashes), LOCK_EX); } }
// ======================== // Initialization // ======================== if (!is_dir(UPLOAD_DIR)) { mkdir(UPLOAD_DIR, 0700, true); file_put_contents(UPLOAD_DIR . '.htaccess', "Deny from all"); file_put_contents(UPLOAD_DIR . 'index.html', ""); } ?>
 | Things to Try |  |
• After implementing any REST API - I think a useful little tool would be a Python or JavaScript test script - which you can run and it will basically test every single test case (e.g., invalid files, getting/setting, etc)
• Try and optimize the code further (make it more readable, compact and secure)
|