php downloads Chinese file name compression packages via header corrupted or non-existent

Posted by Irksome on Fri, 10 May 2019 11:18:03 +0200

We all use utf8 code in development. Yesterday we encountered a strange pit. It was a small problem and wasted an hour to solve.Don't talk too much. Implant the topic:

File download method: download from header binary stream file
Requirement: File upload keeps file name unchanged
Data field file_url value: /public/upload/files/2019/04-29/Chinese test package.rar

linux(Ubuntu 18.04.2 LTS) File directory: /home/wwwroot/web/public/upload/files/2019/04-29

Windows 10 File Directory: D:\webpublicuploadfiles201904-29Chinese Test Pack.rar

Let's first look at file downloads under windows:

<?php
        $file_name = '/public/upload/files/2019/04-29/Chinese Test Pack.rar';
        //$file_name = iconv("utf-8","gbk//IGNORE",$file_name); //Pay special attention!Special attention!Special note here that transcoding must be performed under windows or direct files will not exist

        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// such as windows Here I am "D:/web/public/upload/files/2019/04-29/Chinese Test Pack.rar"
        //Judge that if the file exists, jump to the download path
        if (!file_exists($file_path)) {
            die("file does not exist!");
        }

        $fp = fopen($file_path, "r+") or die('Error Opening File');   //To download a file, you must open it first.Write to memory
        $file_size = filesize($file_path);
        //File stream returned
        Header("Content-type:application/octet-stream");
        //Return in byte format
        Header("Accept-Ranges:bytes");
        //Return file size
        Header("Accept-Length:" . $file_size);
        //Client dialog pops up, corresponding file name
        Header("Content-Disposition:attachment;filename=" . substr($file_name, strrpos($file_name, '/') + 1));
        //Prevent server from instantaneous pressure increase, read in segments
        $buffer = 1024;
        while (!feof($fp)) {
            $file_data = fread($fp, $buffer);
            echo $file_data;
        }
        fclose($fp);

        die("Download Successful!");

?>

Does the file not exist?Magic Horse Goods?.Same code ubutun production environment:

File download succeeded.What about Shenma?
Reason: The default character set of windows system is gbk. The project uses uft8 encoding. The Chinese file name must be transcoded to detect the file using file_exists, otherwise the file could not be found:

The solution under windows is to turn on the paragraph in the note above:

$file_name = iconv("utf-8","gbk//IGNORE",$file_name); // Special attention!Special attention!Pay special attention here, windows Next must be transcoded,Otherwise the direct file does not exist

When executed again under windows, the download was successful:

So here comes the question....The open code looks like this:

<?php
        $file_name = '/public/upload/files/2019/04-29/Chinese Test Pack.rar';
        $file_name = iconv("utf-8","gbk//IGNORE",$file_name); // Special attention!Special attention!Pay special attention here, windows Next must be transcoded,Otherwise the direct file does not exist
        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// such as windows Here I am "D:/web/public/upload/files/2019/04-29/Chinese Test Pack.rar"
        //Judge that if the file exists, jump to the download path
        if (!file_exists($file_path)) {
            die("file does not exist!");
        }

        $fp = fopen($file_path, "r+") or die('Error Opening File');   //To download a file, you must open it first.Write to memory
        $file_size = filesize($file_path);
        //File stream returned
        Header("Content-type:application/octet-stream");
        //Return in byte format
        Header("Accept-Ranges:bytes");
        //Return file size
        Header("Accept-Length:" . $file_size);
        //Client dialog pops up, corresponding file name
        Header("Content-Disposition:attachment;filename=" . substr($file_name, strrpos($file_name, '/') + 1));
        //Prevent server from instantaneous pressure increase, read in segments
        $buffer = 1024;
        while (!feof($fp)) {
            $file_data = fread($fp, $buffer);
            echo $file_data;
        }
        fclose($fp);

        die("Download Successful!");
?>

On the ubutun server we execute:

Is it like solving the East Wall to the West Wall?The character set under ubutun can be passed through:

cat /usr/share/i18n/SUPPORTED

Describes how the system supports Chinese characters, otherwise the uploaded compressed package will display: "Chinese Test Pack.rar".
Problem Description: Verify that Chinese file_exists cannot be Chinese under linux system, so it cannot be transcoded to gbk above.

So the question arises: how do I achieve compatibility?
We know that PHP_OS is a built-in constant that comes with php and returns the server-side operating system label with a value (WINNT, WIN32, etc.) such as:

 echo strtoupper(substr(PHP_OS,0,3))==='WIN'?'windows The server':'No widnows The server';

Another is through the system separator DIRECTORY_SEPARATOR, which is also a built-in constant that comes with php to display the system separator command.

It does not require any definition or inclusion to be used directly.The path delimiter under windows is \ (and, of course, works on some systems), and on linux is /,

The meaning of the constant DIRECTORY_SEPARATOR is that it displays different delimiters according to different operating systems.

Use DIRECTORY_SEPARATOR to determine operating system types such as:

echo DIRECTORY_SEPARATOR=='\\'?'windows The server':'No widnows The server';

There is another way:

PATH_SEPARATOR is also a constant, a':'sign on linux and a';' sign on Windows.

Use PATH_SEPARATOR to determine operating system types such as:

echo PATH_SEPARATOR==';'?'windows The server':'No widnows The server';

Code Compatibility We can verify the system type, make a judgement under windows, and then decide whether to transcode or not.
This highlights Ha's question about file opening prompt "file corruption" after downloading, which I also encountered at the beginning of this period.Guess it must be that there is data loss in reading the file byte stream, that is, not read completely:

Here's a look at this problematic code: Interested friends can think for themselves, where is the problem?I won't say anything here. I'm sure many friends can find the problem too:

<?php
        $http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';

        $file_name = '/public/upload/files/2019/04-29/Chinese Test Pack.rar';

        //Detect if the file exists and is readable
        if (!is_file($file_name) && is_readable($file_name)) {
            die("File does not exist or is not readable!");
        }

        //Determine if a file exists,Jump to download path
        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// such as windows Here I am "D:/web/public/upload/files/2019/04-29/Chinese Test Pack.rar"
        //Judge that if the file exists, jump to the download path
        if (!file_exists($file_path)) {
            die("file does not exist!");
        }

        //Set the maximum execution time of the script to 0 for no time limit
        set_time_limit(0);
        ini_set('max_execution_time', '0');
        //adopt header()Send header information
        //Because you don't know what type of file it is, tell the browser that the output is a byte stream
        header('content-type:application/octet-stream');
        //Tell the browser that the file size type returned is byte
        header('Accept-Ranges:bytes');

        //Get file size
        //$filesize = filesize($filename); //This method cannot get the remote file size

        $header_array = get_headers($http_type . $_SERVER['HTTP_HOST'] . $file_name, true);
        $filesize = $header_array['Content-Length'];
        //Tell the browser the size of the file returned
        header('Accept-Length:' . $filesize);

        //Tell the browser to process the file as an attachment and set the file name for the final Download
        header('content-disposition:attachment;filename=' . substr($file_name, strrpos($file_name, '/') + 1));

        //For large files, specify 4096 bytes for each read, and output data directly
        $buffer = 4096;
        $fp = fopen($file_path, 'rb');
        //Total Buffered Bytes
        $sum_buffer = 0;

        //Read until the end of the file
        while (!feof($fp) && $sum_buffer < $filesize) {
            echo fread($fp, $buffer);
            $sum_buffer += $buffer;
        }

        //Record Downloads
        die("Download Successful!");

?>

 

Interested friends can find bug s, haha

Topics: PHP Windows Linux Ubuntu