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

StackOverflow https://stackoverflow.com/questions/16448597

Question

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
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top