I have the following function:
if (isset($_REQUEST["f"]))
{
//get file details from database
$fileID=$_REQUEST["f"];
$sql = "select * from sds_files where file_id = " .fquery_sanitize($fileID);
$result = fquery_db($sql);
//$file_extension = strtolower(substr(strrchr($filename,"."),1));
if(mysql_num_rows($result) >0)
{
$row = mysql_fetch_assoc($result);
$filename = $row['file_name'];
$file = file_GetPath($fileID);
header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Type: application/octetstream');
header("Content-Transfer-Encoding: Binary");
header("Content-length: ".filesize($file));
header("Content-disposition: attachment; filename=\"".basename($filename)."\"");
readfile("$file");
}
else
{
echo "File cannot be found";
}
}
else
{
echo "No file selected";
}
this gets a file, which could be any type, .xls, .doc .dox, .pdf etc...
For most pdfs this code works, but in isolated cases, I get users claiming they recieve an error like "file not found". But, if I give them a direct link to a file it works fine.
I'm at a loss to understand what the problem is, I've forced the attachment type so that their forced to save it rather than it come up in a browser, as in ie6 it tends to crash if you use the adobe plugin rather than the program.
EDIT:
The error comes as part of an adobe acrobat error, nothing to do with the PHP or apache error codes. More client related. I'm showing the code as the user's behaviour is different.
I've looked into it a little more and it seems the direct link has a MIME type of PDF, which adobe tells the browser belongs to adobe reader, I'm wondering whether save as will force the same behaviour to cause the error.
Any help would be appreciated!
Ok it seems to be isolated to:
header("Content-disposition: attachment;....
This forces the browser to download to content and not use the browser plugin, which leads to permissions problems on some user setups.
Related
I made a small website just for fun for file sharing, each account limited to 20 Files per day,
All members data stored inside MySQL table.
Username, Password, and Download number.
When someone click on download, a function will trigger and store +1 inside the table. and the downloading will begin.
I thought everything working great, after I saw the log file on my server, and I found someone who download a file without triggering the function and without leaving history Inside the database!
How is that possible! and how I can do the same so I can block this bug!
Here is the code:
if (isset($_GET['download']) && !empty($_GET['download'])){
if (!(isset($_GET['username']) && !empty($_GET['username']))){
echo 'Only a member of this website can download this file. However, no username was specified in this download. Sorry for inconvenience.';
die;
}
$dl_username = $this->decrypt($_GET['username']);
if (gator::getUser($dl_username) == false){
echo 'Only a member of this website can download this file. However, the username provided does not exist in the database. Sorry for inconvenience.';
die;
}
$dl_user = gator::getUser($dl_username);
if ($dl_user['downloads'] > 20){
echo 'Cannot download more files for today! You have crossed the limit of downloading 20 files.';
die;
}
gator::updateUser($dl_user['username'], array('downloads' => $dl_user['downloads'] + 1));
$filename = $this->filterInput($this->decrypt($_GET['download']));
if (in_array($filename, gatorconf::get('restricted_files'))) die;
if (!file_exists($_SESSION['cwd'].DS.$filename)) die;
// Set headers
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=\"$filename\"");
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: binary");
// output file
set_time_limit(0);
$file = #fopen($_SESSION['cwd'].DS.$filename,"rb");
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
}
gator::writeLog('download - '.$filename);
echo 'Downloaded';
die;
}
EDIT: There is two log files, one for actions that record members actions on the website and the other one on my Apache server, that record everything and all connections even for non users.
And here is an example of how the file link look like: "no one can use hot links"
https://www.example.com/?download=MLB%20820-2186%20schematic%20diagram.pdf&username=Linda
I am trying to add stats to Google Analytics regarding direct accesses of mp3 files (eg by iTunes) on this server. I want to run this PHP function and deliver the file as though there had been no redirection.
The mod_rewrite piece of this seems to be functioning fine. My onsite download scripts and streaming player can access mp3 files without triggering the rule.
RewriteCond %{HTTP_REFERER} !^http://(www\.)?mysite\.com [NC]
RewriteRule ^audio/episodes/([^/\.]+).mp3$ /audio/episodes/google_analytics_mp3.php?mp3=$1&redirected=1 [L,QSA]
However my initial attempt at delivering mp3 after PHP script runs does not deliver file for direct accesses. Just a white screen in a browser and an error in iTunes.
if ($id = intval($_REQUEST['mp3'])) {
$e = new Episode;
list($ep) = $e->retrieve("id = $id");
if ($ep) {
ga_send_pageview(basename($ep->audio_link), $ep->google_title());
if ($_GET['redirected'] == 1) {
$fileLocation = '/'.$ep->audio_link;
header("Location: $fileLocation");
exit();
}
}
}
Also tried sending header('Content-Type: audio/mpeg'); before the location header. Same result.
My second attempt makes browser download file as attachment which will not work for programs like iTunes and is not desired result.
if ($_GET['redirected'] == 1) {
$fileName = basename($ep->audio_link);
$fileLocation = $_SERVER['DOCUMENT_ROOT'].'/audio/episodes/'.$filename;
header("Content-Transfer-Encoding: binary");
header("Content-Type: audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3");
header('Content-length: ' . filesize($fileLocation));
header('Content-Disposition: attachment; filename="'.$fileName.'"');
header('X-Pad: avoid browser bug');
header('Cache-Control: no-cache');
readfile($fileLocation);
exit();
}
Update: Per Martin's suggestion also tried this second approach but with disposition set to inline header('Content-Disposition: inline; filename="'.$fileName.'"'); but Chrome just shows white screen. If I disable rule to remove script from equation Chrome will show native mp3 player and play file.
How can I simply allow the normal access to mp3 file after PHP script runs?
I have previously had issues with Content-length: , you can try commenting out this line. I think the filesystem filesize reader is different from the browser filesize quantifier so the browser expects (and gets) 99% of the file which is obviously then incomplete so then it can't output the file as it perceives it as incomplete data.
File System File Size as read !== browser file size
However I'm not certain why this is so .
I am allowing users to upload documents to the server. However, i don't want them to obviously see where the files are being stored. What can i do that will allow them to still get the file but without seeing the file location.
You can use a PHP query to accomplish this, lets say you use the following URL:
http://mysite.com/files.php?file=xyz.pdf
In files.php you can check the get variable file and have a hard coded function that retrieves the file. You can do this many ways one by using headers to force a download or read the file into a var and print it's contents to the page. For say like a pdf reading the file and printing it to the page is the same as linking it to the file.
warning though: like with using headers do not print anything to the page except the file. I also recommend declairing you headers still if you read the file and print it so that the end user will not get the gobbly goop that is the source of the file i.e. jpg or pdf.
Oh no, I forgot a header warning, I have been running into a header problem ever since Adobe made the ISO for PDF's open source, depending on the application that produced the PDF and the browser from which the user is uploading the PDF from, the header will be anything from:
'application/pdf', 'application/x-download','application/octet-stream','application/octet','binary/octet-stream'
so be careful hard coding the upload section to a header type, I know this question is about downloads but i just thought i would throw that in there. Also using headers for downloads doesn't matter I would simply use the standard application/pdf there.
There are a few ways todo this but i prefer using .htaccess
So my link would look like http://example.com/files/filename.zip
extra parameters within the url could be used a username or password like:
http://example.com/files/bob/filename.zip
http://example.com/files/18d52c/filename.zip
Then thos could be checked against a database to see if user is allowed to download that specific file, much like you would use for instant downloads after payment.. but a basic method would be like so:
.htaccess
RewriteRule ^files/(.*)$ serve.php?file=$1
serve.php
<?php
if(isset($_GET['file'])){
$file=basename($_GET['file']);
//Protect the index.php && serve.php
if(basename($_GET['file'])=='index.php' || basename($_GET['file'])=='serve.php'){
header("HTTP/1.1 403 Forbidden");die();
}
$downloadFolder="original_location/";
if(file_exists($downloadFolder.$file)){
$fsize = filesize($downloadFolder.$file);
$ctype=finfo_file(finfo_open(FILEINFO_MIME_TYPE), $downloadFolder.$file);
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
if(strstr($_SERVER["HTTP_USER_AGENT"],"MSIE")==false) {
header("Content-Type: application/force-download");
header('Content-Description: File Transfer');
}else{
header("Content-Type: $ctype");
}
header("Content-Disposition: attachment; filename=\"".basename($file)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".$fsize);
ob_clean();
flush();
readfile('original_location/'.$file);
}else{
header("HTTP/1.0 404 Not Found");
}
die();
}
header("HTTP/1.0 404 Not Found");
?>
original_location/index.php
header("HTTP/1.1 403 Forbidden");
Store it using some random unique ID that you can map to the real file, then serve it using a script that does readfile() on the actual file.
The http://php.net/readfile docs also have an example on how to force it being a download.
I have a PHP file that generates xls files using the module found at http://pear.php.net/package/Spreadsheet_Excel_Writer/
I can create the sample document just fine and when I open it, it looks fine.
My next step it to turn it into a downloadable link. To do that, I did this:
$mimeType = "application/vnd.ms-excel";
$file_name = "test.xls";
$file_path = "/tmp/".$file_name;
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header('Content-Type: application/' . $mimeType);
header('Content-Length: '.$size);
header("Content-Disposition: attachment;filename=$file_name ");
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($file_path, 'rb');
if ($file) {
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
echo $err;
}
When I download the file however, it contains a lot of garbage data both in Excel and OpenOffice. The diff says that then binary file in the /tmp folder and the downloaded file are different from each other. I'm guessing that it has something to do with the headers or with fpassthru but I haven't had much luck with debugging the issue.
Any ideas on what the problem is?
The multiple Content-Type headers are uncessary. You're essentially saying that the file is a muffin and a pizza and a ford taurus all at the same time. All you need is the application/octet-stream version, unless you want to serve up the exact mime type.
As well, is there any reason you're trying to turn the file handle returned by fopen() into a reference?
Try something simpler:
<?php
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment;filename=$file_name");
readfile("/tmp/test.xls");
exit();
?>
and see if that does any better.
Just make sure that you don't send ANYTHING out to the browser BEFORE the actual file content gets send.
It might just be some php 'error' or even 'notice' that Spreadsheet_Excel_Writer is producing and you don't even see. Or it might be a closing '?>' tag thats followed by s simple space or newline.
I had a similar error where the file that was generated inside the web folders were working. However the delivery using header('...') gave me corrupt files. This was due to a single space at the end of one php file after the closing '?>' tag.
I am using the same library and I just discovered that the files in the library itself are creating the whitespace.
Solution: In the following files remove the whitespace at the end of the file, or remove the ?> closing tag at the end.
Files to edit (all files in the Spreadsheet_Excel_Writer package):
Writer.php
Workbook.php
Worksheet.php
PPS.php
Parser.php
OLE.php
Parser.php
File.php
BIFFWriter.php
Validator.php
Root.php
Add the following code at the top of the page where the excel file is generated
ob_clean();
This would clear all the gibberish data.Also check for any echo statements.If echo statements are present, remove them. The data should always present in format specified by excel package.
I am coding a file sharing application for my office. One strange problem I am going through is the Illustrator files being opened in PDF when you hit the download button.
This problem is triggered because the mime type of illustrator files is application/pdf. So the browser when it reads the file, triggers Acrobat to open the file. Is there any way I could instruct the browser to open the file in Illustrator?
Or is there any way to modify the mime type after uploading the file? The backend code is PHP.
Thank you for any help.
One way to do this is to force the browser to display the "download file"-dialog. So the user can decide what to do with the file.
This can be done via PHP-Headers. (http://www.php.net/manual/en/function.header.php#83384)
There is also an example on how to this (Post 83384):
<?php
// downloading a file
$filename = $_GET['path'];
// fix for IE catching or PHP bug issue
header("Pragma: public");
header("Expires: 0"); // set expiration time
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
// browser must download file from server instead of cache
// force download dialog
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
// use the Content-Disposition header to supply a recommended filename and
// force the browser to display the save dialog.
header("Content-Disposition: attachment; filename=".basename($filename).";");
/*
The Content-transfer-encoding header should be binary, since the file will be read
directly from the disk and the raw bytes passed to the downloading computer.
The Content-length header is useful to set for downloads. The browser will be able to
show a progress meter as a file downloads. The content-lenght can be determines by
filesize function returns the size of a file.
*/
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
#readfile($filename);
exit(0);
?>
When using this example please consider that using
$filename = $_GET['path'];
is a big security problem. You should work with something like ID's instead or validate the input.
For example:
if($_GET['file'] == 1) {
$filename = foobar.pdf;
} elseif($_GET['file'] == 2) {
$filename = foo.pdf;
} else {
die();
}