PHP generated XML not downloadable in IE - php

I have an XML feed generated by a PHP script with mod_rewrite running. IE opens the feed OK but I cannot get it to save the file to disk or import to Excel.
The file I am trying to access in the url is similar to:
http://domain.com/download/export.xml
This gets written to a download PHP script and is not actually an XML file.
After setting the headers as below, it is still not possible to get IE to save the file. Also, as it is not capable of displaying the plain source, copy & paste into notepad will not work because of various styling changes IE makes to the XML.
<?php
header("Content-type: text/xml");
header('Content-Disposition: attachment; filename="QuoteExport_'.$quoteDate.'_'.$quoteSlot.'.xml"');
Does anyone know the solution? Thanks.
Edit:
Thanks for the help so far. I have tried a combination of these headers and still not getting the results I want. No matter what I set, IE always displays it in-line in the browser with no option to download. The save functionality also still does not work either. Any other ideas how to force IE to save the XML as a file by using headers?
Edit2: The state now is that IE gives the option to open/save but whilst trying to save, there is a popup saying that it is "Unable to open this internet site.". Yet at the same time, the open option displays the content with no issues.

To force ie and other browsers to download you have to specify specific headers like in this example :
header("Content-disposition: attachment; filename=\"".$fileName . ".csv\"");
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
header("Pragma: no-cache");
header("Expires: 0");
Other wise if you use text/xml, ie thinks he knows how to display it and does so.

You need to set a session parameter for some versions of IE. Assuming you have $my_file_name and $my_file_contents set, here's how it would look:
if(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
session_cache_limiter("public");
}
session_start();
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename=".' $my_file_name '. "');
print($my_file_contents);
(Adapted from this article)

Try setting the content-type to application/xml or application/force-download

After spending much time changing the headers, I went for a slightly different approach. No matter what I was setting them to, it just would not work for me.
Anyway the solution implemented was:
When a client lands on the URL (http~://my-domain.com/downloads/export.xml), the PHP script generates a real XML file in the same location as the request-URI and saves it on the server, schedules it for a delete job in the future and then effectively refreshes the browser. Then, the mod-rewrite rules on the second request serve the actual file and then IE can use the file correctly. A future request then causes the cycle to run again.
Slightly round about way of doing things, but it was the fastest method. Also, I had the ability to schedule file operations from another part of the application making life much easier for clean-up!
Anyway thanks for the help and I did learn a thing-or-two about header options.

Related

Best practices & syntax for serving HTML headers with PHP for .mp3 or .pdf file downloads (step 1: remove BOM)

I'm serving an .mp3 file (as a download dialog box, where the end-user can rename and save the file). There are a lot of conflicting reccomendations for this on the forums and even within the comments on the php manual.
I hope to solve my issue and create here a reference on how to best execute this goal: i.e. a clear "best practices summary" usable by begining php coders on the most efficient way to get started delivering downloadable files.
I would really appreciate it if you'd check over the code here and suggest your corrections--this is part of a site that helps artists self-publish music, books, etc.
To test, I am uploading this to my server as index.php; perhaps that is also part of the problem.
The current status of this script is that the browser hangs a bit and then loads the binary as text into the browser display window.
I've thought at many points that my problem was syntax in the important "Content-Length" header, or the order of my headers, so I've tried several versions of all that, but none cut off the download.
Here is the exact code that I am now trying on my own,
where ####### means 7 numbers (the actual file size in bytes),
and everything else should be clear:
<?php
header('Server: ');
header('X-Powered-By: ');
header('Cache-Control: no-cache');
header('Expires: -1');
header('Content-Description: File Transfer');
header('Content-Transfer-Encoding: binary');
header('Content-Type: audio/mpeg');
header('Content-Disposition: attachment;filename="suggested-save-name.mp3"');
header('Content-Length: #######["exact-file-name.mp3"]');
#readfile("http://full-public-url.com/exact-file-name.mp3");
ob_clean();
flush();
exit;
?>
Returns these response headers:
>X-Powered-By:PHP/5.4.xy
>Vary:negotiate
>Transfer-Encoding:chunked
>TCN:choice
>Server:Apache
>Keep-Alive:timeout=2, max=200
>Date:Fri, 10 May 2013 16:mm:ss GMT
>Content-Type:text/html
>Content-Location:filename.php
>Connection:Keep-Alive
I hope it is a simple error (or more than one) in the syntax of my script, or the way I created and saved the .php file. I have verified the settings are at default, php is up to date, and there are no .htaccess issues. I have carefully made sure there are no extra spaces at the end of this file, as well as all other files in the web directory, and as well I've tried the script with and without the closing ?>.
Thank you in advance
...
Best script after reading Answers, below:
<?php
$file-variable=('./exact-file-name.mp3')
$size=filesize($file-variable);
header('Server: ');
header('X-Powered-By: ');
header('Cache-Control: no-cache');
header('Expires: -1');
header('Content-Type: application/octet-stream'); //will need to redirect for older IE
header('Content-Disposition: attachment; filename="suggested-save-name.mp3"');
header('Content-Length: "$size"');
#readfile("$file-variable");
ob_clean();
flush();
exit;
Successful response headers, from the better script (& after removing the UTF-8 BOM):
>Vary:negotiate
>TCN:choice
>Server:Apache //How can I hide this?
>Keep-Alive:timeout=2, max=200
>Expires:-1
>Date:Sat, 11 May 2013 12:mm:ss GMT
>Content-Type:audio/mpeg
>content-transfer-encoding:binary
>Content-Location:filename.php //I would also like to obfuscate this
>Content-Length:#######
>Content-Disposition:attachment; filename="suggested-save-name.mp3"
>Content-Description:File Transfer
>Connection:Keep-Alive
>Cache-Control:no-cache
Step 1 would be to remove all nonsense headers that are not defined in the HTTP standard.
This will eliminate Content-Description and Content-Transfer-Encoding. Both are useless at best, and might interfere with normal browser operations in the worst case.
Step 2 is to optimize the file delivery. Do not download the MP3 with a HTTP request, access the FILE on the server. Do not use a URL, do use a file path. If the MP3 is right next to your script, this will work:
readfile('./exact-file-name.mp3');
At this point you should usually end up with a working download. If not, try changing the Content-Type to something more generic. audio/mpeg might trigger the audio player in some browser, application/octet-stream should work in most browsers but older Internet Explorer, which do inappropriate content sniffing on certain mime types including this one. application/x-ms-download is supposed to work then.
Make sure the header is sent. PHP does not send HTTP headers if the HTTP body was already startet, and any whitespace including the UTF-8 BOM will trigger body output.
Some comments on your "final" headers in general:
Content-Length: should only have an integer stating the length in bytes, nothing more. Especially no mention of any filename in square brackets.
Content-Transfer-Encoding and Content-Description are still useless.
Content-Location is not needed. If you don't know what it does, omit it. Obfuscation will not work here, the browser needs to know the URL he is accessing. Duplicating this URL in this header does not change anything, obfuscating it will likely break things somewhere.
The two headers you really only need for a download are: Content-Type and (if you want to pre-define a filename for the user) Content-Disposition.

Protecting / serving a file via readfile(), force download?

I'm trying to make a simple script that does two things:
Serves up a file and hide's it's destination
Has a download counter
Now, I'm doing this in the wordpress environment, but this question isn't completely wordpress-related so I figured I would ask here.
Basically, the way I have it set up, currently, is I have a link that when you click it sets a $_['GET'] which is then checked if is set. If it is set, the download file is served.
the link: Click here!'
the $_['GET'] code: http://pastebin.com/93nD43gA
There is a bit of wordpress jargon in the code, but basically it's checking a download count user_meta and if it's > 0, serveFile() is called.
The main problem I'm having here is, if I click the link, readfile() loads the actual file contents INTO the window (garbled text). If I add a target=_blank to the <a> it opens a new browser window and loads the contents INTO the window.
This approach seemed to work perfectly fine when I was doing it as stand-alone php files. My main issue is that I need to keep the wordpress space so I can call functions, etc. associated with it.
I have tried using the $_['GET'] on both the self page, another page with a custom template (the code in the pastebin above), and as a stand-alone php file. Both the first two options load the file INTO the window. The third doesn't preserve wordpress functions, even if I include blog-header.php.
Can anyone point me in to the right direction of how to get the file to force download and not load INTO the window?
You need to set the appropriate header for whatever the file type is. For example, if readfile always serves, PDFs, it should be done like this:
// disable browser caching -- the server may be doing this on its own
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Type: application/pdf');
//forces a download
header("Content-Type: application/force-download");
header('Content-Disposition: attachment; filename=filename.pdf');
readfile($file);
Keep in mind that header only works if you have not sent any data in the request at all including whitespace.
The 'garbled' text is what you want however besides that you have to set a mime. This can be accomplished by simply setting a header, e.g. header("Content-Type: image/png");
If the file mimes will vary (e.g. pdf, doc, png, etc) you should look into finfo extension. With it you can get the full and correct mime of the file
<?php
$finfo = new \finfo(FILEINFO_MIME);
$mime = $finfo->file('path/to/file', FILEINFO_MIME_TYPE);
header("Content-Type: $mime");
As noted - headers can be set only if no write to output has been done (no echo's, print, etc. Output buffering could help you here).

PHP force File downloads without applying headers

Is there a way to force any given file specified without giving the headers for it? What I have is a handful of different docs and other file types that a client wishes to offer up as download upon request, however they want to obscure the file path best they can. Unfortunately I don't know what file types most of these are and they want a somewhat dynamic ability. I'm not getting paid for this piece. So I am looking for quick and dirty if at all possible. Something that doesnt force me to come up with a logic for every file type possible just so I can have the headers built right for the file type in question so it downloads proper..
Is there a way to achieve this?
Well, if I undertand your problem, you want an easy way to download any type of file using a php script.
First of, you have a couple of ways to do it...
The most insecure one:
- You have a directory (lets say 'files/') where all files are stored. You create a script that receive a param (filename) and you look for the file in that directory. This is insecure because anyone can try to download any file there is, and also access subdirectories just passing "../" as param. You must need to do some parse there.
Other option is to use a DB, you store the filenames and associates them to an unique ID, later you have you script like: download.php?id=1 and it downloads the file which DB id is 1. This is the best I think.
Anyway, you can choose whatever you think is best, the code to force a download dialog should be something like:
$file = "my_file.zip"; // this is what you will get from a param (i.e. ?file=my_file.zip) or from DB query
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$file"); // this is totally needed
header("Content-Length: ".filesize($file));
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
readfile($file);
That should do the trick.
Good luck!

echo the contents of a File to the user but make it act as a download

I have a script where I get the contents of a file and then echo it to the screen, the problem is that it actually echos the binary file to the page, what I want if for it to act like a download where the download dialog will display.
how can I achieve this?
From the PHP header() manual:
// We'll be outputting a PDF
header('Content-type: application/pdf');
// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// The PDF source is in original.pdf
readfile('original.pdf');
Change the content-type and the filename. You can user readfile over file_get_contents, but either or should work.
You use the unofficial but ubiquitously supported Content-disposition header. See the first example here. There are a few quirks in browser support for the filename.
You could also simply change the Content-type header to application/octet-stream, but then you can't suggesta a filename to the user without more complex mechanisms.
(Note: application/binary is perhaps an option that's better than application/octet-stream because "If Content-Type matches one of generic values, such as application/octet-stream [...], many browsers treat this as a permission to second-guess the value based on the aforementioned signals, and try to come up with something more specific. The rationale for this step is that some badly configured web servers fall back to these types on all returned content." Source: Google Browser Security Handbook).
You have to change the Content-Type of the HTTP response.
In "raw" PHP (ie. without use of any framework), it would look like:
header("Content-Type: application/octet-stream");
It is necessary to invoke it at the beginning of the script, before any of the data is echoed.
Thanks #Brad for the answer. i have done some modifications in it. What i found is if my content is echoed than no need to do "readfile". What i have done is.
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="download.pdf"');
$pdf = $thirdPartyAPI->getPdf($ID);
echo $pdf;
ob_clean();
flush();
exit;

Creating a csv-file, doesn't work with right-click save-as

I have a script that generates data in csv format which is sent to the user along with a set of headers that tell the browser it is a .csv file. Everything works great when users (left)click on the link to the script, they are presented with a download dialog with the filename ending in .csv and it suggests using excel, or calc, to open it. However, when users right-click and choose Save As it is being saved with the php script name.
Here is the header code:
header("Pragma: public");
header("Expires: 0"); // set expiration time
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
$val = date("m_d_Y_g_i");
Header('Content-Disposition: attachment; filename="personal_information_'.$val.'.csv"');
So again, when users left-click it saves the file as personal_information_date.csv; when they right click it saves as download.php. I'm using FF3. Oddly enough, IE7 does not have this problem.
Any ideas?
Use mod_rewrite to alias the file from file.csv to file.php, this is a browser issue rather than PHP because by saving the file it isn't running it before it is saving it.
So to summarise:
Link to personal_information_date.csv
Create a mod_rewrite rule that forwards personal_information_date.csv to download.php (e.g.: RewriteRule ^personal_information_date.csv$ download.php).
The HTTP client may ignore more than one content type header, the two other will be ignored - which of them? Depends on the browser implementation, therefor the different behaviour. The correct mime type is text/csv, not application/octet-stream! The content-disposition header is correct for the download.
I believe that setting three different mimetypes doesn't help
what's $val ? Is this known content or user provided - e.g. could it contain nasty characters (like ") or even linebreaks, e.g. introduce new HTTP header lines?
have a look at the HTTP-Headers that arrive at the client. Either the Firefox built-in information or use LiveHttpHeaders (plugin to be found at the Mozilla site - logs all HTTP-Headers) - I'm sure there are more/other plugins for FF available.
Hope this helps.

Categories