您的位置:首页 > 编程语言 > PHP开发

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

2012-08-18 21:30 399 查看
Creatingadownloadlink

Aquestionthatcropsup(突然出现)regularlyinonlineforumsis,HowdoIcreatealinktoanimage

(orPDFfile)thatpromptstheusertodownloadit?Thequicksolutionistoconvertthefile
intoacompressedformat,suchasZIP.Thisfrequentlyresultsinasmallerdownload,but
thedownside负面isthatinexperiencedusersmaynotknowhowtounzipthefile,ortheymay
beusinganolderoperatingsystemthatdoesn’tincludeanextractionfacility.WithPHPfile
systemfunctions,it’seasytocreatealinkthatautomaticallypromptstheusertodownload
afileinitsoriginalformat.ThescriptsendsthenecessaryHTTPheaders,opensthe
file,andoutputsitscontentsasabinarystream.

<?php
//blockanyattempttoexplorethefilesystem
if(isset($_GET['file']&&basename($_GET['file'])==$_GET['file']){
$getfile=$_GET['file'];
}
else{
$getfile=NULL;
}
//defineerrorhandling
$nogo='Sorry,downloadunavailable.<ahref="prompt.php">Back</a>.';

if(!$getfile){
//gonofurtheriffilenamenotset
echo$nogo;
}
else{
//definethepathnametothefile
$filepath='C:/htdocs/phpsolutions/images/'.$getfile;
//checkthatitexistsandisreadable
if(file_exists($filepath)&&is_readable($filepath)){
//getthefile'ssizeandsendtheappropriateheaders
$size=filesize($filepath);
header('Content-Type:application/octet-stream');
header('Content-Length:'.$size);
header('Content-Disposition:attachment;filename='.$getfile);
header('Content-Transfer-Encoding:binary');
//openthefileinbinaryread-onlymode
//suppresserrormessagesifthefilecan'tbeopened
$file=@fopen($filepath,'rb');
if($file){
//streamthefileandexitthescriptwhencomplete
fpassthru($file);
exit;
}
else{
echo$nogo;
}
}
else{
echo$nogo;
}
}
?>


Content-Disposition很重要,去掉后不行,一般IE有提示,chrome直接下载。
也可以用echofread($file,filesize($file_dir.$file_name));fclose($file)来获取。

Ifyouwanttheusertobepromptedtosavethedatayouaresending,suchasageneratedPDFfile,youcanusethe»Content-Dispositionheadertosupplyarecommendedfilenameandforcethebrowsertodisplaythesavedialog.

某一类型文件:

<?php
//We'llbeoutputtingaPDF
header('Content-type:application/pdf');

//Itwillbecalleddownloaded.pdf
header('Content-Disposition:attachment;filename="downloaded.pdf"');

//ThePDFsourceisinoriginal.pdf
readfile('original.pdf');
?>


readfile—Outputsafile

fpassthru—Outputallremainingdataonafilepointer

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

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

<body>
<h1>下载测试</h1>
<ahref="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)


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


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

1
2
3
4

<?php

setlocale(LC_ALL,
'zh_CN.GBK'
);

//oranyotherlocalethatcanhandlemultibytecharacters.

?>


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

从网上看到一篇文章:

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

basename函数用来获取路径中文件名部分,
可是在redhat4.3或者某些其他系统下,无法正确获得带有中文的路径中的文件名,
下面这个函数来自网上,很好很强大,可以代替basename使用:
functionsbasename($filename){
returnpreg_replace('/^.+[\\\\\\/]/','',$filename);
}

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

至于为什么没用UTF-8,因为中文windows系统的文件名似乎是gbk的,
用utf-8编码的文件名在windows系统下会显示为乱码。所以要用gbk的编码去写文件、目录名。
linux下也设置成gbk,就和windows服务器上的处理一致了。

本身php程序和数据库都是用了utf8的字符集,所以在处理文件名的时候需要来回转码,顺便贴一个比较安全的转码函数:
functionsiconv($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指向一个位于DocumentRoot下面的文件,来引导用户下载文件.

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

<?php

$file="/tmp/dummy.tar.gz";

header("Content-type:application/octet-stream");

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

header("Content-Length:".filesize($file));

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.'"');
}elseif(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+PHPmod,那么还需要发送到Apache的输出缓冲区.最后才发送给用户.而对于Nginx+fpm如果他们分开部署的话,那还会带来额外的网络IO.

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

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

我们可以使用Apache的modulemod_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.'"');
}elseif(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也有类似的模块,大家有兴趣的可以去找找看。

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

/article/1269168.html

我按照以上的:

改成:

<?php
if(isset($_GET['file']))
{
define("DIR",'../../images/');
$fileName=$_GET['file'];
$file=DIR.$fileName;

functionOwnbasename($filename)
{
returnpreg_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.'"');
}
elseif(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():statfailedfor../../images/中文名测试.txtin<b>F:\xampp\htdocs\php\download\downLoad.php</b>online<b>33</b><br/>
<br/>
<b>Warning</b>:readfile(../../images/中文名测试.txt):failedtoopenstream:Invalid
Ie下用

header('Content-Disposition后filesize()和readfile读取不了中文文件名了。当我注释掉header判断,还是用原来的:

header('Content-Disposition:attachment;filename="'.$fileName.'"');
虽然乱码,但可以下载文件。

还没解决这个问题。

使用PHP创建ZIP格式的下载压缩包

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航