PHP File Download
Introduction
When building web applications, you often need to allow users to download files from your server. Whether it's downloading a generated PDF report, an image, a spreadsheet, or any other file type, PHP provides several methods to implement file download functionality.
In this tutorial, you'll learn how to create file download scripts in PHP, handle different file types, and implement security measures to ensure safe file downloads.
Understanding File Downloads
When a user clicks on a download link, the browser needs to know that the response should be treated as a downloadable file rather than content to display. This is achieved using specific HTTP headers that PHP can generate.
The file download process generally works like this:
Basic File Download in PHP
Let's start with a simple example of a PHP script that allows users to download a file:
<?php
// Define the file path
$file = 'documents/sample.pdf';
// Check if file exists
if (file_exists($file)) {
// Define headers
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// Clear output buffer
ob_clean();
flush();
// Read the file and output its contents
readfile($file);
exit;
}
else {
echo "The file does not exist.";
}
?>
Key Components Explained:
- File Path: Specify the location of the file on your server.
- File Existence Check: Verify that the file exists before attempting to download it.
- HTTP Headers:
Content-Description
: Describes what's being sent.Content-Type
: Specifies the MIME type of the file.Content-Disposition
: Instructs the browser to download the file with the specified name.Expires
,Cache-Control
, andPragma
: Control caching behavior.Content-Length
: Tells the browser the size of the file.
- Output Buffer Clearing:
ob_clean()
andflush()
help ensure no extra content is sent. - File Reading:
readfile()
reads the file and outputs its content to the browser.
Handling Different File Types
Different file types require different Content-Type headers. Here's how to handle common file types:
<?php
function downloadFile($file) {
if (file_exists($file)) {
// Get file extension
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
// Define content types for common extensions
$contentTypes = [
'pdf' => 'application/pdf',
'zip' => 'application/zip',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'txt' => 'text/plain',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'csv' => 'text/csv'
];
// Set content type based on file extension
$contentType = isset($contentTypes[$extension]) ? $contentTypes[$extension] : 'application/octet-stream';
// Set headers
header('Content-Description: File Transfer');
header("Content-Type: $contentType");
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// Clear output buffer
ob_clean();
flush();
// Read file
readfile($file);
exit;
} else {
return false;
}
}
// Usage
if (isset($_GET['file'])) {
$file = 'uploads/' . $_GET['file'];
if (!downloadFile($file)) {
echo "File not found.";
}
}
?>
In this example, we've created a reusable function that automatically detects the file type based on its extension and sets the appropriate content type. If the extension isn't in our list, it defaults to application/octet-stream
, which is a generic binary file type that will trigger a download.
Forcing a File to Download
Sometimes, even for file types that browsers typically display (like images or PDFs), you want to force the browser to download the file rather than display it. The key to this is the Content-Disposition
header:
// Force download (will not display in browser)
header('Content-Disposition: attachment; filename="'.basename($file).'"');
// Allow browser to display if possible
header('Content-Disposition: inline; filename="'.basename($file).'"');
Creating Dynamic Files for Download
Sometimes you need to generate files on-the-fly rather than serving existing files. Here's an example that creates a CSV file dynamically:
<?php
// Create a CSV file dynamically
function generateCSV() {
// Sample data
$data = [
['ID', 'Name', 'Email'],
[1, 'John Doe', '[email protected]'],
[2, 'Jane Smith', '[email protected]'],
[3, 'Bob Johnson', '[email protected]']
];
// Set headers for download
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="users.csv"');
// Create a file pointer connected to the output stream
$output = fopen('php://output', 'w');
// Output each row of the data
foreach ($data as $row) {
fputcsv($output, $row);
}
// Close the file pointer
fclose($output);
exit;
}
// Call the function
generateCSV();
?>
Output:
ID,Name,Email
1,"John Doe",[email protected]
2,"Jane Smith",[email protected]
3,"Bob Johnson",[email protected]
Security Considerations
File downloads can introduce security risks if not properly implemented. Here are some best practices:
1. Validate and Sanitize Input
Never trust user input directly when determining file paths:
<?php
// Bad - vulnerable to path traversal
$file = $_GET['file'];
// Better - restrict to specific directory and validate
$filename = basename($_GET['file']);
$file = 'safe_download_directory/' . $filename;
// Check that the file is actually in the intended directory
$realPath = realpath($file);
$downloadDir = realpath('safe_download_directory');
if ($realPath === false || strpos($realPath, $downloadDir) !== 0) {
die("Invalid file request");
}
?>
2. Limit Access to Authorized Files
Create a system to determine which files a user is allowed to download:
<?php
function canDownloadFile($userId, $fileId) {
// Example logic - in a real app, you'd check a database
$allowedFiles = [
1 => [101, 102, 103], // User 1 can access files 101, 102, 103
2 => [101, 104] // User 2 can access files 101, 104
];
return isset($allowedFiles[$userId]) && in_array($fileId, $allowedFiles[$userId]);
}
// Usage
$userId = getUserIdFromSession(); // Get logged-in user's ID
$fileId = (int)$_GET['file_id']; // Get requested file ID
if (canDownloadFile($userId, $fileId)) {
// Get file path from file ID (e.g., from database)
$filePath = getFilePathFromId($fileId);
// Proceed with download
// ...
} else {
http_response_code(403); // Forbidden
echo "You don't have permission to download this file.";
}
?>
3. Use Tokens for Download Links
For extra security, especially for sensitive files, use temporary tokens:
<?php
// Generate a download token
function generateDownloadToken($fileId, $expiryTime = 3600) {
$secret = 'your-secret-key';
$expires = time() + $expiryTime;
$data = $fileId . '|' . $expires;
$token = hash_hmac('sha256', $data, $secret) . '|' . $expires . '|' . $fileId;
return $token;
}
// Validate a download token
function validateDownloadToken($token) {
$secret = 'your-secret-key';
$parts = explode('|', $token);
if (count($parts) !== 3) {
return false;
}
list($signature, $expires, $fileId) = $parts;
// Check if token has expired
if (time() > $expires) {
return false;
}
// Verify signature
$data = $fileId . '|' . $expires;
$expectedSignature = hash_hmac('sha256', $data, $secret);
if ($signature !== $expectedSignature) {
return false;
}
return $fileId;
}
// Create a download link
$token = generateDownloadToken(123);
$downloadUrl = "download.php?token=" . urlencode($token);
// In download.php
if (isset($_GET['token'])) {
$fileId = validateDownloadToken($_GET['token']);
if ($fileId !== false) {
// Get file path from file ID
$filePath = getFilePathFromId($fileId);
// Proceed with download
// ...
} else {
echo "Invalid or expired download link.";
}
}
?>
Practical Example: Download Manager
Let's create a simple file download manager that lists available files and provides secure download links:
<?php
// download-manager.php
// Function to generate a secure download token
function generateDownloadToken($filePath, $expiryTime = 3600) {
$secret = 'your-secret-key';
$expires = time() + $expiryTime;
$data = $filePath . '|' . $expires;
return hash_hmac('sha256', $data, $secret) . '|' . $expires . '|' . $filePath;
}
// List of available files (in a real app, this might come from a database)
$availableFiles = [
[
'id' => 1,
'name' => 'Sample PDF',
'path' => 'documents/sample.pdf',
'size' => '2.5 MB',
'type' => 'PDF'
],
[
'id' => 2,
'name' => 'User Guide',
'path' => 'documents/user-guide.docx',
'size' => '1.8 MB',
'type' => 'Word Document'
],
[
'id' => 3,
'name' => 'Monthly Report',
'path' => 'documents/report.xlsx',
'size' => '4.2 MB',
'type' => 'Excel Spreadsheet'
]
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Download Manager</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; }
.download-btn {
display: inline-block;
padding: 5px 10px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>Available Files</h1>
<table>
<thead>
<tr>
<th>File Name</th>
<th>Type</th>
<th>Size</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($availableFiles as $file): ?>
<tr>
<td><?php echo htmlspecialchars($file['name']); ?></td>
<td><?php echo htmlspecialchars($file['type']); ?></td>
<td><?php echo htmlspecialchars($file['size']); ?></td>
<td>
<?php
$token = generateDownloadToken($file['path']);
$downloadUrl = "download.php?token=" . urlencode($token);
?>
<a href="<?php echo $downloadUrl; ?>" class="download-btn">Download</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
And here's the corresponding download script:
<?php
// download.php
// Function to validate a download token
function validateDownloadToken($token) {
$secret = 'your-secret-key';
$parts = explode('|', $token);
if (count($parts) !== 3) {
return false;
}
list($signature, $expires, $filePath) = $parts;
// Check if token has expired
if (time() > $expires) {
return false;
}
// Verify signature
$data = $filePath . '|' . $expires;
$expectedSignature = hash_hmac('sha256', $data, $secret);
if ($signature !== $expectedSignature) {
return false;
}
return $filePath;
}
// Function to download a file
function downloadFile($file) {
if (file_exists($file)) {
// Get file extension
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
// Define content types for common extensions
$contentTypes = [
'pdf' => 'application/pdf',
'zip' => 'application/zip',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'txt' => 'text/plain',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'csv' => 'text/csv'
];
// Set content type based on file extension
$contentType = isset($contentTypes[$extension]) ? $contentTypes[$extension] : 'application/octet-stream';
// Set headers
header('Content-Description: File Transfer');
header("Content-Type: $contentType");
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// Clear output buffer
ob_clean();
flush();
// Read file
readfile($file);
exit;
} else {
return false;
}
}
// Main download logic
if (isset($_GET['token'])) {
$filePath = validateDownloadToken($_GET['token']);
if ($filePath !== false) {
if (!downloadFile($filePath)) {
http_response_code(404);
echo "File not found.";
}
} else {
http_response_code(403);
echo "Invalid or expired download link.";
}
} else {
http_response_code(400);
echo "No download token provided.";
}
?>
This implementation:
- Lists available files with secure download links
- Uses time-limited tokens to protect the downloads
- Handles different file types appropriately
- Implements security checks to prevent unauthorized access
Sending Files With Custom Names
Sometimes you want the downloaded file to have a different name than the one stored on the server:
<?php
$file = 'documents/report_20240303.pdf';
$customName = 'Annual_Report_2024.pdf';
header('Content-Disposition: attachment; filename="'.$customName.'"');
Tracking Downloads
For analytics purposes, you might want to track file downloads:
<?php
function trackDownload($fileId, $userId) {
// In a real application, you would log to a database
$logFile = 'download_logs.txt';
$timestamp = date('Y-m-d H:i:s');
$logEntry = "$timestamp | User ID: $userId | File ID: $fileId
";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
// Usage in download script
if ($filePath = validateDownloadToken($_GET['token'])) {
$userId = getUserIdFromSession(); // Get current user ID
$fileId = getFileIdFromPath($filePath); // Get file ID from path
// Track the download
trackDownload($fileId, $userId);
// Proceed with download
downloadFile($filePath);
}
?>
Summary
In this tutorial, you've learned how to:
- Create basic file download functionality in PHP
- Handle different file types with appropriate content types
- Force files to download rather than display in the browser
- Generate dynamic files for download
- Implement security measures to protect your downloads
- Create a practical file download manager
- Customize filenames and track downloads
File downloads are an essential part of many web applications, and PHP provides all the tools you need to implement this functionality securely and efficiently.
Additional Resources
Exercises
- Create a simple image gallery with download buttons for each image.
- Implement a file uploader that allows users to upload files and then download them later.
- Build a PDF generator that creates a custom report and offers it for download.
- Extend the download manager example to include user authentication.
- Create a rate-limiting system that prevents users from downloading too many files within a certain time period.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)