PHP文件下载关键在于正确设置四个响应头:Content-Type、Content-Disposition、Content-Transfer-Encoding、Expires与Cache-Control;缺一可能导致乱码、在线打开或错误;输出推荐readfile()。

PHP 实现文件下载,关键不是“输出内容”,而是正确设置响应头 + 强制浏览器不解析、不缓存、不跳转,直接触发保存对话框。用 echo 或 readfile() 输出文件内容本身很简单,但漏掉任一关键响应头,就可能变成网页乱码、图片直接显示、PDF 在线打开,甚至 404 或 500 错误。
必须设置的四个核心响应头
缺一不可,顺序无关,但值必须准确:
-
Content-Type: application/octet-stream(最通用;也可按文件类型设如application/pdf,但非必需) -
Content-Disposition: attachment; filename="xxx.zip"(attachment强制下载;filename建议用rawurlencode()处理中文名) -
Content-Transfer-Encoding: binary(明确传输编码,避免 MIME 解析歧义) -
Expires: 0和Cache-Control: must-revalidate, post-check=0, pre-check=0(防代理或浏览器缓存旧响应)
readfile() vs fpassthru() vs file_get_contents()
小文件(file_get_contents()——它会把整个文件读进内存,极易 OOM。
-
readfile():最常用,边读边输出,内存友好,支持所有本地文件路径 -
fpassthru($fp):需先fopen(),适合需要权限校验或流式处理(如加水印后中转) -
file_get_contents():仅限小文本/配置类文件;若硬要用,务必加ob_end_clean()清空输出缓冲,否则可能混入 HTML 内容
常见错误:中文文件名乱码或下载失败
浏览器对 filename 的编码处理不一致(Chrome 用 UTF-8,IE 用 GBK),直接写 中文.pdf 会导致 Chrome 下载成 ?.pdf,Firefox 可能报错。
立即学习“PHP免费学习笔记(深入)”;
- 安全做法:用
rawurlencode()编码文件名,再拼入Content-Disposition - 兼容老 IE:额外加
filename*=UTF-8''{encoded}(RFC 5987 格式) - 示例代码片段:
$filename = '报告2024.pdf';<br>$encoded = rawurlencode($filename);<br>header('Content-Disposition: attachment; filename="' . $filename . '"; filename*=UTF-8\'\'' . $encoded);
安全边界:不能让用户任意下载服务器任意文件
直接拼接 $_GET['file'] 是高危操作,可导致目录遍历(如传 ../../etc/passwd)。
- 禁止直接使用用户输入构造文件路径
- 白名单校验:只允许下载
/var/www/downloads/下预定义子目录中的文件 - 路径规范化后,用
realpath()+strpos()检查是否仍在允许根目录内 - 敏感文件(.env、.php、.log)一律拒绝,扩展名需二次校验,不依赖客户端传入的
Content-Type
真正难的不是写出能下载的代码,而是让每一次下载都可控、可审计、不泄露路径、不崩内存、不被绕过——尤其当文件来自用户上传或第三方集成时,basename() 和 realpath() 这两个函数用错一次,就可能变成突破口。



















