I'm having no luck serving a .dmg from my online store. I stripped down the code to just the following to debug, but no matter what I get a zero-byte file served:
header('Content-Type: application/x-apple-diskimage'); // also tried octet-stream
header('Content-Disposition: attachment; filename="My Cool Image.dmg"');
$size = filesize('/var/www/mypath/My Cool Image.dmg');
header('Content-Length: '.$size);
readfile('/var/www/mypath/My Cool Image.dmg');
This same code works for a number of other file types that I serve: bin, zip, pdf. Any suggestions? Professor Google is not my friend.
Found the solution. The culprit was readfile() and may have been memory related. In place of the readfile() line, I'm using this:
$fd = fopen ('/var/www/mypath/My Cool Image.dmg', "r");
while(!feof($fd)) {
set_time_limit(30);
echo fread($fd, 4096);
flush();
}
fclose ($fd);
It's now serving all filetypes properly, DMG included.
You should not have spaces in the filename (Spaces should not be used when it comes to web hosted files)
Try something like this or rename your file with no spaces:
<?php
$path ='/var/www/mypath/';
$filename = 'My Cool Image.dmg';
$outfile = preg_replace('/[^a-zA-Z0-9.-]/s', '_', $filename);
header('Content-Type: application/x-apple-diskimage'); // also tried octet-stream
header('Content-Disposition: attachment; filename="'.$outfile.'"');
header('Content-Length: '.sprintf("%u", filesize($file)));
readfile($path.$filename); //This part is using the real name with spaces so it still may not work
?>
Related
The end goal is for the user to download a .csv file. Right now I'm just testing trying to download a simple text file: test.txt. The only thing in this file is the word "test".
Here is the HTML code for files_to_download.php
Test file: <a href='test.php?file=test.txt'>Test.txt</a>
Code for test.php:
if(!(empty($_GET["file"])))
{
$file_name = $_GET["file"];
$path = "path/to/file";
$fullPath = $path . $file_name;
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: inline; attachment; filename=\"$file_name\"");
header("Content-Type: text/plain");
header("Content-Transfer-Encoding: binary");
readfile($fullPath);
}
I've tried variations of the headers above, adding more and removing others. The above seem to be the most common recommended on this site.
I've also tried changing
header("Content-Type: text/plain");
to
header("Content-Type: text/csv");
and get same results: empty .txt or .csv file.
The files are not empty when I open them directly (file browser) from the server. I've checked the permissions of the files and they're both 644, so the entire world can at least read the files. The directory is 777.
Is there a configuration on the Apache server I need to specify that may not be or am I missing something above.
Thanks for looking!
In most cases the path is wrong
Read the text file, then echo the text out after your header() calls.
Here's how I have my csv download set up:
//downloads an export of the user DB
$csv = User::exportUsers();
header('Content-disposition: attachment; filename=userdb.csv');
header('Content-type: text/csv');
echo $csv;
Where exportUsers() creates the csv data. You can easily just replace $csv with the contents of your text file, then echo it out.
And as far as your text file, you can use file_get_contents() to get the contents of your file into a string. Then echo that string.
Try setting the content length of the file:
header('Content-Length: ' . filesize($file));
Also, please have this in mind: file inclusion
In my case, the path was correct. And the download-forcing was working on windows, but not mac.
I figured out after few tests that the header Content-Length was failing. I was using the function filesize on a full url, like :
$url_my_file = "http://my-website.com/folders/file.ext";
header('Content-Length: '.(filesize($url_my_file)));
I replace it by
$url_my_file = "http://my-website.com/folders/file.ext";
$headers = get_headers($url_my_file, 1);
header('Content-Length: '.($headers['Content-Length']));
And ... It's working now :)
I use this code to enable users to download a zip file:
if(file_exists($filename)){
header("Content-Disposition: attachment; filename=".basename(str_replace(' ', '_', $filename)));
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($filename));
flush();
$fp = fopen($filename, "r");
while (!feof($fp))
{
echo fread($fp, 65536);
flush();
}
fclose($fp);
exit;
}
When the file is downloaded, it only downloads 25,632 kilobytes of data. However the zip file is 26,252 kilobytes ...
Why does the browser get all 25MB but then stop?
I checked the Content-Length header to make sure it was correct and it is...
edit
In firefox, when i download the file, it says 'of 25mb' SO the browser thinks that 25mb is the COMPLETE amount... however, the content-length when echo'd is 26252904?
add this before your code
ob_clean();
ob_end_flush();
Your header('Content-Type ...) calls are useless as only the last one will be sent to the browser.
Downloads are triggered by Content-Disposition: attachment. You should send the actual Content-Type: application/zip if you are sending a zip file.
Finally, your read loop is unnecessary.
Putting it all together, your code should look like this:
if (file_exists($filename)) {
$quoted_filename = basename(addcslashes($filename, "\0..\37\"\177"));
header("Content-Disposition: attachment; filename=\"{$quoted_filename}\"");
header('Content-Type: application/zip');
header('Content-Length: '.filesize($filename));
readfile($filename);
}
Use a single MIME type to represent the data.
In this case using application/octet-stream will do just fine. This is when you dont know the MIME before hand. When you know it, you must put it. Do not use multiple content-type headers.
Usually, when the browser doesn't know how to handle a particular MIME, it will trigger the download process. Further, using Content-disposition: Attachment; .. ensures it.
There exists a simple readfile($filename) which will send out the bytes of the file to the requesting process like below:
header("Content-disposition: attachment;filename=" . basename($filename);
readfile($filename);
I had similar problem. The file downloaded fine in Firefox but not in IE. It appeared that Apache was gzipping the files and IE was not able to ungzip so the files were corrupted. The solution was to disable gzipping in Apache. You can also check if PHP is not gzipping on the fly and disable it too.
For Apache you can try:
SetEnv no-gzip 1
And for PHP, in .htaccess:
php_flag zlib.output_compression on
This answer is by No means a REAL ANSWER.
However i did get it to work... I just set the Content-Length to 30000000. Therefor it thinks the file is bigger than it actually is, and then it downloads it all.
Ugly hack i know, but i couldn't find ANY other way
I want to handle big files(1-2Gb) downloads with php script and found two ways to do that:
with file_get_contents()
with readfile()
and use this implementation:
header('Content-type: ' . $string);
header('Content-disposition: attachment; filename=' . $info['filename']);
$file = file_get_contents($filename);
echo $file;
or
readfile($filename);
But it takes too long to output the file. I suppose that the whole file has to be readed, before the output starts.
It is more quickly when point the exact location of the file. Then it starts the output almost immediately.
I am looking for solution that streams the file or something. Any ideas?
You should consider using mod_xsendfile
$handle = fopen($this->_path, 'rb');
while(!feof($handle))
{
echo fread($handle, 4096);
ob_flush();
flush();
}
fclose($handle);
from PHP Readfile() not working for me and I don't know why
The end goal is for the user to download a .csv file. Right now I'm just testing trying to download a simple text file: test.txt. The only thing in this file is the word "test".
Here is the HTML code for files_to_download.php
Test file: <a href='test.php?file=test.txt'>Test.txt</a>
Code for test.php:
if(!(empty($_GET["file"])))
{
$file_name = $_GET["file"];
$path = "path/to/file";
$fullPath = $path . $file_name;
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: inline; attachment; filename=\"$file_name\"");
header("Content-Type: text/plain");
header("Content-Transfer-Encoding: binary");
readfile($fullPath);
}
I've tried variations of the headers above, adding more and removing others. The above seem to be the most common recommended on this site.
I've also tried changing
header("Content-Type: text/plain");
to
header("Content-Type: text/csv");
and get same results: empty .txt or .csv file.
The files are not empty when I open them directly (file browser) from the server. I've checked the permissions of the files and they're both 644, so the entire world can at least read the files. The directory is 777.
Is there a configuration on the Apache server I need to specify that may not be or am I missing something above.
Thanks for looking!
In most cases the path is wrong
Read the text file, then echo the text out after your header() calls.
Here's how I have my csv download set up:
//downloads an export of the user DB
$csv = User::exportUsers();
header('Content-disposition: attachment; filename=userdb.csv');
header('Content-type: text/csv');
echo $csv;
Where exportUsers() creates the csv data. You can easily just replace $csv with the contents of your text file, then echo it out.
And as far as your text file, you can use file_get_contents() to get the contents of your file into a string. Then echo that string.
Try setting the content length of the file:
header('Content-Length: ' . filesize($file));
Also, please have this in mind: file inclusion
In my case, the path was correct. And the download-forcing was working on windows, but not mac.
I figured out after few tests that the header Content-Length was failing. I was using the function filesize on a full url, like :
$url_my_file = "http://my-website.com/folders/file.ext";
header('Content-Length: '.(filesize($url_my_file)));
I replace it by
$url_my_file = "http://my-website.com/folders/file.ext";
$headers = get_headers($url_my_file, 1);
header('Content-Length: '.($headers['Content-Length']));
And ... It's working now :)
I have a issue, I have a PHP script that compresses images in a Zip file and then forcing the zip file to download. Now my issue is it's not showing in IE how much space has been downloaded sofar. Eg 2MB's out of 20MB's..... 15secs remaining. Firefox works perfect.
My code
header("Content-Disposition: attachment; filename=" . urlencode($zipfile));
header("Content-Type: application/x-zip-compressed");
header("Content-Description: File Transfer");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($filename));
$fd = fopen($filename,'r');
$content = fread($fd, filesize($filename));
print $content;
The main issue, as noted in the comment, is that you are storing the whole file into memory prior to sending which may cause a very long wait depending on file size. What you would prefer to do is output the buffer as segments are read into memory, in 1024 byte blocks, for example.
You could try something more along the lines of:
if ($file = fopen($filename, 'rb')) {
while(!feof($file)) {
print(fread($file, 1024*8));
flush();
}
fclose($file);
}
Which will not attempt to read the entire file prior to output, but rather output blocks of the file as they are read into the output buffer.