Readfile is best solution to download external files? - php

I need to get a remote file and give it to user without saving it to my server disk (for hiding original URL) and found a lot of posts about download external files with various functions like file_get_contents or readfile. Already I'm using this one:
function startDownload($url){
if($this->url_exists($url))
{
//get filename from url
$name=$this->getFileName($url);
//first flush clear almost output
ob_end_flush();
//final clear
ob_clean();
//set headers
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . $name . "\"");
//send file to client;
readfile($url);
//exit command is important
exit;
}
else JFactory::getApplication()->enqueueMessage(JText::_('URL_NOT_FOUND'), 'error');
}
And that's working but there is a problem! For a file with 200 MB size it takes ~ 10 seconds to start download in client browser. I think it's because readfile first downloads whole file to my server buffer and then give it to user. Is that right?
And is it possible to make it faster? for example download be started before fetch ended or it isn't possible technically?
In fact I don't know that this method is optimised or not. Any technical advice would be appreciated.
Note :
I know that this function should be changed for big files and that's not my concern now.
I consider to buy the external server in the same datacenter to make this download faster.
Target is that [File server] be separate than the file [online shop].

I tested curl method that mentioned by #LawrenceCherone. It worked nicely but when moved it to my project the result was the same as readfile (white screen for a few seconds).
So suspect to readfile() function. Separate my previous code to a single PHP file and result was amazing! Download starts immediately.
So I think my guess wasn't right and problem was not related to readfile function.
After a little search found a minor modification. I added below line :
while (ob_get_level()) ob_end_clean();
before the :
readfile($url);
And now download starts before whole file fetched in my server.

Related

How to download a file without an extension in PHP

I have a file with no extension on it, but I know it's a tiff. I want to be able to download this file via PHP.
I created a page with a link to another php page, which has the following content:
<?php
$imgPath = 'http://server/23700-b074137f-eb5c-45d6-87c2-13c96812345b';
header("Content-disposition: attachment; filename=invoice.tiff");
header("Content-type: image/tiff");
readfile($imgPath);
?>
When I click the link, I get a prompt to download invoice.tiff, but it's 0 bytes.
However, if I rename the file on the server to 23700-b074137f-eb5c-45d6-87c2-13c96812345b.tiff (and change the $imgPath), it works.
How do I accomplish this without renaming the file to include the extension?
It's possible the 'tiff' extension is registered as a known file type on the server, so when you rename and request the tiff it's permissions will allow you to open it. However, with no extension, the security is probably stopping you from reading it, as mentioned by 'Mike B' above. To check this try just entering the file name in your browser address bar and see if it opens, both with and without the 'tiff' extension. There is no workaround for getting past the security issue, short of changing the severs security which would be very bad.
You are retrieving the file from a URL, therefore activating the 'fopen wrappers' in readfile. In general, you should not do this, especially when working locally since it invokes a lot of unnecessary overhead and (in this case) unwanted 'magic' behaviour.
Just use readfile on the local path to the file, and it'll be fine, or use die(file_get_contents($imgPath)) instead of the last line to circumvent PHP's native behaviour.
It works for me:
$imgPath = 'http://server/23700-b074137f-eb5c-45d6-87c2-13c96812345b';
$f = fopen($imgPath, "r");
header("Content-disposition: attachment; filename=invoice.tiff");
header("Content-type: image/tiff");
fpassthru($f);
You should also add the content-length header like so:
// untested code
header('Content-Length: '.strlen(stream_get_contents($imgPath)));

What does the "PK¿¿¿" response means in PHP

Hi I'm downloading a file to an app on iOS using the function readfile() on a PHP web service and I want to know if the file is downloaded correctly but I don't know how I can do that.
So what I'm trying is to do some echo to know if the file has been downloaded like this:
echo "before";
readfile($file);
echo "after";
But the response I get is this:
beforePK¿¿¿
Any one knows what does this mean or how can I know if the file is downloaded correctly?
UPDATE:
Yes it's a zip file, here are my headers
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$ticket");
header("Content-Type: application/zip");
header("Content-Transfer-Encoding: binary");
You're trying to output the contents of a zip file aren't you?
readfile($file) works the same as echo file_get_contents($file). If you're trying to present someone a file to download, do not add any additional output else you risk breaking the file.
I would also recommend reading up on the header function. That way you can explicitly tell the browser that you're sending a file, not an HTML page that has file-like contents. (See the examples involving Content-Type)
PHP should be setting the correct headers prior to readfile() - this LITERALLY reads the file out to the browser/app... but the browser/app needs to know what to do with it...
Usually you just assume that once the connection has closed that the data is done being transferred. If you want to validate that the file has been transferred fully, and without corruption you'll need to use a data structure like XML or JSON which will:
Delimit the data fields and cause the XML/JSON parser to throw an error if one is omitted, aka the transfer was cut off before it finished.
Allow you to embed more than one piece of data with the response, eg. an MD5 hash of the file that can be re-calculated client-side to verify that the data is intact.
eg:
$file = 'myfile.zip';
$my_data = array(
'file' => base64_encode(file_get_contents($file)),
'hash' => md5_file($file)
)
//header calls
header(...)
echo json_encode($my_data);
exit;

Blocks can only contain strings error when saving to disk

Background
I'm generating an Excel sheet that get's emailed to a certain address. It works perfectly on localhost (saves perfectly, can't test the mailing as I have no local mailserver running). When I host it, I run into some problems.
What problems
I've narrowed it down to this line:
$this->PhpExcel->saveToDisk($path);
$path is a custom path that is set at runtime. When I run that, it saves the file to disk but it gives me this:
This is a function in a custom helper class. Below is the code for the function:
public function saveToDisk($path) {
// set layout
$this->_View->layout = '';
// headers
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="'.$filename.'"');
header('Cache-Control: max-age=0');
// writer
$objWriter = PHPExcel_IOFactory::createWriter($this->xls, 'Excel2007');
ob_end_clean();
$objWriter->save($path);
// clear memory
$this->xls->disconnectWorksheets();
}
But here is the catch. When I output it to browser, by changing $objWriter->save($path); to $objWriter->save('php://output'); it gives me no error and exports the file perfectly.
Question
I've googled the error and tried my best to find the solution, but to no avail. The amount of info available on this error is staggeringly scarce or outdated. Could someone please tell me what it is that I'm missing or doing wrong?
When you've saved the file to disk, CakePHP still expects you to give it something to return to the browser that made the request, even if it's just a message to say that the file has been saved successfully, or that the email has been sent successfully.
If you're saving to disk, you don't want to send the headers because the server isn't going to be returning an Excel file, so no headers.
I fixed it by removing this line:
ob_end_clean();

PHP Download not working with path coming from DB

I have a simple form that, when posted back, calls a function to initiate a download. The path and file name are pulled from the database then I'm using headers to start the download. My code for the download is:
//START DOWNLOAD
header('Content-type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$FILE_PATH.$FILE_NAME.'"');
header("Content-Transfer-Encoding: binary");
header("Connection: close")
In the example above, the $FILE_PATH variable is /downloads/software/ and the $FILE_NAME variable is client-installer.exe. So, what I would expect is a file called client-installer.exe (approximately 70MB) to be downloaded to the client. Instead, I get a file called _downloads_software_client-installer.exe and approximately 10KB.
I thought maybe I needed to urlencode the file path/name but that didn't fix the issue either. So I'm left thinking perhaps I have something wrong with the header but can't seem to find it.
Thank you!
The filename header just denotes what the file should be called. It must contain only a filename, not a path. The internal path on the server's hard disk is irrelevant and of no interest to the client. Your server will have to output the actual file data in the response, the client can't take it from the server given the path.
See readfile.

How to allow downloading a file of 1GB

I want to allow the user to download a file up to 1GB in size, but according to my code only a file of 113MB can be downloaded...
header('Content-type: application/zip');
//open/save dialog box
header('Content-Disposition: attachment; filename="check.zip"');
//read from server and write to buffer
readfile('check.zip');
Can anyone tell me how to download a larger file?
I'm going to guess from what you've said that you're getting an "out of memory" error.
In that case, perhaps this note from the documentation might be of interest:
Note:
readfile() will not present any memory issues, even when sending large files, on its own. If you encounter an out of memory error ensure that output buffering is off with ob_get_level().
So, check ob_get_level() and call ob_end_flush() if necessary to stop output buffering.
Alternatively, you could do something like this:
$f = fopen("check.zip","rb");
while(!feof($f)) {
echo fgets($f);
flush();
}
Another option is this:
header("Location: check.zip");
This will redirect the browser to the check.zip file. Since it's a download, the existing page won't be affected. You can even output the rest of a page to say something like "Your download will begin momentarily" to the user.
Either read and echo the file a chunk at a time, or use something like mod_sendfile to make it Not Your Problem.
Increase your file write buffer chunk size to maximum of file. That will decrease the utilization of resources and your download works fine.
Edit:
Use HTML5 webworkers to download large. Webworkers works in background so you can able to download large files.

Categories