views:

425

answers:

4

I worked on a nice "force download file" script which purpose is to make sure files are being offered to download instead of displayed in the browser, no matter what type of file that is.

It used to work well but recently i received feedback that it wouldn't work, notably for pdf and xls files (MS Excel). The file is either detected as "invalid" in the case of the PDF, or gets part of the html file directory page on top of its content in the case of the xls file.

What is wrong exactly with my function?

Here it is. Note that it can work either with a url or a local path.

    function offerToDownloadFile($filename, $access_type='url') {
    /*
    PHP FORCE DOWNLOAD SCRIPT
    */

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

        if($access_type === 'url') {
        // access type is via the file 's url
            $parsed_url = parse_url($filename);
            $fileinfo = pathinfo($filename);
            $parsed_url['extension'] = $fileinfo['extension'];
            $parsed_url['filename'] = $fileinfo['basename'];
            $parsed_url['localpath'] = LOCALROOT . $parsed_url['path'];
        }
        else {
        // access type is the local file path
            $fileinfo = pathinfo($filename);
            $parsed_url['localpath'] = $filename;
            $parsed_url['filename'] = basename($filename);
            $parsed_url['extension'] = $fileinfo['extension'];
        }


        // just in case there is a double slash created when joining document_root and path
        $parsed_url['localpath'] = preg_replace('/\/\//', '/', $parsed_url['localpath']);

        if (!file_exists($parsed_url['localpath'])) {
            die('File not found: ' . $parsed_url['localpath']);
        }
        $allowed_ext = array('ics','pdf', 'png', 'jpg', 'jpeg', 'zip', 'doc', 'xls', 'gif', 'exe', 'ppt','ai','psd','odt');
        if (!in_array($parsed_url['extension'], $allowed_ext)) {
            die('This file type is forbidden.');
        }

        switch ($parsed_url['extension']) {
            case "ics": $ctype="text/calendar";
                break;
            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/force-download";
        }
        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");
        header("Content-Disposition: attachment; filename=\"" . $parsed_url['filename'] . "\";");
        header("Content-Transfer-Encoding: binary");
        //  header("Content-Length: " . filesize($parsed_url['localpath']));
        readfile($parsed_url['localpath']);
        clearstatcache();
        die();
        exit();
    }
+1  A: 

Forcing files to be downloaded as opposed to be viewed is near on impossible to achieve regardless of how you handle it - 3rd party components like Adobe's PDF Viewer can just force things to ignore Content-Disposition headers and do what it likes. If you're dealing with the general public, you have to accept that users will take the data and they or their software not your website will determine how they use it.

squeeks
it's for our school's extranet. So it's a wide audience (around 1000 people). What i'm concerned right now is that the file not be detected as invalid. That it be viewed occasionally in the browser instead of my intended download is not a concern.
pixeline
A: 

I'm not sure if this would help at all, but try putting following headers as well:

header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");

The problem with MS and PDF (and many others) is they are forced to be opened with 3PP software like MS Excel for example and are ignoring Content-Disposition.

If above does not work, users will have to right click and select Save as; probably not the best solution for your problem.

David Kuridža
A: 

The script now works. I had to make this header() calls instead. I don't really know why, it's trial and error that led me to this solution.

header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"" . $parsed_url['filename'] . "\";");
header("Content-Transfer-Encoding: binary");
header('Accept-Ranges: bytes');
header("Cache-control: private");
header('Pragma: private');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
pixeline
A: 

it may be worth considering a .htaccess alternative to force the download, for example:

AddType application/octet-stream csv
AddType application/octet-stream pdf
AddType application/octet-stream xls
seengee