文件名含有特殊字符、文件名亂碼,如何實現下載——PHP、JS

先上代碼,看懂的可直接方便複製

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url參數可能是個url,也可能是文件相對路徑
    $file = '...對$file_url進行處理找到文件絕對路徑';
    $file_realname = $_REQUEST('file_realname'); //file_realname參數是js經過encodeURIComponent()函數編碼過的,提交之後,nginx一般都自動對url進行urldecode()解碼,所以直接接收就OK(如果發現沒自動解碼,就需要我們手動urldecode)
    $file_realname = rawurlencode($file_realname);  //因爲file_realname要傳回瀏覽器,所以還需要url編碼,才能保證不亂嗎,且這裏應該用rawurlencode()而不是urlencode()
    if(!file_exists($file)){
        echo "<script>alert('文件不存在')</script>";
        return;
    }else{
        header("Content-type:application/octet-stream");
        header("Content-Disposition:attachment; filename = $file_realname; 
                filename*=utf-8''$file_realname");
        header("Accept-ranges:bytes");
        header("Accept-length:".filesize($file));
        readfile($file);
    }
}

分割線下面是詳細原理和過程

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

最近的項目需求

實現文件上傳(文件名爲:&ap;你好;123.rar),且上傳後要求立刻展示文件名(js插件在選擇完文件後默認會立刻調用文件上傳接口),並可以點擊文件名直接進行下載,點擊確定提交保存到數據庫。(說明:上傳後不能立刻刷新頁面,因爲此時還未點擊確定提交,而且服務器上的文件名已經是時間戳編碼後的格式1526019720.rar

 

需求分析

1.文件上傳

2.上傳後文件名展示

3.上傳後按原文件名直接下載

4.提交後,再次打開頁面時的原文件名展示

5.提交後,再次打開頁面時按原文件名下載

 

解決方案

1.文件上傳

上傳的問題好解決,網上的js插件很多,這裏用的是Layui

2.上傳後文件名展示

由於我們項目裏用的是Layui的文件上傳插件,所以展示文件名需要自己處理,該插件中沒有對file對象的name屬性進行html實體編碼,所以如果文件名中有&等特殊字符(如果使用原生的<input type='file'>就不會有這個問題),直接展示會有亂碼,因此必須先對文件名file.name進行實體編碼(而且file對象是js生成的,沒有經過後端php,所以也只能用js對其進行html實體編碼),網上找到了js的html編碼相關函數:http://www.jb51.net/article/93623.htm,爲了方便複製,我直接貼出代碼:

function HTMLEncode ( input )
{
    var converter = document.createElement("DIV");
    converter.innerText = input;
    var output = converter.innerHTML;
    converter = null;
    return output;
}

function HTMLDecode ( input )
{
    var converter = document.createElement("DIV");
    converter.innerHTML = input;
    var output = converter.innerText;
    converter = null;
    return output;
}

-----重點來了------

3.js插件上傳後按原文件名直接下載

由於服務器上的文件名已變成1526019720.rar,所以要使用url直接下載的話,下載後文件名也會是1526019720.rar,所以只能去請求PHP文件,用PHP設置header的方式改變文件名,並把文件路徑和文件名通過參數傳給PHP文件(此時還沒辦法只傳個id,通過id查詢數據庫獲取文件路徑和文件名,因爲還沒有提交到數據庫!!!),參數傳遞一種是GET,一種是POST(強烈建議使用POST方式,這樣就省去了對url進行編碼,如果使用GET方式...你們懂的,下面會講到使用GET方式)

 

先講一下使用php的header方式進行文件下載,一般類似如下這種方式

<?php
public function download(){
    $file = '....具體的文件路徑';
    $file_realname = '....具體的文件名'; 
    header("Content-type:application/octet-stream");
    header("Content-Disposition:attachment; filename = $file_realname;)
    header("Accept-ranges:bytes");
    header("Accept-length:".filesize($file));
    readfile($file);
}

但是使用這種寫法,會發現如果文件名中有特殊字符(比如;等),則下載後的文件名會顯示不正確,因爲“;”分號是 header("Content-Disposition:attachment; filename = $file_realname;)的分隔符

經過一番查找,終於找到一篇博客解惑了:https://www.cnblogs.com/hihtml5/p/7220188.html?utm_source=itdadao&utm_medium=referral

最終確定使用php的自帶函數rawurlencode(),並把Content-Disposition:attachment改爲:

$file_realname = rawurlencode($file_realname);
header("Content-Disposition:attachment; filename = $file_realname; filename*=utf-8''$file_realname");

 

接下來就是使用GET方式傳遞文件路徑和文件名給這個PHP函數,因爲文件名中有“&”符號,url中&是有特殊意義的,所以必須進行url編碼(這裏只需要對file_realname編碼即可,file_url在上傳時重命名變成了時間戳格式),而且這裏是文件上傳後沒有刷新頁面,直接就可以下載,因此不能用PHP的函數,只能是js端使用encodeURIComponent()進行編碼(因爲是url局部編碼,不能用encodeURI(),參見:http://www.w3school.com.cn/jsref/jsref_encodeURIComponent.asp

特別說明:之前剛開始寫這篇博客的時候,說的是js端用base64編碼方式對file_realname編碼,後來發現是有瑕疵的,因爲base64編碼後有可能會產生+等特殊符號,url提交後獲得的參數是空格,因此,在這裏特別更正一下,還是要用標準的url局部編碼encodeURIComponent()

js生成的下載url大致如下

$('#download').attr('href','www.xxx.com/download?file_url='+res.data.src+'&file_realname='+new Base64().encode($('#file_realname').val()));

一般情況下,nginx等Web服務器都會自動對url解碼(無論是get還是post),所以php端無需手動解碼,最終的download函數爲:

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url參數可能是個url,也可能是文件相對路徑
    $file = '...對$file_url進行處理找到文件絕對路徑';
    $file_realname = $_REQUEST('file_realname'); //file_realname參數是js經過encodeURIComponent()函數編碼過的,提交之後,nginx一般都自動對url進行urldecode()解碼,所以直接接收就OK(如果發現沒自動解碼,就需要我們手動urldecode)
    $file_realname = rawurlencode($file_realname);  //因爲file_realname要傳回瀏覽器,所以還需要url編碼,才能保證不亂嗎,且這裏應該用rawurlencode()而不是urlencode()
    if(!file_exists($file)){
        echo "<script>alert('文件不存在')</script>";
        return;
    }else{
        header("Content-type:application/octet-stream");
        header("Content-Disposition:attachment; filename = $file_realname; 
                filename*=utf-8''$file_realname");
        header("Accept-ranges:bytes");
        header("Accept-length:".filesize($file));
        readfile($file);
    }
}

關於中文亂碼,這裏強烈建議前後端統一使用UTF8

 

4.提交後,再次打開頁面時的原文件名展示

數據庫中我設置了兩個字段,file_url和file_realname,這樣就可以將文件路徑和文件名分開保存,直接將原始文件名存入file_realname,因爲是重新打開的頁面,所以就可以使用php的htmlspecialchars函數進行實體編碼了(如果要使用htmlspecialchars過濾單引號和雙引號,還需加上第二個參數ENT_QUOTES)

 

5.提交後,再次打開頁面時按原文件名下載

file_realname直接用rawurlencode編碼即可

<a style="color:#0b76e8" id="download" href="<?php echo (!empty($file_url)?"www.xxx.com/download?file_url=$file_url&file_realname=".rawurlencode($file_realname):'');?>"><?php echo htmlspecialchars($file_realname);?></a>

 

終於寫完了,文字純手打+代碼複製,如果覺得對你有些許幫助,求手抖給個贊

總結一下用到的編碼

1.js 實體編碼

2.php 實體編碼(htmlspecialchars)

3.js url編碼(encodeURIComponent())

4.php url編碼(rawurlencode)

5.本篇最關鍵的:

$file_realname = rawurlencode($file_realname);
header("Content-Disposition:attachment; filename = $file_realname; filename*=utf-8''$file_realname");

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章