If you are finding that header() is not working for no obvious reason, make use of the headers_sent() function to drill down to the cause of the problem.
Consider the following scenario,
<?php
// Sign out
signOut();
// Redirect back to the main page
header("Location: http://mysite.com/mainpage");
?>
If for some reason, the header() call to redirect is not working, there won't be any error messages, making it difficult to debug. However, using headers_sent(), you may easily find the source of the problem.
<?php
// Sign out
signOut();
/*
* If headers were already sent for some reason,
* the upcoming call to header() will not work...
*/
if(headers_sent($file, $line)){
// ... where were the mysterious headers sent from?
echo "Headers were already sent in $file on line $line...";
}
// Redirect back to the main page
header("Location: http://mysite.com/mainpage");
?>
As the documentation states, "header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP." In the above debugging solution, you will find out exactly where any of these problematic output or blank lines exist. You should then be able to resolve the issues with much more ease than if you hunted for the problems aimlessly.
header
(PHP 4, PHP 5)
header — Sendet einen HTTP-Header in Rohform
Beschreibung
header() wird zum Senden von HTTP-Anfangsinformationen (Headern) im Rohformat benutzt. Weitere Informationen über die HTTP Header finden Sie in der » HTTP/1.1 Spezifikation.
Beachten Sie, dass Sie die Funktion header() aufrufen müssen, bevor Sie irgendeine andere Art von Ausgabe (seien es normale HTML-Tags, Leerzeilen in einer Datei oder von PHP) zum Client schicken. Es handelt sich hier um einen typischen Fehler, der zum Beispiel auftritt, wenn Sie Code mittels include() oder require() oder einer anderen Dateizugriffs-Funktion einlesen, die Leerzeichen oder Leerzeilen enthalten, die ausgegeben werden, bevor header() aufgerufen wird. Das gleiche Problem kann auch auftreten, wenn Sie eine Datei verwenden, in der HTML und PHP vermischt wurden.
<html>
<?php
/* Dies wird einen Fehler provozieren. Beachten Sie die vorangehende Ausgabe,
* die vor dem Aufruf von header() erzeugt wird */
header('Location: http://www.example.com/');
?>
Parameter-Liste
- string
-
Der Header-String.
Es gibt zwei Spezialfälle von Header-Aufrufen. Der erste ist ein Header, der mit "HTTP/" beginnt (ob Groß- oder Kleinschreibung ist nicht relevant) und zum Herausfinden des zu sendenden HTTP Statuscodes verwendet wird. Wenn Sie zum Beispiel Apache konfiguriert haben, um ein PHP Skript zum Bearbeiten von Anforderungen fehlender Dateien (mittels der ErrorDocument-Direktive) zu verwenden, möchten Sie bestimmt sicherstellen, dass Ihr Skript den passenden Statuscode generiert.
<?php
header("HTTP/1.0 404 Not Found");
?>Wenn FastCGI verwendet wird, muss folgendes benutzt werden um einen 404 Response Header zu senden:
<?php
header("Status: 404 Not Found");
?>Der zweite Spezialfall ist der "Location:" Header. Es wird nicht nur der Header an den Browser geschickt, sondern auch ein REDIRECT (302) Statuscode, wenn nicht bereits der 201- oder ein 3xx-Statuscode gesendet wurde.
<?php
header("Location: http://www.example.com/"); /* Browser umleiten */
/* Stellen Sie sicher, dass der nachfolgende Code nicht ausgefuehrt wird, wenn
eine Umleitung stattfindet. */
exit;
?> - replace
-
Der optionale Parameter replace gibt an, ob der Header einen vorhergehenden gleichartigen Header ersetzten soll, oder ob ein zweiter Header des selben Typs hinzugefügt werden soll. Standardmäßig wird ersetzt; wenn Sie als zweites Argument FALSE übergeben, können Sie so mehrere Header desselben Typs erzwingen. Zum Beispiel:
<?php
header('WWW-Authenticate: Negotiate');
header('WWW-Authenticate: NTLM', false);
?> - http_response_code
-
Forciert einen HTTP-Response-Code des angegebenen Wertes. Dieser Parameter hat nur einen Effekt, wenn string nicht leer ist.
Rückgabewerte
Es wird kein Wert zurückgegeben.
Changelog
| Version | Beschreibung |
|---|---|
| 4.4.2 and 5.1.2 | Die Funktion unterbindet das gleichzeitige Versenden mehrerer Header zum Schutz gegen Header-Injection-Angriffe. |
| 4.3.0 | Der Parameter http_response_code wurde hinzugefügt. |
| 4.0.4 | Der Parameter replace wurde hinzugefügt. |
Beispiele
Beispiel #1 Download-Dialog
Wollen Sie den Benutzer auffordern, die von Ihnen gesendeten Daten wie z.B. eine generierte PDF Datei zu speichern, können Sie den Header » Content-Disposition verwenden, um einen empfohlenen Dateinamen anzubieten und den Browser zu zwingen, den Dialog zum Speichern anzuzeigen.
<?php
// Wir werden eine PDF Datei ausgeben
header('Content-type: application/pdf');
// Es wird downloaded.pdf benannt
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// Die originale PDF Datei heißt original.pdf
readfile('original.pdf');
?>
Beispiel #2 Caching-Direktiven
PHP-Skripte erzeugen oft dynamische Inhalte, die weder vom Browser noch von irgendeinem Proxy zwischen Web-Server und Client-Browser gepuffert ("gecached") werden sollen bzw. dürfen. Bei vielen Proxies und Browsern kann das Cachen wie folgt unterbunden werden:
<?php
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Datum in der Vergangenheit
?>
Hinweis:
Es könnte auch sein, dass Ihre Seiten nicht zwischengespeichert werden, auch wenn Sie obigen Header nicht ausgeben. Es gibt eine Anzahl Optionen, welche die Benutzer in ihren Browser einstellen können, um das standardmäßige Caching-Verhalten zu verändern. Durch das Senden obiger Header sollten Sie jedwede Einstellungen, die die Ausgabe Ihres Skriptes zwischenspeichern würden, außer Kraft setzen.
Weiterhin können Sie session_cache_limiter() und die Konfigurationsoption session.cache_limiter verwenden, um die korrekten Header bezüglich Caching automatisch generieren zu lassen, sollten Sie Sessions benutzen.
Anmerkungen
Hinweis:
Header sind nur dann zugänglich und werden nur dann gesendet, wenn die genutzte SAPI sie unterstützt.
Hinweis:
Sie können die Limitierung, dass Header vor jeglicher Ausgabe gesendet werden müssen, umgehen, indem Sie Outputbuffering benutzen, mit dem Overhead, dass Ihre gesamten Ausgaben an den Browser auf dem Server gepuffert werden, bis Sie diese senden. Sie können dies tun, indem Sie in Ihrem Skript ob_start() und ob_end_flush() verwenden, oder indem Sie die Konfigurationsdirektive output_buffering in der php.ini bzw. in den Server-Konfigurationsdateien auf On setzen.
Hinweis:
Die HTTP-Status-Headerzeile wird immer die erste zum Client gesendete sein, egal ob der aktuelle header()-Aufruf der erste ist oder nicht. Der Status kann mittels header() jederzeit mit einer neuen Statuszeile überschrieben werden, sofern die HTTP-Header noch nicht gesendet wurden.
Hinweis:
Der Microsoft Internet Explorer 4.01 hat einen Bug, der diese Funktionalität verhindert, und es gibt keinen Workaround. Auch im Microsoft Internet Explorer 5.5 existiert ein Bug, der dies behindert, dieser kann jedoch mittels eines Upgrades auf Service Pack 2 oder höher behoben werden.
Hinweis: Ist safe mode aktiviert, wird die UID des Skriptes dem realm-Teil des Headers WWW-Authenticate (für HTTP Authentifizierung verwendet) hinzugefügt, sollten Sie diesen Header setzen.
Hinweis:
HTTP/1.1 verlangt einen absoluten URI inklusive dem Schema, Hostnamen und absoluten Pfad als Argument von » Location:, aber manche Clients akzeptieren auch relative URIs. Gewöhnlich können Sie mittels $_SERVER['HTTP_HOST'], $_SERVER['PHP_SELF'] und dirname() aus einem relativen Link einen absoluten URI selbst erstellen:
<?php
/* Redirect auf eine andere Seite im aktuell angeforderten Verzeichnis */
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$extra = 'mypage.php';
header("Location: http://$host$uri/$extra");
exit;
?>
Hinweis:
Die Session-ID wird nicht mit dem Location-Header übermittelt, selbst wenn session.use_trans_sid eingeschaltet ist. Sie muss daher manuell durch Verwendung der SID-Konstante hinzugefügt werden.
Siehe auch
- headers_sent() - Prüft, ob oder wo die Header bereits gesendet wurden
- setcookie() - Sendet ein Cookie
- The section on HTTP authentication
header
12-Jul-2007 04:30
10-Jul-2007 11:40
It says above:
"Remember that header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP. ..."
But check this out: this code runs fine: (take a closer look!)
...
put your html stuff here
...
Then this:
if(isset($_POST['ok'])) {
if(isset($_POST['yesNo']))
header('Location: http://www.google.com/');
else
header('Location: http://www.yahoo.com/');
}
You can see it live at:
http://labella-pizza.com/header.php
01-Jul-2007 06:27
This code works perfectly on explorer 6. It displays NOTHING on firefox 2. I tried every combination in this page and I always get the same thing in firefox. I am running on iis5, windows xp, php 5.2.3
Why firefox is not capable of displaying the pdf and explorer has no problems???
$filename='test.pdf';
$len = filesize($filename);
header('Content-type: application/pdf');
header('Content-Length: $len');
header('Content-Disposition: inline; filename="test.pdf"');
readfile($filename);
28-Jun-2007 02:02
I thought I'd share this nifty little code that works with headers.
In many situations, you may be asked to obfuscate actual downloadable files location, or perhaps only allow logged users to download a file.
In this example, I'm offering alternate download link for those who use old Internet Explorer; feel free to remove the whole alternate part as needed.
This is translated fragment of actual used code.
<?php
echo '<a href="./provide_file.php?file='.$id.'relid='.$url.'">Direct download link</a>'. // COMMENT: call provide_file.php with ID of the file and relation identifier (relid) that is used to authenticate user.
//output continues here:
'<br /><br />
If your browser doesn't offer you the file for saving, please try the following link instead:
'.(IsSet($_REQUEST['dl'])?'<a href="'.$FileRecord['filename'].'">DOWNLOAD FILE</a>':
'<a href="./?mat='.$id.'&dl=1">Go to download</a>'); // the trick with "dl" parameter allows us to count number of downloads (we store that into database when dl is present in the URL).
?>
Now, what does provide_file.php do?
First, we have to somehow get the filename:
$query1 = "select filename from files where file_id = ".intval($_REQUEST['file']);
(call mysql_query($query1) to make this query. If it fails or finds zero entries, abort execution (die(1);) )
If a record is found (and eventually the user is authenticated using the relid string), the following sequence will prompt the user to download the file:
<?php
$res = mysql_fetch_row($q); // Get the record from DB.
$tmp1 = $res[0]; // the following sequence extracts the filename from the complete path stored in filename column:
while (strpos($tmp1,"/")!==false) // originally, $tmp1 is something like /files/filename.zip; while there are slashes, cut the first char:
{
$tmp1 = substr($tmp1,1); // cut the first char.
}
Header ("Content-type: application/x-zip-compressed"); // assuming the file is zip
header('Content-Disposition: attachment; filename="'.$tmp1.'"'); // This ensures prompt "What do you want to do with the file", with "Save to disk" preferred.
$tmp = file_get_contents($res[0]); // read the contents of the file into a variable...
echo $tmp; // ... and output it after the headers.
?>
If you have multiple options of file types, you need to store the type or extract it from the filename (substr($filename,-3) gives you the extension); this can also be used for images if you want to block hotlinking - just set: src="provide_image.php?..." which works similarly and has Content-Type: image/gif or image/png or image/jpg as content type; in the case of images, don't use Content-disposition header; just read the file and echo it.
26-Jun-2007 03:28
Javascript vars with php - To use serverside variables in a html document (unfortunately html files on IIS can not be simple associated with php) as client-side javascript, you can send a javascript header in a javascript-container that contains a php
script and generate the js vars.
markup: script src="serverdate.php"
serverdate.php:
<?php
error_reporting(0);
header("content-type: application/x-javascript");
echo 'var year = "'.date("Y").'";'; // var year = 2007;
//echo 'alert(" y:"+parseInt(year))';
?>
21-Jun-2007 06:16
In case anyone else is having trouble:
using a web-server behind the pound load balancer, we found that trying to redirect to https://example.com for requests to http://example.com were getting into an infinite loop because pound, by default, 'fixes' changes of protocol for you. You want to set RewriteLocation to 0 to turn this behaviour off.
15-Jun-2007 10:50
Even with no-caching tags, images displayed on the page may be cached.
You can prevent the cached image from displaying by appending a random number to the SRC in the IMG tag
for example:
<img src="image_name.jpg?<?=rand(100,999);?>" />
the "?" and everything following it make the browser re-request the image.
10-Jun-2007 09:11
This is a good function to send an image to the browser having the path.
function PE_img_by_path($PE_imgpath = "")
{
if (file_exists($PE_imgpath)) {
$PE_imgarray = pathinfo($PE_imgpath);
$iconcontent = file_get_contents($PE_imgpath);
header("Content-type: image/" . $PE_imgarray["extension"]);
header('Content-length: ' . strlen($iconcontent));
echo $iconcontent;
die(0);
}
return false;
}
03-Jun-2007 12:04
This is the Headers to force a browser to use fresh content (no caching) in HTTP/1.0 and HTTP/1.1:
<?PHP
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' );
?>
27-May-2007 01:21
You can use the Header command to force a browser to use fresh content (no caching).
However, this only works for the HTML code your code generates. When you have updated images for example (with the same filename) then there's a chance that these are still cached.
The easiest way to solve this problem I found is changing:
<?
print "<img src='yourfile.jpg'>";
?>
into:
<?
print "<img src='yourfile.jpg?".time()."'>";
?>
This adds an unique number to the url and wont hurt at all.
17-May-2007 08:58
When not using "exit;" after the header("Location: ..."), the execution of the script continue below the header, until the end is reached. After this, it's doing the "Redirect".
if($error)
{
header("Location: http://url.com");
}
//the script continue with or without an error and execute some function that we do not want...
delete($file);
It could be important to put exit always after a Redirect.
if($error)
{
header("Location: http://url.com");
exit;
}
//the script continue only without error. It will execute what we want to do.
11-May-2007 09:12
I use this to stream and download media files:
<?php
if (isset($_GET['action']) && isset($_GET['file']) && file_exists(stripslashes($_GET['file']))) {
$file = stripslashes($_GET['file']);
$base = basename($file);
$extension = "";
$i = strlen($base);
while (substr($base, $i, 1) != ".") {
$extension = substr($base, $i--, 1) . $extension;
}
switch (strtolower($extension)) {
case "wmv":
case "mpg":
case "mpeg":
case "avi":
case "mov":
include "../mysql_settings.php";
$con = mysql_connect($mysql_host, $mysql_user, $mysql_password) or die(mysql_error());
mysql_select_db($mysql_database) or die(mysql_error());
switch ($_GET['action']) {
case "download":
$query = "INSERT INTO downloads (file,ip_address) VALUES ('". addslashes($file) ."','". $_SERVER['REMOTE_ADDR'] ."')";
mysql_query($query) or die(mysql_error());
mysql_close($con);
$message = "Hello Michael,<br><br>\n"<b>". $file ."</b>" has been downloaded from your website at ". date("g:i:s A - M j, Y") .".<br><br>\nIP Address: <b>". $_SERVER['REMOTE_ADDR'] ."</b>";
email($file ." has been downloaded from your website!", $message);
// tell the browser how big the file is so the user has a percentage complete progress bar
header("Content-Type: ". $extension);
header("Content-Length: ". filesize($file));
header("Content-Disposition: attachment; filename=\"". str_replace(" ", "%20", $base) ."\"");
readfile($file);
break;
case "stream":
$query = "INSERT INTO stream (file,ip_address) VALUES ('". addslashes($file) ."','". $_SERVER['REMOTE_ADDR'] ."')";
mysql_query($query) or die(mysql_error());
mysql_close($con);
$message = "Hello Michael,<br><br>\n"<b>". $file ."</b>" is or has been streamed from your website at ". date("g:i:s A - M j, Y") .".<br><br>\nIP Address: <b>". $_SERVER['REMOTE_ADDR'] ."</b>";
email($file ." is or has been streamed from your website!", $message);
header("location: ". str_replace(" ", "%20", $base));
break;
default:
// action is invalid
break;
}
break;
default:
// invalid file extension
break;
}
}
?>
10-May-2007 06:00
You may find that you want to process a PHP file, and have the browser treat the output as an XML file. Unfortunately, most browsers will only do this if the file extension is ".xml"
In those situations where you can't/don't want to change your apache config to put XML files through the PHP processor, you can use the header function to change the output to something the browser will recognise as XML:
header('Content-Type: text/xml');
header('Content-Disposition: inline; filename=sample.xml');
(The inline portion helps ensure the browser renders the page itself, instead of prompting the user to download the page)
~D
02-May-2007 10:53
More on downloading files...
Here's a slight improvement to the method provided by Nick Sterling.
I tried this and it works great, but using fopen ran into memory limit problems.
By using readfile($filename), I solved this problem without having to change the ini settings.
readfile() reads a file and writes it to the output buffer.
Here's my version of the code:
<?php
$filename = "theDownloadedFileIsCalledThis.mp3";
$myFile = "/absolute/path/to/my/file.mp3";
$mm_type="application/octet-stream";
header("Cache-Control: public, must-revalidate");
header("Pragma: hack"); // WTF? oh well, it works...
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($myFile)) );
header('Content-Disposition: attachment; filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary\n");
readfile($myFile);
?>
02-May-2007 01:24
A brief amendment to the comment by Brandon K [ brandonkirsch uses gmail ]...
The issue with IE downloading dynamic files produced on an SSL encrypted connection with the no-cache Cache Control settings affects most types of files, including .csv, .txt and all Office document types in addition to .pdf files.
This problem affects IE7 as well as IE6 and IE5.5. I haven't found a user using anything earlier so I cannot confirm if it is happening in earlier IE browsers, though I suspect it is. As far as my users have reported, this is not an issue in FF, Opera or Safari.
There is more information on this issue at http://support.microsoft.com/kb/812935 in addition to the many other MSDN reports on this same issue.
25-Apr-2007 09:34
I just lost six hours of my life trying to use the following method to send a PDF file via PHP to Internet Explorer 6:
<?php
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="downloaded.pdf"');
readfile('original.pdf');
?>
When using SSL, Internet Explorer will prompt with the Open / Save dialog, but then says "The file is currently unavailable or cannot be found. Please try again later." After much searching I became aware of the following MSKB Article titled "Internet Explorer file downloads over SSL do not work with the cache control headers" (KBID: 323308)
PHP.INI by default uses a setting: session.cache_limiter = nocache which modifies Content-Cache and Pragma headers to include "nocache" options. You can eliminate the IE error by changing "nocache" to "public" or "private" in PHP.INI -- This will change the Content-Cache header as well as completely remove the Pragma header. If you cannot or do not want to modify PHP.INI for a site-wide fix, you can send the following two headers to overwrite defaults:
<?php
header('Cache-Control: maxage=3600'); //Adjust maxage appropriately
header('Pragma: public');
?>
You will still need to set the content headers as listed above for this to work. Please note this problem ONLY effects Internet Explorer, while Firefox does not exhibit this flawed behavior.
21-Apr-2007 05:25
Refreshing/redirecting a/to a Page Using the "header" command in PHP.
I found the following code to help me refresh a page or really redirecting to a page after a certain number of secionds. (I'm using php 5.x)
<?php
print(" <p align=\"center\"> User Not Found</p><br><br>"); // err msg
header('Refresh: 3; url=index.html'); // waits 3 seconds & sends to homepage
?>
Explaination:
1. I used php to set up a simple html message as to what is happening
2. with header, there is a command, "Refresh" and here is how it works:
a. 'Refresh: 3; -- start all header commands with a single quote
b. Refresh requires a parameter that is in seconds which tells the browser
to refresh after this time period has passed.
c. The ; is necessary to separate it from the rest of the Refresh parameters
d. Refresh must be uppercased - I think
3. After the ; put the url= command and put in your desired web page
4. Location doesn't work! with in this context.
5. Order matters. Refresh first, then it's second parameter, url=
--Allen
20-Mar-2007 01:52
Response to hervard at gmail dot com (18-Apr-2006 06:53)
The code you gave for preventing the message appearing with the back button did not work for IE7, but changing the line:
$offset = 60 * 60 * 24 * -1;
to
$offset = 60 * 60 * 24 * 1;
makes it work OK, with my set up there's no issues with serving cached versions of the page.
<?php
// Original code found at http://www.mnot.net/cache_docs/
// thanks to hervard at gmail dot com for this solution to browser back button problems
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);
?>
I've tested this in a standalone version of IE6
Also tested in IE7, Firefox 1.5, Opera 7.54
18-Mar-2007 07:07
This is an Example of an UID based download Platform:
document 1:
<?php
if($_GET['verify']==broken){
die('Please do not leech.');
}
if($_GET['accepted']==1){
//Generate a password:
$password ="";
$pool .= "123456789";
srand ((double)microtime()*1000000);
for($index = 0; $index < 200; $index++)
{
$password .= substr($pool,(rand()%(strlen ($pool))), 1);
}
//use on a windows server:
passthru('echo '.$passwort.' >> verify.pajaxtxt');
//Use on a linux server:
//$fneu = fopen("verify.pajaxtxt","w+");
//fputs ($fneu,$password);
//fclose ($fneu);
header('location: document2.php?UID='.$password);
}
else
{
echo '<a href="?accepted=1">I accept (Insert what do you think)</a>';
}
?>
document2:
<?php
//Check if $get exist:
if($_GET){
//Parameter for UID:
$s = $_GET['UID'];
//file definition:
$sa = file("verify.pajaxtxt");
for($i=0; $i<count($sa); $i++)
{
if (ereg($s, $sa[$i]))
{
//if verification code exist in the file:
echo "Finished!";
//end.
//
}
}
}
else
{
//Go back if the verification code does not exist in the file
header('location: document1.php?verify=broken');
//end.
}
?>
The file verify.pajaxtxt must deleted every day!
14-Mar-2007 09:12
This is simple but was driving me nuts: the '&' delimiter is usually expressed as & in HTML, but will not work in a header.
Does not work:
<?php
header("Location: processes/add.php?project=$project&customerID=$customerID");
?>
Works:
<?php
header("Location: processes/add.php?project=$project&customerID=$customerID");
?>
02-Mar-2007 12:18
I had a site where a flash swf was importing xml
The xml was parsed as php by adding
AddType application/x-httpd-php .xml to the .htaccess file of that directory.
All was working well the php was outputting the xml that the swf needed, but then we moved the site to an https domain, and internet explorer 7 stopped being able to read the xml (firefox still worked correctly).
by adding
header('Pragma: private');
header('Cache-control: private, must-revalidate');
to the xml (parsed as php) page, the problem was cured, and ie7 was able to run the swf on the https domain.
To download anything I tried both the solutions by "Fred p" and by "info /at/ storytellermusic /dot/ nl" but neither worked.
I found that Fred P's solution worked in no browser!
And found that the info solution worked in everything but IE!
In the end I found that the solution which allows you to download any file in any browser is the one by:
Nick Sterling
25-Jul-2006 06:17
Here's his code again if you can't find it:
<?php
$mm_type="application/octet-stream";
header("Cache-Control: public, must-revalidate");
header("Pragma: hack");
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($url)) );
header('Content-Disposition: attachment; filename="'.basename($url).'"');
header("Content-Transfer-Encoding: binary\n");
$fp = fopen($url, 'rb');
$buffer = fread($fp, filesize($url));
fclose ($fp);
print $buffer;
?>
Worked in Firefox 2.0.0.1, IE 7 and Opera 9. The downloader even knows the size of the download which isn't the case with many solutions. Cheers!
09-Feb-2007 10:22
We experienced some very odd problems with an iframe setup that used header('Location:'); somewhere in the flow.
On our Mac machines (Safari, Firefox) the whole setup just stalled, but on our XP machines (IE, Firefox, Opera) it worked just fine!
The solution to the problem was to use exit(); after the header('Location:');. Don't know if this only is a problem for our setup. Just a little reminder to always use exit(); ! It will save you alot of gray hairs!!!
28-Dec-2006 02:31
If you wish to force a file to be downloaded and saved, instead of being rendered, remember that there is no such MIME type as "application/force-download". The correct type to use in this situation is "application/octet-stream", and using anything else is merely relying on the fact that clients are supposed to ignore unrecognised MIME types and use "application/octet-stream" instead (reference: Sections 4.1.4 and 4.5.1 of RFC 2046).
28-Dec-2006 02:15
As an alternative to using header('Content-Type: ****') on almost every page, the default Content-Type (and character set, too, if needed) can be set in php.ini under the "default_mimetype" and "default_charset" entries.
15-Nov-2006 11:00
Careful! This line of code cause IIS to crash on PHP 4.3.4 and maybe others.
<?
header("location:/currentfile.php");
// forward slash causes crash
// currentfile.php is the exact basename of this file
?>
Learned this the hard way.
07-Nov-2006 06:43
When messing with custom headers, it is extremely helpful to make sure that they are actually sent to the server. Nothing is more frustrating than discovering that some custom header was improperly formatted or not sent at all. There is an Internet Explorer add-in called ieHTTPHeaders that I've found to be immensely useful:
http://www.blunck.info/iehttpheaders.html
Firefox (and Mozilla) users can use an equivalent tool/plugin called LiveHTTPHeaders:
http://livehttpheaders.mozdev.org/
12-Oct-2006 02: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)
27-Sep-2006 06: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
19-Aug-2006 11: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.
17-May-2006 11: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
04-May-2006 10: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.
30-Apr-2006 11: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.
18-Apr-2006 08: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.
23-Mar-2006 03: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.
15-Feb-2006 02: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. :)
12-Oct-2005 01: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).
25-Feb-2005 12: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.
21-Feb-2005 09: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.
28-Dec-2004 11: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.
14-Dec-2004 10: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.
10-Dec-2004 07: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.
06-Dec-2004 10: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....
24-Nov-2004 01: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.
25-Oct-2004 08: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.
16-Oct-2004 02: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.
14-Oct-2004 09: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.';
}
14-Oct-2004 08: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");
05-Oct-2004 08: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!!!
Raúl Raja Martinez. dobleerre@estudiowebs.com
28-Sep-2004 11: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
18-Sep-2004 04: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
17-Sep-2004 02: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();
?>
10-Sep-2004 06: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!
20-Jul-2004 01: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
12-Jul-2004 08: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
22-Apr-2004 10: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();
22-Nov-2003 12: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.
18-Nov-2003 12: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.
04-Nov-2003 02: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.
01-Jun-2003 12: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");
23-Sep-2002 11: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
20-Jul-2002 02: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.
24-May-2002 02: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.