使用PHP创建一个.jpg文件的下载链接

4
这个应该很简单,我想。我有一个分页的图库,在每张图片下面都有一个小链接,上面写着“下载Comp”。这应该允许人们快速地将带有PHP生成水印的.jpg文件下载到他们的计算机上。
现在,我知道我可以直接链接到.jpg文件,但这需要用户在新窗口中打开图像,右键单击,保存为...等等。相反,我希望“下载Comp”链接立即启动文件的下载。
PHP.net似乎建议使用readfile(),因此每个“下载Comp”链接都被回显为“?download=true&g={$gallery}&i={$image}”。
然后在页面顶部,我捕获看看$_GET['download']变量是否设置,如果是,我运行以下代码:
if(isset($_GET['download'])) {
$gallery = $_GET['g'];
$image = $_GET['i'];
$file = "../watermark.php?src={$gallery}/images/{$image}";
header('Content-Description: File Transfer');
    header('Content-Type: application/jpeg');
    header('Content-Disposition: attachment; filename='.basename($file));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: public');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    ob_clean();
    flush();
readfile($file);

链接加载时间很长,然后弹出一个提示对话框,询问是否“打开”或“保存”文件,但是一旦您保存并尝试打开该文件,它会显示文件已损坏,无法打开。
有什么想法吗?

1
你是否已经开启了 fopen_wrappers?否则,readfile 无法接受 URL 参数。 - Ken Keenan
@Ken:很好的发现,我甚至没有注意到它是一个URL而不是路径。 - Powerlord
readfile()函数只会尝试下载绝对URL。它需要以HTTP://hostname…开头。更好的方法是不要使用URL。请参见下面的答案。 - mcrumley
5个回答

4

不要将$file设置为相对url。readfile函数将尝试在服务器上访问php文件。这不是您想要的。在您的情况下,看起来watermark.php文件将发送您想要的内容,因此您可能只需设置它所需的环境并包含它即可。

<?php
if(isset($_GET['download'])) {
    $gallery = $_GET['g'];
    $image = $_GET['i'];
    $_GET['src'] = "{$gallery}/images/{$image}";

    header('Content-Description: File Transfer');
    header('Content-Type: image/jpeg');
    header('Content-Disposition: attachment; filename='.basename($image));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: public');
    header('Pragma: public');
    ob_clean();
    include('../watermark.php');
    exit;
}

另一种(更简单的)方法是修改watermark.php。添加一个查询参数,使其发送适当的标头以强制下载并链接到该文件。

<a href="watermark.php?src=filename.jpg&download=true)">...</a>

watermark.php:

<?php
if (isset($_GET['download']) && $_GET['download'] == 'true') {
    header('Content-Description: File Transfer');
    header('Content-Type: image/jpeg');
    header('Content-Disposition: attachment; filename='.basename($src));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: public');
    header('Pragma: public');
}
// continue with the rest of the file as-is

此外,您不需要调用flush()。此时不应发送任何输出,因此没有必要这样做。

mcrumley - 谢谢。目前我选择了你的第一个选项,只是为了让它工作起来,现在文件下载速度快多了。然而,该文件仍然无法打开。预览显示“无法打开文件。它可能已损坏或是一种预览不识别的文件格式。”有什么想法吗?页面在这里:http://homewoodphoto.jhu.edu//gallery/20030101_test_gallery/ - rhodesjason
我尝试的文件是空的。包含文件路径正确吗?在错误日志中有任何错误吗? - mcrumley

1
header('Content-Type: image/jpeg');

也许呢?


我认为Content-Type头只是告诉浏览器如何处理它。如果二进制数据存在,无论MIME类型如何,它都应该正常工作。 - Marc W

1

这似乎是一个安全问题。

如果有人输入:

$g = '../../../../../../';
$i = '../../sensitive file at root';

你觉得怎样把 .htaccess(如果你正在使用 Apache)应用到画廊目录,把 JPEG 作为下载而不是正常呈现?

1

我认为你可能需要在调用readfile()后紧接着调用exit(),以确保没有其他内容被写入输出缓冲区。


0

另外,尝试使用file_get_contents()代替readfile()。我发现它在更多情况下都能正常工作。我还建议在输出图像数据后使用ob_flush()。我从来没有需要使用ob_clean()flush()来使这种事情工作。

正如Eric所说的那样,如果仍然不能正常工作,您可能还想为了保险起见加上一个调用exit(),以防万一您在末尾得到了一些垃圾数据。


ob_clean()会清除缓冲区。必须在写入图像或数据之前调用它,否则数据将被清除。 - mcrumley
我的错。我在想ob_flush()。更改答案以反映这一点。 - Marc W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接