PHP download doesn't work properly - php

I just wanted to make some kind of "download service" but it isn't working.
Whole download.php:
<?php
include "utils/config.php";
include "utils/utils.php";
if($isLoggedIn && isset($_POST['id']) && hasProduct($userid,$_POST['id'])){
$ID = $_POST['id'];
$pdata = getProduct($ID);
$file = $pdata['file'];
$name = $pdata['dlfile'];
$mime = $pdata['file_mime_type'];
#echo $file."<br>".$name."<br>".$mime;
/**
* $file = "assets/files/151708025047541"
* $name = "API.jar"
* $mime = "application/java-archive"
*/
download($file,$name,$mime); // <-- wouldn't work
#download("assets/files/151708025047541","API.jar","application/java-archive"); // <-- wouldn't work
}
#download("assets/files/151708025047541","API.jar","application/java-archive"); // <-- would work fine
?>
Function "download" in utils.php:
function download($f_location, $f_name, $type){
header('Content-Description: File Transfer');
header('Content-Type: '.$type);
header('Content-Length: ' . filesize($f_location));
header('Content-Disposition: attachment; filename="' . basename($f_name).'"');
ob_clean();
readfile($f_location);
}
The only thing "config.php" contains are some variables.
The only thing "utils.php" contains are some functions and it's connecting to the database (which is needed for $isLoggedIn, hasProduct (...) and getProduct (...).
The download itself is wokring, but it wants to download a file with zero content and with the name "download.php".
At my comments you see, if I try the same function (with static args) outside the if-block the download works fine. But if I try to use function (with static args) inside the if-block it will give me the same problem.
Funny thing is that the same function still worked a few months ago.
Am I doing something wrong? How can I download these files dynamic with an authorization-check (as I have already tried)?
I hope you understand my problem:)

A Download-Manager add-on was trying to catch the download..
Just added an exception/disabled the add-on and now it's working!

Related

Downloading excel file from controller in code igniter

I am having the following issue:
I am using code igniter as php framework, and from one of my views I do an ajax call to a function that generates and xlsx file using PHPExcel and data from mysql database. The file is correctly created in the server but when I try to force_download it won't download anything. Here is the php function:
public function generar_excel($idCotizacion){
isLogged($this->session->userdata('logged_in'));
isAdmin($this->session->userdata('logged_in'));
$cotizacion = $this->cotizacion_model->get_cotizacion_by_id($idCotizacion);
$propuestas = $this->propuesta_model->getPropuestasPorCotizacion($idCotizacion);
error_reporting(E_ALL);
date_default_timezone_set('Europe/London');
require_once 'application/libraries/PHPExcel-1.8/Classes/PHPExcel/IOFactory.php';
require_once 'application/libraries/PHPExcel-1.8/Classes/PHPExcel.php';
$excel2 = PHPExcel_IOFactory::createReader('Excel2007');
$excel2 = $excel2->load('prueba.xlsx'); // Empty Sheet
$excel2->setActiveSheetIndex(0);
$excel2->getActiveSheet()->setCellValue('C8', $cotizacion["capitas"])
->setCellValue('C2', $cotizacion["empresa_nombre"])
->setCellValue('C9', $cotizacion["masa_salarial"])
->setCellValue('B11', $cotizacion["aseguradora_actual"])
->setCellValue('B13', $cotizacion["variable"]/100)
->setCellValue('B14', '0.6')
->setCellValue('B12', '0');
$letra = 'C';
foreach($propuestas->result_array() as $row) {
$excel2->getActiveSheet()->setCellValue($letra.'11', $row["nombre"])
->setCellValue($letra.'13', $row["variable"]/100)
->setCellValue($letra.'14', '0.6')
->setCellValue($letra.'12', '0')
->setCellValue($letra.'16', '=C$8*'.$letra.'12+C$9*'.$letra.'13+C$8*'.$letra.'14')
->setCellValue($letra.'17','=(B$16-'.$letra.'16)*13')
->setCellValue($letra.'18','=1-('.$letra.'16/B16)');
++$letra;
}
$objWriter = PHPExcel_IOFactory::createWriter($excel2, 'Excel2007');
$nombreArchivo = 'CuadroComparativo-'.$cotizacion["empresa_nombre"].'.xlsx';
$objWriter->save('uploads/'.$nombreArchivo);
$fileContents = file_get_contents('http://rpm.singleton.com.ar/uploads/'.$nombreArchivo);
//print_r($fileContents);
$this->load->helper('download');
force_download($nombreArchivo, $fileContents);
}
The response and preview from inspecting the browser are unreadable.
Thank you!
Set the MIME type for the data you're sending in the force_download() function. The browser may be either trying to best guess it, or just outputting exactly what you send it (which may be the 'unreadable' data you're referring to).
Try changing your force_download line to:
force_download($nombreArchivo, $fileContents, TRUE);
This will set the MIME type based on your file extension (xlsx), with should force the browser to download the file.
I solved it by adding in the success function of ajax the following:
success: function(json) {
var content = JSON.parse(json);
//alert(content);
if (content.error) {
$('#error').html(content.response);
$('#error').show();
$('#success').hide();
} else {
// alert(content);
location.href="http://rpm.singleton.com.ar/uploads/"+content.response;
//location.href = "<?php echo site_url('administrador'); ?>/" + content.response;
}
},
And in the php i echo the file name.
Try this code:
header('Content-Type: application/vnd.ms-excel');
header("Content-Disposition: attachment; filename={$nombreArchivo}.xls");
$this -> load -> view ('view_file',$fileContents);

php header <!-- Core Error --> and file currupted

I'm having a issue since I update my PHP to version 5.4.41.
I'm running on a nginx server with php 5.4.41.
I've made a php code that allow me to retrieve a file and download it. But since I updated my php code all my file that i'm trying to download get corrupted. So I upload a txt file and to see what was happening.
EX: If I upload a text file with test inside after I download it, it will become
<!--core error-->test
Here the complete code
<?php
require_once("include.php");
if(!xml2php("customer")) {
$smarty->assign('error_msg',"Error in language file");
}
//Grab the key from the url
$key = $VAR['key'];
if ($VAR['escape'] == 1) {
if (isset($key)) {
$q = "SELECT * FROM ".PRFX."TABLE_CUSTOMER_BACKUP WHERE CUSTOMER_BACKUP_KEY=".$db->qstr($key);
if(!$rs = $db->Execute($q)) {
force_page('core', 'error&error_msg=MySQL Error: '.$db- >ErrorMsg().'&menu=1&type=database');
exit;
} else {
$customer_backup_filename = $rs->fields['CUSTOMER_BACKUP_FILENAME'];
$customer_backup_ext = $rs->fields['CUSTOMER_BACKUP_EXT'];
$customer_backup_file_link = $rs->fields['CUSTOMER_BACKUP_FILE_LINK'];
$customer_backup_filetype = $rs->fields['CUSTOMER_BACKUP_FILETYPE'];
}
if(empty($customer_backup_file_link)){
force_page('core', 'error&error_msg=This key is not valid');
exit;
}
} else {
force_page('core', 'error&error_msg=This key is not valid');
exit;
}
$fakeFileName= $customer_backup_filename.".".$customer_backup_ext;
$file = $customer_backup_file_link;
if (isset($customer_backup_file_link, $customer_backup_ext, $customer_backup_filename, $customer_backup_filetype) && file_exists($customer_backup_file_link)){
ignore_user_abort(true);
set_time_limit(0); // disable the time limit for this script
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: ".$customer_backup_filetype);
header("Content-Disposition: attachment; filename=".$fakeFileName);
header("Content-Length: ".filesize($file));
header("Content-Transfer-Encoding: binary");
readfile($file);
} else {
force_page('core', 'error&error_msg=There was a problem downloading your file. Please contact your administrator.');
exit;
}
} else {
force_page('core', 'error&error_msg=There was a problem downloading your file. Please contact your administrator.');
exit;
}
$smarty->display('customer'.SEP.'download.tpl');
//exit;
?>
I discover if I put // in front of
header("Content-Type: ".$customer_backup_filetype);
header("Content-Disposition: attachment; filename=".$fakeFileName);
to ignore it the file will display into the browser without issue
I need some help I've been working on this issue for 2 days and haven't found nothing.
Update :
I discover if I try to escape the smarty template engine it will not work same issue. But if I load the smarty template engine the text will display without error.
http://www.exemple.com/index.php?page=customer:download&key=1414830421&escape=1 //Escaping the smarty template engine
The <!--core error--> has to originate to some place before:
readfile($file);
Since this is the line where your content 'test' is written, which displays after it.
My guesses would be, that the source is either one of:
include.php
xml2php()
$db->Execute()
Maybe you could add additional output to your code, to narrow it down to the line, where <!--core error--> gets written.
I forget to put in the code
ob_clean();
flush();

How to allow only logged in user to access files in a folder on linux server

I created .htaccess file and place this piece of code in it :
Order Deny,Allow
Deny from all
Here is my php code which is working fine on my windows machine with WAMP SERVER on it :
$path = $data['path']; // complete path to file
if (is_user_logged_in()) {
//return $path;
if (file_exists($path)) {
header('Cache-Control: public');
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Transfer-Encoding: binary');
readfile($path);
}
} else {
return 'Welcome, visitor!';
}
But when I run it on server, it didn't work at all. There is a locked folder, in which I have placed .htaccess file. And under locked folder there are many subfolders which I want to limit access to only logged in users.
Your .htaccess file is a (slow) way to stop anyone requesting your files. It does not care if the user is logged in or not.
The approach I would take is to
a) Move the files you want to give logged in access to out of the root. That way no-one can request the path directly - they have to use your php file to get access.
b) In your php file, test if your user is logged in. If not, present them with the log-in screen or error message as appropriate.
c) As they are logged in read the file you want to show them and echo the contents, (or read the data and build the reply page).
I use a another way to download file by loged user (without connect to DB and running all framework...).
First of all, when user is loged a generate special link using: sessionID, pathDir, userId, seprarator "=" and secureKey.
$tx = rand(999,99999).'='.$dirUQ.'='.$user_id.'='.$fileUQ.'='. session_id();
$checkSum = hash('sha256', $tx.$this->keySecure );
$sx = $tx.'='.$checkSum;
Thanks for this I get special param, unique for every user using get download file: /download.php?get_sx=91653%3D1%3D1%3D1655104198_9592081.jpg%3D123e2592526883ebfa426898f09c7c9e%3Dec30217a28a564f24dfed226bf3b37b11e0f6a0d732a98a1cfd598dc7cfe2fb2&gofull=600
change you private secure key
generate params "get_sx" for your files
exectue method "downloadIfAuth" in your index.php file
My PHP Class
class FileSecure {
//put your code here
private $keySecure = '***My_KEY_TO_GEN_LINK';
private $sessionID = '';
public function __construct($get)
{
$this->sessionID = $_COOKIE['PHPSESSID'] ?? '';
}
/**
* check if param get_sx added
*/
public function downloadIfAuth()
{
if(isset($_GET['get_sx']) && $this->sessionID != '')
{
$tx = $_GET['get_sx'];
$params = explode('=', $tx);
if(count($params) == 6)
{
$rand = $params[0];
$dirUQ = $params[1];
$px_user_id = $params[2];
$fileUQ = $params[3];
$sessionId = $params[4];
$checkSum = $params[5];
$tx2 = $rand.'='.$dirUQ.'='.$px_user_id.'='.$fileUQ.'='. $this->sessionID;
$checkSum2 = hash('sha256', $tx2.$this->keySecure );
if($checkSum == $checkSum2)
{
$path = 'files/'.$dirUQ.'/'.$fileUQ;
if(file_exists($path))
{
$type = #mime_content_type($path);
header('Content-type: '.$type);
header('Content-Length: '.filesize($path) );
header('Content-Disposition: filename='.$fileUQ );
echo file_get_contents($path);
die;
}
}else
{
header('Location: /?crc=false');
die;
}
}
header('Location: /?crc2=false');
die;
}
}
public function getSX( string $dirUQ, int $user_id, string $fileUQ)
{
$tx = rand(999,99999).'='.$dirUQ.'='.$user_id.'='.$fileUQ.'='. session_id();
$checkSum = hash('sha256', $tx.$this->keySecure );
$sx = $tx.'='.$checkSum;
$sx = urlencode($sx);
return $sx;
}
}
How to generate link
$fileSecure = new FileSecure(array());
$imgs = '<img src="/index.php?get_sx='.$fileSecure->getSX("myHomeDir", $this->getUser()->GetId(), "myFile.jpg").'" class="img_browser" >
Put below code to you index.php to check if get_sx is exist
$file = new FileSecure($_GET);
$file->downloadIfAuth();
The most important thing is "$_COOKIE['PHPSESSID']". When browser execute index.php send cookie. Thanks for this, special sum and secureKey we can download file by loged user. And... we don't need to connect to DB.
If you see any bug in my way, please show me :)
Sorry for my english :(

File only downloading when opened in new window/tab

I have a function that creates different file types depending on a variable, I have it generating an XML, but when I click the link to the page to do so (XML), nothing happens. If I click to open it in a new tab or manually enter the url in the title bar then the file will download as I want it to.
function asset($asset_id, $display = ''){
$this->load->model('model_asset');
$asset = $this->model_asset->get_by_id($asset_id, true);
switch($display) {
case 'xml':
$this->load->helper('array_to_xml_helper');
$asset_arr = get_object_vars($asset);
$filename = $asset->title .'-'. $asset->subtitle . '.xml';
$xml = Array2XML::createXML('asset', $asset_arr);
header ("Content-Type:text/xml");
header('Content-Disposition: attachment; filename="'. $filename .'"');
echo $xml->saveXML();
break;
}
}
How can I make this work with dynamically generated files (I'm using a arraytoxml utility function I found here)
You can try to set file in attachement
Add this to your headers :
Content-disposition: attachment
filename=huge_document.pdf

PHP force download and refresh SOLUTION not working

End goal:
Click link on page 1, end up with file downloaded and refresh page 1. Using PHP to serve downloads that are not in public html.
Approach:
Page 1.
Link transfers to page 2 with get variable reference of which file I am working with.
Page 2.
Updates relevant SQL databases with information that needs to be updated before refresh of page 1. Set "firstpass" session variable. Set session variable "getvariablereference" from get variable. Redirect to page 1.
Page 1.
If first pass session variable set. Set Second pass session variable. Unset first pass variable. Refresh Page. On reload the page will rebuild using updated SQL database info (changed on page 2.).
Refreshed Page 1.
If second pass session variable set. Run download serving header sequence.
This is page 1. I am not showing the part of page 1 that has the initial link. Since it doesn't matter.
// REFERSH IF FIRSTPASS IS LIVE
if ($_SESSION["PASS1"] == "YES"){
$_SESSION["PASS1"] = "no";
$_SESSION["PASS2"] = "YES";
echo "<script>document.location.reload();</script>";
}
if ($_SESSION["PASS2"] == "YES"){
// Grab reference data from session:
$id = $_SESSION['passreference'];
// Serve the file download
//First find the file location
$query = "SELECT * from rightplace
WHERE id = '$id'";
$result = mysql_query($query);
$row = mysql_fetch_array($result);
$filename = $row['file'];
$uploader = $row['uploader'];
// Setting up download variables
$string1 = "/home/domain/aboveroot/";
$string2 = $uploader;
$string3 = '/';
$string4 = $filename;
$file= $string1.$string2.$string3.$string4;
$ext = strtolower (end(explode('.', $filename)));
//Finding MIME type
if($ext == "pdf" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/pdf');
readfile($file);
}
if($ext == "doc" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/msword');
readfile($file);
}
if($ext == "txt" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: text/plain');
readfile($file);
}
if($ext == "rtf" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/rtf');
readfile($file);
}
if($ext == "docx" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
readfile($file);
}
if($ext == "pptx" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/vnd.openxmlformats-officedocument.presentationml.presentation');
readfile($file);
}
if($ext == "ppt" && file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/vnd.ms-powerpoint');
readfile($file);
}
}
The script on page 2 is working correctly. It updates the sql database and redirects to the main page properly. I have also checked that it sets the "$_SESSION['passreference'];" correctly and nothing on page 1 would unset it.
So, thats the whole long explanation of the situation. I am stumped. What happens is, as I said page 2 works fine. Then it kicks to page 1, refreshes and then doesnt push any download. I know that the download script works and that the files are there to be downloaded (checked without the whole refresh sequence).
I essentially have two questions:
Can anyone spot whats going wrong?
Can anyone conceptualize a better approach?
It is hard to debug something like this remotely even given the code, the segment you posted works as you say. Have you checked your error logs? The most likely culprit is a problem with sending header() after other output has been done.
When dealing with file downloads, I think it is easier wherever possibly to initiate the download on a new page/window so there can be no risk of breaking headers. Maybe a slightly altered sequence using a third page that initiates the actual download:
Page 1 links to the second page to do magic, which redirects back to page 1
Page 1 then spawns page 3 in a new window, which initiates the download.
There's a good example code for loading a new window for a download in this answer.
Looking at your code, the download problem may be that the $ext variable contains an unexpected value or that the $file variable contains the name of a file that really doesn't exist.
In either this cases, none of your "if" conditions would be true, so the download would'nt start.
My suggestion is to add the following statements just before the "//Finding MIME type" comment line:
$log = "file='".$file."'\n";
$log .= "ext='".$ext."'\n";
#file_put_contents("/tmp/page1.log", $log, FILE_APPEND);
This way, looking at the "/tmp/page1.log" file you should be able to check if the $file and $ext variabiles effectively contain the expected values.
I've used "/tmp/page1.log" as the log file name since I suppose that you're working on linux; if not, please adjust the first argument of the "file_put_contents" function with a valid path and file name for your enviroment.
Also, I would replace the sequence of "if" tests with the following code:
$content_types = array(
"pdf" => "application/pdf",
"doc" => "application/msword",
"txt" => "text/plain",
"rtf" => "application/rtf",
"docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation"
);
if (isset($content_types[$ext])) {
if (file_exists($file)) {
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: '.$content_types[$ext]);
readfile($file);
die("");
} else {
die("** '".$file."' does not exist **");
}
} else {
die("** Unhandled '".$ext."' extension **");
}
Obviously, you should implement the error handling in a much more robust way, not simply using the "die()" function as I did, but this is only an example.
Finally, be aware that there are also better ways of getting the content-type corresponding to the file extension; for example one solution is to use the PHP Fileinfo functions.
Have a look at this answer for further information about this topic.
Keep also in mind that in safe mode the file_exists function always returns FALSE, and that the results of the file_exists function are cached; see the clearstatcache() function on the PHP manual for further details.
I just reworked your PHP code a bit. Especially you'll get more information about what's going wrong. Just try this code and read the following comments, which explain what happend, if you get one of the new error messages. Also read the NOTE part below, which explains why you probably can't access a file from PHP, even it's existing and is in the right directory.
Using window.location.reload(); instead of document.location...
I added an error()-function. You can add more HTML to it, so it's producing a page in the layout you want. And you could log the error to a local file, too. There is a private info parameter used to pass sensible information as database errors (can contain SQL) to the function. For productive use you shouldn't display that to the user. Instead you can log it into a file or only display it for privileged users (e.g. Administrators).
Checks weather $id is set. Returns error() message if not; Could happen if session was not updated correctly.
I added "$id = addslashes($id);" for security reasons. If your id could be set to values like $id = "' OR 1" (SQL-Injection) for example, you could get into trouble. If you are sure this can not happen, you can remove it.
It checks the $result variable after the DB query. If e.g. your database connection wasn't established or the script cannot connect this will produce an error()-output that informs you. The same happens if you have an error in your SQL syntax, e.g. wrong table name.
It's also checked weather a valid $row is fetched from the database. If there isn't a row returned your $id is problably wrong (there isn't such an entry in your database).
I rewrote your string operations to $filepath = $rootpath . "/" . $uploader . "/" . $filename; where $rootpath is set before without "/" at the end; This is easier to read...
Extensions and MIME-Types are now put into an array, instead of using a lot of "if-then"-blocks, that's easier to maintain. Also the code inside that blocks were similar... so we only need to write it once.
A default MIME type (Content-Type:"application/octet-stream) is sent, if the file extension is not known.
We check for file_exists() and output an error message, with $filename given to allow checking weather the path is correct...
So here is the source code:
<?php
function error($message, $info = "") {
echo "ERROR: $message<br>";
echo "PRIVATE-INFO: $info"; // probably you only want to log that into a file?
exit;
}
// REFERSH IF FIRSTPASS IS LIVE
if ($_SESSION["PASS1"] == "YES") {
$_SESSION["PASS1"] = "no";
$_SESSION["PASS2"] = "YES";
echo "<script>window.location.reload();</script>";
exit;
}
if ($_SESSION["PASS2"] == "YES") {
// Grab reference data from session:
$id = $_SESSION['passreference'];
if (!$id) error("Internal Error ('id' not set)");
// Select file location from DB
$id = addslashes($id);
$query = "SELECT * from rightplace WHERE id = '$id'";
$result = mysql_query($query);
if (!$result) error("DB-query execution error", mysql_error());
$row = mysql_fetch_array($result);
mysql_free_result($result);
if (!$row) error("File with ID '$id' was not found in DB.");
$filename = $row['file'];
$uploader = $row['uploader'];
// Setting up download variables
$rootpath = "/home/domain/aboveroot";
$filepath = $rootpath . "/" . $uploader . "/" . $filename;
$ext = strtolower(end(explode('.', $filename)));
// Serve the file download
// List of known extensions and their MIME-types...
$typelist = array(
"pdf" => "application/pdf",
"doc" => "application/msword",
"txt" => "text/plain",
"rtf" => "application/rtf",
"docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"ppt" => "application/vnd.ms-powerpoint"
);
// set default content-type
$type = "application/octet-stream";
// for known extensions, assign specific content-type
if (!isset($typelist[$ext])) $type = $typelist[$ext];
if (file_exists($filepath)) {
header("Content-disposition: attachment; filename= '$filename'");
header("Content-type: $type");
readfile($filepath);
} else {
error("Error: File '$filepath' was not found!", $filepath);
}
}
?>
NOTES:
The file not found error can happen even the file exists. If this happens, this is most probably a security mechanism that prevents the PHP script to access files outside the HTML-root directory. For example php scripts could be executed in a "chrooted" environment, where the root directory "/" is mapped e.g. to "/home/username/". So if you want to access "/home/username/dir/file" you would need to write "/dir/file" in your PHP script. It can be even worse, if your root is set like "/home/username/html"; then you'll not be able to access directories below your "html" directory. To work around that, you can create a directory inside the HTML-root and put a file named ".htaccess" there. Write "DENY FROM ALL" in it, which prevents access to the directory by browser request (only scripts can access it). This works for apache servers only. But there are solutions like that for other server software too... More info on this can be found under: http://www.php.net/manual/en/ini.core.php#ini.open-basedir
Another possibility is that your file access right (for uploaded files) are not set in a way, that your script is allowed to access them. With some security settings enabled (on a linux server), your PHP script can only access files owned by the same user as the "owner" set for the script file. After upload via "ftp" this is most probably the usersname of the ftp user. If edited on the shell, this will be the current users username. => But: Uploaded files are sometimes assigned to the user the webserver is running as (e.g. "www-data", "www-run" or "apache"). So find out which it is and assign your script to this owner.
For file uploads you should use move_uploaded_file(...) which is explained here: www.php.net/manual/en/function.move-uploaded-file.php ; If you don't do this, the file access right may be wrong or you might not be able to access the file.

Categories