PHP,读取文件错误,下载的文件无法打开


Php, error with readfile, downloaded files do not open

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

  $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告诉"有错误阅读",word告诉它缺少一个转换器。然而,如果我使用 ftp 下载相同的文件并手动打开它们,文件会正确打开,显示文件没有损坏。

有关信息,这是从另一个页面调用

的: file.php?file='. $filename

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

您似乎缺少文件的路径:

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

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

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

我推断$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 扩展名。

您的脚本有各种问题,总而言之会阻止它正常工作。我粗略地浏览了几行并留下了一些评论,然后写了一个小摘要,并提供另一个包含注释的代码示例:

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

您不需要将$_GET超全局括在大括号中,然后用双引号括起来。该参数只是没有必要。此时你似乎分心了。

无论如何,这个哑剧类型的东西是不必要的,因为如果你想提供下载,哑剧类型并不有趣。你改用application/octet-stream,以后可以注意更具体的哑剧类型:

$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($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);