readfile

(PHP 4, PHP 5, PHP 7, PHP 8)

readfile输出文件

说明

readfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false

读取文件并将其写入输出缓冲区。

参数

filename

要读取的文件名。

use_include_path

您可以使用可选的第二个参数并将其设置为 true,如果您还想在 include_path 中搜索文件。

context

一个 上下文流 resource

返回值

成功时返回从文件中读取的字节数,失败时返回 false

错误/异常

失败时,会发出一个 E_WARNING

示例

示例 #1 使用 readfile() 强制下载

<?php
$file
= 'monkey.gif';

if (
file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
?>

上面的示例将输出类似以下内容

Open / Save dialogue

注释

注意:

readfile() 本身不会出现任何内存问题,即使发送大型文件也是如此。如果您遇到内存不足错误,请确保使用 ob_get_level() 关闭输出缓冲。

提示

如果启用了 fopen 包装器,则可以使用 URL 作为此函数的文件名。有关如何指定文件名的更多详细信息,请参见 fopen()。请参见 支持的协议和包装器,以获取有关各种包装器的功能、使用说明以及它们可能提供的任何预定义变量的信息的链接。

参见

添加注释

用户贡献的注释 36 个注释

riksoft at gmail dot com
10 年前
对于那些在包含空格的名称(例如“test test.pdf”)上遇到问题的人来说,这是一个注意事项。

在示例(99% 的时间)中,您可以找到
header('Content-Disposition: attachment; filename='.basename($file));

但设置文件名的正确方法是引用它(双引号)
header('Content-Disposition: attachment; filename="'.basename($file).'"' );

一些浏览器可能在没有引号的情况下也能工作,但 Firefox 肯定不行,正如 Mozilla 所解释的那样,内容配置中的文件名引号符合 RFC
http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download
yura_imbp at mail dot ru
16 年前
如果您需要限制下载速度,请使用以下代码

<?php
$local_file
= 'file.zip';
$download_file = 'name.zip';

// 设置下载速度限制 (=> 20.5 kb/s)
$download_rate = 20.5;
if(
file_exists($local_file) && is_file($local_file))
{
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($local_file));
header('Content-Disposition: filename='.$download_file);

flush();
$file = fopen($local_file, "r");
while(!
feof($file))
{
// 将当前文件部分发送到浏览器
print fread($file, round($download_rate * 1024));
// 将内容刷新到浏览器
flush();
// 休眠一秒钟
sleep(1);
}
fclose($file);}
else {
die(
'Error: The file '.$local_file.' does not exist!');
}

?>
flobee at gmail dot com
19 年前
关于 php5
我发现 @php-dev 已经就 readfile() 和 fpassthru() 展开了一场讨论,其中只传递了 2 MB 的数据。

因此,您可以在 php5 上使用以下代码来获取更大的文件
<?php
function readfile_chunked($filename,$retbytes=true) {
$chunksize = 1*(1024*1024); // 每个块的大小(字节)
$buffer = '';
$cnt =0;
// $handle = fopen($filename, 'rb');
$handle = fopen($filename, 'rb');
if (
$handle === false) {
return
false;
}
while (!
feof($handle)) {
$buffer = fread($handle, $chunksize);
echo
$buffer;
if (
$retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if (
$retbytes && $status) {
return
$cnt; // 返回已传送的字节数,与 readfile() 相同。
}
return
$status;

}
?>
marro at email dot cz
16 年前
我的脚本在 IE6 和 Firefox 2 上运行正常,可以处理任何类型的文件(我希望是这样 :))

function DownloadFile($file) { // $file = 包含路径
if(file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}

}

在 Apache 2 (WIN32) PHP5 上运行
levhita at gmail dot com
15 年前
关于 gaosipov 的 smartReadFile 函数的一个说明

更改 preg_match 匹配中的索引

$begin = intval($matches[1]);
if( !empty($matches[2]) ) {
$end = intval($matches[2]);
}

否则 $begin 将被设置为匹配的整个部分,而 $end 将被设置为应该是 $begin 的值。

有关详细信息,请参阅 preg_match。
Hayley Watson
16 年前
为了避免用户通过修改请求和插入 "../" 等内容来选择他们自己想要下载的文件,请记住 URL 不是文件路径,并且没有理由让它们之间的映射如此直接,例如 "download.php?file=thingy.mpg" 会导致下载文件 "thingy.mpg"。

它是你的脚本,你可以完全控制它如何将文件请求映射到文件名,以及哪些请求检索哪些文件。

但即使如此,一如既往,永远不要信任请求中的任何内容。这是安全原则的基础,应该从第一天就开始学习。
TimB
16 年前
对于任何在使用 Readfile() 读取大型文件时遇到内存问题的人来说,问题不在于 Readfile() 本身,而是因为你开启了输出缓冲。只需在调用 Readfile() 之前立即关闭输出缓冲即可。使用类似 ob_end_flush() 的东西。
Paulinator
6 年前
始终使用 MIME 类型 'application/octet-stream' 并不是最佳选择。大多数(如果不是全部)浏览器只会下载具有该类型的文件。

如果你使用正确的 MIME 类型(和内联 Content-Disposition),浏览器将对其中的一些文件有更好的默认操作。例如,在图像的情况下,浏览器将显示它们,这可能就是你想要的。

要使用正确的 MIME 类型来提供文件,最简单的方法是使用

header('Content-Type: ' . mime_content_type($file));
header('Content-Disposition: inline; filename="'.basename($file).'"');
gaosipov at gmail dot com
15 年前
使用 HTTPRange 支持发送文件(部分下载)

<?php
function smartReadFile($location, $filename, $mimeType='application/octet-stream')
{ if(!
file_exists($location))
{
header ("HTTP/1.0 404 Not Found");
return;
}

$size=filesize($location);
$time=date('r',filemtime($location));

$fm=@fopen($location,'rb');
if(!
$fm)
{
header ("HTTP/1.0 505 Internal server error");
return;
}

$begin=0;
$end=$size;

if(isset(
$_SERVER['HTTP_RANGE']))
{ if(
preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
{
$begin=intval($matches[0]);
if(!empty(
$matches[1]))
$end=intval($matches[1]);
}
}

if(
$begin>0||$end<$size)
header('HTTP/1.0 206 Partial Content');
else
header('HTTP/1.0 200 OK');

header("Content-Type: $mimeType");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Range: bytes $begin-$end/$size");
header("Content-Disposition: inline; filename=$filename");
header("Content-Transfer-Encoding: binary\n");
header("Last-Modified: $time");
header('Connection: close');

$cur=$begin;
fseek($fm,$begin,0);

while(!
feof($fm)&&$cur<$end&&(connection_status()==0))
{ print
fread($fm,min(1024*16,$end-$cur));
$cur+=1024*16;
}
}
?>

用法

<?php
smartReadFile
("/tmp/filename","myfile.mp3","audio/mpeg")
?>

对于大型文件,使用 fread 读取可能会很慢,但这是在严格边界内读取文件的唯一方法。你可以修改此代码并添加 fpassthru 而不是 fread 和 while,但这会从开头发送所有数据——如果请求是来自 100mb 文件的第 100 到 200 字节,则这将没有意义。
jorensmerenjanu at gmail dot com
3 年前
对于任何在下载文件中输出 HTML 页面的人来说:在 readfile() 之前调用函数 ob_clean() 和 flush()
daren -remove-me- schwenke
13 年前
如果你很幸运不在共享主机上并且有 apache,请考虑安装 mod_xsendfile。
这是我发现的唯一一种使用 PHP 传输大型文件(千兆字节)的方法,既能保护也能传输。
对于基本上任何文件来说,它也已被证明要快得多。
自此前的其他说明以来,可用的指令已经发生了变化,XSendFileAllowAbove 被 XSendFilePath 取代,以允许更多地控制对 webroot 之外文件的访问。

下载源代码。

使用以下命令安装:apxs -cia mod_xsendfile.c

将适当的配置指令添加到你的 .htaccess 或 httpd.conf 文件中
# 启用
XSendFile on
# 将目标目录列入白名单。
XSendFilePath /tmp/blah

然后在你的脚本中使用它
<?php
$file
= '/tmp/blah/foo.iso';
$download_name = basename($file);
if (
file_exists($file)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$download_name);
header('X-Sendfile: '.$file);
exit;
}
?>
chrisputnam at gmail dot com
19 年前
回复 [email protected] --

当使用此处提到的 readfile_chunked 函数处理大于 10MB 的文件时,我仍然遇到内存错误。这是因为编写者遗漏了每次读取后的重要 flush()。因此,这是正确的分块读取文件(实际上并不是 readfile,应该交叉发布到 passthru()、fopen() 和 popen(),以便浏览器可以找到此信息)

<?php
function readfile_chunked($filename,$retbytes=true) {
$chunksize = 1*(1024*1024); // 每块多少字节
$buffer = '';
$cnt =0;
// $handle = fopen($filename, 'rb');
$handle = fopen($filename, 'rb');
if (
$handle === false) {
return
false;
}
while (!
feof($handle)) {
$buffer = fread($handle, $chunksize);
echo
$buffer;
ob_flush();
flush();
if (
$retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if (
$retbytes && $status) {
return
$cnt; // 返回已传递的字节数,与 readfile() 一样。
}
return
$status;

}
?>

我所添加的只是一行 echo 后的 flush();。确保包含它!
simbiat at outlook dot com
3 年前
flobee.at.gmail.dot.com 分享了“readfile_chunked” 函数。它确实有效,但是使用“fread”可能会遇到内存耗尽问题。同时,"stream_copy_to_stream" 似乎与 "readfile" 使用相同数量的内存。至少在我测试我的 https://github.com/Simbiat/HTTP20 库的 1.5G 文件的 "download" 函数时,在 256M 内存限制的情况下就是这样: "fread" 的峰值内存使用量为 ~240M,而 "stream_copy_to_stream" 的峰值内存使用量为 ~150M。
但这并不意味着您可以完全避免内存耗尽:如果您一次读取太多内容,您仍然可能会遇到这种情况。这就是为什么在我的库中,我使用一个辅助函数("speedLimit")来计算选定的速度限制是否适合可用的内存(同时允许一些余量)。
您可以在代码本身的注释中查看更多详细信息,如果您认为那里有什么错误,可以向库提出问题(尤其是在撰写本文时它还是 WIP),但到目前为止,我能够使用它获得一致的行为。
mAu
17 年前
不要使用
<?php
header
('Content-Type: application/force-download');
?>
使用
<?php
header
('Content-Type: application/octet-stream');
?>
一些浏览器在强制下载方面存在问题。
antispam [at] rdx page [dot] com
18 年前
注意:如果您使用 bw_mod(当前版本 0.6)在 Apache 2 中限制带宽,它 *不会* 在 readfile 事件期间限制带宽。
Brian
10 年前
如果您正在寻找一个允许您下载(强制下载)大文件的算法,那么这个算法可能会对您有所帮助。

$filename = "file.csv";
$filepath = "/path/to/file/" . $filename;

// 关闭会话以防止用户等待
// 下载完成(如果需要,取消注释)
//session_write_close();

set_time_limit(0);
ignore_user_abort(false);
ini_set('output_buffering', 0);
ini_set('zlib.output_compression', 0);

$chunk = 10 * 1024 * 1024; // 每块字节数(10 MB)

$fh = fopen($filepath, "rb");

if ($fh === false) {
echo "无法打开文件";
}

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));

// 重复读取直到 EOF
while (!feof($fh)) {
echo fread($handle, $chunk);

ob_flush(); // 刷新输出
flush();
}

exit;
Anonymous
5 年前
为了避免错误,
只需注意 $file_name 参数开头是否允许斜杠“/”。

在我的情况下,尝试通过 PHP 发送 PDF 文件以进行访问日志记录,
必须在 PHP 7.1 中删除开头的“/”。
planetmaster at planetgac dot com
18 年前
使用强制下载脚本的片段,添加 MySQL 数据库函数,并隐藏文件位置以确保安全性,这就是我们需要从成员创建的内容中下载 wmv 文件而不会提示媒体播放器,以及保护文件本身并仅使用数据库查询所需要的。效果如下,可以高度定制以进行私有访问、远程文件,并保持在线媒体的顺序。

<?
# 防止 SQL 注入
$fileid=intval($_GET[id]);
# 设置 SQL 语句
$sql = " SELECT id, fileurl, filename, filesize FROM ibf_movies WHERE id=' $fileid' ";

# 执行 SQL 语句
$res = mysql_query($sql);

# 显示结果
while ($row = mysql_fetch_array($res)) {
$fileurl = $row['fileurl'];
$filename= $row['filename'];
$filesize= $row['filesize'];

$file_extension = strtolower(substr(strrchr($filename,"."),1));

switch ($file_extension) {
case "wmv": $ctype="video/x-ms-wmv"; break;
default: $ctype="application/force-download";
}

// IE 所需,否则会忽略 Content-disposition
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');

header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: video/x-ms-wmv");
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".@filesize($filename));
set_time_limit(0);
@readfile("$fileurl") or die("文件未找到。");

}

$donwloaded = "downloads + 1";

if ($_GET["hit"]) {
mysql_query("UPDATE ibf_movies SET downloads = $donwloaded WHERE id=' $fileid'");

}

?>

顺便说一句,我在 download.php 中添加了一个点击(下载)计数器。当然,您需要设置 DB、表和列。如果您需要完整的设置,请给我发电子邮件// 会话标记也是一个安全/记录选项
在链接的上下文中使用
http://www.yourdomain.com/download.php?id=xx&hit=1

[由 [email protected] 编辑:添加了防止 SQL 注入的保护]
peavey at pixelpickers dot com
18 年前
也可以通过使用以下方法来执行与 mime 类型无关的强制下载

<?
(...)
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // 过去某一天
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Content-type: application/x-download");
header("Content-Disposition: attachment; filename={$new_name}");
header("Content-Transfer-Encoding: binary");
?>

干杯,

Peavey
Thomas Jespersen
19 年前
请记住,如果您创建了如下所述的“强制下载”脚本,请对您的输入进行 *清理*!

我见过很多下载脚本,这些脚本没有进行测试,因此您可以下载服务器上的任何东西。

尤其要测试诸如“..”之类的字符串,这些字符串可以实现目录遍历。如果可能,只允许字符 a-z、A-Z 和 0-9,并允许仅从一个“下载文件夹”下载。
TheDayOfCondor
19 年前
小心 - Rob Funk 建议的分块 readfile 可能会轻松超过您最大的脚本执行时间(默认情况下为 30 秒)。

我建议您在 while 循环中使用 set_time_limit 函数来重置 php 看门狗。
Zambz
14 年前
如果您使用本文中概述的步骤来强制将文件发送给用户,您可能会发现某些服务器上没有发送“Content-Length”标头。

出现这种情况的原因是,某些服务器默认情况下会启用 gzip 压缩,这会为此类操作发送额外的标头。此额外的标头是“Transfer-Encoding: chunked”,它实质上会覆盖“Content-Length”标头,并强制进行分块下载。当然,如果您使用的是本文中的智能 readfile 版本,则不需要这样做。

缺少 Content-Length 标头意味着以下内容

1) 您的浏览器不会在下载时显示进度条,因为它不知道它们的长度
2) 如果您在 readfile 函数后输出任何内容(例如空格)(错误),浏览器会将其添加到下载的末尾,导致数据损坏。

禁用此行为的最简单方法是使用以下 .htaccess 指令。

SetEnv no-gzip dont-vary
Anonymous
18 年前
这是一个不错的强制下载脚本

$filename = 'dummy.zip';
$filename = realpath($filename);

$file_extension = strtolower(substr(strrchr($filename,"."),1));

switch ($file_extension) {
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpe": case "jpeg"
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}

if (!file_exists($filename)) {
die("此处没有文件");
}

header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".@filesize($filename));
set_time_limit(0);
@readfile("$filename") or die("文件未找到。");
anon
7 年前
在 C 源代码中,此函数只是以读+二进制模式打开路径,不加锁,并使用 fpassthru()

如果您需要锁定读取,请直接使用 fopen()、flock(),然后使用 fpassthru()。
johniskew
15 年前
@marks 的建议 -

这是一种方法,但是可以避免这种情况。例如,在 Zend Framework 中,您可以执行以下操作

<?php

// Action 控制器
public function someAction() {

$response = $this->_response;

// 禁用视图和布局渲染
$this->_helper->viewRenderer->setNoRender();
$this->_helper->layout()->disableLayout();

// 处理文件
$file = 'whatever.zip';
$bits = @file_get_contents($file);
if(
strlen($bits) == 0) {
$response->setBody('抱歉,我们找不到您请求的下载文件。');
}
else {
$response->setHeader('Content-type', 'application/octet-stream', true);
$response->setBody($bits);
}
}

?>
sakai at d4k dot net
15 年前
为了减轻服务器负担,您可能希望在 http 响应头中输出 “Etag” 和/或 “Last-Modified”。 但是,PHP 本身会自动输出一些头信息,这会干扰此操作。 所以我写了这个函数来擦除这些头信息。

如果您知道如何判断函数 “stat” 的返回值,以避免使用 “is_file” 或 “is_readable”(或 “is_dir”),请告诉我或直接写在这里。

如果您不需要在 404 时执行任何特殊操作,则 “header('HTTP/1.x xxx xxxxx');” 可以放在函数内部。

<?php

$filename
= '/foo/bar/myfeed.rss';
$http_stat_code = readfile_if_modified($filename, array('Content-Type: text/xml'));
switch(
$http_stat_code) {
case
404:
header('HTTP/1.0 404 Not Found');
echo
'<html><head></head><body><a href="http://example.com/">http://example.com/<a></body></html>';
exit;
default:
header('X-System-Url: http://example.com/', true, $http_stat_code);
}

function
readfile_if_modified($filename, $http_header_additionals = array()) {

if(!
is_file($filename)) {
// header('HTTP/1.0 404 Not Found');
return 404;
}

if(!
is_readable($filename)) {
// header('HTTP/1.0 403 Forbidden');
return 403;
}

$stat = @stat($filename);
$etag = sprintf('%x-%x-%x', $stat['ino'], $stat['size'], $stat['mtime'] * 1000000);

header('Expires: ');
header('Cache-Control: ');
header('Pragma: ');

if(isset(
$_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
header('Etag: "' . $etag . '"');
// header('HTTP/1.0 304 Not Modified');
return 304;
} elseif(isset(
$_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $stat['mtime']) {
header('Last-Modified: ' . date('r', $stat['mtime']));
// header('HTTP/1.0 304 Not Modified');
return 304;
}

header('Last-Modified: ' . date('r', $stat['mtime']));
header('Etag: "' . $etag . '"');
header('Accept-Ranges: bytes');
header('Content-Length:' . $stat['size']);
foreach(
$http_header_additionals as $header) {
header($header);
}

if(@
readfile($filename) === false) {
// header('HTTP/1.0 500 Internal Server Error');
return 500;
} else {
// header('HTTP/1.0 200 OK');
return 200;
}

}

?>
cOrti
13 年前
您好,

这是一个用于浏览器(FF、IE)在文件名中使用地址空间的 Content-Disposition 的简单技巧。

例如:<?php header ("Content-Disposition:attachment; filename=\"$filename\""); ?>

文件名之前和之后添加 \ ” 才是关键。
TheDayOfCondor
19 年前
我认为 readfile 会受到最大脚本执行时间的影响。 即使超过默认的 30 秒限制,readfile 也会始终完成,然后脚本被中止。
请注意,您不仅会在处理大型文件时遇到非常奇怪的行为,而且在用户连接速度缓慢的情况下,即使是处理小型文件也会出现这种行为。

最好的做法是使用

<?
set_time_limit(0);
?>

在 readfile 之前,如果您打算使用 readfile 调用将文件传输给用户,则可以完全禁用看门狗。
Elliott Brueggeman
17 年前
我注意到 Internet Explorer 6 的一些异常行为,值得注意。 我的网站上有一个链接指向一个脚本,该脚本使用以下代码将 XML 文件输出到浏览器。

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
@readfile($file);

当流行的 IE 设置 “Reuse Window for Launching Shortcuts” 未选中时(从工具菜单 > Internet 选项 > 高级选项卡访问此设置),该脚本会将文件输出到浏览器,并且如果用户单击 IE 提示中的打开按钮,则会将其打开到另一个窗口中。 但是,如果选中了此设置并且正在重复使用浏览器窗口,则它将在单击链接以访问脚本的页面顶部打开。

如果我改为将 html 链接目标选项设置为 “_blank”,则如果选中了 “Reuse Window for Launching Shortcuts”,脚本将按预期在新窗口中打开。 但是,如果未选中此设置,则输出的 XML 文件将在新窗口中打开,并且还将打开另一个空白窗口,其中包含脚本的地址,以及我们最初的窗口。

这远非理想,我们无法知道用户是否选中了此选项。 我们不得不面对这样的可能性:我们的一半访问者可能会看到一个烦人的第三个空白窗口被打开,或者脚本覆盖了他们的原始窗口,具体取决于他们的 “Reuse Window for Launching Shortcuts” 设置。
Philipp Heckel
19 年前
为了使用 readfile(),必须先设置 mime 类型。 如果您使用的是 Apache,找出正确的 mime 类型非常简单。 Apache 有一个名为 “mime.types” 的文件,所有用户都可以读取(在正常情况下)。

使用此函数(或其他函数)来获取 mime 类型列表。

<?php

function mimeTypes($file) {
if (!
is_file($file) || !is_readable($file)) return false;
$types = array();
$fp = fopen($file,"r");
while (
false != ($line = fgets($fp,4096))) {
if (!
preg_match("/^\s*(?!#)\s*(\S+)\s+(?=\S)(.+)/",$line,$match)) continue;
$tmp = preg_split("/\s/",trim($match[2]));
foreach(
$tmp as $type) $types[strtolower($type)] = $match[1];
}
fclose ($fp);

return
$types;
}

# [...]

# 读取 mime-types
$mimes = mimeTypes('/usr/local/apache/current/conf/mime.types');

# 使用它们($ext 是你文件的扩展名)
if (isset($mimes[$ext])) header("Content-Type: ".$mimes[$ext]);
header("Content-Length: ".@filesize($fullpath));
readfile($fullpath); exit;

?>

如果你不想直接从 mime.types 文件读取,你当然可以将它复制到另一个文件夹!
祝好 Philipp Heckel
kevin dot goodman at itmsoil dot com
11 年前
我浪费了几天时间试图找出这个问题,最后才发现问题很容易解决。

我相信你们中的许多人在尝试使用 readfile 输出带有 php 文件作为“img”标签的“src”的图像时也遇到了类似的问题。
它在 Firefox 中“按原样”工作,但在 IE、Safari 或 g.Chrome 中不行。

我在谷歌上找到了数百条结果,都说“你的代码末尾必须有空格”,“你需要这个或那个头”。
我简直不敢相信解决方案是什么,但无论如何,它就在这里!

从你的“img”标签中删除“Width”和“Height”属性
匿名
9 年前
<form action = "open.php" method = "post" >
<input ="text" name ="brian" >
<input type = "submit" name = "download" value = "download">
</form>
<?php

if(isset($_POST['download']))
{
$img ='img';
$pic = '\marcus2';
$fad = 'pdf';

$file = $img.''. $pic .'.'. $fad;

echo
$file;

if (
file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}

}

?>
chad 0x40 herballure 0x2e com
17 年前
回复 herbert dot fischer at NOSPAM dot gmail dot com

PHP5 中的流 API 试图使事情尽可能高效;在 Linux 上的 php-5.1.6 中,fpassthru 比循环中的“echo fread($fp, 8192)”更快,而 readfile 对磁盘上的文件更快。我没有进一步进行基准测试,但我愿意打赌,非 mmap 可用流仍然会获胜,因为它们可以在 C 中而不是 PHP 中循环。
Kniht
17 年前
@Elliott Brueggeman
如果用户的设置不是为了确定他们的环境,那还有什么意义?如果他们设置了特定方式,就遵守他们的设置。
Sinured
17 年前
回复 “grey - greywyvern - com”

如果你知道目标_不能_是远程文件(例如,在它前面加上目录),你应该使用 include 而不是。
如果用户设法将目标设置为某种配置文件(Joomla! 中的 configuration.php!),他们将得到一个空白页面 - 除非使用 readfile()。使用 include 将仅仅像正常请求一样运行(没有输出)。
但是对于远程文件,使用 readfile()。
Mike
14 年前
注意使用下载管理器... 我试图在 IE8 中使用 readfile,并一直收到消息“无法获取 'type' 的数据”。最终发现问题是我安装了 LeechGet,它拦截了下载,这反过来又阻止了下载的进行。
To Top