PHP readfile() replacement cURL?

Status
Not open for further replies.

ziycon

New Member
I'm unable to use the code snippet below for a file download on a site as readfile is disabled for security reasons, I'm told cURL can be used, any advice on how to change it or on the below replacement would be great, thanks.
PHP:
header('Content-type: '.$contentType);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Content-length: '.$filesize);
readfile($file);

Is there any issues with using this code as a replacement or anything I should be aware of?
PHP:
$url  = $file;
$path = $file;

$fp = fopen($path, 'w');

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);

$data = curl_exec($ch);
curl_close($ch);
fclose($fp);
 

php.allstar

New Member
Hi,

Not sure if I'm on the same wavelength with you, but if you want to force a file download in php you can use this:

PHP:
<?php

    $contentType = 'text/plain'; // Change this to suit your file type
    $file = 'whatever.txt'; // Change this to a relative path to your file

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Type: $contentType");
    header("Content-Transfer-Encoding: binary");
    header("Content-Disposition: attachment; filename=$file");

?>
 

ziycon

New Member
Got the new code working, only question is:

PHP:
header("Content-Disposition: attachment; filename=$filename");

The above, does it need to take the relative path for the file or the full path? And if it needs to a path, how can I change the name so it just shows the name of the file and not use the whole path as the name for the file?

I nope this makes sense!?

Also will this code work across most browsers?
 

php.allstar

New Member
Hi,

Use any path, but I find relative paths are best.

Should be cross platform compatible afaik

to remove the path name from the file name:

Change:

PHP:
header("Content-Disposition: attachment; filename=$file");

To:

PHP:
header("Content-Disposition: attachment; filename=" . basename($file));

Wrapped basename() around $file;

Let me know if this works for you.
 

ziycon

New Member
Having problems, I'm getting a 0 bytes file every time. I'm using the below code.
PHP:
$contentType = "application/pdf";
$file = 'media/'.$row['filepath'];
$filesize = sizeof($file);

header("Cache-Control: public");
header("Content-length: ".$filesize);
header("Content-Description: File Transfer");
header("Content-Type: ".$contentType);
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=".basename($file));

It seems that it requires readfile() at the end to actually read the file and output it otherwise you get a 0 bytes file. Is there any other option apart from readfile() as its disabled on my hosting?

Any ideas?
 

ziycon

New Member
I've tried the below code but it will limit the file size people can download based on the server resource as its using file_get_contents(), any ideas how to get my previous post working or any other ideas, I'm really stumped!?

PHP:
        $filepath = 'media/'.$row['filepath'];
        $filename = basename($filepath);
        $file = file_get_contents($filepath);
        $filesize = filesize($filepath);

            $contentType = "application/pdf";

        header("Cache-Control: public");
        header("Content-Length: ".$filesize);
        header("Content-Description: File Transfer");
        header("Content-Type: ".$contentType);
        header("Content-Transfer-Encoding: binary");
        header("Content-Disposition: attachment; filename=".$filename);
        echo $filepath;
 

ziycon

New Member
Here is my most recent attempt, not working though. :S

PHP:
<?php
$url  = 'http://www.example.com/files/testing.txt';
$path = 'media/test.txt';

$fp = fopen($path, 'w');

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);

curl_exec($ch);

curl_close($ch);
fclose($fp);
?>
 

php.allstar

New Member
Hi,

Sorry forgot to add in a line to the bottom of my solution...to output the file contents...

PHP:
 <?php

    $contentType = 'text/plain'; // Change this to suit your file type
    $file = 'whatever.txt'; // Change this to a relative path to your file

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Type: $contentType");
    header("Content-Transfer-Encoding: binary");
    header("Content-Disposition: attachment; filename=" . basename($file));
    echo file_get_contents($file);

?>

So applying this to your example we get:

PHP:
<?php
        $filepath = 'media/'.$row['filepath'];
        $filename = basename($filepath);
        $filesize = filesize($filepath);

            $contentType = "application/pdf";

        header("Cache-Control: public");
        header("Content-Length: ".$filesize);
        header("Content-Description: File Transfer");
        header("Content-Type: ".$contentType);
        header("Content-Transfer-Encoding: binary");
        header("Content-Disposition: attachment; filename=".$filename);
        echo file_get_contents($filepath); 

?>
 

ziycon

New Member
Will file_get_contents() work if there are large files, I thought that it usings the system memory to load the file for the user?
 

php.allstar

New Member
Hi,

You didn't mention anything about performance requirements in your original post! Please be more specific in future, so we don't have all this table tennis!

file_get_contents writes to memory so if you write a 2GB file and your machine has 2GB ram you'll be up the proverbial creek without a paddle. (not that max_execution_time will let you get that far...)

So the alternative is to use cURL:

PHP:
<?php

$contentType = 'text/plain'; // Change this to suit your file type
$file = 'http://localhost/whatever.txt'; // Change this to an absolute path to your file

header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: $contentType");
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=$file");
    
$ch = curl_init();
    
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_URL, $file);
    
$data = curl_exec($ch);
curl_close($ch);
    
echo $data;

?>
 

ziycon

New Member
Thanks php.allstar , sorry about omitting that fact that there could be performance issues depending on file size. All your help is greatly appreciated. I've gotten the test instance of the file download working locally.

Is there anything I need to be aware of with headers on different browsers, I keep seeing people talking about using a different cache header for IE or safari browsers?
 

php.allstar

New Member
I've been using these headers for as long as I remember, down as far as IE6, not sure after that.

The only problem I ever really encountered was if the files are compressed using mod_deflate or mod_gzip - the dialog box just shows, unknown time remaining on IE as opposed to giving you the actual filesize and time remaining for your download. And the solution to that issue is another day's work which I'll be only too happy to share if you need to cross that bridge in future!

Good luck with it!
 

ziycon

New Member
Have the download bit working but now getting issues with maximum memory being reached so files wont download, I've pasted the bones of the download file below, the filesize() function does that use much memory? if so can I get around it by releasing memory after the filesize function runs by using 'unset' or something?

Any other observations on memory usage more then welcome.

PHP:
<?php
$did = $_GET['did'];
$contentType;

$sql = mysql_query("SELECT `file type`,filepath FROM docs WHERE id=".mysql_real_escape_string($did)." AND active=1 LIMIT 1");
$row = mysql_fetch_array($sql);

$filepath = 'media/'.$row['filepath'];
$filesize = filesize($filepath);

//mySQL queries

if($row['file type'] == 2)
    $contentType = "application/pdf";

header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: $contentType");
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=".basename($filepath));
    
$ch = curl_init();
    
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_URL, DDB_SITE.$filepath);
    
$data = curl_exec($ch);
curl_close($ch);
    
echo $data;
?>
 

php.allstar

New Member
Hi,

Although you are declaring $filesize, you are not using it in your script, so if it's not needed, drop the declaration!

A few questions:

What is the size of the file you are trying to process?
What is your memory limit set to in PHP.ini?

Put this code above your curl_init() to see what memory is used by your script up to that point:

PHP:
echo '<p style="background-color: #FFFF00;">Memory usage so far = ' . memory_get_usage() . '</p>';

To ensure the bottle neck is not in your pre curl code.

Can you post the exact message you are getting?

Did you try to up the memory limit with something like:

PHP:
ini_set("memory_limit","30M");

If you are unsure where the error is occuring, echo a few breakpoints through your code so see what you're hitting.
 

ziycon

New Member
Although you are declaring $filesize, you are not using it in your script, so if it's not needed, drop the declaration!
Its used in the sql queries after the filessize is retrieved.

What is the size of the file you are trying to process?
They can be from 0.1MB upto currently the biggest is around 220MB

What is your memory limit set to in PHP.ini?
Is this the correct variable? 'memory_limit = 32M'

Put this code above your curl_init() to see what memory is used by your script up to that point:

PHP:
echo '<p style="background-color: #FFFF00;">Memory usage so far = ' . memory_get_usage() . '</p>';

To ensure the bottle neck is not in your pre curl code.
I'll have to wait till I get home to get access to the code to do this.

Can you post the exact message you are getting?
Don't have the original error, it was basically: Allowed memory size of xxx bytes exhausted.

Also anything being done in the script for example the sql SELECT query is just returning one record which are all very small strings. The referrer page calls the filesize function once also, thats the only thing I can think of, theres very little else going on.
 

php.allstar

New Member
Its used in the sql queries after the filessize is retrieved.

Can you explain this to me in detail please. From the code you have shown me, there is no need for the variable $filesize, you are not using it here!

32M is an average setting for memory_limit, a good starting point.

They can be from 0.1MB upto currently the biggest is around 220MB

Wow, that big eh? OK, now I see, whats happening, with this setup and large files, the file is being read to memory, putting totally unneccessary strain on the server and throwing that error.

The solution here is to get cURL to write directly to a temporary file and serve that as the download:

PHP:
$fp = fopen('/path/to/temp/file.ext', 'w');       $ch = curl_init('http://www.domain.com/file.ext');     curl_setopt($ch, CURLOPT_FILE, $fp);       $data = curl_exec($ch);       curl_close($ch);     fclose($fp);

I've really donated enough time on this now, you should be pointed in the right direction, if you need my assistance any futher, please feel free to hire some of my time!
 

ziycon

New Member
I really appreciate all your help and advice, if I had the money and it wasn't a personal website I'd by all means hire assistance.

Thanks again,
ziy
 
Status
Not open for further replies.
Top