2

I have a website that has a downloads section. I need for the files not to be accessed directly by an annonymous user, so I am putting them in a directory not accessable to users, but is accessible to the web server. When the user clicks the link to download the file, I need it to redirect to a download page that will stream the file to the user without him knowing the location of the directory or the file and it will ask him to save it to his computer. I found the following code on a previous post, but I can't get it to work correctly. Could be that I don't know the correct names of the variables that it wants to be passed to it. Please include an explanation of how to use your code.

$filename='Firefox%20Setup%203.6.13.exe';
$file_path='http://ftp.byfly.by/pub/mozilla.org/firefox/releases/3.6.13/win32/fr';
$file= $file_path."/".$filename;
$len=filesize($file);
header("content-type: application/save");
header("content-length: $len");
header("content-disposition: attachment; filename=$filename");
$fp=fopen($file, "r");
fpassthru($fp);
prodigitalson
  • 60,050
  • 10
  • 100
  • 114
Hamada Mido
  • 383
  • 5
  • 14

3 Answers3

1

This is how I would do it.

<?php

function getFile($file_location) {
    header('Content-Description: File Transfer');
    header('Content-type: application/exe');
    header('Content-Disposition: attachment; filename="supercoolFF.exe"');
    header('Content-Transfer-Encoding: binary');
    ob_end_clean();
    $url_info = parse_url($file_location);
    if (!isset($url_info['query'])) $url_info['query'] = '';
    $http = fsockopen($url_info['host'],$url_info['port']);
    $req = "GET " . $url_info['path'] . "?" . $url_info['query'] . " HTTP/1.1\r\n";
    $req .= "Host: " . $url_info['host'] . ":" . $url_info['port'] . "\r\n";
    $req .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
    $req .= "User-Agent  Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8\r\n";
    $req .= "Accept-Language: en-us,en;q=0.5\r\n";
    $req .= "Accept-Encoding: gzip,deflate\r\n";
    $req .= "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n";
    if ($len = strlen($url_info['query']) {
        $req .= 'Content-Length: ' . $len . "\r\n";
        $req .= "Connection: Close\r\n\r\n";
        $req .= $query . "\r\n\r\n";
    } else {
        $req .= "Connection: Keep-Alive\r\n\r\n";
    }
    fputs($http, $req);

    $content = "";
    $content_encountered = FALSE;
    ob_end_clean();
    while(strlen($part = fgets($http, 4096))) {
        if ($content_encountered) {
            echo $part;
            $content .= $part; 
        }
        if ($part == "\r\n") {
            $content_encountered = TRUE;
        }
    }
    fclose($http);
    exit;
}

$filename='Firefox%20Setup%203.6.13.exe?';
$file_path='http://ftp.byfly.by:80/pub/mozilla.org/firefox/releases/3.6.13/win32/fr';

getFile($file_path . '/' . $filename);

Of course, it would be better to do a HEAD request first to get the filesize and include a Content-Length header in the response so the user can have some idea about how long it is going to take. Or, you can hardcode that number, if you are always going to be serving up the same file.

sberry
  • 128,281
  • 18
  • 138
  • 165
  • it works good, As you say,if there is a way to know how filesize it would be better. I have just a question how can i use this script and make the download resuming works – Hamada Mido Dec 30 '10 at 21:53
0

Let's say you have a file in a directory called "hiddenaccess" and the file name is "test.mp3" you can load the path in php variable and let it for download

<?php
$file = "./hiddenaccess/test.mp3";
header("Content-Disposition: attachment; filename=" . urlencode($file));
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");             
header("Content-Length: " . filesize($file));

@readfile($file);

?>

Note: Do NOT have any other echo or print statements in this file.

  • 1
    You should call `session_write_close()` before `readfile`, so the user can browse the page while the file's downloading. – Maerlyn Dec 30 '10 at 20:36
  • when i download the file is like only 305 octets – Hamada Mido Dec 30 '10 at 20:39
  • sorry, for that, i kept testing on the first example (the Firefox link) !! it works, but only on the same sever. How can i make it work if it was an external server ? – Hamada Mido Dec 30 '10 at 20:52
-2

Here's a function I (Apparently Others?!!?) developed to do just that, it's a meaty function but it checks and does everything. To call it is simple though, the important part. I have this inside a class but you can just make it a php function as there is no other class functions it depends on. Hope this helps you.

   public static function output_file($path, $filename, $mime_type='') {

    $err = 'Sorry, the file you are requesting is unavailable.';

    $filename = rawurldecode($filename);

    // check that file exists and is readable
    if (file_exists($path) && is_readable($path)) {

        /* Figure out the MIME type (if not specified) */
        $known_mime_types=array(
         "pdf" => "application/pdf",
         "txt" => "text/plain",
         "html" => "text/html",
         "htm" => "text/html",
         "exe" => "application/octet-stream",
         "zip" => "application/zip",
         "doc" => "application/msword",
         "xls" => "application/vnd.ms-excel",
         "ppt" => "application/vnd.ms-powerpoint",
         "gif" => "image/gif",
         "png" => "image/png",
         "jpeg"=> "image/jpg",
         "jpg" =>  "image/jpg",
         "php" => "text/plain"
        );

        if($mime_type==''){
            $file_extension = strtolower(substr(strrchr($filename,"."),1));
            if(array_key_exists($file_extension, $known_mime_types)){
             $mime_type=$known_mime_types[$file_extension];
            } else {
             $mime_type="application/force-download";
            };
        };

        @ob_end_clean(); //turn off output buffering to decrease cpu usage

        if(ini_get('zlib.output_compression')) { //otherwise the filesize is way off
            ini_set('zlib.output_compression', 'Off');
        }

        // get the file size and send the http headers
        $size = filesize($path);
        header("Content-Type: $mime_type");
        header('Content-Disposition: attachment; filename="'.$filename.'"');
        header('Content-Transfer-Encoding: binary');
        header('Accept-Ranges: bytes');
        header('Cache-control: private');
        header('Pragma: private');
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');

        // multipart-download and download resuming support
        if(isset($_SERVER['HTTP_RANGE'])) {

            list($a, $range) = explode("=", $_SERVER['HTTP_RANGE'], 2);
            list($range) = explode(",", $range, 2);
            list($range, $range_end) = explode("-", $range);

            $range=intval($range);

            if(!$range_end) {
                $range_end = $size-1;
            } else {
                $range_end = intval($range_end);
            }

            $new_length = $range_end-$range+1;

            header("HTTP/1.1 206 Partial Content");
            header("Content-Length: $new_length");
            header("Content-Range: bytes $range-$range_end/$size");
        } else {
            $new_length = $size;
            header("Content-Length: " . $size);
        }

        /* output the file itself */
        $chunksize = 1*(1024*1024); //may want to change this
        $bytes_send = 0;

        if ($file = fopen($path, 'r')) {
            if(isset($_SERVER['HTTP_RANGE'])) {
                fseek($file, $range);
            }

            while(!feof($file) && (!connection_aborted()) && ($bytes_send<$new_length)) {
                $buffer = fread($file, $chunksize);
                print($buffer); //echo($buffer); // is also possible
                flush();
                $bytes_send += strlen($buffer);
            }

            fclose($file);

        } else {
            die($err);
        }

    } else {

        die($err);

    }

    die();
}

To use it would be something like this.

$path = '/var/www/site/httpdocs/upload/private/myfile.txt';
$dl_filename = 'NDA_'.time();
$mime_type = 'doc';
output_file($path,$dl_filename,$mime_type);
Dreamcube
  • 165
  • 1
  • 10
  • but can you just tell my exactly what should i put in the $dl_filename, as i see in $path you put the both (path and the filename) – Hamada Mido Dec 30 '10 at 21:09
  • 2
    -1. Taking credit for someone else's work is a no-no. http://stackoverflow.com/questions/386845/http-headers-for-file-downloads – sberry Dec 30 '10 at 21:16
  • $dl_filename would just be a string of what you want the user to download the file as. So "my_game" it will add the extension on for you. @sberry2A this has been in our library for years as you can see it's old php3 compatible code and we continue to use and shape it, for all I know they took it from us. I had no idea this was such a popular function I'll have to take a look at this. – Dreamcube Dec 30 '10 at 21:25
  • I've been developing longer than you sberry2A and I'm 7 yrs younger than you, what's that tell ya? It couldn't possibly be my code could it, just cause site A has it. Well I'm here to say I did this from scratch 5yrs ago, we continue to use it on our smaller clients that want some security in their downloads. It's not perfect, there is better but low traffic clients don't need better they need working. Keep downgrading me, last time I offer help on this site. – Dreamcube Dec 30 '10 at 21:42
  • @Dreamcube... I'm not here to play judge and jury, but the fact that it is the same code, almost line for line, and his is posted over 2 years ago leads me to believe that he didn't copy it from you. It is certainly interesting that this line is the same as well `header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');` If you are continuing to "use and shape it" then I would suggest changing this to a variable expiration time. – sberry Dec 30 '10 at 21:42
  • google header('Expires: Mon, 26 Jul 1997 05:00:00 GMT') It's a common header to use to keep a date in the past. But fair enough I'll drop it you can believe what you want. – Dreamcube Dec 30 '10 at 22:17