...

View Full Version : Reposted: PHP Downloading files problem, can anyone help please?



bottyz
03-02-2010, 03:09 PM
Hi all,


I'm pulling my hair out now as i've spent the best part of 8 hours trying to setup a download file script in php without any success. I have tried many different methods from many different sources and all perform the same.

basically, i have a user login area from which users can download software etc zip, jpg, pdf and so on. Obviuously, i don't want the users to know where the files are stored and also i don't want them accessible to the whole world.

My problem is that when i have a script which looks good on screen, when it comes to download say a zip file it will only download it as 1kb. Even if it states the actual size in the download dialog.

the current revision of my code is as follows:



// grab the requested file's name
//$filename = $_GET['file'];

// CODE FOR CHECKING USER DETAILS GOES HERE
// if file exists and user access granted:

// define the path to your download folder plus assign the file name
$path = 'files/'.$filename;

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



// make sure it's a file before doing anything!
if(is_file($filename))
{

// required for IE
if(ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off'); }

//get the file size and send the headers
$size = filesize($filename);

// get the file mime type using the file extension
switch(strtolower(substr(strrchr($filename,'.'),1)))
{
case 'pdf': $mime = 'application/pdf'; break;
case 'zip': $mime = 'application/zip'; break;
case 'jpeg':
case 'jpg': $mime = 'image/jpg'; break;
default: $mime = 'application/force-download';
}
header('Pragma: public'); // required
header('Expires: 0'); // no cache
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Last-Modified: '.gmdate ('D, d M Y H:i:s', filemtime ($filename)).' GMT');
header('Cache-Control: private',false);
header('Content-Type: '.$mime);
header('Content-Disposition: attachment; filename="'.basename($filename).'"');
header('Content-Transfer-Encoding: binary');
header('Content-Length:' .$size); // provide file size
header('Connection: close');
readfile($filename); // push it out
exit;

}


i've even tried hardcoding the filesize and it still picks it up wrong

I'm urgent for this now as a deadline is looming, so any help would be great!

angst
03-02-2010, 03:16 PM
maybe try removing this line:

header('Connection: close');

Fou-Lu
03-02-2010, 03:25 PM
$filename correct, or is it supposed to be $path?
Specifically for the readfile and filesize calls.

angst
03-02-2010, 03:33 PM
ah yes, it should be the path, ( just comparing it to my download function ).

Fou-Lu
03-02-2010, 03:34 PM
ah yes, it should be the path, ( just comparing it to my download function ).

Thats what I'm thinking, but I'm also concerned about the close call. I can't test it and its been so long I cannot recall if thats pushed immediately with the download, which will also cause a failure.

bottyz
03-02-2010, 03:52 PM
thanks guys,


I've tried you're suggestions but still getting the same problem. The example i'm using at the moment is a 32.9mb zip file. I want to download it via the following revised code (i've gone back to the code i had this morning):



session_start();

//connect to db
include('dbconnect/lh.php');
mysql_select_db($database_lh, $lh);

// block any attempt to the filesystem
if (isset($_GET['file']) && basename($_GET['file']) == $_GET['file']) {

//set variable to retrieve id number from url
$fileid = $_GET['file'];

// query the db for access levels
$sql="SELECT * FROM filelist WHERE id='$fileid' LIMIT 1";
$data = mysql_query($sql, $lh) or die(mysql_error());

// retrieve the file and its access levels
while($retrieve = mysql_fetch_array( $data )) {
$filename = $retrieve['filename'];
$category = $retrieve['category'];
$subcategory = $retrieve['subcategory'];
}

} else {
$filename = NULL;
}

$allowed = 0;
include('downloadconfig.php');

//checks if user is from allowed domain
if($allowblank > 0) { if($_SERVER['HTTP_REFERER']=="") { $allowed = 1; }}
$domains = count($alloweddomains);

for($y=0;$y<$domains+1;$y++) {
if((stristr($_SERVER['HTTP_REFERER'], $alloweddomains[$y]))) {
$allowed = 1;
}
}

//if domain allowed...
if($allowed > 0) {

//if no filename...
if (!$filename) {
// if variable $filename is NULL or false return message
if($logging > 0){
$status = "FileNotFound";
include('logit.php');
}
$errors = 'ERROR: That file wasnt found!';
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header("Location: ". $redirect );
exit;
}
//if file exists need to check authorision levels

//set access to no
$access = NULL;

//retrieve current user levels
$cpm = $_SESSION['MM_CPMGroup'];
$cpmh = $_SESSION['MM_CPMHGroup'];
$cm = $_SESSION['MM_CMGroup'];
$cj = $_SESSION['MM_CJGroup'];

//set file category type & set access if allowed
if ($category == 'cpm') {
if ($cpm == '1') {
$access = 1;
}
}
elseif ($category == 'cpmh') {
if ($cpmh == '1') {
$access = 1;
}
}
elseif ($category == 'cm') {
if ($cm == '1') {
$access = 1;
}
}
elseif ($category == 'cj') {
if ($cj == '1') {
$access = 1;
}
}

if ($access < 1) {
// if user access not granted to file category return message
if($logging > 0){
$status = "WrongPermissions";
include('logit.php');
}
$errors = "ERROR: You don't have permission to access that file!";
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header("Location: ". $redirect );
exit;
}

// if file exists and user access granted:

// define the path to your download folder plus assign the file name
$path = 'files/'.$filename;

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

// get the file size and send the http headers
$size = filesize($path);

// fix for IE catching or PHP bug issue
header("Pragma: public");

// set expiration time
header("Expires: 0");

// browser must download file from server instead of cache
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

//content type
switch(strtolower(substr(strrchr($filename,'.'),1)))
{
case 'pdf': $mime = 'application/pdf'; break;
case 'zip': $mime = 'application/zip'; break;
case 'jpeg':
case 'jpg': $mime = 'image/jpg'; break;
default: $mime = 'application/force-download';
}
header("Content-Type: ".$mime);

// force download dialog
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");

// set file size
header('Content-Length: '.$size);

// set file name
header('Content-Disposition: attachment; filename='.$filename);

//set encoding
header('Content-Transfer-Encoding: binary');

// open the file in binary read-only mode
// display the error messages if the file canīt be opened
$file = @ fopen($path, 'rb');

if ($file) {
if($logging == 1){
$status = "Granted";
include('logit.php');
}
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
if($logging == 1){
$status = "CantOpen";
include('logit.php');
}
$errors = "ERROR: Can't open requested file!";
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header("Location: ". $redirect );
exit;
}
}

//if domain not allowed
} else {
if($logging > 0){
$status = "DomainDenied";
include('logit.php');
}
//quiet leecher kill
exit;
}


The file dialog comes up but the file size is only showing as 1.15kb and the resulting file is corrupted. I have echoed $size and its correct before entering the content-length header.

any ideas?

Oh n the above code is called via a download.php?file=1 link from a seperate page.

bottyz
03-02-2010, 03:55 PM
Thats what I'm thinking, but I'm also concerned about the close call. I can't test it and its been so long I cannot recall if thats pushed immediately with the download, which will also cause a failure.

if i run the Live HTTP headers add on for firefox whilst running the script i do stil get a connection: close header. Even though i have removed it completely from the code.

angst
03-02-2010, 03:57 PM
here's the function that I built a few years back, maybe this will help ( I know this function works ).



// force file download - include full local path
function ForceDownload($filename){

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');

$file_extension = strtolower(substr(strrchr($filename,"."),1));

if( $filename == "" )
{
echo "download file NOT SPECIFIED.";
exit;
} elseif ( ! file_exists( $filename ) )
{
echo "File not found.";
exit;
};
switch( $file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "mp3": $ctype="audio/x-mp3"; break;
case "zip": $ctype="application/zip"; break;
case "rar": $ctype="application/zip"; break;
case "tar": $ctype="application/zip"; break;
case "sit": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":$ctype="image/jpg"; break;
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");
exit();
}

Fou-Lu
03-02-2010, 03:59 PM
fpassthru($file);

$path.

Also, you're headers are probably supposed to append, not replace. Change you're multiple content-type headers to take a second parameter for false.


My bad, sorry you used path with fopen duh. I was thinking you're doing readfile again.
Hmm, needs a closer inspection then.

bottyz
03-02-2010, 04:25 PM
here's the function that I built a few years back, maybe this will help ( I know this function works ).



// force file download - include full local path
function ForceDownload($filename){

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');

$file_extension = strtolower(substr(strrchr($filename,"."),1));

if( $filename == "" )
{
echo "download file NOT SPECIFIED.";
exit;
} elseif ( ! file_exists( $filename ) )
{
echo "File not found.";
exit;
};
switch( $file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "mp3": $ctype="audio/x-mp3"; break;
case "zip": $ctype="application/zip"; break;
case "rar": $ctype="application/zip"; break;
case "tar": $ctype="application/zip"; break;
case "sit": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":$ctype="image/jpg"; break;
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");
exit();
}


Right ok so i even tried modifying my code to make it similar to yours above... but the code is still behaving the same.

the portion of my code in question now looks like:


if (file_exists($path) && is_readable($path)) {

// get the file size and send the http headers
$size = filesize($path);

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off');

//content type
switch(strtolower(substr(strrchr($filename,'.'),1)))
{
case "pdf": $mime="application/pdf"; break;
case "mp3": $mime="audio/x-mp3"; break;
case "zip": $mime="application/zip"; break;
case "rar": $mime="application/zip"; break;
case "tar": $mime="application/zip"; break;
case "sit": $mime="application/zip"; break;
case "doc": $mime="application/msword"; break;
case "xls": $mime="application/vnd.ms-excel"; break;
case "ppt": $mime="application/vnd.ms-powerpoint"; break;
case "gif": $mime="image/gif"; break;
case "png": $mime="image/png"; break;
case "jpeg":$mime="image/jpg"; break;
case "jpg": $mime="image/jpg"; break;
default: $mime="application/force-download";
}

header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: ".$mime);
header("Content-Disposition: attachment; filename=\"".$filename."\";");
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$size);
readfile("$path");
exit();


BTW.. the zip file is now being served up as a 647 byte file. :S

MattF
03-02-2010, 04:33 PM
Open the downloaded file with a text editor and see what the contents of the file are.

bottyz
03-02-2010, 04:36 PM
Open the downloaded file with a text editor and see what the contents of the file are.

Right... once all my logit.php code is removed, and i've downloaded the zip, this is what the file contains:

<b>Fatal error</b>: Allowed memory size of 33554432 bytes exhausted (tried to allocate 34560001 bytes) in <b>/home/a6964942/public_html/LHU/filedownload.php</b> on line <b>146</b><br />


I've found out that this message occurs because there is a default limited of 8mb in my php.ini.

I've added the following line to the beginning of my script
ini_set("memory_limit","100M");

this should now set the limit to 100mb. Everything now appears to work, zip files shows as 32.9mb but it only downloadeds part of it now. the original file size unpacked was 42,492,074bytes but the downloaded zip's unpacked size is only 12,217,510 bytes.

Thanks for your help so far guys but now what????

MattF
03-02-2010, 07:49 PM
Replace this block:



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

// get the file size and send the http headers
$size = filesize($path);

// fix for IE catching or PHP bug issue
header("Pragma: public");

// set expiration time
header("Expires: 0");

// browser must download file from server instead of cache
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

//content type
switch(strtolower(substr(strrchr($filename,'.'),1)))
{
case 'pdf': $mime = 'application/pdf'; break;
case 'zip': $mime = 'application/zip'; break;
case 'jpeg':
case 'jpg': $mime = 'image/jpg'; break;
default: $mime = 'application/force-download';
}
header("Content-Type: ".$mime);

// force download dialog
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");

// set file size
header('Content-Length: '.$size);

// set file name
header('Content-Disposition: attachment; filename='.$filename);

//set encoding
header('Content-Transfer-Encoding: binary');

// open the file in binary read-only mode
// display the error messages if the file canīt be opened
$file = @ fopen($path, 'rb');

if ($file) {
if($logging == 1){
$status = "Granted";
include('logit.php');
}
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
if($logging == 1){
$status = "CantOpen";
include('logit.php');
}
$errors = "ERROR: Can't open requested file!";
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header("Location: ". $redirect );
exit;
}


with:



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

$size = filesize($path);

header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '.$size);
header("Content-Type: application/octet-stream");
header('Content-Disposition: attachment; filename='.$filename);
header('Content-Transfer-Encoding: binary');

if($logging == 1)
{
$status = 'Granted';
include('logit.php');
}
ob_clean();
flush();
readfile($path);
exit(0);
}
else
{
if($logging == 1)
{
$status = 'CantOpen';
include('logit.php');
}
$errors = 'ERROR: Can\'t open requested file!';
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header('Location: '. $redirect);
exit(0);
}


and see how that fares.

Fou-Lu
03-02-2010, 08:07 PM
Replace this block:



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

// get the file size and send the http headers
$size = filesize($path);

// fix for IE catching or PHP bug issue
header("Pragma: public");

// set expiration time
header("Expires: 0");

// browser must download file from server instead of cache
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

//content type
switch(strtolower(substr(strrchr($filename,'.'),1)))
{
case 'pdf': $mime = 'application/pdf'; break;
case 'zip': $mime = 'application/zip'; break;
case 'jpeg':
case 'jpg': $mime = 'image/jpg'; break;
default: $mime = 'application/force-download';
}
header("Content-Type: ".$mime);

// force download dialog
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");

// set file size
header('Content-Length: '.$size);

// set file name
header('Content-Disposition: attachment; filename='.$filename);

//set encoding
header('Content-Transfer-Encoding: binary');

// open the file in binary read-only mode
// display the error messages if the file canīt be opened
$file = @ fopen($path, 'rb');

if ($file) {
if($logging == 1){
$status = "Granted";
include('logit.php');
}
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
if($logging == 1){
$status = "CantOpen";
include('logit.php');
}
$errors = "ERROR: Can't open requested file!";
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header("Location: ". $redirect );
exit;
}


with:



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

$size = filesize($path);

header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '.$size);
header("Content-Type: application/octet-stream");
header('Content-Disposition: attachment; filename='.$filename);
header('Content-Transfer-Encoding: binary');

if($logging == 1)
{
$status = 'Granted';
include('logit.php');
}
ob_clean();
flush();
readfile($path);
exit(0);
}
else
{
if($logging == 1)
{
$status = 'CantOpen';
include('logit.php');
}
$errors = 'ERROR: Can\'t open requested file!';
$_SESSION['$fileERROR'] = $errors;
$redirect = $_SESSION['PrevUrl'];
header('Location: '. $redirect);
exit(0);
}


and see how that fares.

You thinking output buffering issues with a default output buffer? Yeah I guess that would be problematic if not set to display errors eh?
Also, can't help but notice the error is exhausted memory. Abandon the readfile and use fopen with fread to drag chunks in instead.

MattF
03-02-2010, 08:12 PM
You thinking output buffering issues with a default output buffer? Yeah I guess that would be problematic if not set to display errors eh?

It's one possibility. I added the set_time_limit line too, just to make sure it's not simply a case of the script timing out.




Also, can't help but notice the error is exhausted memory. Abandon the readfile and use fopen with fread to drag chunks in instead.

I changed that over to readfile from fopen, just whilst he tests it. :D He was running into the problem using fopen earlier, but he's upped the memory limit now, so that shouldn't be a problem just for testing purposes. The test file size is well below his memory limit.

Fou-Lu
03-02-2010, 08:44 PM
I thought the exhaustion was from the readfile above O.o

Y'know, I could swear this exact problem came up just a few months ago?

MattF
03-02-2010, 09:27 PM
Now you mention it, I do seem to recall something extremely similar a while back. The best problems never disappear. :D

angst
03-02-2010, 10:17 PM
Now you mention it, I do seem to recall something extremely similar a while back. The best problems never disappear. :D


lol :thumbsup:



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum