header

(PHP 3, PHP 4, PHP 5)

header -- 发送一个原始 HTTP 标头

说明

void header ( string string [, bool replace [, int http_response_code]] )

header() 函数用来发送一个原始 HTTP 标头。有关 HTTP 标头的更多内容见 HTTP/1.1 规范

可选参数 replace 指明是替换掉前一条类似的标头还是增加一条相同类型的标头。默认为替换,但如果将其设为 FALSE 则可以强制发送多个同类标头。例如:

<?php
header
('WWW-Authenticate: Negotiate');
header('WWW-Authenticate: NTLM', false);
?>

第二个可选参数 http_response_code 强制将 HTTP 响应代码设为指定值(此参数是 PHP 4.3.0 新加的)。

有两种特殊的 header 调用。第一种是标头以字符串“HTTP/”(大小写不重要)开头的,可以用来确定要发送的 HTTP 状态码。例如,如果配置了 Apache 用 PHP 来处理找不到文件的错误处理请求(使用 ErrorDocument 指令),需要确保脚本产生了正确的状态码。

<?php
header
("HTTP/1.0 404 Not Found")
?>

注: HTTP 状态码标头行总是第一个被发送到客户端,而并不管实际的 header() 调用是否是第一个。除非 HTTP 标头已经发送出去,任何时候都可以通过用新的状态行调用 header() 函数来覆盖原先的。

第二种特殊情况是以“Location:”标头。它不只是把这个标头发送回浏览器,它还将一个 REDIRECT(302)状态码返回给浏览器,除非之前已经发出了某个 3xx 状态码。

<?php
header
("Location: http://www.example.com/"); /* 重定向浏览器 */

/* 确保重定向后,后续代码不会被执行 */
exit;
?>

注: HTTP/1.1 标准需要一个绝对地址的 URI 做为 Location: 的参数, 但有一些客户端支持相对 URI。通常可以使用 $_SERVER['HTTP_HOST']$_SERVER['PHP_SELF']dirname() 函数来自己从相对 URI 产生出绝对 URI:

<?php
header("Location: http://".$_SERVER['HTTP_HOST']
                       . rtrim(dirname($_SERVER['PHP_SELF']), '/\\')
                      ."/".$relative_url);
?>

注: 即使启用了 session.use_trans_sid,Session ID 也不会随着 Location 头信息被传递。必须手工用 SID 常量来传递。

PHP 脚本通常会产生一些动态内容,这些内容必须不被浏览器或代理服务器缓存。很多代理服务器和浏览器都可以被下面的方法禁止缓存:

<?php
header
("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // 过去的时间
?>

注: 可能会发现即使不输出上面所有的代码,网页也没有被缓冲。用户有很多选项可以设置来改变浏览器的默认缓存行为。通过发送上述标头,应该可以覆盖任何可以导致脚本页面被缓存的设置。

另外,当使用了 session 时,利用 session_cache_limiter() 函数和 session.cache_limiter 选项可以用来自动产生正确的缓存相关标头。

要记住 header() 必须在任何实际输出之前调用,不论是来自普通的 HTML 标记,空行或者 PHP。有一个常见错误就是在通过 include()require() 或一些其它的文件存取类函数读取代码时,有一些空格或者空行在调用 header() 之前被发送了出去。同样在一个单独的 PHP/HTML 文件中这个错误也很普遍。

<html>
<?php
/* 这将产生一个错误,因为在调 header()
* 之前已经输出了东西 */
header('Location: http://www.example.com/');
?>

注: 自 PHP 4 起,可以通过一些输出缓冲函数来解决这个问题。代价是把所有向浏览器的输出都缓存在服务器,直到下命令发送它们。可以在代码中使用 ob_start()ob_end_flush() 来实现这样的功能,或者通过修改 php.ini 中的 output_buffering 配置选项来实现,也可以通过修改服务器配置文件来实现。

如果想提示用户保存所发送的数据,例如一个生成的 PDF 文件,可以通过发送 Content-Disposition 标头提供推荐的文件名来强制浏览器弹出一个保存文件对话框。

<?php
// 这样将会直接输出一个 PDF 文件
header('Content-type: application/pdf');

// 这样做就会提示下载 PDF 文件 downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');

// 这是 original.pdf 的源文件
readfile('original.pdf');
?>

注: Microsoft Internet Explorer 4.01 中的一个漏洞使得该机制无法正常工作,无解决方案。在 Microsoft Internet Explorer 5.5 中也有个漏洞影响到这一点,升级到 Service Pack 2 或更高版本可以解决。

注: 安全模式下,如果设定了 WWW-Authenticate 标头(用于 HTTP 认证)则脚本的 UID 会添加到其中的 realm 部分中去。

参见 headers_sent()setcookie()HTTP 认证一章。


add a note add a note User Contributed Notes
jeremiah at jkjonesco dot com
31-Oct-2006 07:23
If you are trying to modify headers to work with dynamic binaries such as videos or images, the new IE 7 appears to require the ETag header.  You will need to make sure that you follow the specifications for how ETag works in order for your cache control to work properly.  Mozilla supports the ETag header as well, but does NOT require it for caching.  If you need to cache a dynamic image, video, or other binary file, and are having trouble in IE7, be sure to set your ETag and then check for the If-Not-Modified header on subsequent requests so that you can properly return the 304 Not Modified page.  If you do not, then it will NEVER cache the file (as of IE version 7.0.5730.11
Asim Siddiqui
21-Oct-2006 12:26
For those having the problem with IE adding underscores to spaces... here's a workaround:

$filename = 'test file with spaces.txt';

  if(stristr($_SERVER['HTTP_USER_AGENT'],'MSIE') !== FALSE)
  {
   $filename = str_replace(' ','%20',$filename); // replace space with %20 so IE will read it as a space
  }

header('Content-Disposition: attachment; filename="'.basename($filename).'";');
readfile($fileLocation);
kamermans at teratechnologies dot net
12-Oct-2006 08:49
If you are trying to send image data to a mobile phone from PHP, some models (Motorola RAZOR V3 on Cingular) for whatever reason require the "Last-Modified" header or they will not show the image.

<?php
ob_start
();
// assuming you have image data in $imagedata
$length = strlen($imagedata);
header('Last-Modified: '.date('r'));
header('Accept-Ranges: bytes');
header('Content-Length: '.$length);
header('Content-Type: image/jpeg');
print(
$imagedata);
ob_end_flush();
?>

date('r') produces the date with the numeric timezone offset (-0400) versus Apache, which uses timezone names (GMT), but according to the HTTP/1.1 RFC, dates should be formatted  by RFC 1123 (RFC 822) which states: "There is a strong trend towards the use of numeric timezone indicators, and implementations SHOULD use numeric timezones instead of timezone names.  However, all implementations MUST accept either notation." (http://www.ietf.org/rfc/rfc1123.txt)
Olivier Mengu
08-Oct-2006 08:13
If this doesn't work:
<?php
header
("HTTP/1.0 404 Not Found");
?>

You should set option cgi.rfc2616_headers in php.ini.

If you can't, use this:
<?php
function header_status($status)
{
  
// 'cgi', 'cgi-fcgi'
  
if (substr(php_sapi_name(), 0, 3) == 'cgi')
      
header('Status: '.$status, TRUE);
   else
      
header($_SERVER['SERVER_PROTOCOL'].' '.$status);
}
header_status('404 Not Found');
?>

See also http://bugs.php.net/bug.php?id=27345
Fred P.
05-Oct-2006 12:46
// This works on both Firefox and IE6
// It display the PDF properly
// in Adobe Reader embedded browser viewer

<?php

ini_set
('zlib.output_compression', 'Off');

/* IE FIX : FireFox Compatible */
header('HTTP/1.1 200 OK');
header('Status: 200 OK');
header('Accept-Ranges: bytes');
header('Content-Transfer-Encoding: Binary');
header('Content-Type: application/force-download');
header('Content-Disposition: inline; filename=text.pdf');

#header('Pragma: anytextexeptno-cache', true);
#header('Cache-control: private');
#header('Expires: 0');

include_once('class.ezpdf.php');

$pdf =& new Cezpdf();
$pdf->selectFont('./fonts/Helvetica.afm');
$pdf->ezText($_GET['text'],50);
$pdf->ezStream();
exit;
?>
support at sumago dot de
05-Oct-2006 04:46
If you don't want people to go to
http://www.sumago.de,

but want them automatically redirected to
http://www.sumago.de
or
http://sumago.de

(so with the 'www.' or the 'prefix.'), this function might be useful:

<?
if (!strstr($_SERVER['HTTP_HOST'],"www."))
{
Header("Location:http://www.".$_SERVER['HTTP_HOST'].
"/?".$_SERVER['QUERY_STRING']);
}
?>

Replace the 'http://www.' to your own prefix. E.g. 'http://prefix.'
alex dot pleiades at gmail dot com
03-Oct-2006 02:06
Re downloading MP3 content.  I think you had a typo with "length" and if you set the content type to 'audio/MP3' it seems to behave in IE,Firefox,SeaMonkey (Mozilla) and Opera 9

This works for me...

$fileHandle = '../'.$productCode.'.mp3'; // gets it from a spot that you can't access via a browser
header('HTTP/1.1 200 OK');
header('Date: ' . date("D M j G:i:s T Y"));
header('Last-Modified: ' . date("D M j G:i:s T Y"));
header("Content-Type: audio/mp3");
header("Content-Length: " . (string)(filesize($fileHandle)) );
header("Content-Transfer-Encoding: Binary");
header('Content-Disposition: attachment; filename="'.$productCode.'.mp3"' );
readfile($fileHandle);
info /at/ storytellermusic /dot/ nl
29-Sep-2006 04:46
I've been trying to force a download in IE, but everytime it displayed the MP3 data as text in my IE window.
None of the suggestions made below worked for me.

I've based the headers on the download of a normal .GIF file. Here's mine, it's very basic and seems to work on both IE and FireFox.

<?php
header
('HTTP/1.1 200 OK');
header('Date: ' . date("D M j G:i:s T Y"));
header('Last-Modified: ' . date("D M j G:i:s T Y"));
header("Content-Type: application/force-download"); // changed to force download
header("Content-Lenght: " . (string)(filesize($filelocation)));
header("Content-Transfer-Encoding: Binary"); // added
header("Content-Disposition: attachment; // added
filename="
.str_replace(" ", "", basename($filelocation)).""); // added to remove spaces in filename when saving, it seemed to cause some problems with spaces.
readfile($filelocation);
?>
Michael J. Hudson
28-Sep-2006 12:12
There has been some discussion on the best way to FORCE a
refresh of a page.  In general, the solutions revolve on
reducing or eliminating caching.  However, I think that what
some people are looking for... is how does one force a real
refresh of the page after some internal action on the page
(be it a javascript action OR a button was pressed).  I found
the following code VERY helpful when I encountered this
need.  Specifically, I had a listbox where one could press a
button to delete a selected entry in that listbox.  The listbox
was dynamically linked to the contents of an external source
only in the sense that the listbox got updated with the
current list ONLY when the page got refreshed.  Thus, I
needed a way to force a refresh after the person pressed the
delete button.  I did this by adding the following code after
my actual delete action.

<?php
header
( "Location: http" .
(isset(
$_SERVER['HTTPS']) && $_SERVER['HTTPS']
==
"on" ? "s" : "") . "://" .
$_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] .
( isset(
$_SERVER['REQUEST_URI']) ? $_SERVER
['REQUEST_URI'] .
(
$_SERVER['QUERY_STRING'] ? "?" . $_SERVER
['QUERY_STRING'] : "" ) : "" ) );
?>

Do note, the above code is written to be VERY generic. 
You can probably simplify it greatly if the port is a standard
port and/or you are always using http or https and/or
you don't expect to have a query string appended to the end.

BTW, I didn't come up this code myself...  I happened across
it while reading the following article on auto-login techniques
for Mediawiki:
http://meta.wikimedia.org/wiki/
User:Otheus/Auto_Login_via_REMOTE_USER/code
digideath at web dot de
24-Aug-2006 03:38
Hello,

i have found a working method for IE Cache BUG, it's not the best but it is working on all IE's in our Company..

i send only the http/1.x 205 OK header additionaly to the rest of the anti cache bug header things (;

and it is working! (:

<?php
//[HTTP_USER_AGENT] => Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
if(!strpos(strtolower($_SERVER[HTTP_USER_AGENT]), "msie") === FALSE)
{
  
header("HTTP/1.x 205 OK");
} else {
  
header("HTTP/1.x 200 OK");
}

header("Pragma: no-cache");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");    // Datum aus Vergangenheit
//header("Expires: -1");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");  // immer gendert
//header("Cache-Control: no-store, no-cache, must-revalidate");  // HTTP/1.1
header("Cache-Control: no-cache, cachehack=".time());
header("Cache-Control: no-store, must-revalidate");
header("Cache-Control: post-check=-1, pre-check=-1", false);
?>
aooa83(a)dsldotpipexdotcom
19-Aug-2006 05:26
apache_request_headers() is only available if PHP is running as an apache module. Various request header values are available in the $_SERVER array, for example:

$_SERVER["HTTP_IF_MODIFIED_SINCE"]

Gives the if modified date in "Sat, 12 Aug 2006 19:12:08 GMT" format.
admin at commoncents dot net dot au
06-Aug-2006 12:00
Thanks for everyones help with this guy's,

I am using session_start and need to have files available for users to download whilst ensuring that only users who are logged in can download the files.

The other priority is that I needed to make sure that users cannot type something like download.php?dl=index.php or ../index.php

I have taken the best points from everything above and now use:

<?php

include "config.php"config.php contains the session information
   header
('Cache-Control: public');

if (
$_SESSION["logged_in"] == 1){

$dir="downloadfolder/";
$file=$dir.$_GET["dl"];

if (isset(
$_REQUEST['dl']) && file_exists($file) ) {
  
header('Pragma: anytextexeptno-cache', true);
  
header('Content-type: application/force-download');
  
header('Content-Transfer-Encoding: Binary');
  
header('Content-length: '.filesize($file));
  
header('Content-disposition: attachment;
   filename='
.basename($file));
  
readfile($file);
} else {
   echo
'No file with this name for download.';
}

}else{
   echo
"You are not logged in<br>";
}

?>
yomi at hotmail dot co dot uk
27-Jul-2006 06:45
IE download bug issue

header('Pragma: private');
header('Cache-control: private, must-revalidate');

This worked for me, I have been having this problem for some weeks with IE download bug problem, a quick copy and paste of the 2 lines into my script from the user notes did the trick for me
devonmitton at gmail dot com
26-Jul-2006 04:38
I ran into the problem of losing the session after i redirect the user with header( location:);

I found that the problem doesn't occur in php5. Atleast for myself. I tested it on IIS as well as Apache and both times using php5 fixed the issue.

Secondly, I found a weird solution: The website i'm building required Cyrillic type, so i encode the pages using UTF-8. The problem that happens is that UTF-8 sends information before the php tags, and therefore before the session_start(); which ultimately renders the session_start(); call ineffective.

The UTF-8 encoding sends a small chunk of information before anything. This is called BOM (byte order marks).

To ensure that they don't show up, you need to save your files with a UTF-8 encoding which bypasses BOM. BBEdit for the Macintosh with allow for this under the file encoding options.

To recap: In this instance and for my situation, UTF-8, no BOM will allow sessions to pass through the header( location:); redirect, or using PHP5.

Hope this helps someone! [i will also repeat this comment under the session_start() function]
Nick Sterling
26-Jul-2006 02:17
Internet Explorer could not find the temporary file it was trying to download, so after many hours of searching, this is what I have come up with (and it's working)...

<?php

header
("Cache-Control: public, must-revalidate");
header("Pragma: hack");
header("Content-Type: " . $mime_type);
header("Content-Length: " .(string)(filesize($downloadfile)) );
header('Content-Disposition: attachment; filename="'.basename($downloadfile).'"');
header("Content-Transfer-Encoding: binary\n");
                  
$fp = fopen($downloadfile, 'rb');
$buffer = fread($fp, filesize($downloadfile));
fclose ($fp);
                  
print
$buffer;

 
?>
pulstar at ig dot com dot br
17-Jul-2006 09:25
There is a very good page about browsers caching and PHP at:

http://www.sitepoint.com/article/php-anthology-2-5-caching

I'm sure it will be helpful.
spongebob3 at hotmail dot com
03-Jun-2006 07:47
If you don't want people to go to
http://example.com/index.php?id=5,

but want them automatically redirected to
http://www.example.com/index.php?id=5
or
http://prefix.example.com/index.php?id=5

(so with the 'www.' or the 'prefix.'), this function might be useful:

<?
if (!strstr($_SERVER['HTTP_HOST'],"www."))
{
Header("Location:http://www.".$_SERVER['HTTP_HOST'].
"/?".$_SERVER['QUERY_STRING']);
}
?>

Replace the 'http://www.' to your own prefix. E.g. 'http://prefix.'.
dotmg at wikkwiki dot ORG
26-May-2006 02:40
<?php header('Content-Length: ', $size); ?>
is not a so good idea. If the server has session.use_transient_sid enabled, you cannot know the correct size of output sent to client 'coz urls will be suffixed with strings like '?PHPSESSID=somemd5hash'. If you don't use this header, you have no problem because PHP will use Transfer-encoding: chuncked.

<?php header('Vary: Content-language'); ?>
may be ignored if you use ob_gzhandler.
jherman at digitalgravy dot net
18-May-2006 05:16
PHP as CGI treats header differently.

header("HTTP/1.0 404 Not Found"); returns "404 ok"

When using PHP (3,4 and 5) as CGI to return a 404 you need to use:

header("Status: 404 Not Found"); this returns "404 Not Found"

See: http://bugs.php.net/bug.php?id=27345
pechkin at zeos dot net
05-May-2006 09:00
I've created script that gives users ability to download files from closed directory. It supports multithread download and download resuming

<?php
$fname
= $_GET['file'];
$fpath = "downloads/$fname";
$fsize = filesize($fpath);
$bufsize = 20000;

if(isset(
$_SERVER['HTTP_RANGE']))  //Partial download
{
   if(
preg_match("/^bytes=(\\d+)-(\\d*)$/", $_SERVER['HTTP_RANGE'], $matches)) { //parsing Range header
      
$from = $matches[1];
      
$to = $matches[2];
       if(empty(
$to))
       {
          
$to = $fsize - 1// -1  because end byte is included
                               //(From HTTP protocol:
// 'The last-byte-pos value gives the byte-offset of the last byte in the range; that is, the byte positions specified are inclusive')
      
}
      
$content_size = $to - $from + 1;

      
header("HTTP/1.1 206 Partial Content");
      
header("Content-Range: $from-$to/$fsize");
      
header("Content-Length: $content_size");
      
header("Content-Type: application/force-download");
      
header("Content-Disposition: attachment; filename=$fname");
      
header("Content-Transfer-Encoding: binary");

       if(
file_exists($fpath) && $fh = fopen($fpath, "rb"))
       {
          
fseek($fh, $from);
          
$cur_pos = ftell($fh);
           while(
$cur_pos !== FALSE && ftell($fh) + $bufsize < $to+1)
           {
              
$buffer = fread($fh, $bufsize);
               print
$buffer;
              
$cur_pos = ftell($fh);
           }

          
$buffer = fread($fh, $to+1 - $cur_pos);
           print
$buffer;

          
fclose($fh);
       }
       else
       {
          
header("HTTP/1.1 404 Not Found");
           exit;
       }
   }
   else
   {
      
header("HTTP/1.1 500 Internal Server Error");
       exit;
   }
}
else
// Usual download
{
  
header("HTTP/1.1 200 OK");
  
header("Content-Length: $fsize");
  
header("Content-Type: application/force-download");
  
header("Content-Disposition: attachment; filename=$fname");
  
header("Content-Transfer-Encoding: binary");

   if(
file_exists($fpath) && $fh = fopen($fpath, "rb")){
       while(
$buf = fread($fh, $bufsize))
           print
$buf;
      
fclose($fh);
   }
   else
   {
      
header("HTTP/1.1 404 Not Found");
   }
}
?>
cameron at prolifique dot com
05-May-2006 04:24
I've had several people ask me about my multi-featured custom 404 page (see post on 11-Jun-2005 below) and even request the full code for it. So here it is:

www.prolifique.com/404.php.txt

I've tried to clean it up to be self-contained (rather than rely on copious other include files) and to remove any extraneous functionality.

Works on both Windows and Apache servers.

Enjoy. There may be mistakes from the clean-up; feel free to mention them. Suggestions for improvements welcome.
abrikos dot org
04-May-2006 12:38
<?
if(strstr($_SERVER[HTTP_USER_AGENT],"MSIE"))
{
  
$fname=str_replace("+"," ",urlencode($fname));
}
header("Content-Disposition: attachment; filename=\"$fname\"");
?>

this code help to set valid names of attachment for IE users
php.net[at]macosbrain[dot]com
30-Apr-2006 05:19
at first one advise to Jon's post (09-Mar-2006 01:38)
the "function downloadFile ($file, $mimetype)" can NOT resume downloads.
the function sent every time the same file just with different header informations.

this function will work
<?php
function output_file($file,$name)
{
//do something on download abort/finish
//register_shutdown_function( 'function_name'  );
if(!file_exists($file))
die(
'file not exist!');
$size = filesize($file);
$name = rawurldecode($name);

if (
ereg('Opera(/| )([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
$UserBrowser = "Opera";
elseif (
ereg('MSIE ([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
$UserBrowser = "IE";
else
$UserBrowser = '';

/// important for download im most browser
$mime_type = ($UserBrowser == 'IE' || $UserBrowser == 'Opera') ?
 
'application/octetstream' : 'application/octet-stream';
@
ob_end_clean(); /// decrease cpu usage extreme
header('Content-Type: ' . $mime_type);
header('Content-Disposition: attachment; filename="'.$name.'"');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header('Accept-Ranges: bytes');
header("Cache-control: private");
header('Pragma: private');

/////  multipart-download and resume-download
if(isset($_SERVER['HTTP_RANGE']))
{
list(
$a, $range) = explode("=",$_SERVER['HTTP_RANGE']);
str_replace($range, "-", $range);
$size2 = $size-1;
$new_length = $size-$range;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range$size2/$size");
}
else
{
$size2=$size-1;
header("Content-Length: ".$size);
}
$chunksize = 1*(1024*1024);
$this->bytes_send = 0;
if (
$file = fopen($file, 'r'))
{
if(isset(
$_SERVER['HTTP_RANGE']))
fseek($file, $range);
while(!
feof($file) and (connection_status()==0))
{
$buffer = fread($file, $chunksize);
print(
$buffer);//echo($buffer); // is also possible
flush();
$this->bytes_send += strlen($buffer);
//sleep(1);//// decrease download speed
}
fclose($file);
}
else
die(
'error can not open file');
if(isset(
$new_length))
$size = $new_length;
die();
}
?>
http://macosbrain.ath.cx/wordpress/2006/04/30/
for some comments in german.
hervard at gmail dot com
18-Apr-2006 02:53
When using HTML forms, using the browser's back button will sometimes display a message regarding using cached data, and will ask you to refresh the page. This can be very disconcerting for some users, as they might not know whether to hit Refresh or not.

To force pages to always load the data that was entered in the form prior to hitting a submit button, and prevent the browser's cache message from displaying, use the following code:

<?php

  
// Original code found at http://www.mnot.net/cache_docs/

  
header("Cache-Control: must-revalidate");
  
$offset = 60 * 60 * 24 * -1;
  
$ExpStr = "Expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
  
header($ExpStr);

?>

This will tell the browser that the page will expire in one day, and the cached form data will be used without prompting the user at all.

I have tested this in Internet Explorer 6, Firefox 1.5, and Opera 8.51, and it works as intended. I have tried other cache-control and expiry variants, but they either do not work, or do not work in every browser. This code appears to be a winner.
nath at dbpixels dot net
14-Apr-2006 03:47
not worked out a way round yet, but just to let you all know that the following code messes up IE2 beta 2 and causes pages to load blank... Any of you who use this and have clients who like to keep up with the latest technologies may want to remove, edit swiftly..

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

Cheers all
bkatz at usefulengineering dot com
09-Apr-2006 06:46
When doing a simple redirect using header, i.e.:

header("Location: login.php");

you may want to place a command to terminate your script following this, like exit:

header("Location: login.php");
exit();

Otherwise, you may find, unexpectedly, that your script following the redirect continues execution. Not sure if this is expected behavior, but regardless, in the case of, for instance, a redirect to a login page when a user is not logged in, that's definitely something you'll want to add to prevent "secured" code from running.
shawnfess at comcast dot net
09-Apr-2006 05:53
Internet Explorer will not accept certain filenames in the filename attribute of the Content-Disposition header. It will Sit 'N Spin until it finally times out. The following will not work when $fn contains "Web", "Browser" or "WebBrowser" (or any case variation thereof):

$fn = $row["sFilename"];
$fs = $row["lFileSize"];
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=$fn");
header("Content-length: $fs");
echo $row["blobData"];

Guess I should have looked here first. Yes, it does seem we're all searching for workarounds for IE.
-SHAWN-
gnoise at gmail dot com
03-Apr-2006 02:21
A small note on the header('Content-Disposition: attachment; filename="downloaded.pdf"') -usage:

I found out that the use of double-quotes (") around the filename doesn't work in Firefox, and is not needed for the header to work, even for filenames with spaces in it.
So use this instead:

<?

   header
('Content-Disposition: attachment; filename=filename spaces are ok.pdf')

?>
nick dot b at kingstransport dot com dot au
23-Mar-2006 10:57
Constructing an absolute URL (for redirecting or other purposes) is full of pitfalls.  As well as considering the notes at http://www.php.net/manual/en/function.header.php#63006 and http://www.php.net/manual/en/function.header.php#61746 you need to consider this issue, applicable if you use ProxyPass (in apache).

Example httpd.conf
...
ProxyPass    /dir/  http: //10.1.1.1/dir/
...
Member of public  --->  Unix/Apache server  --->  IIS/PHP server
http: //nick.com/          nick.com              myphpsvr.nick.com
dir/phpinfo.php            20.10.5.1              10.1.1.1

When your script is on the IIS/PHP server, browsing from member of public, you will have script variables
<?php
// browsing from member of public
$_SERVER['HTTP_HOST'] == '10.1.1.6';
$_SERVER['HTTP_X_FORWARDED_HOST'] == 'nick.com';
$_SERVER['SERVER_NAME'] == '10.1.1.6';
?>

However, if you browse internally (like, when testing, http: //myphpsrv.nick.com/dir/phpinfo.php), the variables turn out like this
<?php
// browsing from internal, direct
$_SERVER['HTTP_HOST'] == 'myphpsvr.nick.com';
$_SERVER['SERVER_NAME'] == 'myphpsvr.nick.com';
// no $_SERVER['HTTP_X_FORWARDED_HOST']
?>

The point is, you need to test $_SERVER['HTTP_X_FORWARDED_HOST'] and use that in preference to either $_SERVER['HTTP_HOST'] or $_SERVER['SERVER_NAME'].

Nick Bishop.
URL's have intentionally been broken up to stop them being clickable.
steve at stevedix dot de
21-Mar-2006 09:39
Referring to the infamous Microsoft Explorer bug, particularly when outputting PDF : The problem is this -

Most well-behaved browsers require a mime-type before they attempt to display anything.  If they don't receive a mime-type from the server, then they revert to text/plain.

Not Microsoft Explorer. 

Microsoft Explorer attempts to ascertain what type of file it is recieving by looking in the file first.  You can test this by renaming a graphic file so that it does not have a .extension (ie rename test.gif to be test), and upload it to your server.  Look at it via a URL, and any compliant browser will render it as text.  Not Microsoft Explorer, which renders the file as a graphic.  Unfortunately, this behavior is not consistent.

The really big problem comes with the Adobe postscript plugin.  Most browsers will shut up and listen when given a mime type for PDF.  Not Microsoft Explorer.  It sees that the url generating the file contains a .php, and gets confused.

I eventually discovered that the best way to stop this problem was to generate the pdf to a file, move it to a subdirectory under the htdocs, and then issue a redirect to it.  Examples of this method can be seen in use at www.onvista.de, such as

http://fonds.onvista.de/snapshot.html?ID_INSTRUMENT=6628444

Which launches a separate window, generates the pdf, and then redirects to it.

Naughty, naughty Microsoft browser.  No biscuit for you.
vaughan montgomery
11-Mar-2006 09:57
i was rackin my brains trying to figure out this IE bug..

when you hit the button to start the download,  in IE you get the dialogue box asking you whether you want to save the file to disk or open it. non of the methods on this page seemed to work.

after a lot of experimenting i found a solution which works in IE and firefox etc.  i don't know about mac as i don't own one.

the solution i used is >

$mimeType = $download->getVar('filetype');

$filePath = '<your path to file>/'.trim($filename);

if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');

header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Transfer-Encoding: binary");
if(isset($mimeType))
{
header("Content-Type: " . $mimeType);
}
header("Content-Disposition: attachment; filename=" . $filename);

if(isset($mimeType) && strstr($mimeType, "text/"))
{
$fp = fopen($filePath, "r");
}
else
{
$fp = fopen($filePath, "rb");
}
fpassthru($fp);
exit();

exit() is also required.

note.  you maybe asking why Content-Length is not added, that is because for some reason adding content-length stopped the actual file downloading correctly or opening. i don't know why, but as it worked without, i wasn't worried about not having it.

please note tho, in the script, $download->getVar('filetype');  is a value that is pulled from a DB, the submission part of the script stores the mimetype in the db aswell, it was easier to do it that way, so adjust mimetype to whatever you require b4 you use it..

even pdf files download and also open aswell from the browser dialogue box in IE.
nino at clan-csc dot com
11-Mar-2006 01:32
There is actually a way to allow header() function after something.

Open your php.ini file and change the line
output_buffering = Off
to
output_buffering = On
this allows you to send headers no matter what.

But then you'll get an perfomance lowering.

So if perfomance matters alot to you, do this:

output_buffering = 4096

It is a REALLY small perfomance lowering, and you can send some header() 's.

-Nino
Jon
10-Mar-2006 05:38
Seems like we are all trying to get round this IE bug (Apache 2 + PHP Server (Solaris) / IE6), after going through the solutions here I've not been that sucessful - also yet to solve the https problem. The solution I've adopted for http is a modified version of the following link:

http://www.phpbuilder.com/board/showthread.php?threadid=10318152

Jon

function downloadFile ($file, $mimetype)
{
  $status = 0;
  if (($file != NULL) && file_exists($file)) {
   if(isset($_SERVER['HTTP_USER_AGENT']) &&
       preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT']))
   {
     // IE Bug in download name workaround
     ini_set( 'zlib.output_compression','Off' );
   }
   // header ('Content-type: ' . mime_content_type($file)
   header ('Content-type: ' . $mimetype);
   header ('Content-Disposition: attachment; filename="'.basename($file).'"');
   header ('Expires: '.gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y"))).' GMT');
   header ('Accept-Ranges: bytes');
   // Use Cache-control: private not following:
   // header ('Cache-control: no-cache, must-revalidate');
   header("Cache-control: private");                 
   header ('Pragma: private');
  
   $size = filesize($file);
   if(isset($_SERVER['HTTP_RANGE'])) {
     list($a, $range) = explode("=",$_SERVER['HTTP_RANGE']);
     //if yes, download missing part
     str_replace($range, "-", $range);
     $size2 = $size-1;
     $new_length = $size2-$range;
     header("HTTP/1.1 206 Partial Content");
     header("Content-Length: $new_length");
     header("Content-Range: bytes $range$size2/$size");
   }
   else
   {
     $size2=$size-1;
     header("Content-Range: bytes 0-$size2/$size");
     header("Content-Length: ".$size);
   }
  
   if ($file = fopen($file, 'r')) {
     while(!feof($file) and (connection_status()==0)) {
       print(fread($file, 1024*8));
       flush();
     }
     $status = (connection_status() == 0);
     fclose($file);
   }
  }
  return($status);
}

function downloadStream(&$stream, $filename, $mimetype)
{
  $status = 0;
  $size = strlen($stream);
  if ($size > 0) {
   if(isset($_SERVER['HTTP_USER_AGENT']) &&
       preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT']))
   {
     // IE Bug in download name workaround
     ini_set( 'zlib.output_compression','Off' );
   }
   // header ('Content-type: ' . mime_content_type($file)
   header ('Content-type: ' . $mimetype);
   header ('Content-Disposition: attachment; filename="' . $filename . '"');
   header ('Expires: '.gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y"))).' GMT');
   header ('Accept-Ranges: bytes');
   // Use Cache-control: private not following:
   // header ('Cache-control: no-cache, must-revalidate');
   header("Cache-control: private");                 
   header ('Pragma: private');
  
   if(isset($_SERVER['HTTP_RANGE'])) {
     list($a, $range) = explode("=",$_SERVER['HTTP_RANGE']);
     //if yes, download missing part
     str_replace($range, "-", $range);
     $size2 = $size-1;
     $new_length = $size2-$range;
     header("HTTP/1.1 206 Partial Content");
     header("Content-Length: $new_length");
     header("Content-Range: bytes $range$size2/$size");
   }
   else
   {
     $size2=$size-1;
     header("Content-Range: bytes 0-$size2/$size");
     header("Content-Length: ".$size);
   }
  
   // Dump the content.
   echo $stream;
   $status = 1;
  }
  return($status);
}
marc.giroux at pointpubmedia dot com
08-Mar-2006 05:58
i tried alot of ways to pass the IE bug and i finally stumbled upon 1 and here it is.. it work very well for me .. and it is IE/FireFox compatible

/* Force Download Function *IE WORKING* */
function force_download($filename) {
    
   $filesize = filesize($filename);
   if($filesize) {
       ini_set('zlib.output_compression', 'Off');

       /* IE FIX : FireFox Compatible */
       header("Content-Type: application/force-download\n");
       header("Content-Disposition: attachment; filename=$filename");
       /* Read It */
       $contents = fread(fopen($filename, "rb"), filesize($filename));
       /* Print It */
       echo $contents;
       exit;
   } else {
       return 0;
   }
  
}
kim at bonfils dot com
07-Mar-2006 05:31
In response to fig dot miguel at gmail dot com:

Please be aware that the "Refresh" header is not part of the official HTTP specification. Most browsers (like IE and Firefox) seem to support it, but I've run into problems with Safari (apparently, it adds an extra blank space to the URL, making it unusable).

So don't rely on the "Refresh" header. If you use it, make sure you test it on all relevant browsers.
cameron at prolifique dot com
01-Mar-2006 03:36
One further note about forcing downloads in Internet Explorer.

If the server has not yet issued an HTTP status code or has issued something other than a 200, you need to send HTTP/1.1 200 OK to the browser so that IE accepts the download without an error:

header("HTTP/1.1 200 OK");
and/or
header("Status: 200 OK");

I use a custom 404 error page to handle secure downloads. The user clicks on a link to a file such as 'http://mysite.com/user_files/file.txt'. That file does not exist, so my 404 page is invoked. It parses the address and if it finds the 'user_files' folder in the address, it attempts to open the file 'file.txt' from a non-web-accessible folder and sends it as a download instead of showing the 404 message.

The problem I had was that IE always showed this error when you attempted to download or open the file:
"Internet Explorer cannot download file.txt from mysite.com. Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later." The download worked fine in FireFox, and strangely, even worked fine on my local test server (Windows XP) in IE. But on the remote server (Apache) in IE, it threw the error above until I added the above status header(s). Now the code reads:

     header("HTTP/1.1 200 OK");
     header("Status: 200 OK");
     header('Content-Type: application/force-download');
     header('Content-Disposition: attachment; filename="'.$downloadFile.'"');
     header('Content-Length: '.filesize($downloadFilePath));
     readfile($downloadFilePath);
einavb
28-Feb-2006 07:54
For all of you IE SSL desperados,
All the listed download tweaks did not work for me,
until I removed the "?" sign for the query string (IE SSL cache "feature").
me at macronesia dot net
22-Feb-2006 10:46
Since PHP 4.4.2, you'll need to have header(); on each line rather than header(" ... \r\n" . " .... \r\n");
mandor at mandor dot net
15-Feb-2006 09:14
When using PHP to output an image, it won't be cached by the client so if you don't want them to download the image each time they reload the page, you will need to emulate part of the HTTP protocol.

Here's how:

<?php

  
// Test image.
  
$fn = '/test/foo.png';

  
// Getting headers sent by the client.
  
$headers = apache_request_headers();

  
// Checking if the client is validating his cache and if it is current.
  
if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($fn))) {
      
// Client's cache IS current, so we just respond '304 Not Modified'.
      
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($fn)).' GMT', true, 304);
   } else {
      
// Image not cached or cache outdated, we respond '200 OK' and output the image.
      
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($fn)).' GMT', true, 200);
      
header('Content-Length: '.filesize($fn));
      
header('Content-Type: image/png');
       print
file_get_contents($fn);
   }

?>

That way foo.png will be properly cached by the client and you'll save bandwith. :)
timmerca.com
15-Feb-2006 12:07
I store a lot of images in databases rather than files, and PHP was causing my user's web browsers to not cache these images, which means that every time they went to my web site, they were reloading the same images over and over again.  To fix this, I have added the following headers to my download.php script:

Header("Expires: " . date("D, j M Y H:i:s", time() + (86400 * 30)) . " UTC");
Header("Cache-Control: Public");
Header("Pragma: Public");

This causes the user's web browsers to cache the image data (in this case for 30 days, but you can make that whatever you want).  This sped up my web sites and cut down on hits to my server considerably.  So, if you are delivering images through PHP, you should consider adding these headers to your script too.
fig dot miguel at gmail dot com
11-Feb-2006 01:42
A function to redirect, using different approaches. The destination page can include a full URL, a full path or a local path.
<?
function g_redirect($url,$mode)
/*  It redirects to a page specified by "$url".
 *  $mode can be:
 *    LOCATION:  Redirect via Header "Location".
 *    REFRESH:  Redirect via Header "Refresh".
 *    META:      Redirect via HTML META tag
 *    JS:        Redirect via JavaScript command
 */
{
  if (
strncmp('http:',$url,5) && strncmp('https:',$url,6)) {

    
$starturl = ($_SERVER["HTTPS"] == 'on' ? 'https' : 'http') . '://'.
                 (empty(
$_SERVER['HTTP_HOST'])? $_SERVER['SERVER_NAME'] :
                
$_SERVER['HTTP_HOST']);

     if (
$url[0] != '/') $starturl .= dirname($_SERVER['PHP_SELF']).'/';

    
$url = "$starturl$url";
  }

  switch(
$mode) {

     case
'LOCATION':

       if (
headers_sent()) exit("Headers already sent. Can not redirect to $url");

      
header("Location: $url");
       exit;

     case
'REFRESH':

       if (
headers_sent()) exit("Headers already sent. Can not redirect to $url");

      
header("Refresh: 0; URL=\"$url\"");
       exit;

     case
'META':

      
?><meta http-equiv="refresh" content="0;url=<?=$url?>" /><?
      
exit;

     default:
/* -- Java Script */

      
?><script type="text/javascript">
       window.location.href='<?=$url?>';
       </script><?
 
}
  exit;
}
?>
vsnake at email dot ru
06-Feb-2006 02:51
in situation when I use SSL, sessions and send file to user technique, MSIE dont get a file.
This code don't work:

session_start();
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="file.pdf"');
header('Content-Length: ' . filesize($fileName));
readfile($fileName);

And I get a solution:

session_start();
header('Pragma: anytextexeptno-cache', true);
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="file.pdf"');
header('Content-Length: ' . filesize($fileName));
readfile($fileName);

The point is: replace header 'Pragma: no-cache'
Good luck!
spingary at yahoo dot com
13-Jan-2006 04:53
I was having trouble with streaming inline PDf's using PHP 5.0.2, Apache 2.0.54.

This is my code:

<?
header
("Pragma: public");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate");
header("Content-type: application/pdf");
header("Content-Length: ".filesize($file));
header("Content-disposition: inline; filename=$file");
header("Accept-Ranges: ".filesize($file));
readfile($file);
exit();
?>
It would work fine in Mozilla Firefox (1.0.7) but with IE (6.0.2800.1106) it would not bring up the Adobe Reader plugin and instead ask me to save it or open it as a PHP file.

Oddly enough, I turned off ZLib.compression and it started working.  I guess the compression is confusing IE.  I tried leaving out the content-length header thinking maybe it was unmatched filesize (uncompressed number vs actual received compressed size), but then without it it screws up Firefox too. 

What I ended up doing was disabling Zlib compression for the PDF output pages using ini_set:

<?
ini_set
('zlib.output_compression','Off');
?>

Maybe this will help someone. Will post over in the PDF section as well.
Ciantic
24-Dec-2005 10:07
This is a bit extended the idea what Per Eckerdal had. Note that this contains all HTTP status responses defined in RFC2616 section 6.1.1 http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1

<?PHP
/**
 * HTTP Protocol defined status codes
 * @param int $num
 */
function HTTPStatus($num) {
  
   static
$http = array (
      
100 => "HTTP/1.1 100 Continue",
      
101 => "HTTP/1.1 101 Switching Protocols",
      
200 => "HTTP/1.1 200 OK",
      
201 => "HTTP/1.1 201 Created",
      
202 => "HTTP/1.1 202 Accepted",
      
203 => "HTTP/1.1 203 Non-Authoritative Information",
      
204 => "HTTP/1.1 204 No Content",
      
205 => "HTTP/1.1 205 Reset Content",
      
206 => "HTTP/1.1 206 Partial Content",
      
300 => "HTTP/1.1 300 Multiple Choices",
      
301 => "HTTP/1.1 301 Moved Permanently",
      
302 => "HTTP/1.1 302 Found",
      
303 => "HTTP/1.1 303 See Other",
      
304 => "HTTP/1.1 304 Not Modified",
      
305 => "HTTP/1.1 305 Use Proxy",
      
307 => "HTTP/1.1 307 Temporary Redirect",
      
400 => "HTTP/1.1 400 Bad Request",
      
401 => "HTTP/1.1 401 Unauthorized",
      
402 => "HTTP/1.1 402 Payment Required",
      
403 => "HTTP/1.1 403 Forbidden",
      
404 => "HTTP/1.1 404 Not Found",
      
405 => "HTTP/1.1 405 Method Not Allowed",
      
406 => "HTTP/1.1 406 Not Acceptable",
      
407 => "HTTP/1.1 407 Proxy Authentication Required",
      
408 => "HTTP/1.1 408 Request Time-out",
      
409 => "HTTP/1.1 409 Conflict",
      
410 => "HTTP/1.1 410 Gone",
      
411 => "HTTP/1.1 411 Length Required",
      
412 => "HTTP/1.1 412 Precondition Failed",
      
413 => "HTTP/1.1 413 Request Entity Too Large",
      
414 => "HTTP/1.1 414 Request-URI Too Large",
      
415 => "HTTP/1.1 415 Unsupported Media Type",
      
416 => "HTTP/1.1 416 Requested range not satisfiable",
      
417 => "HTTP/1.1 417 Expectation Failed",
      
500 => "HTTP/1.1 500 Internal Server Error",
      
501 => "HTTP/1.1 501 Not Implemented",
      
502 => "HTTP/1.1 502 Bad Gateway",
      
503 => "HTTP/1.1 503 Service Unavailable",
      
504 => "HTTP/1.1 504 Gateway Time-out"       
  
);
  
  
header($http[$num]);
}
?>
cory at lavacube dot com
17-Nov-2005 12:26
Using apache and mod_vhost_alias, you have the oportunity to produce some amazing websites. Take this for example:

Using mod_vhost_alias, you can have apache point to your index.php file... Doing so, you can use PHP to control EVERY aspect of your website, including making "human readable paths" (mysite.com/info/contact_us/) very easily.

This is done with the Apache/mod_vhost_alias directive "VirtualDocumentRoot".
(eg: VirtualDocumentRoot "/www/mysite.com/index.php")

However, this makes it harder to display images and documents/files you would normally have static. You will require a page to actually handle the request and load the appropriate file... A simple example:

Assume URL is: http://mysite.com/images/firefox_button.png

<?php

$path
= './images/';
$image = preg_replace('/^\/images\//i', null, $_SERVER['REQUEST_URI']);
$image = str_replace('..', null, $image);

$filename = $path . $image;
$file_info = pathinfo($filename);
$extn = $file_info['extension'];

if(
file_exists($filename) )
{
  
header('Content-Type: image/'.$extn);
  
$open = fopen($filename, 'r');
  
fpassthru($open);
  
fclose($open);
}
else
{
  
header('HTTP/1.0 404 Not Found');
}

exit();

?>

The fun thing about this, is you can also supply something like this to control a cache of these images... This will greatly lighten the load on your server:
   Before:
       header('Content-Type: image/'.$extn);
   Add:
       header('Content-Control: cache');
       header('Content-Type: image/'. date('r', time()+604800)); // expire in 7 days

With VirtualDocumentRoot, you can do almost _anything_...
Some prime examples where using VirtualDocumentRoot would be helpful:
- create, handle and manage 'human readable' URLs
- potentially thwart "leeching" of media, using sessions/cookies(eg: require a hit to a specific page before any images can be loaded, etc.)
- a "mod_speling" handling system
- create a "wiki" style site with ease
- and many, many others... just use your imagination

Enjoy. :-)
brian at pjcservices dot com
28-Oct-2005 01:10
For Internet Explorer:

When using an attachement Content-Dispositon, it seems the following headers are also required:

header("Cache-Control: cache, must-revalidate");   
header("Pragma: public");

If you have Cache-Control as no-cache, IE fails.

I have done several tests and those two lines fixed my script. (It was trying to download a .csv)

The above seems to be especially true on https connections.
hsmcdonald at gmail dot com
22-Oct-2005 01:39
I had a special situation where I wanted to have a "Download" button on a PHP script, and have the the script post to itself to call a special block of code to initiate download of dynamically created content (rather than display contents of a file).

I was running in circles trying to get IE SP1, SP2 and Firefox  on Win98, 2000, and XP to all work where the download prompt is displayed AND the filename is controlled by me and not the browser.  I found the following headers from a form that seem to work in all cases.:

<?php

// load content into var

$filecontent="Some text/code im creating in the script";
$downloadfile="somefile.txt";

header("Content-disposition: attachment; filename=$downloadfile");
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".strlen($filecontent));
header("Pragma: no-cache");
header("Expires: 0");

?>

Hope this helps someone..
wraezor at gmail dot com
21-Oct-2005 11:08
Adding onto all the helpful material here, I have something to add, discovered by trial and error.

When initiating a file download with the following command:
header('Content-Disposition: attachment; filename="downloaded.pdf"');

Ensure that the 'filename' does not contain a directory path.  It will work, but Internet Explorer will not pick up the filename.

Example:

I had a script, called save.php which did just that, grabbing individual files in a subdirectory ("results/downloaded.pdf").  Since it was in a subdirectory, it could find the file, but IE (Using 6sp1) wouldn't pick up the name when suggesting for the 'Save' option.  Firefox and Opera were fine.

So, moral of the story, the 'filename' should only ever contain a filename, no directories.  This is only really relevant when people couldn't be expected to know the filename/extension, like in my case with FRF files.  (Note: This was my experience at least.)
me at macronesia dot net
21-Oct-2005 08:27
Make sure you place Content-Type in front of Content-Disposition or else you will run into problems!

It will also save you an hour of head scratching.
http://pye.dyndns.org
19-Oct-2005 06:18
The example by digibluez at gmail dot com, 06-Sep-2005 08:12 contains an error, the 'charset' part of the 'Content-Type' header should be written with an '=' sign, so instead:

<?php
header
("Content-type: text/css; charset=UTF-8");
?>
denilson at vialink dot com dot br
12-Oct-2005 07:24
If you want to enable caching of your page, you have two solutions: use Etag or use Last-Modified.

Someone has already posted here the code to use Etag. However, sometimes, it is easier (better) to use Last-Modified. The full code is here:

<?php
function get_http_mdate()
{
   return
gmdate("D, d M Y H:i:s",filemtime($SCRIPT_FILENAME))." GMT";
}

function
check_modified_header()
{
  
// This function is based on code from http://ontosys.com/php/cache.html

  
$headers=apache_request_headers();
  
$if_modified_since=preg_replace('/;.*$/', '', $headers['If-Modified-Since']);
   if(!
$if_modified_since)
       return;

  
$gmtime=get_http_mdate();

   if (
$if_modified_since == $gmtime) {
      
header("HTTP/1.1 304 Not Modified");
       exit;
   }
}

check_modified_header();
header("Last-Modified: ".get_http_mdate());
?>

The script checks if time from "If-Modified-Since" header is equal to current modified-time. If it is, a 304 code is returned, and PHP exits. If it is not, PHP continues normally.

You may want to change how "get_http_mdate()" function gets the time. You may want to get time from another file, or from somewhere else (like a date field on database).
memoimyself at yahoo dot com dot br
08-Aug-2005 11:01
PROBLEM: File downloads work fine with browsers other than Internet Explorer, but when IE is used, the following error message is issued: "Internet Explorer cannot download file from server. Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later."

PROBABLE CAUSE: File is being downloaded over a secure connection (SSL, a.k.a. HTTPS protocol) and IE has received a "no-cache" header (as also mentioned by Harry and james at charity dot org). More info on the problem: http://support.microsoft.com/default.aspx?scid=kb;en-us;316431

SOLUTION: The following have worked for me:

header('Pragma: private');
header('Cache-control: private, must-revalidate');

or simply

header('Pragma: ');
header('Cache-control: ');

(The first option is probably safer, but that's just a guess.)
boris at psyonline dot ru
05-Aug-2005 07:37
On some servers IE tries to use the script name rather than the supplied filename in the Content-Disposition header. But it works correctly with unknown Content-Encoding! :-)

<?php
header
("Content-Encoding: AnyTrash");
header("Content-Disposition: attachment; filename=download.txt");
echo
"Content";
?>
cameron at prolifique dot com
12-Jun-2005 03:29
I use a custom PHP 404 page that does several things:

 Checks the database for a "virtual" page matching the missing page address, and if found, includes and displays it, issuing an HTTP 200 OK status. The client browser receives the page contents and never even knows that the page doesn't actually exist on the server.

 Checks the database for a redirect from this missing page address to another address, and if found, does the redirect by issuing an HTTP 302 Object Moved status and Location header.

 Tries to fix common address errors, such as removing periods or extra slashes at the end of the address, and then checks for a physical page with a matching address, a virtual page with a matching address, or a redirect, and takes the appropriate action.

 If all of the above fail, it simply shows a friendly "Page Not Found" error.

 In the case of redirects and genuine missing pages, the missing page address (and the number of hits on it) is logged to the database. In the case of genuine missing pages, an email is also sent to me so I can identify and fix broken links.

The "virtual" page behavior is perhaps the most useful, as it allows me to create totally dynamic pages on the server but use static addresses for them.

However, on one particular server on which I used this system, my HTTP 200 header wasn't getting sent correctly:

<?php
header
("HTTP/1.1 200 OK");
?>

Instead, the browser received a 404, and even if the virtual page content was included and sent correctly (it was), IE showed its typical 404 error page instead based on the status code (but only if "show friendly HTTP error messages" is checked, which is the default).

Finally found this note in an older or alternate version of the PHP manual (at http://academ.hvcc.edu/~kantopet/php/manual/function.header.html):

Note: In PHP 3, this only works when PHP is compiled as an Apache module. You can achieve the same effect using the Status header.

<?php
header
("Status: 404 Not Found");
?>

Though our server uses PHP/4.3.10, I thought this was worth a try, so I tried issuing both commands:

<?php
header
("HTTP/1.1 200 OK");
header("Status: 200 OK");
?>

And, in fact, it worked.

So if you find that using header("HTTP/1.1 ###") isn't working, try the header("Status: ###") instead or as well.
tylerd gmail
22-May-2005 05:19
I was sick of the cookie domain handling issues presented by my situation:
www.foo.com/phpBB2/ - my forum
forum.foo.com - vhost to www.foo.com/phpBB2/
forums.foo.com - vhost to www.foo.com/phpBB2/

So to force all users to use the vhost I prefer (forum.*) while still passing on the entire URI (like if they were linked to a post) I added this to phpBB2/includes/page_header.php:
<?php

if($_SERVER['HTTP_HOST'] != 'forum.foo.com')
{
   if(!
strstr($_SERVER['REQUEST_URI'], "phpBB2"))    { header("Location: http://forum.foo.com" . $_SERVER['REQUEST_URI']); }
   else {
header("Location: http://forum.foo.com" . substr($_SERVER['REQUEST_URI'], 7)); }
}

// yadda yadda yadda
?>
I'm sure this would work for many other situations.
com dot gmail at schaefer dot peter
10-May-2005 02:02
Regarding the IE pdf problem, doing the variation of not sending an
Accept-Ranges: bytes
header seems to make a difference. Strangely enough, some pdfs just work fine without these complications, so maybe changing that header just forced invalidation of the IE cache(I had emptied it, but who knows how IE really works).

       //header("Accept-Ranges: bytes");
       $filename= 'Labels'. date("ymdhis") . '.pdf';

       //FIX: IE6
       header('Content-type: application/pdf');
       header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
       header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
       header("Pragma: public");
       header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

       //header("Cache-Control: private");
       $pdf->stream( array('Content-Disposition'=>$filename) );

This is a large pdf file.

For a smaller pdf file, the straightforward thing worked too:

       header("Accept-Ranges: bytes");
       header("Cache-Control: private");
       $pdf->stream();

Weird :-/
TJ
04-May-2005 06:49
I had made a download script for a client that would just register the number of times a document had been downloaded in a database. When using headers and the readfile()-function to pass the file through the browser I got some weird errors with half of the powerpoint files I tried to download.

My solution:
 - Register the hitcount in the database
 - Instead of using headers and readfile, forward the user to the file he/she originally requested by using the following code:

header("HTTP/1.0 307 Temporary redirect");
echo header("Location: http://www.example.com/filename");    exit();
pornel at despammed dot com
28-Apr-2005 11:00
Please read caching tutorial and test scripts with: http://www.web-caching.com/cacheability.html before copying & pasting all "urban legend" caching headers.

Another note:
Content-Disposition is non-standard header and sending files with readfile() doesn't support resuming, caching and might be problematic with large files. In some cases it's better to use Apache mod_rewrite instead.
mjs15451 at hotmail dot com
23-Apr-2005 03:04
I was looking at this section and I noticed that it didn't mention anything about javascript includes using php files.  I tested this code on Gecko browsers (Firefox, Mozilla, Netscape), MSIE and Opera and they all can include the php file with this header:

header("Content-type: application/x-javascript");

This would be the code you would use to include it:

<script type="text/javascript" src="javascript.php"></script>

MSIE doesn't understand the file only if it is called directly in the location bar; it still executes the file when called through html.

I believe this header is necessary for legacy browsers, i.e.  Netscape 4.x or less.
james at charity dot org
06-Apr-2005 12:15
If you're creating a download page for Internet Explorer, the following code works:

   if(isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT'])) {
       // IE Bug in download name workaround
       ini_set( 'zlib.output_compression','Off' );
   }
  
   header('Content-type: ' . $file['type']);
   header("Pragma: public");
   header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
   header('Content-Disposition: attachment; filename="' . $file['filename'] . '"');
   print($file['data']);

I will note that setting content length or size headers will cause IE to see the file being opened/saved as 0 bytes and will on occasion open the content of the file in the browser itself after the download fails.

I also recieved file not found problems until I added the Pragma and Cache-Control headers.

Yet another one of those "Thanks Bill" situations.

--james
snagnever at gmail dot com
01-Apr-2005 05:56
Ok, maybe it can be usefull -- i was trying to save space on my disk and my transfer-rate, but i didn't want to sacrifice the quality of my pictures.
So, it can be usefull for people like me.

The file, when is sent to the server, is compressed in gzip:

<?php
$content
= file_get_contents($_FILES['userfile']['tmp_name']);
$content = gzencode($content,9);
$id = fopen("/new/path/images/thename.png.gz","w+");
fwrite($id,$content);
fclose($id);
?>

So, it is saved in a new path, that cannot be accessed remotely, to avoid unwanted remote-users accessing it.

But there's a script that offers it to the user: show_image.php?image=thename.png

<?php
/* start of the script */
$file_path  = "/new/path/images/";
$file_name = basename($_GET['image']);

if(
file_exists($file_path . $file_name . ".gz") ){
  
header("Content-Encoding: gzip");
  
header("Content-type: image/png");
  
$content = file_get_contents( $file_path . $file_name . ".gz" );
   echo
$content;
} else {
   echo
"The file <i> $file_names </i> does not exist.";
}
/* end of the script */
?>

Maybe you want to control if the client does support the gzip compression, so u can use it:
<?php
if( eregi("gzip",$_SERVER['HTTP_ACCEPT_ENCODING']) ){
 
/* the script outputs the headers and the gz file content, as show_image.php script does */
} else {
   echo
"Your browser does not support gzip compression. Update it.";
}
?>
markus dot amsler at oribi dot org
15-Mar-2005 01:19
I had some troubles with additional numbers being sent in the raw http response. This was because of the chunked encoding of the http response. To disable chunking you can simply send a Content-Length header with the apropriate length set.
fedenuche at gamail dot com
11-Mar-2005 12:03
These headers only works with include, css, js, html, etc. But doesn't work with images files (jpg, gif, etc.)

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
 
To no cache images you can use a trick:

$var = md5(time());
$picture_name = "picture.jpg?var=".$var;

picture.jpg is the name of the file that you don't want cache.
ninj at chello dot fr
04-Mar-2005 11:18
A note about the redirection:
the way to redirect is:
header ("location: target.php");

and it is advised to add, before, a:
header ("HTTP/1.0 307 Temporary redirect");
if the redirection is temporary.

But if the redirection occurs after a the submission of a post form, your browser will ask you if you want to keep the post data for the redirected page! This could be very annoying in the case the redirection is wanted.
The solution to avoid this browser question is not to add any redirection info header at all.

So nor the temporary neither the permanent redirect are to use in the case of a redirection after a post (login page for example).
jukkissh at hotmail dot com
25-Feb-2005 07:04
If you are building a download script and you are afraid of someone exploiting it, I got a solution.

Store the information of the downloadable files into a (SQL or text file) database or even in array variable in the code if your list of files is very static. You should store at least the path & filename and unique id-number. You can be creative when thinking what info to store...

Build a download script that GETs an id number and checks the database for the file with the given id. Then force download for that file, if it's found, otherwise print an error message.

Example use:
http://some.host.com/download.php?id=256
  --> Downloading file...
http://some.host.com/download.php?id=h4x.txt
  --> Error! File not found!

The force-download script can be built many ways stated in this page (in the notes at least). Pick one that forces the download well. It does not need any extra security features, because we got them already.
nickesh at o2 dot pl
22-Feb-2005 04:36
I've tried to use the script written above...
(aarondunlap.com 28-Dec-2004 11:17)
It doesn't work for PDF extenction corectly. The file is downloaded but as PHP file, not PFD.
I've removed: $ctype="application/pdf"; from the script (in: case "pdf").
Now it works, but don't ask me why.
aarondunlap.com
29-Dec-2004 06:17
I just made a function to allow a file to force-download (for a script to disallow file links from untrusted sites -- preventing mp3/video leeching on forums), and I realized that a script like that could potentially be very dangerous.

Someone could possibly exploit the script to download sensitive files from your server, like your index.php or passwords.txt -- so I made this switch statement to both allow for many file types for a download script, and to prevent certain types from being accessed.

<?php

function dl_file($file){

  
//First, see if the file exists
  
if (!is_file($file)) { die("<b>404 File not found!</b>"); }

  
//Gather relevent info about file
  
$len = filesize($file);
  
$filename = basename($file);
  
$file_extension = strtolower(substr(strrchr($filename,"."),1));

  
//This will set the Content-Type to the appropriate setting for the file
  
switch( $file_extension ) {
         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;
     case
"mp3": $ctype="audio/mpeg"; break;
     case
"wav": $ctype="audio/x-wav"; break;
     case
"mpeg":
     case
"mpg":
     case
"mpe": $ctype="video/mpeg"; break;
     case
"mov": $ctype="video/quicktime"; break;
     case
"avi": $ctype="video/x-msvideo"; break;

    
//The following are for extensions that shouldn't be downloaded (sensitive stuff, like php files)
    
case "php":
     case
"htm":
     case
"html":
     case
"txt": die("<b>Cannot be used for ". $file_extension ." files!</b>"); break;

     default:
$ctype="application/force-download";
   }

  
//Begin writing headers
  
header("Pragma: public");
  
header("Expires: 0");
  
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
  
header("Cache-Control: public");
  
header("Content-Description: File Transfer");
  
  
//Use the switch-generated Content-Type
  
header("Content-Type: $ctype");

  
//Force the download
  
$header="Content-Disposition: attachment; filename=".$filename.";";
  
header($header );
  
header("Content-Transfer-Encoding: binary");
  
header("Content-Length: ".$len);
   @
readfile($file);
   exit;
}

?>

This works in both IE and Firefox.
imoore76 at yahoo dot com
15-Dec-2004 05:12
I'd been trying to figure out why I couldn't get Internet Explorer to download a file if I used session_start().  It only happened with certain files.  Here's the undocumented (I couldn't find anything on it) skinny that I've come up with:

If the content type (sent in the Content-type header) is not known to Windows AND the cache-control header contains 'no-store' or 'no-cache' I.E. will error out.  I'm using WindowsXP Home and I.E. 6.0.

Both 'no-store' and 'no-cache' cause the error by themselves and individually.  I've found that always replacing the header with one that excludes those lines works:

<?
header
("Cache-Control: must-revalidate, post-check=0, pre-check=0");
?>

This error would happen when using any PHP function (or anything else for that matter) that may set 'no-store' and/or 'no-cache' in the Cache-control header.  Provided the content type is not known to Windows.

E.g.

<?

// this works
header("Content-type: text/plain");
header("Cache-Control: no-store, no-cache");
echo(
$data);

// this does not work
header("Content-type: foo/bar");
header("Cache-Control: no-store, no-cache");
echo(
$data);

?>

On a side note, content types are kept in the registry under HKEY_CLASSES_ROOT under each file extension.  My downloads were failing with zip files sent as application/zip.  After adding this content type entry under .zip in the registry, I was able to download the file without a problem.  But this was just to test my theory.  Changing the cache-control header was the solution.

I am unsure how/if this effects I.E. on other platforms.
Harry
10-Dec-2004 02:26
Regarding IE choking on PHP-based "downloads" over SSL: problem is that you set "no-cache" and Internet Explorer interprets this to mean "never save to disk. EVER. Even if user explicitly asks for it.". It doesn't distinguish between caching stuff and user-requested save actions.

Dumb, I know. There's at least 3 or 4 Microsoft KB articles related to the issue; google "internet explorer ssl download fails" for more than you ever wanted to know.

Try removing the no-cache directive. And, if like I found, that *STILL* doesn't fix it, check to see if you are using PHP sessions anywhere - if you are, it also sets no-cache, so you need to use session_cache_limiter() to get rid of it there.

I hope this note saves someone the 2 days of hair-pulling I went through.
kbrown at fulchesterunited dot com
07-Dec-2004 05:01
Interesting one this - I have a client who is selling MP3's online. To protect his files, they are obsucrely named, with that name being stored in the Postgresql backend. When the user clicks an MP3 to download, we wrote a script that would dump the file contents and rename etc. to allow the user to download but not guess other files names.

Now, testing was done in Firefox, everything worked just ducky, loaded 'er up in IE. Nada. "File could not be cached..." error and the browser just hung waiting for the transfer to begin. After tinkering, reading these pages, trying the inline/image trick, trying all kinds of things I went on a hard search on Google. Nothing I treid seemed to work.

The following code did not work in IE:

<?
// append .mp3 onto he file name
$name = addslashes(str_replace(" ", "_", $name).".mp3");

// make sure this thing doesn't cache
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
            
// create the necessary MP3 headers, this will type the page as MP3 and force download
$len = filesize("/path/client/mp3/".md5("prefix_$id"));
header("Content-type: audio/mpeg;\r\n");
header("Content-Length: $len;\r\n");
$head = "Content-Disposition: attachment; filename=\"$name\";\r\n";
header($head);
      
// hash the id to create the correct path
$realpath = "/path/client/mp3/".md5("prefix_$id");
      
// spit out the contents of the MP3 file to the browser
readfile($realpath);
?>

As a last resort, I moved the whole system off the SSL to test some other issues (server & platform)
The problem ended up being that the file download was taking place on a Secure Socket Layer. https://somedomain/download_mp3.php rendered the code invalid. Removing the s, to make http://somedomain/download_mp3.php (I know this is not the best way of maintaining security, but after paying for files, people would be pretty upset if they didn't download!) works like a charm.

I have not found out why this problem occurs yet, I've emailed Microsoft and told them about this (rather stupid) oversight on their part - who does testing over there any way!!! I am assuming that the way IE handles certificates and codec processing is not implemented correctly.

So, I hope this helps - if you're having problems forcing http headers in order to force downloads, content-types, if you're using SSL, try checking your code on non-SSL....
jamief at qubesoft dot com
24-Nov-2004 08:19
If you specify a filename with a number in a content disposition header, Internet Explorer 6 tries to be clever with it, and plays with the filename to try to version it. However, if you don't specify a filename, it will happily use the filename in the URL, and will NOT try to version it. Useful if you have filenames like Q-1.1-win32-bin.msi and you don't want them to end up as Q-1(1).1-win32-bin.msi.

Unfortunately Firefox performs similar mangling regardless of how it received the filename.
guvnor
26-Oct-2004 02:38
One that tripped me up for a while...

When I use PHP sessions, the following headers are sent automatically to force the browser not to cache:

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

If you are having trouble with inline PDFs etc., thses may be causing you problems. As per other notes here if you overwrite these headers before outputting your file, the download problems will go away.

header('Cache-Control:');
header('Pragma:');

If you wish to retain the dynamic content, only send the above if the document you are returning is not HTML.

Its well worth examining your own headers (call your PHP script from another script using get_headers() for instance) before going mad trying to fix something - better still compare the headers from your script with headers from a static web page - it might save you hours of time.
marcelo at redcetus dot com
16-Oct-2004 08:01
Redirect the POST like this:

header("HTTP/1.0 307 Temporary redirect");
header("Location: https://myserver.redcetus.com/otherlocation");

if you dont put the 307 status code, the browser will use the GET method even if the original request was a POST

WARNING: the POST method should not be idempotent. If you need to use this, you better take a closer look to your design.
Jonas Forsman <jfo123 (- at -) hotmail dot com>
14-Oct-2004 03:14
I realized that a download script easily could be made to download the whole server in clear code. That is, all your jewels revealed. To avoid this, use a directory where only your downloadable files resides and second, strip the URL from everything that can be used as evil code.

Evil code coould look like this:
download.php?dl=../../index.php or
download.php?dl=../../password.txt

The below example protects you fairly well from these types of attacks.

$dir  = $_SERVER['DOCUMENT_ROOT'].'/download/';
$file = $dir.basename($_REQUEST['dl']);
  
if (isset($_REQUEST['dl']) && file_exists($file) ) { 
   header('Content-type: application/force-download');
   header('Content-Transfer-Encoding: Binary');
   header('Content-length: '.filesize($file));
   header('Content-disposition: attachment;
   filename='.basename($file));
   readfile($file);
} else {
   echo 'No file with this name for download.';
}
smlsml AT g m a i l
14-Oct-2004 02:51
In IE, you must allow inline PDFs to be cached or they will not load. 
Errors like:

File type: Adobe Acrobat Control for ActiveX
and
Internet Explorer was unable to open this site

Are caused by headers like:

header("Pragma: no-cache");
header("Cache-Control: no-store, no-cache");

Use the following and generate a unquie filename:

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate");

$pdf_file = $filename . date("ymdhis") . ".pdf";

header("Content-type: application/pdf");
header("Content-Disposition: attachment; filename=\"$pdf_file\"");
readfile("original.pdf");
dobleerre at estudiowebs dot com
05-Oct-2004 02:50
If you use the content type image/png instead of application/pdf when sending a PDF file on the header you can override the Famous IExplorer Bug ;).

$len = filesize($filename);
header('Content-type: image/jpeg');
header('Content-Length: $len');
header('Content-Disposition: inline;    filename="filename.pdf"');
readfile($filename);

IExplorer tries to open the picture but because of the file-extension .pdf , voila. The acrobat reader plugin opens the file without even ask you for download.

I've read somewhere, "there is no turnarround on this bug, please install service pack 2".

There is always a turnarround :)

Have fun!!!

Ral Raja Martinez. dobleerre@estudiowebs.com
j dot gizmo at aon dot at
28-Sep-2004 05:09
some browsers always reload stylesheets, javascripts and other seldomnly changing files, which causes nasty delays when loading a website (Safari on MacOS is an example)

to tell the browser to keep files in cache for at least a day, you can use
<?php
header
('Expires: ' . gmdate('D, d M Y H:i:s', time()+24*60*60) . ' GMT');
?>
This has the nice sideeffect of telling other browser that never refresh pages to refresh them at least once a day.

PS: i figure this is trivial, but it cost me some headache
bjfield at gmail com
18-Sep-2004 10:25
After upgrading to Windows XP Service Pack 2 (which may or may not be related to this issue), my IE became unable to fetch PDF files served up by the following code:

header('Content-type: application/pdf');
readfile ($filepath);

The error was "file not found," or something to that effect.  This is the solution:

header('Content-type: application/pdf');
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: ' . filesize($filepath));
readfile ($filepath);

Ben
ondrew at quick dot cz
17-Sep-2004 08:19
How to force browser to use already downloaded and cached file.

If you have images in DB, they will reload each time user views them. To prevent this, web server must identify each file with ID.

When sending a file, web server attaches ID of the file in header called ETag.
header("ETag: \"uniqueID\");

When requesting file, browser checks if the file was already downloaded. If cached file is found, server sends the ID with the file request to server.

Server checks if the IDs match and if they do, sends back
header("HTTP/1.1 304 Not Modified");
else
Server sends the file normally.

<?php

  $file
= getFileFromDB();

 
// generate unique ID
 
$hash = md5($file['contents']);
  
 
$headers = getallheaders();
  
 
// if Browser sent ID, we check if they match
 
if (ereg($hash, $headers['If-None-Match']))
  {
  
header('HTTP/1.1 304 Not Modified');
  }
  else
  {
  
header("ETag: \"{$hash}\"");
  
header("Accept-Ranges: bytes");
  
header("Content-Length: ".strlen($file['content']));
  
header("Content-Type: {$mime}");
  
header("Content-Disposition: inline; filename=\"{$file['filename']}\";");

   echo
$file['content'];
  }
  exit();
?>
Jeremy
11-Sep-2004 12:34
I have a download script that uses HTTP headers to download files that are located outside of the web path (on a Linux web server).

I was doing what was posted in the normal examples:

$SRC_FILE = "/path/outside/web/server/test.txt";
$download_size = filesize($SRC_FILE);
$filename = basename($SRC_FILE);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=$filename");
header("Accept-Ranges: bytes");
header("Content-Length: $download_size");
@readfile($SRC_FILE);

But in Internet Explorer, whenever I would click the link to downlad, I could only save the file.  If I tried opening the file directly, it gave an error about not being able to find the temporary file.

After digging around for a while, I found the solution.  You have to set the Cache-Control to private.  So I added the following headers (on top/before the others):

header("Pragma: public");
header("Expires: 0");
header("Cache-Control: private");

And now it works.  I had a different Cache-Control setting before, and it wasn't working.  So I'm certain that setting the Cache-Control to private is what fixed it.

The server environment is Apache 1.3.29 with PHP 4.3.8 running over mod_ssl and using PHP sessions.

Hope this helps someone!
pete at flifl dot com
20-Jul-2004 07:08
Is Unicode, UTF-8 and setcookie, session_start at the same time impossible...?

Well, then you might need this...:

1) Keep your source files in ASCII to avoid the Byte Order Mark (BOM) confusion hell when include'ing or require'ing multiple files and avoid cookies not working because of the "header already sent" thing..

2) use this source:
-------->
<?
   header
('Content-Type: text/html; charset=utf-8');
  
header('Set-Cookie: track=978268624934537');
?>
<html>
   <head>
       <meta http-equiv=Content-Type content="text/html; charset=utf-8" />
<--------

Output through Apache to the browser will be UTF-8 and does not require browser to get page twice and the cookie works.
Your Chinese or cyrillic characters will work and come on out right too, provided you make an input script to put them into mysql using this scheme too.

Seems to me to be the way to use utf-8 with cookies. I hope you like it.

Peter Sierst Nielsen
smgallo at buffalo dot edu
13-Jul-2004 02:08
I've noticed many suggestions to re-order the headers in a script, especially the 'Content-Type: application/octetstream' and 'Content-Type: application/octet-stream' headers.

Note that without adding the "false" option to the header() call, you are simply overwriting all previous headers with the last one called.  For example:

header("Cache-Control: must-revalidate");  // HTTP/1.1
header("Cache-Control: post-check=0, pre-check=0, max-age=0");

will return only

Cache-Control: post-check=0, pre-check=0, max-age=0

Steve
motion at abv dot bg
23-Apr-2004 04:18
In response to Martin Anso and for your benefit, here's an even better routine for forcing the browser to save a file, rather than opening it.

-----------
$f = fopen("file.txt", "rb");
$content_len = (int) filesize($f, "file.txt");
$content_file = fread($f, $content_len);
fclose($f);

$output_file = 'something.txt';

@ob_end_clean();
@ini_set('zlib.output_compression', 'Off');
header('Pragma: public');

header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1
header('Cache-Control: pre-check=0, post-check=0, max-age=0'); // HTTP/1.1
header('Content-Transfer-Encoding: none');
header('Content-Type: application/octetstream; name="' . $output_file . '"'); //This should work for IE & Opera
header('Content-Type: application/octet-stream; name="' . $output_file . '"'); //This should work for the rest
header('Content-Disposition: inline; filename="' . $output_file . '"');
header("Content-length: $content_len");

echo $content_file;
exit();
jp at webgraphe dot com
22-Nov-2003 07:56
A call to session_write_close() before the statement

   header("Location: URL");
   exit();

is recommended if you want to be sure the session is updated before proceeding to the redirection.

We encountered a situation where the script accessed by the redirection wasn't loading the session correctly because the precedent script hadn't the time to update it (we used a database handler).

JP.
manuzhai dot REMOVE dot THIS at php dot net
18-Nov-2003 07:00
If you are using a redirect to an ErrorDocument, you may want to prefix your output with header("HTTP/1.0 200 OK"); to make sure automated clients don't think your file wasn't found.
emmett_the_spam at yahoo dot com
04-Nov-2003 09:17
This is a heads-up not just for php, but for any method of creating a 302 redirect.  Mac IE 5.1.4 (osx) has a serious bug when it comes to the 302.

Say you have a form post page A with action pointing to a submit page B, and the submit page B processes and sends a 302 redirect back to the form page A.  All works fine with that part.  Now hit refresh while on page A, and the last form POST is suddenly delivered to page A!

This can be a very confusing bug to deal with, depending on how your code handles incoming post data.  It could also be potentially very dangerous in terms of data loss, if it occurs within database administration pages (where I ran into it).  What you may want to do is plan your site so that the form page itself never needs to read POST data, and then ignore all POST data.  Either that, or in the location url from your header function add a query argument such as "nopost=1" which, when present, indicates to your page A code to ignore the POST data.

I've tested with Firebird Mac/PC, and IE6 on PC, and those browsers do not exhibit this behaviour.
bMindful at fleetingiamge dot org
01-Jun-2003 06:08
If you haven't used, HTTP Response 204 can be very convenient. 204 tells the server to immediately termiante this request. This is helpful if you want a javascript (or similar) client-side function to execute a server-side function without refreshing or changing the current webpage. Great for updating database, setting global variables, etc.

     header("status: 204");  (or the other call)
     header("HTTP/1.0 204 No Response");
mpriatel at rogers dot com
24-Sep-2002 05:41
Another general fix for many of the problems that occure when trying to prompt a filename to download is to append a '/' to your download URL.  This fixes a NS7 problem where it wants to add a .PHP extension to all filenames.

For example, change:

http://www.url.com/download.php?file=23
to
http://www.url.com/download.php/?file=23
dadarden_nospamola at iti2 dot nospamola dot net
20-Jul-2002 08:38
If you use session_start() at the top of a php script that also has header() calls later in the script for a file download then you must add some form of cache control for IE to work properly.  I use header('Cache-Control: public'); immediately after the code at the top of the script with the session_start() call that verifies that I have a properly logged in user.  That allows the header() and fpassthru() calls to download a file later in the script using IE 5.5 SP2.
dpiper at stens dot com
24-May-2002 08:34
For inline images (JPEG for example):

header('Content-Type: image/jpeg');
header('Content-Disposition: inline; filename=file.jpg);

For attachments (Adobe PDF for example):

header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename=file.pdf);

NOTE: In Internet Explorer, the Content-Disposition header is important, otherwise it will be inline. 'Content-Disposition: attachment' will ALWAYS make IE download it.

NOTE: In Netscape, if you want to force it to be a download (i.e. not inline), use header('Content-Type: application/octet-stream').
Netscape doesn't appear to care about the Content-Disposition header apart from when it's in an email message, then the header controls behaviour as expected.

It's best to be specific about the file you're sending. Don't rely on the interpretation of the browsers in the face of missing or default headers.

Content-Length is good to set for downloads, since it will allow the browser to show a progress meter. It has to be accurate otherwise the browser will stall in downloading.