file_put_contents 保存同一文件卻正常輸出緩衝區污染 + 缺少二進制傳輸聲明。
echo file_get_contents() 會將這些「垃圾數據」混入圖片二進制流,導致文件頭損壞。Content-Transfer-Encoding: binary 聲明,部分代理/瀏覽器可能嘗試文本模式傳輸破壞二進制數據。<?php
// ❌ 錯誤示範
$filetype = 'image/pjpeg';
header("Content-type: {$filetype}");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");
$file_content = file_get_contents($filepath);
echo $file_content; // 垃圾數據混入!
exit;
?>
<?php
// 1. 設置標準下載 Header
$filetype = 'image/jpeg'; // 標準 MIME,推薦 jpeg 而非 pjpeg
header("Content-Type: {$filetype}");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");
header("Content-Transfer-Encoding: binary"); // 關鍵:聲明二進制傳輸
header('Content-Disposition: attachment; filename="' . basename($filepath) . '"'); // 強制下載並指定文件名
header('Content-Length: ' . filesize($filepath)); // 關鍵:告知文件大小,支持斷點續傳/進度條
// 2. 清空輸出緩衝區(核心步驟!)
ob_clean(); // 丟棄之前所有輸出(空白、BOM、錯誤信息等)
flush(); // 發送 Header 到客戶端(可選,視環境)
// 3. 直接輸出文件流(最高效、最安全)
readfile($filepath); // 內部使用流式讀取,不佔用 PHP 內存,比 file_get_contents+echo 更優
exit;
?>
| 函數 | 內存佔用 | 適用場景 | 推薦度 |
|---|---|---|---|
readfile() | 極低(流式) | 大文件下載、圖片/視頻輸出 | ⭐⭐⭐⭐⭐ |
file_get_contents() + echo | 高(全載入內存) | 小文件、需修改內容後輸出 | ⭐⭐ |
fpassthru() | 低 | 已打開文件指針的流式輸出 | ⭐⭐⭐ |
ob_clean()
ob_clean()丟棄當前輸出緩衝區的內容,但保留緩衝區開啟狀態。相當於 ASP.NET 的Response.Clear()。
配合flush()可確保 Header 先行發送,避免「Header 已發送」錯誤。
<?php
/**
* 安全文件下載輸出
* @param string $filePath 服務器絕對路徑
* @param string $fileName 下載顯示文件名(可選)
* @param string $mimeType MIME 類型(可選,自動檢測)
*/
function safeDownload($filePath, $fileName = '', $mimeType = '') {
if (!is_file($filePath) || !is_readable($filePath)) {
header('HTTP/1.1 404 Not Found');
exit('File not found or not readable');
}
$fileName = $fileName ?: basename($filePath);
$fileSize = filesize($filePath);
$mimeType = $mimeType ?: mime_content_type($filePath); // 需 fileinfo 擴展
// 防止中文文件名亂碼:RFC 5987 編碼
$encodedName = rawurlencode($fileName);
$headerValue = "attachment; filename*=UTF-8''{$encodedName}; filename=\"{$fileName}\"";
header("Content-Type: {$mimeType}");
header("Content-Transfer-Encoding: binary");
header("Content-Length: {$fileSize}");
header("Content-Disposition: {$headerValue}");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");
// 清理緩衝區並輸出
if (ob_get_level()) {
ob_clean();
}
flush();
readfile($filePath);
exit;
}
// 使用示例
safeDownload('/data/images/photo.jpg', '風景照.jpg', 'image/jpeg');
?>
$filePath 是絕對路徑且文件可讀(is_readable())。output_buffering(php.ini 或 ob_start()),必須配合 ob_clean()。<?php 之前輸出(包括 BOM、空行、HTML)。ini_set('display_errors', 0)),防止 Warning 混入二進制流。fastcgi_buffering off; 避免 Nginx 緩衝干擾大文件流。