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

PHP 跨瀏覽器文件下載中文亂碼完美解決方案

     編輯:PHP 相關知識

問題背景

PHP 實現文件下載時,`Content-Disposition` 標頭中的中文文件名在不同瀏覽器編碼規則不一致:IE/Edge 需 URL 編碼、Firefox 支援 RFC 5987 `filename*` 格式、Chrome/Safari 等現代瀏覽器支援 UTF-8 直傳。直接輸出中文會導致文件名亂碼、截斷或下載失敗。

標準解決方案:User-Agent 判斷分發

<?php
/**
 * 跨瀏覽器安全輸出下載文件名
 * @param string $filename 原始文件名 (UTF-8)
 * @return string 適配當前瀏覽器的 Content-Disposition 值
 */
function getContentDisposition($filename) {
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    // URL 編碼,空格轉 %20 而非 +
    $encoded = str_replace('+', '%20', urlencode($filename));
    
    if (preg_match('/MSIE|Trident|Edge/i', $ua)) {
        // IE / Edge (舊版) / Trident 內核
        return 'attachment; filename="' . $encoded . '"';
    } elseif (preg_match('/Firefox/i', $ua)) {
        // Firefox 支援 RFC 5987
        return "attachment; filename*=utf-8''" . $encoded;
    } else {
        // Chrome, Safari, Opera, 現代 Edge (Chromium) 等
        // 直接發送 UTF-8 (RFC 6266 建議雙重保險)
        return 'attachment; filename="' . $filename . '"; filename*=utf-8\'\'' . $encoded;
    }
}

// --- 使用示例 ---
$filename = "年度報表_2024_中文版.xlsx";
$filepath = "/data/reports/" . $filename;

if (!file_exists($filepath)) {
    header("HTTP/1.1 404 Not Found");
    exit("文件不存在");
}

header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($filepath));
header('Content-Disposition: ' . getContentDisposition($filename));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Expires: 0');

// 輸出文件流 (大文件分塊讀取避免內存溢出)
$handle = fopen($filepath, 'rb');
while (!feof($handle)) {
    echo fread($handle, 8192);
    flush(); // 及時發送到客戶端
}
fclose($handle);
exit;

進階方案:RFC 6266 雙重標頭 (推薦現代項目)

現代瀏覽器 (Chrome 65+, Firefox 52+, Edge 79+, Safari 13+) 均支援 `filename*` 參數。可簡化為統一發送雙標頭,無需 UA 判斷:

function getModernContentDisposition($filename) {
    $encoded = str_replace('+', '%20', urlencode($filename));
    // 同時提供 filename (向後兼容) 和 filename* (標準 UTF-8)
    // 瀏覽器會優先使用 filename*
    return 'attachment; filename="' . $encoded . '"; filename*=utf-8\'\'' . $encoded;
}

關鍵技術細節

1. 編碼細節

  • urlencode() 將空格編碼為 +,但 HTTP 標頭要求 %20,必須替換。
  • 文件名不應包含換行、雙引號、分號等特殊字符,建議預處理:str_replace(['"', "\r", "\n", ';'], '', $filename)。

2. 大文件下載優化


// 關閉輸出緩衝,避免佔用 PHP 內存
while (ob_get_level()) ob_end_clean();

// 分塊輸出
$chunkSize = 8192; // 8KB
$handle = fopen($filepath, 'rb');
while (!feof($handle)) {
    echo fread($handle, $chunkSize);
    flush(); // 強制發送到網絡層
}
fclose($handle);

3. 斷點續傳支援 (HTTP Range)

生產環境大文件下載建議支援 Range 請求,可參考 x-sendfile (Nginx/Apache 模塊) 或 PHP 實現範圍讀取,減少腳本佔用資源。

4. 安全考量

  • 嚴格驗證文件路徑,防止目錄遍歷:realpath() 確保在允許目錄內。
  • 設置 Content-Type 為具體 MIME 或 application/octet-stream 強制下載。
  • HTTPS 環境下添加 Content-Security-Policy 相關標頭。

瀏覽器兼容性測試表

瀏覽器版本filename (URL編碼)filename* (RFC 5987)UTF-8 直傳
IE6-11✓✗✗
Edge (Legacy)< 79✓✗✗
Edge (Chromium)79+✓✓✓
Chrome65+✓✓✓
Firefox52+✓✓✓
Safari13+✓✓✓

總結

針對中文文件名下載亂碼,核心在於根據瀏覽器特性發送正確編碼的 `Content-Disposition`。現代項目推薦使用 RFC 6266 雙標頭方案 (`filename` + `filename*`) 一體化兼容;老項目維護可保留 UA 判斷邏輯。配合大文件分塊輸出、路徑安全校驗、Range 請求支撐,可構建健壯的下載服務。

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