文件下载的 HTTP 标头

发布于 2024-07-09 23:10:00 字数 3828 浏览 4 评论 0原文

我编写了一个 PHP 脚本来处理文件下载,确定正在请求哪个文件并设置正确的 HTTP 标头以触发浏览器实际下载文件(而不是在浏览器中显示它)。

我现在遇到一个问题,一些用户报告某些文件被错误识别(因此无论扩展名如何,浏览器都会将其视为 GIF 图像)。 我猜这是因为我没有在响应标头中设置“内容类型”。 这最有可能是这种情况吗? 如果是这样,是否有一个相当通用的类型可以用于所有文件,而不是尝试考虑每种可能的文件类型?

目前,我仅设置值“Content-disposition:attachment; filename=arandomf.ile”

更新:我在此处遵循本指南来构建更强大的文件下载流程(http://w-shadow.com/blog /2007/08/12/how-to-force-file-download-with-php/),但是执行脚本和浏览器下载对话框出现之间存在明显的延迟。 任何人都可以找出导致此问题的瓶颈吗?

这是我的实现:

/**
 * Outputs the specified file to the browser.
 *
 * @param string $filePath the path to the file to output
 * @param string $fileName the name of the file
 * @param string $mimeType the type of file
 */
function outputFile($filePath, $fileName, $mimeType = '') {
    // Setup
    $mimeTypes = array(
        'pdf' => 'application/pdf',
        'txt' => 'text/plain',
        'html' => 'text/html',
        'exe' => 'application/octet-stream',
        'zip' => 'application/zip',
        'doc' => 'application/msword',
        'xls' => 'application/vnd.ms-excel',
        'ppt' => 'application/vnd.ms-powerpoint',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'jpeg' => 'image/jpg',
        'jpg' => 'image/jpg',
        'php' => 'text/plain'
    );
    
    $fileSize = filesize($filePath);
    $fileName = rawurldecode($fileName);
    $fileExt = '';
    
    // Determine MIME Type
    if($mimeType == '') {
        $fileExt = strtolower(substr(strrchr($filePath, '.'), 1));
        
        if(array_key_exists($fileExt, $mimeTypes)) {
            $mimeType = $mimeTypes[$fileExt];
        }
        else {
            $mimeType = 'application/force-download';
        }
    }
    
    // Disable Output Buffering
    @ob_end_clean();
    
    // IE Required
    if(ini_get('zlib.output_compression')) {
        ini_set('zlib.output_compression', 'Off');
    }
    
    // Send Headers
    header('Content-Type: ' . $mimeType);
    header('Content-Disposition: attachment; filename="' . $fileName . '"');
    header('Content-Transfer-Encoding: binary');
    header('Accept-Ranges: bytes');
    
    // Send Headers: Prevent Caching of File
    header('Cache-Control: private');
    header('Pragma: private');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    
    // Multipart-Download and Download Resuming Support
    if(isset($_SERVER['HTTP_RANGE'])) {
        list($a, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
        list($range) = explode(',', $range, 2);
        list($range, $rangeEnd) = explode('-', $range);
        
        $range = intval($range);
        
        if(!$rangeEnd) {
            $rangeEnd = $fileSize - 1;
        }
        else {
            $rangeEnd = intval($rangeEnd);
        }
        
        $newLength = $rangeEnd - $range + 1;
        
        // Send Headers
        header('HTTP/1.1 206 Partial Content');
        header('Content-Length: ' . $newLength);
        header('Content-Range: bytes ' . $range - $rangeEnd / $fileSize);
    }
    else {
        $newLength = $fileSize;
        header('Content-Length: ' . $fileSize);
    }
    
    // Output File
    $chunkSize = 1 * (1024*1024);
    $bytesSend = 0;
    
    if($file = fopen($filePath, 'r')) {
        if(isset($_SERVER['HTTP_RANGE'])) {
            fseek($file, $range);
            
            while(!feof($file) && !connection_aborted() && $bytesSend < $newLength) {
                $buffer = fread($file, $chunkSize);
                echo $buffer;
                flush();
                $bytesSend += strlen($buffer);
            }
            
            fclose($file);
        }
    }
}

I've written a PHP script that handles file downloads, determining which file is being requested and setting the proper HTTP headers to trigger the browser to actually download the file (rather than displaying it in the browser).

I now have a problem where some users have reported certain files being identified incorrectly (so regardless of extension, the browser will consider it a GIF image). I'm guessing this is because I haven't set the "Content-type" in the response header. Is this most likely the case? If so, is there a fairly generic type that could be used for all files, rather than trying to account for every possible file type?

Currently I'm only setting the value "Content-disposition: attachment; filename=arandomf.ile"

Update: I followed this guide here to build a more robust process for file downloads (http://w-shadow.com/blog/2007/08/12/how-to-force-file-download-with-php/), but there is a significant delay between when the script is executed and when the browser's download dialog appears. Can anyone identify the bottleneck that is causing this?

Here's my implementation:

/**
 * Outputs the specified file to the browser.
 *
 * @param string $filePath the path to the file to output
 * @param string $fileName the name of the file
 * @param string $mimeType the type of file
 */
function outputFile($filePath, $fileName, $mimeType = '') {
    // Setup
    $mimeTypes = array(
        'pdf' => 'application/pdf',
        'txt' => 'text/plain',
        'html' => 'text/html',
        'exe' => 'application/octet-stream',
        'zip' => 'application/zip',
        'doc' => 'application/msword',
        'xls' => 'application/vnd.ms-excel',
        'ppt' => 'application/vnd.ms-powerpoint',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'jpeg' => 'image/jpg',
        'jpg' => 'image/jpg',
        'php' => 'text/plain'
    );
    
    $fileSize = filesize($filePath);
    $fileName = rawurldecode($fileName);
    $fileExt = '';
    
    // Determine MIME Type
    if($mimeType == '') {
        $fileExt = strtolower(substr(strrchr($filePath, '.'), 1));
        
        if(array_key_exists($fileExt, $mimeTypes)) {
            $mimeType = $mimeTypes[$fileExt];
        }
        else {
            $mimeType = 'application/force-download';
        }
    }
    
    // Disable Output Buffering
    @ob_end_clean();
    
    // IE Required
    if(ini_get('zlib.output_compression')) {
        ini_set('zlib.output_compression', 'Off');
    }
    
    // Send Headers
    header('Content-Type: ' . $mimeType);
    header('Content-Disposition: attachment; filename="' . $fileName . '"');
    header('Content-Transfer-Encoding: binary');
    header('Accept-Ranges: bytes');
    
    // Send Headers: Prevent Caching of File
    header('Cache-Control: private');
    header('Pragma: private');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    
    // Multipart-Download and Download Resuming Support
    if(isset($_SERVER['HTTP_RANGE'])) {
        list($a, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
        list($range) = explode(',', $range, 2);
        list($range, $rangeEnd) = explode('-', $range);
        
        $range = intval($range);
        
        if(!$rangeEnd) {
            $rangeEnd = $fileSize - 1;
        }
        else {
            $rangeEnd = intval($rangeEnd);
        }
        
        $newLength = $rangeEnd - $range + 1;
        
        // Send Headers
        header('HTTP/1.1 206 Partial Content');
        header('Content-Length: ' . $newLength);
        header('Content-Range: bytes ' . $range - $rangeEnd / $fileSize);
    }
    else {
        $newLength = $fileSize;
        header('Content-Length: ' . $fileSize);
    }
    
    // Output File
    $chunkSize = 1 * (1024*1024);
    $bytesSend = 0;
    
    if($file = fopen($filePath, 'r')) {
        if(isset($_SERVER['HTTP_RANGE'])) {
            fseek($file, $range);
            
            while(!feof($file) && !connection_aborted() && $bytesSend < $newLength) {
                $buffer = fread($file, $chunkSize);
                echo $buffer;
                flush();
                $bytesSend += strlen($buffer);
            }
            
            fclose($file);
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

许你一世情深 2024-07-16 23:10:00

正如 Alex 的链接所解释的,您可能缺少 Content-Type 顶部的 Content-Disposition 标题。

所以像这样:

Content-Disposition: attachment; filename="MyFileName.ext"

As explained by Alex's link you're probably missing the header Content-Disposition on top of Content-Type.

So something like this:

Content-Disposition: attachment; filename="MyFileName.ext"
帅气称霸 2024-07-16 23:10:00

根据 RFC 2046(多用途互联网邮件扩展)

建议采取的行动
接收
的实现
“应用程序/八位字节流”实体是
只是提出将数据放入
一个文件

所以我会选择那个。

Acoording to RFC 2046 (Multipurpose Internet Mail Extensions):

The recommended action for an
implementation that receives an
"application/octet-stream" entity is
to simply offer to put the data in
a file

So I'd go for that one.

葬花如无物 2024-07-16 23:10:00

您可以尝试这个强制下载脚本。 即使您不使用它,它也可能会为您指明正确的方向:

<?php

$filename = $_GET['file'];

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
  ini_set('zlib.output_compression', 'Off');

// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));

if( $filename == "" ) 
{
  echo "<html><title>eLouai's Download Script</title><body>ERROR: download file NOT SPECIFIED. USE force-download.php?file=filepath</body></html>";
  exit;
} elseif ( ! file_exists( $filename ) ) 
{
  echo "<html><title>eLouai's Download Script</title><body>ERROR: File not found. USE force-download.php?file=filepath</body></html>";
  exit;
};
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 "jpeg":
  case "jpg": $ctype="image/jpg"; break;
  default: $ctype="application/octet-stream";
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers 
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");
exit();

You can try this force-download script. Even if you don't use it, it'll probably point you in the right direction:

<?php

$filename = $_GET['file'];

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
  ini_set('zlib.output_compression', 'Off');

// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));

if( $filename == "" ) 
{
  echo "<html><title>eLouai's Download Script</title><body>ERROR: download file NOT SPECIFIED. USE force-download.php?file=filepath</body></html>";
  exit;
} elseif ( ! file_exists( $filename ) ) 
{
  echo "<html><title>eLouai's Download Script</title><body>ERROR: File not found. USE force-download.php?file=filepath</body></html>";
  exit;
};
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 "jpeg":
  case "jpg": $ctype="image/jpg"; break;
  default: $ctype="application/octet-stream";
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers 
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");
exit();
甜尕妞 2024-07-16 23:10:00

更少的代码和一些安全改进

    <?php

$filename = filter_input(INPUT_GET,'file'); // recommended solution replaced by MalcolmX

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
  ini_set('zlib.output_compression', 'Off');

if(!file_exists($filename)) 
{
  echo "<script>alert('File is not available, check file name');</script>";
}; // filename empty doesn't exist too - MalcolX

// theres mimetype implemented in PHP - MalcolmX
$ctype= mime_content_type($filename);
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers 
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile($filename); // and ofcourse quotes not necessary
exit();

less code and some security improvements

    <?php

$filename = filter_input(INPUT_GET,'file'); // recommended solution replaced by MalcolmX

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
  ini_set('zlib.output_compression', 'Off');

if(!file_exists($filename)) 
{
  echo "<script>alert('File is not available, check file name');</script>";
}; // filename empty doesn't exist too - MalcolX

// theres mimetype implemented in PHP - MalcolmX
$ctype= mime_content_type($filename);
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers 
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile($filename); // and ofcourse quotes not necessary
exit();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文