0

I have a bunch of files available for download that I wanted to protect by login and hide the path to prevent hotlinking. I am using a PHP script to do this (Thanks to Mike Zriel for the download script, I have simply added my own database call and user login check).

/**
* Force file download and hide real Path
* @version        11.03.11 March 11, 2011
* @author         Mike Zriel, http://www.zriel.com
* @copyright      Copyright (C) 2010
* @license        http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 only
* @params     
*   filePath = Real Path of file
*   fileName = File Name
*/

//CHECK USER LOGIN
if(!isset($_COOKIE['login'])) {
echo "You are not authorised to download this file.";
exit;
} else {

include('database_connection.php');

//VALIDATE VARIABLES
if(isset($_GET['fileid'])) {
    if(!preg_match("/^\d+$/",$_GET['fileid'])) {
        echo "Invalid File ID.";
        exit;
    }
} else {
    echo "No File Specified.";
    exit;
}

try {
    $sql = $pdo->prepare("SELECT * FROM files WHERE id = ?");
    $sql->execute(array($_GET['fileid']));
    $array = $sql->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    echo "Error downloading file: ".$e->getCode();
}

if(!empty($array)) {
    $filePath = "http://www.example.com/PathToFile/";
    $fileName = $array['path']);
}

if(substr($filePath,-1)!="/") $filePath .= "/";

$pathOnHd = $filePath . $fileName;

if(isset($_GET['debug'])) {
echo "<br />".$pathOnHd;
}

if ($download = fopen ($pathOnHd, "br")) {

$size = filesize($pathOnHd);
$fileInfo = pathinfo($pathOnHd);
$ext = strtolower($fileInfo["extension"]);

switch ($ext) { 
case "pdf": 
header("Content-type: application/pdf");
header("Content-Disposition: attachment; filename=\"{$fileInfo["basename"]}\"");
break;
default;
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"{$fileInfo["basename"]}\"");
}
header("Content-length: $size");

while(!feof($download)) {
    $buffer = fread($download, 2048);
    echo $buffer;
}
fclose ($download);
} else {
    echo "There was an error accessing the file: ".$array['name'].". <br />";
}
exit;
}

The problem I am having is for some o the smaller ZIP or PDF files (<1MB or so) this works fine, but for some larger ZIP files I have (15-20MB) the browser (tested in Chrome and Firefox) throws a network error and fails at the end of the download. I think it has something to do with this bit but changing the buffer size doesn't seem to have any effect?

while(!feof($download)) {
    $buffer = fread($download, 2048);
    echo $buffer;
}

Can anyone spot what's wrong?

Edit: Tried the following from answers below...

readfile($pathOnHd); //Results in Unknown Network Error

while(!feof($download)) {
$buffer = fread($download, 2048);
echo $buffer;
flush();
}   //Not using ob_start() so not sure why this would change anything and it doesn't

while (($buffer = fread($download, 2048)) != FALSE) {
echo $buffer;
// Results in Unknown Network Error
}

Note: If I echo the path to the browser and paste it in as a direct link the file downloads ok. So I's something to do with PHP not liking these larger files.

Barbs
  • 1,115
  • 2
  • 16
  • 30
  • Doesn't answer the question, though, but if 15-20MB is the larger filesizes, why not just use `echo file_get_contents($download)` instead? – Repox Oct 22 '14 at 08:22
  • 1
    Why not simply use [readfile()](http://www.php.net/manual/en/function.readfile.php) instead of the loop and echo? – Mark Baker Oct 22 '14 at 08:23

3 Answers3

0

Instead of reading the file in little blocks try using the readfile() function instead. This will read the whole file in one go.

So change this

while(!feof($download)) {
    $buffer = fread($download, 2048);
    echo $buffer;
}

To

readfile($pathOnHd)

You can also remove the fopen() as this is not required as readfile() opens and closes the file automatically.

See the manual for a great example that is also relevant to what you are doing

RiggsFolly
  • 93,638
  • 21
  • 103
  • 149
0

Your problem is output buffering. Browser can not get any data while you echoing read bytes. if you are using output buffering eg. ob_start() you have to remove that. if you are not using, then it maybe related with server configuration. php server wide settings can force it too. you can reverse this via flush() functions. example:

while(!feof($download)) {
    $buffer = fread($download, 2048);
    echo $buffer;
    flush();
}

if this is not working, you may need to change php's output_buffering setting to off.

ps: do not try to read the whole file with fread(), if it's really big. you will have timeouts in your browser.

risyasin
  • 1,325
  • 14
  • 24
0

In addition to what has already been said, don't use feof. Like the C function with the same name, it doesn't return TRUE until you have tried to read past the end of the file. The fread function reports end-of-file by returning FALSE, and this is a common idiom:

while (($buffer = fread($download, 2048)) != FALSE) {
    echo $buffer;
    // flush output if needed
}
Joni
  • 108,737
  • 14
  • 143
  • 193