PHP File Uploads
Introduction to File Uploads
File uploads are a common requirement in web applications, allowing users to submit documents, images, videos, and other files. However, file uploads also present significant security risks and technical challenges that must be carefully addressed to prevent vulnerabilities and ensure reliable functionality.
Understanding how to implement secure, efficient file upload systems is essential for building robust web applications that handle user-generated content safely.
Why Secure File Uploads Matter
Security Risks: Malicious file uploads can lead to code execution, server compromise, and data breaches if not properly validated and secured.
User Experience: Reliable file upload functionality with proper feedback and error handling improves user satisfaction and application usability.
Performance: Efficient file handling prevents server overload and ensures responsive application performance even with large files.
Data Management: Proper file organization, naming, and storage facilitate content management and retrieval operations.
Compliance: Many applications require secure file handling to meet regulatory requirements and data protection standards.
Common Upload Challenges
File Type Validation: Ensuring uploaded files are of expected types and don't contain malicious code.
Size Limitations: Managing file size limits to prevent resource exhaustion and storage issues.
Security Vulnerabilities: Preventing path traversal, code injection, and other file-based attacks.
Error Handling: Providing meaningful feedback for various upload failure scenarios.
Multiple Files: Handling batch uploads and managing multiple file processing efficiently.
Basic File Upload Implementation
Simple Upload Form and Handler
<?php
/**
* Basic File Upload Implementation
*
* Demonstrates fundamental file upload handling with
* basic security measures and error management.
*/
/**
* Simple file upload handler
*/
class BasicFileUpload
{
private string $uploadDir;
private array $allowedTypes;
private int $maxSize;
private array $errors = [];
public function __construct(string $uploadDir = 'uploads/', array $allowedTypes = ['jpg', 'jpeg', 'png', 'gif'], int $maxSize = 2097152)
{
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->allowedTypes = array_map('strtolower', $allowedTypes);
$this->maxSize = $maxSize; // 2MB default
$this->setupUploadDirectory();
}
/**
* Setup secure upload directory
*/
private function setupUploadDirectory(): void
{
if (!is_dir($this->uploadDir)) {
if (!mkdir($this->uploadDir, 0755, true)) {
throw new RuntimeException("Cannot create upload directory: {$this->uploadDir}");
}
}
// Create .htaccess for security
$htaccessFile = $this->uploadDir . '.htaccess';
if (!file_exists($htaccessFile)) {
$htaccessContent = "# Prevent execution of uploaded files\n";
$htaccessContent .= "php_flag engine off\n";
$htaccessContent .= "Options -ExecCGI\n";
$htaccessContent .= "AddHandler cgi-script .php .pl .py .jsp .asp .sh .cgi\n";
file_put_contents($htaccessFile, $htaccessContent);
}
// Create index.php to prevent directory listing
$indexFile = $this->uploadDir . 'index.php';
if (!file_exists($indexFile)) {
file_put_contents($indexFile, '<?php http_response_code(403); exit; ?>');
}
}
/**
* Handle file upload
*/
public function handleUpload(string $inputName): ?array
{
$this->errors = [];
if (!isset($_FILES[$inputName])) {
$this->errors[] = 'No file uploaded';
return null;
}
$file = $_FILES[$inputName];
// Validate upload
if (!$this->validateUpload($file)) {
return null;
}
// Move uploaded file
return $this->moveUploadedFile($file);
}
/**
* Validate uploaded file
*/
private function validateUpload(array $file): bool
{
// Check for upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
$this->errors[] = $this->getUploadErrorMessage($file['error']);
return false;
}
// Check file size
if ($file['size'] > $this->maxSize) {
$this->errors[] = 'File size exceeds limit (' . $this->formatBytes($this->maxSize) . ')';
return false;
}
if ($file['size'] === 0) {
$this->errors[] = 'File is empty';
return false;
}
// Check file extension
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedTypes)) {
$this->errors[] = 'File type not allowed. Allowed types: ' . implode(', ', $this->allowedTypes);
return false;
}
// Verify file is actually uploaded
if (!is_uploaded_file($file['tmp_name'])) {
$this->errors[] = 'Invalid file upload';
return false;
}
// Additional MIME type check
$mimeType = mime_content_type($file['tmp_name']);
if (!$this->isAllowedMimeType($mimeType, $extension)) {
$this->errors[] = 'File content does not match extension';
return false;
}
return true;
}
/**
* Check if MIME type matches expected type for extension
*/
private function isAllowedMimeType(string $mimeType, string $extension): bool
{
$allowedMimeTypes = [
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png'],
'gif' => ['image/gif'],
'pdf' => ['application/pdf'],
'txt' => ['text/plain'],
'doc' => ['application/msword'],
'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document']
];
if (!isset($allowedMimeTypes[$extension])) {
return false;
}
return in_array($mimeType, $allowedMimeTypes[$extension]);
}
/**
* Move uploaded file to destination
*/
private function moveUploadedFile(array $file): array
{
$originalName = $file['name'];
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
// Generate unique filename
$filename = $this->generateUniqueFilename($originalName);
$filepath = $this->uploadDir . $filename;
if (!move_uploaded_file($file['tmp_name'], $filepath)) {
$this->errors[] = 'Failed to move uploaded file';
return null;
}
// Set secure permissions
chmod($filepath, 0644);
return [
'original_name' => $originalName,
'filename' => $filename,
'filepath' => $filepath,
'size' => $file['size'],
'mime_type' => mime_content_type($filepath),
'extension' => $extension,
'uploaded_at' => date('Y-m-d H:i:s')
];
}
/**
* Generate unique filename
*/
private function generateUniqueFilename(string $originalName): string
{
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
$basename = pathinfo($originalName, PATHINFO_FILENAME);
// Sanitize basename
$basename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $basename);
$basename = substr($basename, 0, 50); // Limit length
// Add timestamp and random string
$uniqueId = uniqid() . '_' . mt_rand(1000, 9999);
return $basename . '_' . $uniqueId . '.' . $extension;
}
/**
* Get upload error message
*/
private function getUploadErrorMessage(int $error): string
{
return match($error) {
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize directive',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE directive',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'Upload stopped by extension',
default => 'Unknown upload error'
};
}
/**
* Format bytes to human readable format
*/
private function formatBytes(int $size): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$unitIndex = 0;
while ($size >= 1024 && $unitIndex < count($units) - 1) {
$size /= 1024;
$unitIndex++;
}
return round($size, 2) . ' ' . $units[$unitIndex];
}
/**
* Get validation errors
*/
public function getErrors(): array
{
return $this->errors;
}
}
// HTML form example
?>
<!DOCTYPE html>
<html>
<head>
<title>File Upload Example</title>
<style>
.upload-form { max-width: 500px; margin: 20px; }
.form-group { margin-bottom: 15px; }
.error { color: red; margin-top: 5px; }
.success { color: green; margin-top: 5px; }
.progress { width: 100%; background: #f0f0f0; margin-top: 10px; }
.progress-bar { height: 20px; background: #4caf50; width: 0%; }
</style>
</head>
<body>
<div class="upload-form">
<h2>File Upload</h2>
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Choose file:</label>
<input type="file" id="file" name="upload_file" accept=".jpg,.jpeg,.png,.gif,.pdf" required>
<small>Allowed: JPG, PNG, GIF, PDF (max 2MB)</small>
</div>
<div class="form-group">
<button type="submit" name="upload">Upload File</button>
</div>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload'])) {
$uploader = new BasicFileUpload('uploads/', ['jpg', 'jpeg', 'png', 'gif', 'pdf'], 2097152);
$result = $uploader->handleUpload('upload_file');
if ($result) {
echo '<div class="success">';
echo '<h3>Upload Successful!</h3>';
echo '<p><strong>Original name:</strong> ' . htmlspecialchars($result['original_name']) . '</p>';
echo '<p><strong>File size:</strong> ' . number_format($result['size']) . ' bytes</p>';
echo '<p><strong>MIME type:</strong> ' . htmlspecialchars($result['mime_type']) . '</p>';
echo '<p><strong>Uploaded at:</strong> ' . $result['uploaded_at'] . '</p>';
echo '</div>';
} else {
echo '<div class="error">';
echo '<h3>Upload Failed!</h3>';
foreach ($uploader->getErrors() as $error) {
echo '<p>• ' . htmlspecialchars($error) . '</p>';
}
echo '</div>';
}
}
?>
</div>
</body>
</html>
Advanced Upload Features
Multiple File Uploads and Image Processing
<?php
/**
* Advanced File Upload System
*
* Handles multiple files, image processing, and
* provides comprehensive upload management.
*/
class AdvancedFileUpload
{
private string $uploadDir;
private array $config;
private array $errors = [];
public function __construct(string $uploadDir = 'uploads/', array $config = [])
{
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->config = array_merge([
'max_file_size' => 5 * 1024 * 1024, // 5MB
'max_files' => 10,
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'],
'image_resize' => true,
'max_image_width' => 1920,
'max_image_height' => 1080,
'create_thumbnails' => true,
'thumbnail_width' => 150,
'thumbnail_height' => 150,
'virus_scan' => false,
'watermark' => false
], $config);
$this->setupDirectories();
}
/**
* Setup upload directories
*/
private function setupDirectories(): void
{
$directories = [
$this->uploadDir,
$this->uploadDir . 'images/',
$this->uploadDir . 'documents/',
$this->uploadDir . 'thumbnails/',
$this->uploadDir . 'temp/'
];
foreach ($directories as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// Create security files
$this->createSecurityFiles($dir);
}
}
/**
* Create security files in directory
*/
private function createSecurityFiles(string $dir): void
{
// .htaccess
$htaccessFile = $dir . '.htaccess';
if (!file_exists($htaccessFile)) {
$content = "Options -Indexes\n";
$content .= "php_flag engine off\n";
$content .= "<Files ~ \"\\.(php|phtml|php3|php4|php5|pl|py|jsp|asp|sh|cgi)$\">\n";
$content .= " deny from all\n";
$content .= "</Files>\n";
file_put_contents($htaccessFile, $content);
}
// index.php
$indexFile = $dir . 'index.php';
if (!file_exists($indexFile)) {
file_put_contents($indexFile, '<?php http_response_code(403); ?>');
}
}
/**
* Handle multiple file uploads
*/
public function handleMultipleUploads(string $inputName): array
{
$this->errors = [];
$results = [];
if (!isset($_FILES[$inputName])) {
$this->errors[] = 'No files uploaded';
return [];
}
$files = $this->normalizeFilesArray($_FILES[$inputName]);
if (count($files) > $this->config['max_files']) {
$this->errors[] = "Too many files. Maximum {$this->config['max_files']} allowed";
return [];
}
foreach ($files as $index => $file) {
$result = $this->processSingleFile($file, $index);
if ($result) {
$results[] = $result;
}
}
return $results;
}
/**
* Normalize $_FILES array for multiple uploads
*/
private function normalizeFilesArray(array $files): array
{
$normalized = [];
if (is_array($files['name'])) {
$fileCount = count($files['name']);
for ($i = 0; $i < $fileCount; $i++) {
$normalized[] = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];
}
} else {
$normalized[] = $files;
}
return $normalized;
}
/**
* Process a single uploaded file
*/
private function processSingleFile(array $file, int $index): ?array
{
if ($file['error'] !== UPLOAD_ERR_OK) {
$this->errors[] = "File $index: " . $this->getUploadErrorMessage($file['error']);
return null;
}
if (!$this->validateFile($file, $index)) {
return null;
}
$fileInfo = $this->analyzeFile($file);
// Determine target directory
$targetDir = $this->getTargetDirectory($fileInfo['type']);
// Generate secure filename
$filename = $this->generateSecureFilename($file['name']);
$filepath = $targetDir . $filename;
// Move uploaded file
if (!move_uploaded_file($file['tmp_name'], $filepath)) {
$this->errors[] = "File $index: Failed to move uploaded file";
return null;
}
chmod($filepath, 0644);
$result = [
'original_name' => $file['name'],
'filename' => $filename,
'filepath' => $filepath,
'size' => $file['size'],
'type' => $fileInfo['type'],
'mime_type' => $fileInfo['mime_type'],
'uploaded_at' => date('Y-m-d H:i:s')
];
// Process images
if ($fileInfo['type'] === 'image') {
$result = array_merge($result, $this->processImage($filepath, $filename));
}
return $result;
}
/**
* Validate uploaded file
*/
private function validateFile(array $file, int $index): bool
{
// Size check
if ($file['size'] > $this->config['max_file_size']) {
$maxSize = $this->formatBytes($this->config['max_file_size']);
$this->errors[] = "File $index: Size exceeds limit ($maxSize)";
return false;
}
// Extension check
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->config['allowed_types'])) {
$this->errors[] = "File $index: File type not allowed ($extension)";
return false;
}
// MIME type check
$mimeType = mime_content_type($file['tmp_name']);
if (!$this->isValidMimeType($mimeType, $extension)) {
$this->errors[] = "File $index: Invalid file content";
return false;
}
// Image-specific validation
if ($this->isImageFile($mimeType)) {
if (!$this->validateImage($file['tmp_name'], $index)) {
return false;
}
}
// Virus scan (if enabled)
if ($this->config['virus_scan']) {
if (!$this->scanForVirus($file['tmp_name'], $index)) {
return false;
}
}
return true;
}
/**
* Analyze uploaded file
*/
private function analyzeFile(array $file): array
{
$mimeType = mime_content_type($file['tmp_name']);
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$type = 'document';
if ($this->isImageFile($mimeType)) {
$type = 'image';
}
return [
'type' => $type,
'mime_type' => $mimeType,
'extension' => $extension
];
}
/**
* Check if file is an image
*/
private function isImageFile(string $mimeType): bool
{
return strpos($mimeType, 'image/') === 0;
}
/**
* Validate image file
*/
private function validateImage(string $filepath, int $index): bool
{
$imageInfo = getimagesize($filepath);
if ($imageInfo === false) {
$this->errors[] = "File $index: Invalid image file";
return false;
}
[$width, $height] = $imageInfo;
// Check dimensions
if ($width > 5000 || $height > 5000) {
$this->errors[] = "File $index: Image dimensions too large";
return false;
}
if ($width < 1 || $height < 1) {
$this->errors[] = "File $index: Invalid image dimensions";
return false;
}
return true;
}
/**
* Process uploaded image
*/
private function processImage(string $filepath, string $filename): array
{
$result = [];
$imageInfo = getimagesize($filepath);
[$width, $height] = $imageInfo;
$result['dimensions'] = ['width' => $width, 'height' => $height];
// Resize if needed
if ($this->config['image_resize'] &&
($width > $this->config['max_image_width'] || $height > $this->config['max_image_height'])) {
$resizedPath = $this->resizeImage($filepath, $filename);
if ($resizedPath) {
$result['resized'] = $resizedPath;
}
}
// Create thumbnail
if ($this->config['create_thumbnails']) {
$thumbnailPath = $this->createThumbnail($filepath, $filename);
if ($thumbnailPath) {
$result['thumbnail'] = $thumbnailPath;
}
}
return $result;
}
/**
* Resize image
*/
private function resizeImage(string $filepath, string $filename): ?string
{
$imageInfo = getimagesize($filepath);
[$originalWidth, $originalHeight, $imageType] = $imageInfo;
// Calculate new dimensions
$maxWidth = $this->config['max_image_width'];
$maxHeight = $this->config['max_image_height'];
$ratio = min($maxWidth / $originalWidth, $maxHeight / $originalHeight);
$newWidth = intval($originalWidth * $ratio);
$newHeight = intval($originalHeight * $ratio);
// Create source image
$sourceImage = $this->createImageFromType($filepath, $imageType);
if (!$sourceImage) {
return null;
}
// Create target image
$targetImage = imagecreatetruecolor($newWidth, $newHeight);
// Preserve transparency for PNG and GIF
if ($imageType === IMAGETYPE_PNG || $imageType === IMAGETYPE_GIF) {
imagealphablending($targetImage, false);
imagesavealpha($targetImage, true);
$transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
imagefill($targetImage, 0, 0, $transparent);
}
// Resize
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0,
$newWidth, $newHeight, $originalWidth, $originalHeight);
// Save resized image
$resizedPath = $this->uploadDir . 'images/resized_' . $filename;
$saved = $this->saveImageByType($targetImage, $resizedPath, $imageType);
// Cleanup
imagedestroy($sourceImage);
imagedestroy($targetImage);
return $saved ? $resizedPath : null;
}
/**
* Create thumbnail
*/
private function createThumbnail(string $filepath, string $filename): ?string
{
$imageInfo = getimagesize($filepath);
[$originalWidth, $originalHeight, $imageType] = $imageInfo;
$thumbWidth = $this->config['thumbnail_width'];
$thumbHeight = $this->config['thumbnail_height'];
// Create source image
$sourceImage = $this->createImageFromType($filepath, $imageType);
if (!$sourceImage) {
return null;
}
// Calculate crop dimensions for square thumbnail
$size = min($originalWidth, $originalHeight);
$x = ($originalWidth - $size) / 2;
$y = ($originalHeight - $size) / 2;
// Create thumbnail
$thumbnail = imagecreatetruecolor($thumbWidth, $thumbHeight);
// Preserve transparency
if ($imageType === IMAGETYPE_PNG || $imageType === IMAGETYPE_GIF) {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefill($thumbnail, 0, 0, $transparent);
}
// Create square crop and resize
imagecopyresampled($thumbnail, $sourceImage, 0, 0, $x, $y,
$thumbWidth, $thumbHeight, $size, $size);
// Save thumbnail
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$thumbnailFilename = pathinfo($filename, PATHINFO_FILENAME) . '_thumb.' . $extension;
$thumbnailPath = $this->uploadDir . 'thumbnails/' . $thumbnailFilename;
$saved = $this->saveImageByType($thumbnail, $thumbnailPath, $imageType);
// Cleanup
imagedestroy($sourceImage);
imagedestroy($thumbnail);
return $saved ? $thumbnailPath : null;
}
/**
* Create image resource from file type
*/
private function createImageFromType(string $filepath, int $imageType)
{
return match($imageType) {
IMAGETYPE_JPEG => imagecreatefromjpeg($filepath),
IMAGETYPE_PNG => imagecreatefrompng($filepath),
IMAGETYPE_GIF => imagecreatefromgif($filepath),
default => false
};
}
/**
* Save image by type
*/
private function saveImageByType($image, string $filepath, int $imageType): bool
{
return match($imageType) {
IMAGETYPE_JPEG => imagejpeg($image, $filepath, 90),
IMAGETYPE_PNG => imagepng($image, $filepath),
IMAGETYPE_GIF => imagegif($image, $filepath),
default => false
};
}
/**
* Get target directory based on file type
*/
private function getTargetDirectory(string $type): string
{
return match($type) {
'image' => $this->uploadDir . 'images/',
default => $this->uploadDir . 'documents/'
};
}
/**
* Generate secure filename
*/
private function generateSecureFilename(string $originalName): string
{
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
$basename = pathinfo($originalName, PATHINFO_FILENAME);
// Sanitize
$basename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $basename);
$basename = substr($basename, 0, 30);
// Add unique identifier
$uniqueId = uniqid() . '_' . bin2hex(random_bytes(4));
return $basename . '_' . $uniqueId . '.' . $extension;
}
/**
* Validate MIME type
*/
private function isValidMimeType(string $mimeType, string $extension): bool
{
$validMimeTypes = [
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png'],
'gif' => ['image/gif'],
'pdf' => ['application/pdf'],
'doc' => ['application/msword'],
'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document']
];
return isset($validMimeTypes[$extension]) &&
in_array($mimeType, $validMimeTypes[$extension]);
}
/**
* Scan file for viruses (placeholder implementation)
*/
private function scanForVirus(string $filepath, int $index): bool
{
// This would integrate with ClamAV or similar
// For now, just a placeholder
return true;
}
/**
* Format bytes
*/
private function formatBytes(int $size): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$unitIndex = 0;
while ($size >= 1024 && $unitIndex < count($units) - 1) {
$size /= 1024;
$unitIndex++;
}
return round($size, 2) . ' ' . $units[$unitIndex];
}
/**
* Get upload error message
*/
private function getUploadErrorMessage(int $error): string
{
return match($error) {
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize directive',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE directive',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'Upload stopped by extension',
default => 'Unknown upload error'
};
}
/**
* Get errors
*/
public function getErrors(): array
{
return $this->errors;
}
}
// Usage example for multiple uploads
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_multiple'])) {
$uploader = new AdvancedFileUpload('uploads/', [
'max_file_size' => 3 * 1024 * 1024, // 3MB
'max_files' => 5,
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif'],
'image_resize' => true,
'create_thumbnails' => true
]);
$results = $uploader->handleMultipleUploads('multiple_files');
if (!empty($results)) {
echo "<h3>Upload Successful!</h3>";
foreach ($results as $index => $result) {
echo "<div style='border: 1px solid #ccc; padding: 10px; margin: 10px;'>";
echo "<h4>File " . ($index + 1) . "</h4>";
echo "<p><strong>Original:</strong> " . htmlspecialchars($result['original_name']) . "</p>";
echo "<p><strong>Size:</strong> " . number_format($result['size']) . " bytes</p>";
if ($result['type'] === 'image') {
echo "<p><strong>Dimensions:</strong> {$result['dimensions']['width']}x{$result['dimensions']['height']}</p>";
if (isset($result['thumbnail'])) {
echo "<p><img src='" . htmlspecialchars($result['thumbnail']) . "' alt='Thumbnail' style='max-width: 150px;'></p>";
}
}
echo "</div>";
}
} else {
echo "<h3>Upload Failed!</h3>";
foreach ($uploader->getErrors() as $error) {
echo "<p style='color: red;'>• " . htmlspecialchars($error) . "</p>";
}
}
}
?>
<!-- Multiple file upload form -->
<form method="POST" enctype="multipart/form-data" style="margin: 20px;">
<h3>Multiple File Upload</h3>
<input type="file" name="multiple_files[]" multiple accept="image/*" required>
<br><br>
<button type="submit" name="upload_multiple">Upload Files</button>
</form>
Related Topics
For more PHP web development topics:
- PHP Forms Processing - Complete form handling
- PHP Security Best Practices - Security guidelines
- PHP File Operations - File system operations
- PHP Error Handling - Error management
- PHP Image Processing - Advanced image manipulation
Summary
Secure file uploads require comprehensive planning and implementation:
Security First: Always validate file types, sizes, and content; prevent code execution; use secure directories and proper permissions.
User Experience: Provide clear feedback, support multiple files, show upload progress, and handle errors gracefully.
Image Processing: Implement resizing, thumbnail generation, and format optimization for better performance and storage efficiency.
Performance: Use appropriate file size limits, implement chunked uploads for large files, and optimize image processing operations.
Error Handling: Provide meaningful error messages, log security incidents, and implement proper fallback mechanisms.
Best Practices: Use unique filenames, implement virus scanning when possible, separate uploads by type, and maintain audit trails.
Proper file upload implementation protects your application while providing users with reliable, efficient file handling capabilities.