php 创建下载链接和中文文件名乱码解决

Creating a download link

A question that crops up( 突然出现)regularly in online forums is, How do I create a link to an image

(or PDF file) that prompts the user to download it? The quick solution is to convert the file

into a compressed format, such as ZIP. This frequently results in a smaller download, but

the downside 负面 is that inexperienced users may not know how to unzip the file, or they may

be using an older operating system that doesn’t include an extraction facility. With PHP file

system functions, it’s easy to create a link that automatically prompts the user to download

a file in its original format. The script sends the necessary HTTP headers, opens the

file, and outputs its contents as a binary stream.

<?php
// block any attempt to explore the filesystem
if (isset($_GET['file'] && basename($_GET['file']) == $_GET['file']) {
  $getfile = $_GET['file'];
  }
else {
  $getfile = NULL;
  }
// define error handling
$nogo = 'Sorry, download unavailable. <a href="prompt.php">Back</a>.';

if (!$getfile) {
  // go no further if filename not set
  echo $nogo;
  }
else {
  // define the pathname to the file
  $filepath = 'C:/htdocs/phpsolutions/images/'.$getfile;
  // check that it exists and is readable
  if (file_exists($filepath) && is_readable($filepath)) {
    // get the file's size and send the appropriate headers
    $size = filesize($filepath);
    header('Content-Type: application/octet-stream');
    header('Content-Length: '.$size);
    header('Content-Disposition: attachment; filename='.$getfile);
    header('Content-Transfer-Encoding: binary');
    // open the file in binary read-only mode
    // suppress error messages if the file can't be opened
    $file = @ fopen($filepath, 'rb');
    if ($file) {
      // stream the file and exit the script when complete
      fpassthru($file);  
      exit;
      }
    else {
      echo $nogo;
      }
    }
  else {
    echo $nogo;
    }
  }
?>
Content-Disposition很重要,去掉后不行,一般IE有提示,chrome直接下载。
也可以用 echo fread ( $file, filesize ( $file_dir . $file_name ) ); fclose($file)来获取。

If you want the user to be prompted to save the data you are sending, such as a generated PDF file, you can use the » Content-Disposition header to supply a recommended filename and force the browser to display the save dialog.

某一类型文件:

<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');

// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');

// The PDF source is in original.pdf
readfile('original.pdf');
?>

readfile — Outputs a file

fpassthru — Output all remaining data on a file pointer

---------------------------------------------------------------------

我在文件1里面写了如何代码:

<body>
<h1>下载测试</h1>
<a href="downLoad.php?file=中文名测试.jpg">点击下载</a>
</body>
downLoad.php内容如下:
<?php
if(isset($_GET['file']))
{
    define("DIR",'../../images/');
    $fileName=$_GET['file'];
    
    $fileName=DIR.$fileName;
    
    header("Content-type:application/octet-stream"); //octet八位字节 octet-stream 字节流
    header('Content-disposition:attachment;filename="'.basename($fileName).'"');
    header("Content-length:".filesize($fileName));
    readfile($fileName);
}
?>
    

明一点,filename能含有空格,最好用双引号括起来。

文件名是因为ok,但是中文,如“中文名测试.jpg” 下载的文件名就是jpg.怎么回事。

想了下,只能是basename出了问题。

果不其然。

<?php
define("DIR",'../../images/');
$fileName=DIR.'中文名测试.jpg';
echo  $fileName;
echo '<br/>'.basename($fileName);
?>

输出:

../../images/中文名测试.jpg

.jpg

可以看到,basename不支持中文。发现如果是中文的文件名返回只有后缀的空文件名(如:.pdf)

string basename ( string path [, string suffix] )

说明

给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。如果文件名是以 suffix 结束的,那这一部分也会被去掉。

按照网站上找到说法是此函数依赖于区域设置,如果是多字节名称返回为空可以通过setlocale函数如下设置

1

2

3

4

<?php

setlocale(LC_ALL,'zh_CN.GBK');

// or any other locale that can handle multibyte characters.

?>

最好是修改服务器的区域设置来整体解决

从网上看到一篇文章:

PHP basename 函数 linux下中文路径的问题解决方法

basename函数用来获取路径中文件名部分,

可是在redhat4.3或者某些其他系统下,无法正确获得带有中文的路径中的文件名,

下面这个函数来自网上,很好很强大,可以代替basename使用:

function sbasename($filename) {

return preg_replace('/^.+[\\\\\\/]/', '', $filename);

}

如果linux系统不能正确显示中文目录和文件名,可以在/etc/profile的最后加入:

export

至于为什么没用UTF-8,因为中文windows系统的文件名似乎是gbk的,

用utf-8编码的文件名在windows系统下会显示为乱码。所以要用gbk的编码去写文件、目录名。

linux下也设置成gbk,就和windows服务器上的处理一致了。

本身php程序和数据库都是用了utf8的字符集,所以在处理文件名的时候需要来回转码,顺便贴一个比较安全的转码函数:

function siconv($str, $out_charset, $in_charset) {

if (strtoupper($out_charset) != strtoupper($in_charset)) {

if (function_exists('iconv') && (@$outstr = iconv("$in_charset//IGNORE", "$out_charset//IGNORE", $str))) {

return $outstr;

} elseif (function_exists('mb_convert_encoding') && (@$outstr = mb_convert_encoding($str, $out_charset, $in_charset))) {

return $outstr;

}

}

return $str;

}

声明:文章转载自http://hi.baidu.com/cnkarl/item/0268dbd4835de690270ae7c6,仅供学习使用。

我换成sbasename函数,果然就ok了。basename正确输出"中文名测试.jpg".

但是无语的是在chrome下没问题,但在Ie下文件名乱码了。看了php鸟哥的那篇文章:

让PHP更快的提供文件下载

一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.

但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.

  1. <?php
  2. $file = "/tmp/dummy.tar.gz";
  3. header("Content-type: application/octet-stream");
  4. header('Content-Disposition: attachment; filename="' . basename($file) . '"');
  5. header("Content-Length: ". filesize($file));
  6. readfile($file);

但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.

于是, 我们做一下修改(参考: :

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = rawurlencode($filename);
    if (preg_match("/MSIE/", $ua)) {
     header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
     header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
    } else {
     header('Content-Disposition: attachment; filename="' . $filename . '"');
    }
 
    header("Content-Length: ". filesize($file));
    readfile($file);

恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.

输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.

那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?

今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.

我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = rawurlencode($filename);
    if (preg_match("/MSIE/", $ua)) {
     header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
     header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
    } else {
     header('Content-Disposition: attachment; filename="' . $filename . '"');
    }
 
    //让Xsendfile发送文件
    header("X-Sendfile: $file");

X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.

Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看。

解决乱码的关键是判断user agent,这篇文章仔细说了原因:

http://www.jb51.net/article/26196.htm

我按照以上的:

改成:

<?php
if(isset($_GET['file']))
{
    define("DIR",'../../images/');
    $fileName=$_GET['file'];
    $file=DIR.$fileName;
    
    function Ownbasename($filename)
    {  
         return preg_replace('/^.+[\\\\\\/]/', '', $filename);  
    } 

    header("Content-type:application/octet-stream"); //octet八位字节 octet-stream 字节流
   
    $fileName=Ownbasename($file);
  
    $ua=$_SERVER['HTTP_USER_AGENT'];
    $encoded_fileName=rawurlencode($fileName);
      
    if(preg_match("/MSIE/",$ua))
    {
        header('Content-Disposition;attachment;filename="'.$encoded_fileName.'"');
    } 
    else if(preg_match("/Firefox/", $ua)) 
    {
         header("Content-Disposition: attachment; filename*=\"utf8''" . $fileName . '"');
    } 
    else   
    {
         header('Content-Disposition: attachment; filename="' . $fileName . '"');
    }
    
    header("Content-Length:".filesize($file));
    readfile($file);
}
?>
    

结果在Ie下报错:

b>Warning</b>:  filesize(): stat failed for ../../images/中文名测试.txt in <b>F:\xampp\htdocs\php\download\downLoad.php</b> on line <b>33</b><br />
<br />
<b>Warning</b>:  readfile(../../images/中文名测试.txt): failed to open stream: Invalid 
Ie下用
header('Content-Disposition后filesize()和readfile读取不了中文文件名了。当我注释掉header判断,还是用原来的:
header('Content-Disposition: attachment; filename="' . $fileName . '"');
虽然乱码,但可以下载文件。

还没解决这个问题。

http://www.phpbest.com/html/content-9-170-1.html

http://www.laruence.com/2012/05/02/2613.html

http://book.51cto.com/art/201112/307805.htm