PHP: Force file download and IE, yet again

Folks, I know there have been lots of threads about forcing the download dialog to pop up, but none of the solutions worked for me yet.

My app sends mail to the user's email account, notifying them that "another user sent them a message". Those messages might have links to Excel files. When the user clicks on a link in their GMail/Yahoo Mail/Outlook to that Excel file, I want the File Save dialog to pop up.

Problem: when I right-click and do "Save As" on IE, i get a Save As dialog. When I just click the link (which many of my clients will do as they are not computer-savvy), I get an IE error message: "IE cannot download file ... from ...". May be relevant: on GMail where I'm testing this, every link is a "target=_blank" link (forced by Google).

All other browsers work fine in all cases.

Here are my headers (captured through Fiddler):

HTTP/1.1 200 OK
Proxy-Connection: Keep-Alive
Connection: Keep-Alive
Content-Length: 15872
Via: **** // proxy server name
Expires: 0
Date: Tue, 20 Oct 2009 22:41:37 GMT
Content-Type: application/vnd.ms-excel
Server: Apache/2.2.11 (Unix) DAV/2 mod_ssl/2.2.11 OpenSSL/0.9.8i mod_python/3.3.1 Python/2.5.2 SVN/1.4.6 mod_apreq2-20051231/2.6.0 mod_perl/2.0.4 Perl/v5.10.0
Cache-Control: private
Pragma: no-cache
Last-Modified: Tue, 20 Oct 2009 22:41:37 GMT
Content-Disposition: attachment; filename="myFile.xls"
Vary: Accept-Encoding
Keep-Alive: timeout=5, max=100

I want IE's regular left-click behavior to work. Any ideas?


This will check for versions of IE and set headers accordingly.

// assume you have a full path to file stored in $filename
if (!is_file($filename)) {
  die('The file appears to be invalid.');
}

$filepath = str_replace('\\', '/', realpath($filename));
$filesize = filesize($filepath);
$filename = substr(strrchr('/'.$filepath, '/'), 1);
$extension = strtolower(substr(strrchr($filepath, '.'), 1));

// use this unless you want to find the mime type based on extension
$mime = array('application/octet-stream');

header('Content-Type: '.$mime);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.sprintf('%d', $filesize));
header('Expires: 0');

// check for IE only headers
if (preg_match('~MSIE|Internet Explorer~i', $_SERVER['HTTP_USER_AGENT']) || (strpos($_SERVER['HTTP_USER_AGENT'], 'Trident/7.0; rv:11.0') !== false)) {
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Pragma: public');
} else {
  header('Pragma: no-cache');
}

$handle = fopen($filepath, 'rb');
fpassthru($handle);
fclose($handle);

Just use:

header('Content-Disposition: attachment');

That's all. (Facebook does the same.)