15

I have a very similar situation to the person who asked: Can I serve MP3 files with PHP? Basically I am trying to protect mp3 files from direct download, so users have to go through php to get authenticated first. Here's my code:

header('Content-type: audio/mpeg');
header('Content-length: ' . filesize($file));
header('X-Pad: avoid browser bug');
Header('Cache-Control: no-cache');
header("Content-Transfer-Encoding: binary"); 
header("Content-Type: audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3");
readfile($file);

Here's my problem: The file only plays a very small chunk of the beginning (via Quicktime in the browser) and then stops - Quicktime seems to think the length of the file is only as long as the chunk it managed to download. When I reload - it plays a slightly larger chunk - whatever it managed to download up to that point.

Is that a problem in the headers I am sending? How would I stream such a file? Is it a problem if an swf is reading from that file?

Thanks!


Thank you guys for all the answers. Although none of these things were exactly what solved the problem, many of them sent me in the right direction. Much appreciated. For the full solution see my answer below

Community
  • 1
  • 1
Yuval Karmi
  • 26,277
  • 39
  • 124
  • 175
  • I presume you don't have that typo ("Header") in your real code? Also, you don't need the X-Pad hack. This is only necessary for ancient Netscape (see http://george.hotelling.net/90percent/geekery/why_is_apache_sending_a_xpad_header.php). You don't need Content-Transfer-Encoding, and you should only specify Content-Type once (audio/mpeg is fine). – Matthew Flaschen Mar 01 '10 at 19:02
  • @Matthew Flaschen: PHP functions are case-insensitive, so it's only a consistency issue. The Content-Type may be the real issue here. – Piskvor left the building Mar 01 '10 at 19:53

8 Answers8

12

Here's what did the trick.

$dir = dirname($_SERVER['DOCUMENT_ROOT'])."/protected_content";
$filename = $_GET['file'];
$file = $dir."/".$filename;

$extension = "mp3";
$mime_type = "audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3";

if(file_exists($file)){
    header('Content-type: {$mime_type}');
    header('Content-length: ' . filesize($file));
    header('Content-Disposition: filename="' . $filename);
    header('X-Pad: avoid browser bug');
    header('Cache-Control: no-cache');
    readfile($file);
}else{
    header("HTTP/1.0 404 Not Found");
}
Yuval Karmi
  • 26,277
  • 39
  • 124
  • 175
  • 2
    You might want to do some validation/sanitising on $_GET['file'] before reading it... – Simon Mar 02 '10 at 22:59
  • I check if the file exists `if(file_exists($file)){`... Is that what you meant? – Yuval Karmi Mar 03 '10 at 09:36
  • 6
    No he didn't mean that. Think for yourself, what happens if someone tries this http://yoursite.com/yourscript.php?file=../../../home/someonesgirlfriend/secretdiary.doc – Jonny Aug 17 '10 at 09:57
  • 1
    Its great but its not seekable !! – Sam May 14 '15 at 08:20
  • 2
    To make it seekable just add the following header line, but no support for resume in this code. **`header('Accept-Ranges: bytes');`** If you need the Content-Transfer-Encoding better try to use `header("Content-Transfer-Encoding: chunked");` – Ajmal PraveeN Jul 02 '18 at 00:25
  • I checked this code in Google Chrome and Download Window appears unless $mime_type be changed to $mime_type = "audio/mpeg"; – Kranchi Mar 01 '23 at 16:19
1

You can try HTTP chunking. Set the "Transfer-Encoding" header to "chunked", then output the size of each chunk before sending it. End each chunk size and chunk with a CRLF.

For anything more complex, I recommend using a streaming server, such as Icecast.

outis
  • 75,655
  • 22
  • 151
  • 221
1

Two things stand out:

  1. You've got a Content-Length set. If you server is set to automatically gzip your output, this can mess with things. Try turning off Content-Length and see if that fixes it.
  2. You've got about a thousand Content-Types set. Since it's Mp3 that you're serving, just use audio/mpeg. You can effectively get rid of the whole last header() command. It's easy to get carried away with HTTP headers.

Try it out and let us know how it goes!

mattbasta
  • 13,492
  • 9
  • 47
  • 68
0

For these solution you also need to configure xsendfile in apache (mod_xsendfile) or nginx HttpSecureLinkModule - they will give you exact mp3, so browser will play it correctly

nvvetal
  • 1,756
  • 1
  • 17
  • 19
0

if your server is running on apache or lighty, i would suggest you look into x-sendfile

http://tn123.ath.cx/mod_xsendfile/

this allows you to handle authentication in your php application, but let's your webserver handle the transmission of the file. the performance improvement you gain should be a nice additional benefit

roman
  • 11,143
  • 1
  • 31
  • 42
0

Applying all these solutions, which are valid to hide the original path and filename, unfortunately doesn't prevent from unauthorized download. Indeed the client (on my case: Chrome) downloads the file.

Here the lines I put on my server:

<?php
$dir = dirname($_SERVER['DOCUMENT_ROOT'])."/mp3";
$filename = $_GET['file'];
$file = $dir."/".$filename;

$extension = "mp3";
$mime_type = "audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3";

if(file_exists($file)){
    header('Content-type: {$mime_type}');
    header('Content-length: ' . filesize($file));
    header("Content-Transfer-Encoding: binary"); 
    header('Content-Disposition: filename="' . $filename);
    header('X-Pad: avoid browser bug');
    header('Cache-Control: no-cache');
    readfile($file);
}else{
    header("HTTP/1.0 404 Not Found");
}
?>

with or without the line

    header("Content-Transfer-Encoding: binary"); 

the final result doesn't change The directory /mp3 is located into

/home/myuser/

(thus /home/myuser/mp3) and the public HTML directory is

/home/myuser/public_html

thus calling my domain and giving

/player.php?file=music.mp3

it downloads a file called music.mp3 with all the original content.

Tormy Van Cool
  • 658
  • 10
  • 29
0

Remove header("Content-Transfer-Encoding: binary"); And you'll be set!

Roozbeh15
  • 4,047
  • 6
  • 27
  • 30
-1

I had ChatGPT write this for me just now. Use it like stream.php?file=myFile and it'll find myFile.m4a or mp3 etc.

<?php
// Sanitize and build filename
function sanitizeAndBuildFilename($filename) {
    $allowedExtensions = array('mp3', 'm4a', 'wav', 'aiff');
    $filename = preg_replace("/[^a-zA-Z0-9.]/", "", $filename); // Remove any characters that are not alphanumeric or a period

    // Check each allowed extension
    foreach ($allowedExtensions as $extension) {
        $fileWithExtension = $filename . '.' . $extension;
        if (file_exists($fileWithExtension)) {
            return $fileWithExtension;
        }
    }

    return false;
}

// Stream the file
function streamFile($file) {
    header('Content-Type: audio/mpeg');
    header('Content-Length: ' . filesize($file));
    $fp = fopen($file, 'rb');

    while (!feof($fp)) {
        echo fread($fp, 8192);
        flush();
    }

    fclose($fp);
}

// Handle 404 error
function handle404() {
    header('HTTP/1.0 404 Not Found');
    echo 'Unsupported type or file not found.';
}

$filename = sanitizeAndBuildFilename($_GET['file']);

if ($filename) {
    streamFile($filename);
} else {
    handle404();
}
?>
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421