Php,readfile错误,下载的文件无法打开

时间:2014-12-23 18:44:18

标签: php readfile

我使用以下代码下载存储在公用文件夹之外的文件。

  $mime_type = mime_content_type("{$_GET['file']}");
  define("IMG_LOC","/var/www/domain.com/upload/");
  $filename = $_GET['file'];
  header('Content-Description: File Transfer');
  header('Content-Type: '.$mime_type);
  header('Content-Disposition: attachment; filename='.basename(IMG_LOC.$filename));
  header('Expires: 0');
  header('Cache-Control: must-revalidate');
  header('Pragma: public');
  header('Content-Length: ' . filesize($filename));
  readfile($filename);
  exit;

问题是,使用此脚本下载的文件不可用。 Excel打开为空,powerpoint告诉“读取错误”,并且单词告诉它缺少转换器。然而,如果我使用ftp下载相同的文件并手动打开它们,文件会正常打开,表明文件没有损坏。

有关信息,这是从另一个页面调用的:file.php?file='. $filename

欢迎任何帮助。谢谢你的时间。

3 个答案:

答案 0 :(得分:2)

您似乎错过了文件的路径:

  header('Content-Length: ' . filesize(IMG_LOC . $filename));
  readfile(IMG_LOC . $filename);

您还应该为文件名添加验证以避免安全问题。

如果您仍然遇到问题,还应该检查脚本的确切输出,也许在您的文件之前有php警告或消息。

答案 1 :(得分:1)

我推断$filename不是您正在寻找的文件的绝对路径,因此您为什么要使用路径定义IMG_LOC常量。从那里可以清楚地看出filesize($filename)readfile($filename)不太可能给你你想要的东西。

尝试在$filename变量之前连接常量,如此...

header('Content-Length: ' . filesize(IMG_LOC . $filename));
readfile(IMG_LOC . $filename);

此外,请考虑此代码易受标头注入攻击以及其他安全问题的影响,例如用户在您的服务器上提供了您可能不希望他们看到的文件名。例如,如果我使用查询字符串?file=yourscript.php调用您的脚本,我将能够下载您的实际PHP代码,并且可能会看到您可能不希望暴露的任何敏感信息,例如您的数据库密码,或者更糟。

此外,mime_content_type已弃用的功能,应替换为Fileinfo扩展程序。

答案 2 :(得分:1)

您的脚本存在各种问题,这些问题都会阻止正常正常工作。我大致仔细阅读并留下一些评论,然后编写一些摘要,并提供另一个代码示例,其中包含注释:

$mime_type = mime_content_type("{$_GET['file']}");

您不需要将$_GET超全局包装在大括号中,然后再用双引号括起来。这个参数不是必需的。你似乎在这一点上分心了。

无论如何,如果你想提供下载,那么这个mime类型的东西是没有必要的,因为mime-type不是很有趣。您可以使用application/octet-stream,稍后您可以注意更具体的mime类型:

$mime_type = "application/octet-stream";

然后在错误的位置定义IMG_LOC常量:

define("IMG_LOC", "/var/www/domain.com/upload/");

这就属于脚本的最顶层,而不是你定义配置。

在行中:

$filename = $_GET['file'];

你没有做任何进一步的错误检查这会打开脚本到目录遍历和路径注入攻击,这实际上将脚本转换为后门。可以下载脚本可以访问该服务器上的任何文件。

接下来的两行或多或少都是正确的:

header('Content-Description: File Transfer');
header('Content-Type: '.$mime_type);

对于下一个标题:

header('Content-Disposition: attachment; filename='.basename(IMG_LOC.$filename));

我会先提取基本名称,然后在这里传递一个变量。以后内容长度标题相同:

header('Content-Length: ' . filesize($filename));

然后你有这个缓存标题块,因为你从磁盘提供文件我不认为那些实际上是必要的,所以我会删除它们:

header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');

readfile行似乎没问题,你可以做一些错误检查:

readfile($filename);

最后一行我不明白,因为剧本最后还是为什么退出?

exit;

在这篇小小的评论后我的建议:

收集应该提供哪些文件以及如何命名它们的信息。收集此类信息将允许您关闭首先必须关闭的目录遍历问题。

第二步将逻辑部分置于输出上方(以及逻辑上方的配置)应允许您以更有用的方式对脚本进行排序,从而允许您处理mime类型的问题,例如在维护脚本时更容易(或者缓存,如果它确实是一个问题)。

<?php
/**
 * download a file
 *
 * parameter:
 *
 *  file - name of the relative to upload folder
 */

const IMG_LOC = "/var/www/domain.com/upload";

// validate filename input
if (!isset($_GET['file'])) {
    return;
}
$filename = $_GET['file'];
$path     = realpath(IMG_LOC . '/' . $filename);
if (0 !== strpos($path, IMG_LOC)) {
    return;
}
if (!is_readable($filename)) {
    return;
}

// obtain data
$basename  = basename($filename);
$mime_type = "application/octet-stream"; # can be improved later
$size      = filesize($path);

// output
header('Content-Description: File Transfer');
header('Content-Type: ' . $mime_type);
header('Content-Disposition: attachment; filename=' . $basename);
header('Content-Length: ' . $size);
readfile($filename);