直接鏈接文件 URL 會導致瀏覽器直接打開圖片、HTML、PDF 等可識別類型,而非下載。通過 PHP 發送正確 Header 強制觸發下載對話框。
<?php
// 參數驗證
if (empty($_GET['FileName']) || empty($_GET['FileDir']) || empty($_GET['FileId'])) {
exit('非法請求');
}
$file_name = basename($_GET['FileName']); // 防止目錄遍歷
$file_dir = rtrim($_GET['FileDir'], '/') . '/';
$file_path = $file_dir . $file_name;
// 安全檢查:路徑必須在允許目錄內
$allowed_base = realpath('/var/www/downloads');
$real_path = realpath($file_path);
if (!$real_path || strpos($real_path, $allowed_base) !== 0 || !file_exists($real_path)) {
exit('文件不存在或無權限');
}
// 發送下載 Header
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($file_name) . '"');
header('Content-Length: ' . filesize($real_path));
header('Accept-Ranges: bytes');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache'); // HTTP/1.0 兼容
// 輸出文件內容
readfile($real_path); // 比 fopen/fread/fclose 更高效、內存友好
exit;
?>
| Header | 作用 | 關鍵點 |
|---|---|---|
| Content-Type: application/octet-stream | 聲明二進制流,瀏覽器不嘗試解析 | 核心觸發下載 |
| Content-Disposition: attachment | 指示為附件下載而非內聯顯示 | filename 建議用 rawurlencode 編碼 |
| Content-Length | 文件大小,支持進度條、斷點續傳 | 必須準確 |
| Accept-Ranges: bytes | 支持斷點續傳/多線程下載 | 需配合 Range 請求處理 |
| Cache-Control / Pragma | 防緩存干擾 | 下載類通常不緩存 |
basename() + realpath() + 基準目錄校驗../、..\\、空字節
3. 權限控制:結合用戶身份、文件所有權、下載次數限制
4. 大文件支持:readfile() 流式輸出,不佔用 PHP 內存;超大文件考慮 X-Sendfile/X-Accel-Redirect 交給 Web 服務器
5. 斷點續傳:解析 $_SERVER['HTTP_RANGE'],發送 206 Partial Content + Content-Range
# PHP 僅驗證權限,返回內部重定向 Header
header('X-Accel-Redirect: /protected/files/' . $file_name);
header('Content-Disposition: attachment; filename="' . rawurlencode($file_name) . '"');
exit;
# Nginx 配置
location /protected/files/ {
internal; # 僅內部訪問
alias /var/www/downloads/;
}
優勢:PHP 進程瞬間釋放,Nginx 直接發送文件,支持極高併發與大文件。
rawurlencode() 編碼,或 RFC 5987 格式 filename*=UTF-8''encoded_namereadfile() 或分塊輸出,禁用 file_get_contents()掌握 Header 機制與安全實踐,構建健壯的文件下載服務。