How to make a safe file function in PHP? - php

This method receives a foldername and filename. It should make sure that the filename actually really exists in the folder path before doing anything. If so then it can proceed to get the file, send headers and send the response content. Is there a better and more secure way to to do this?
public function is_file_in_path($filename, $filepath)
{
if (file_exists($filepath)) {
$filename = realpath($filepath);
}
if ($filename) {
$image_mime = get_mime_by_extension($filename);
header('Content-Type: ' . $image_mime);
header('Content-Length: ' . filesize($filename));
echo file_get_contents($filename);
} else {
show_error('No image found.', 404);
}
}

To make the function to have higher compatibility and cleaner:
consider removing the word "public"
remove the 1st parameter, which is a bit redundant
change get_mime_by_extension to mime_content_type
change "No image found" to "No File found" (because this function can also be used if you supply a PDF file as parameter).
<?php
function is_file_in_path($filepath)
{
if (file_exists($filepath)) {
$filename = realpath($filepath);
}
if ($filename) {
$file_mime = mime_content_type($filename);
header('Content-Type: ' . $file_mime);
header('Content-Length: ' . filesize($filename));
echo file_get_contents($filename);
} else {
// show_error('No image found.', 404);
echo "No File Found";
}}
// is_file_in_path("./photo.jpg");
is_file_in_path("./ess.pdf");
?>

Related

PHP get image from server

I am trying to display an image from a PHP server.
Here it is my function:
function get_file($path) {
$fileToGet = $GLOBALS['homedir'].$path;
//echo $fileToGet.PHP_EOL;
if (file_exists($fileToGet)) {
//echo 'file exists';
header('Content-Type: image/png');
header('Content-Length: '.filesize($fileToGet));
echo file_get_contents($file);
}
}
I am using the browser or postman and the image is invalid.
What I am missing?
I edited a little bit your function:
function get_file( $path ){
$fileToGet = $GLOBALS['homedir'];
if( substr( $fileToGet, -1) != '/' ){
// add trailing slash if needed
$fileToGet .= '/';
}
$fileToGet .= $path;
if (file_exists($fileToGet)) {
header('Content-Type: image/png');
header('Content-Length: '.filesize($fileToGet));
echo file_get_contents($fileToGet);
}
}
Just a security hint: if $path comes from the user there may be a problem because he will be able to access to some other file.
Think about this code:
get_file( $_GET['path'] );
then the user can call this url
yoursite/yourpage.php?path=../../../mypreciousimage.png
You're not outputting the contents of the file you're reading:
file_get_contents($file);
You'll need to echo it:
echo file_get_contents($file);
Or:
readfile($file);
You'll probably also want to add exit; to the end of that function, to ensure that no other code runs and that no other output gets sent.
Try
print file_get_contents($file);
Instead of
file_get_contents($file);

file exist in php download

I'm download file but i want. IF file exist from drive C i can download,
ELSE IF file exist automatic can donwload file from drive D . Else "File Not Found"
i'm confused :(
This is my quoute script
$id = $_GET['id'];
$query = "SELECT * FROM upload WHERE id = '$id'";
$jl = mysql_query($query);
$data = mysql_fetch_array($jl);
header("Content-Disposition: attachment; filename=".$data['name']);
header("Content-length: ".$data['size']);
header("Content-type: ".$data['type']);
$filename = $data['name'];
if (file_exists($filename)) {
$fp = fopen("d:/result/".$data['name'], 'r');
$content = fread($fp, filesize('d:/result/'.$data['name']));
fclose($fp);
}
else if (file_exists($filename)) {
$fp = fopen("c:/result/".$data['name'], 'r');
$content = fread($fp, filesize('c:/result/'.$data['name']));
fclose($fp);
}
else {
echo "File Not Found";
}
// show file download
echo $content;
exit;
You could specify the list of paths in an array.
$paths ['c:/result/', 'd:/result/];
then you loop through these paths:
foreach($paths as $path){
if(file_exists($path.$data['name'])){
print file_get_contents($path.$data['name']);
}
}
Full solution for you:
//prevent sql injection
$data = mysql_fetch_array(mysql_query("SELECT * FROM upload WHERE id = '".intval($_GET['id'])."'"));
//now you can add as much paths as you like
$paths = [
'd:/result/',
'c:/result/'
];
foreach ($paths as $path) {
if (file_exists($path . $data['name'])) {
header("Content-Disposition: attachment; filename=" . $data['name']."; ".
"Content-length: " . $data['size']."; ".
"Content-type: " . $data['type']);
print file_get_contents($path . $data['name']);
exit;
}
}
echo "File not found";
exit;
Your code is backwards. You've output your content headers already, so if the file doesn't exist, your user will be downloading a file whose contents are "File Not Found". That'd just confuse them when they double-click on their pdf/word/whatever file and get a "file is corrupted" error.
Your code should be more like:
$c_source = 'C:/...';
$d_source = 'D:/...';
if (is_readable($c_source)) {
header(...); // <--do this only if you found a file
readfile($c_source);
} else if (is_readable($d_source)) {
header(...); // do this only if you found a file
readfile($d_source);
} else {
die("File not found");
}

After downloading file, it is corrupted

I'm currently making a controller to download files from the server.
It all happens in the index action:
public function indexAction() {
$schuurName = $this->_getParam('storageID');
$fileName = $this->_getParam('fileName');
$name = explode('.', $fileName)[0];
$path = '..' . DIRECTORY_SEPARATOR . 'schuren' . DIRECTORY_SEPARATOR . $schuurName . DIRECTORY_SEPARATOR . $fileName;
if (file_exists($path)) {
$mimeType = mime_content_type($fileName);
header('Content-Type: ' . $mimeType);
header('Content-Length: ' . filesize($path));
header('Content-Disposition: attachment; filename=' . $name . ';');
$resource = fopen($path, 'r');
while (!feof($resource)) {
$chunk = fread($resource, 4096);
echo $chunk;
}
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
}
else {
echo 'file doesn\'t exist';
}
}
So the downloading works right now, I'm testing it with an image of 725 bytes. The problem is.. The image is corrupted so it couldn't be seen/edited. What am I doing wrong in my code?
Thanks!
You should use binary mode. Use the 'rb' flag.
From the php Manual : If you do not specify the 'b' flag when working with binary files, you may experience strange problems with your data, including broken image files and strange problems with \r\n characters.
http://www.php.net/manual/en/function.fopen.php

Zip file is downloading with 20 bytes and when extracts the file it shows corrupted file and 'no archive found'

When i trying to download the zip file with the following code in my local system,the file is downloading with all the files but where as in server it is downloading with 20 bytes and when extracts file is corrupted and no archive found.So i added some checks for this code and run but this time File not found,going inside this check(if (!is_file($archive_file_name))).What might be the problem on server?
function zipFilesAndDownload($file_names,$archive_file_name,$file_path)
{
$zip = new ZipArchive();
//create the file and throw the error if unsuccessful
if ($zip->open($archive_file_name, ZIPARCHIVE::CREATE )!==TRUE) {
exit("cannot open <$archive_file_name>\n");
}
//add each files of $file_name array to archive
foreach($file_path as $file_path)
{
if(basename($file_path) =="documents")
{
foreach($file_names as $value=>$files)
{
$customer_files = $this->fetchCustomFieldValuesAndFieldNamesByOrder($files['order_id']);
if($customer_files['field_value'] !="" && file_exists($file_path.$customer_files['field_value']))
{
$file_extension = pathinfo($customer_files['field_value'], PATHINFO_EXTENSION);
$zip_file_name = $files['customer_name']."_"."Files.".$file_extension;
$zip->addFile($file_path.$customer_files['field_value'],$zip_file_name);
}
//echo $customer_files['field_value']."<br>";
}
} else
{
foreach($file_names as $value=>$files)
{
if($files['file_name'] !="" && file_exists($file_path.$files['file_name']))
{
$file_extension = pathinfo($files['file_name'], PATHINFO_EXTENSION);
$zip_file_name = $files['customer_name']."_"."Application".".$file_extension";
$zip->addFile($file_path.$files['file_name'],$zip_file_name);
// echo $file_path.$files['file_name'],$zip_file_name."<br>";
}
}
}
}
// echo "numfiles: " . $zip->numFiles . "\n";
// echo "status:" . $zip->status . "\n";
$zip->close();//exit;
//then send the headers to foce download the zip file
if (headers_sent()) {
echo 'HTTP header already sent';
} else {
if (!is_file($archive_file_name)) {
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
echo 'File not found';
} else if (!is_readable($archive_file_name)) {
header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden');
echo 'File not readable';
} else {
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
exit;
}
}
}
If you're getting status 11 from $zip->status, it means that the file you're trying to ZIP does not exist. Here are docs on error codes from the Zip / ZipArchive class: http://php.net/manual/en/zip.constants.php
Verify that the path you're on is correct using getcwd() and that the file on this path really exists.

PHP Readfile() not working for me and I don't know why

I am trying to get this code to work but for some reason, all the echo's are able to output correct content, but the headers don't seem to want to force the download of my document. What follows is the file I am trying to build for file downloads. It is set to input code like this: downloader.php?f=13&t=doc to download a file that is named 201-xxx.doc or 201-xxx.pdf from one of two folders depending on the users privileges.
All the logic works up to the header info at the bottom. If I comment out the header content type and the header content disposition, then it will read the file into the browser. With either of those lines included, it give me an error that says "Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found."
<?php
//ob_start();
if ( !defined('__DIR__') ) define('__DIR__', dirname(__FILE__));
define( "TLOJ_FSROOT", __DIR__ . "/" );
define('WP_USE_THEMES', false);
require('./wp-blog-header.php');
$lessonnumber = $_REQUEST['f'];
$type = $_REQUEST['t'];
if ( $lessonnumber < '10' ) { $threedigitlesson = '00' . $lessonnumber; }
elseif ( $lessonnumber < '100' ) { $threedigitlesson = '0' . $lessonnumber; }
else { $threedigitlesson = $lessonnumber; }
$filenamestart = "201-" . $threedigitlesson;
$contenttype = 'application/octet-stream';
switch ($type) {
case 'pdf':
$extension = '.' . $type;
$contenttype = 'application/pdf';
break;
case 'doc':
$extension = '.' . $type;
$contenttype = 'application/msword';
break;
default:
$contenttype = '';
exit("It appears that you are trying to download a file that is not a lesson document. Please contact us if you believe this to be an error.");
}
$filename = $filenamestart . '.' . $type;
$current_user = wp_get_current_user();
//$siteurl = site_url();
$pathroot = TLOJ_FSROOT;
$download_path = $pathroot . "1hoefl4priaspoafr/";
if ( current_user_can("access_s2member_ccap_extendedlessons")) {
$download_path = $download_path . "ex/";
} else {
$download_path = $download_path . "st/";
}
$file_path = $download_path . $filename;
$tlojmemberlength = tlojunlocklessons();
if ( !is_user_logged_in() ) { exit("Please log in to access the file"); }
if ( !current_user_can("access_s2member_ccap_downloadlessons") ) { exit("You don't have access to download the lessons!"); }
if ( $lessonnumber > $tlojmemberlength ) { exit("It appears you are trying to jump ahead! While I am excited at your enthusiam, let's not rush our study time."); }
if ( ($lessonnumber > '195') && (!current_user_can("access_s2member_ccap_lastweek")) ) { exit("Upgrade now to access the downloads for the five bonus lessons!"); }
// build Final File Name
$extendedmessage = "";
if ( current_user_can("access_s2member_ccap_extendedlessons")) { $extendedmessage = " - Extended"; }
$myfinishedlessonname = "Lesson " . $lessonnumber . $extendedmessage . " -- The Life of Jesus Study" . "." . $type;
// echo 'Download Path: ' . $download_path . '<br />';
// echo 'Source/Lesson Number: ' . $lessonnumber . '<br />';
// echo 'File Name: ' . $filename . '<br />';
// echo 'File Type: ' . $type . '<br />';
// echo 'Allowed Lessons: ' . $tlojmemberlength . '<br />';
// echo 'Final File Name: ' . $myfinishedlessonname . '<br />';
// echo 'File Path: ' . $file_path . '<br />';
// echo 'Content Type: ' . $contenttype . '<br />';
// echo 'File Size: ' . filesize($file_path) . '<br />';
if (headers_sent()) { exit("Sorry but the headers have already been sent."); }
ob_end_clean();
if (file_exists($file_path)) {
header('Content-Description: File Transfer');
header('Content-type: ' . $contenttype);
header('Content-disposition: attachment; filename="' . $myfinishedlessonname . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: ');
header('Pragma: ');
header('Content-Length: ' . filesize($file_path));
flush();
ob_clean();
readfile($file_path);
exit;
} else { exit("No file present."); }
?>
Please help as I have been at this all day and am confused to no end why this won't work. Filesize() pulls the correct length so I know there is a file in the path that I am looking at. (I am also new to PHP, so if there is something that I am missing, please share.)
Thanks in advance!
If it's a big file, it cannot be sent with readfile. Try to use this:
$handle = fopen($file_path, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, 4096);
echo $buffer;
ob_flush();
flush();
}
fclose($handle);
I am not sure why this worked, but I was able to solve this issue by breaking my php file into two pieces. Piece 1 loads WordPress and performs the logic validation. Then file 1 passes the information over to file 2 to do the download logic and write the header information.
Like I said, I am not sure why this worked, however a friend who knows PHP better than I do said that sometimes if the script takes too long to process then the headers won't take. It is possible that WordPress was hanging the script too long for these headers.
Hopefully this explanation will help someone else who is having this difficulty.
If you are trying to force browser to download the file with the use of
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="your-file.jpg"
but Chrome gives you ERR_FILE_NOT_FOUND and Firefox also fails with "Not found" (strangely Opera seems to work) try adding:
header('HTTP/1.0 200 OK', true, 200);
Chrome was telling me: "Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found."
And Firefox as saying the file did not exist.
Although the same php file was handling a different type of download I was having issues with PNG and ICO, I tried some methods that only displayed the picture but did not prompt for a download box.
Finally I found out thanks to Crazycoolcam, that Wordpress was the issue.
I was including a php to a file I had called "tools.php",
inside of tools.php it had an include to wordpress's main header file,
to remedy the issue I split my tools file into a wordpress version and a non wordpress version and included the wordpress half after it had written the file out.
Just another possibility here as to why its not working. This was the cause for me. Interestingly, file_exists was returning true but no form of serving the file to the public for download was working without having the below set correctly.
PHP has a setting called open_basedir
Make sure this is set correctly relevant to your hosting environment. open_basedir can be edited via php.ini

Categories