首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 強制下載檔案:以 PDF 為例的完整實作

     編輯:PHP 相關知識

核心原理:HTTP Header 控制瀏覽器行為

瀏覽器預設根據 Content-Type 決定顯示方式(如 PDF 內嵌顯示)。若要強制下載,需發送以下 Header 組合:

  • Content-Type: application/octet-stream 或 application/force-download:宣告為二進位串流,勿嘗試解析顯示。
  • Content-Disposition: attachment; filename="...":指示為附件下載,並建議檔名。
  • Content-Length: ...:告知檔案大小,支援進度條與斷點續傳。
  • Cache-Control / Pragma:防止快取導致下載舊檔或空檔(特別是 IE)。

通用強制下載函數

<?php
/**
 * 強制下載檔案
 *
 * @param string $filePath 檔案絕對路徑
 * @param string $fileName 下載顯示檔名(可選,預設取原檔名)
 * @return bool 成功輸出 true,檔案不存在 false
 */
function forceDownload($filePath, $fileName = '') {
    // 1. 檔案存在性檢查
    if (!is_file($filePath) || !is_readable($filePath)) {
        http_response_code(404);
        return false;
    }

    // 2. 取得檔案資訊
    $fileName = $fileName ?: basename($filePath);
    $fileSize = filesize($filePath);
    $mimeType = mime_content_type($filePath) ?: 'application/octet-stream';

    // 3. 清除輸出緩衝(防止前有輸出導致 Header 失效)
    if (ob_get_level()) {
        ob_end_clean();
    }

    // 4. 發送 Header
    header('Content-Type: ' . $mimeType);
    header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
    header('Content-Length: ' . $fileSize);
    header('Content-Transfer-Encoding: binary');
    header('Accept-Ranges: bytes'); // 支援斷點續傳

    // IE 相容快取控制
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    if (strpos($ua, 'MSIE') !== false || strpos($ua, 'Trident') !== false) {
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
    } else {
        header('Cache-Control: no-cache, must-revalidate');
        header('Pragma: no-cache');
    }

    // 5. 分塊輸出(避免大檔案佔滿記憶體)
    $handle = fopen($filePath, 'rb');
    if ($handle) {
        while (!feof($handle)) {
            echo fread($handle, 8192); // 8KB 緩衝
            flush(); // 立即推送給瀏覽器
        }
        fclose($handle);
    }

    return true;
}

// 使用範例
forceDownload('/var/www/files/report.pdf', '2024_Q1_Report.pdf');
?>

進階:支援斷點續傳 (HTTP Range Requests)

大檔案下載需支援 Range Header,允許瀏覽器/下載工具分段請求:

<?php
function forceDownloadWithRange($filePath, $fileName = '') {
    if (!is_file($filePath)) { http_response_code(404); return; }

    $fileSize = filesize($filePath);
    $fileName = $fileName ?: basename($filePath);

    // 解析 Range Header
    $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : '';
    $start = 0;
    $end   = $fileSize - 1;
    $statusCode = 200;

    if (preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)) {
        $start = intval($matches[1]);
        $end   = $matches[2] ? intval($matches[2]) : $fileSize - 1;
        if ($start > $end || $start >= $fileSize) {
            header('HTTP/1.1 416 Range Not Satisfiable');
            header('Content-Range: bytes */' . $fileSize);
            return;
        }
        $statusCode = 206; // Partial Content
    }

    // Header
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
    header('Content-Length: ' . ($end - $start + 1));
    header('Accept-Ranges: bytes');
    if ($statusCode === 206) {
        header('HTTP/1.1 206 Partial Content');
        header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
    }

    // 輸出指定區間
    $handle = fopen($filePath, 'rb');
    fseek($handle, $start);
    $remaining = $end - $start + 1;
    $bufferSize = 8192;
    while ($remaining > 0 && !feof($handle)) {
        $read = min($bufferSize, $remaining);
        echo fread($handle, $read);
        $remaining -= $read;
        flush();
    }
    fclose($handle);
}
?>

安全性關鍵點

  1. 路徑遍歷防護: 絕對不可直接信任使用者傳入的檔案路徑參數,必須經過 basename()、白名單驗證、或對應資料庫 ID 解析真實路徑。
  2. 權限控管: 下載前檢查使用者是否具備該檔案存取權限。
  3. 記憶體管理: 大檔案務必使用 fread() 分塊輸出,禁用 readfile() 或 file_get_contents() 一次載入記憶體。
  4. 執行時間: 長時間下載可能觸發 max_execution_time,可在函數開頭 set_time_limit(0) 移除限制。

總結

強制下載的關鍵在於正確的 HTTP Header 組合與分塊輸出機制。封裝通用 forceDownload() 函數並嚴格執行安全檢查,即可滿足 PDF、Excel、圖片等各類檔案的下載需求,並支援斷點續傳提升大檔案下載體驗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved