Simple file downloader - php

I am fairly new to php and have managed to string together this simple download script by reading some of the questions listed here at SO and would like to ask those of you that are more familiar with php to take a look at the code below and see if there are any glaring flaws with my implementation or anything that should be changed.
Just letting you know that the during my limited testing, everything seemed to work fine, but as I said I am fairly new to php and want to make sure I am not missing something that might break the script later down the road.
<?php
//Settings
$filesPath = './files';
$fileName = $_GET['file'];
$allowedExts = array('jpg','png','gif');
//Functions
//Returns the extension portion of a filename.
function file_extension($fileName)
{
$path_info = pathinfo($fileName);
return strtolower($path_info['extension']);
}
//Validation and processing
//Check that a file is actually being requested
if (empty($fileName)) {
die('no file was requested');
}
//Check that the file is allowed to be downloaded
if (!in_array(file_extension($fileName), $allowedExts)) {
die('you cannot download this file');
}
//Get the file
if (file_exists($filesPath . DIRECTORY_SEPARATOR . $fileName)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($fileName));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($fileName));
ob_clean();
flush();
readfile($fileName);
exit;
}
?>
TIA,
Dave

Your script may be vulnerable to directory traversal. In your case, I would use realpath() on the filename and check that it is a valid file within .files/.
There is a possibility that someone can traverse up the directory tree and steal files such as /etc/passwd and so on.

you are open to hackers attacks, someone could just do ?file=../etc/passwd/ and bam owned
you better do some checks on how to protect against lfi

Related

downloading large zip files to the browser in php, chunking

I am trying to download large zip files (800MB to 1GB) containing audio files to the browser. As I have seen so far, chunking seems to be the most popular approach, but I am having zero luck. The code I have been working with is
$filename = $_SERVER['DOCUMENT_ROOT'] . $filepath;
$download_rate = 5000;
$progress = 0;
if (file_exists($filename)) {
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filename));
header('Content-Disposition: filename='.basename($filename));
flush();
$file = fopen($filename, "r");
while(!feof($file)) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
}
fclose($file);
} else echo 'File does not exist!';
This works for a wide variety of file types and sizes up to a certain point -- I have no problem downloading typical PDFs, etc. But the browser just spins and spins when I try to download large zip files, and eventually dies (around 2 minutes). I am really needing some help here. I've tried a number of code variants for chucking, but something always seems to die and I'm not sure what I'm doing wrong. Perhaps there's a better approach to chunking?
Consider change change for readfile will not present any memory issues, your lost download rate and progress show in browser. your limit download rate in .httaccess.
$filename = $_SERVER['DOCUMENT_ROOT'] . $filepath;
if (file_exists($filename)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filename).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.filesize($filename));
readfile($filename);
} else echo 'File does not exist!';
https://www.php.net/manual/en/function.readfile.php

(PHP) Problem with filename after downloading it

While writing a PHP-Script, im stuck at an issue i cant resolve.
The PHP-Script consist in letting a user download a .mp4 file. The download works without any issues but the file downloaded can not be played.
Heres the code:
<?php
$filepath = "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header("Content-type: video/mp4");
header("Content-Disposition: attachment; filename=.$filename");
readfile($filename);
exit;
?>
After the .mp4 file gets downloaded, it cannot be played.
It looks like this:
The error message means that Windows Media Player cannot play back the file because probably the player doesnt support the codec. I already tried with VLC but it does not work either.
EDIT:
Comparing both file sizes, the downloaded file is only a couple bytes large instead of the 3,73 MB of the file on the server
Your code is not well-formed, you miss to escape double-quotes by adding single-quotes as I done here, please test my answer.
<?php
$filepath = "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header('Content-Type: video/mp4');
header('Content-Disposition: attachment; filename="' . $filename . '"');
readfile($filename);
exit;
?>
But I suggest a more complex way:
<?php
$filepath = $_SERVER['DOCUMENT_ROOT'] . "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header('Content-Type: video/mp4');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . sprintf("%u", filesize($filepath)));
set_time_limit(0);
$fh = fopen($filepath, "rb");
while (!feof($fh)) {
echo fgets($fh);
ob_flush();
flush();
}
fclose($fh);
exit;
?>
$_SERVER['DOCUMENT_ROOT'] is useful to get the full path from the server
set_time_limit(0) is useful to avoid any timeout during download
fgets() is useful for reading large files
ob_flush() and flush() assure that there is not other output in the buffer
I hope this helps.
Is the downloaded file the exact same filesize?
Does the content type exist in your webserver?
header("Content-Type: video/mp4"); Note capital 'T' for type.
This maybe worth testing with to see you can serve the file content inline:
http://www.phpmind.com/blog/2016/10/how-to-use-php-to-output-an-mp4-video/

PHP Downloading a file using url query - what are all the threats?

Had no luck finding an answer on the internet so I figured I'd ask the experts. What are the security threats that come with downloading files using url query, for example a code like this:
if(isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != ""){
$file = urldecode($_SERVER['QUERY_STRING']);
if(file_exists("files/".$file)){
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize("files/".$file));
ob_clean();
flush();
readfile("files/".$file);
exit;
}
}
would be vulnerable to query strings like "?..\..\..\any\file\here" but isn't that all? Can't this be filtered out by a single if?
$file = urldecode($_SERVER['QUERY_STRING']);
if(strpos($file, ".\\") !== false || strpos($file, "./") !== false){
echo "No you won't get my system files";
exit;
}
If there's no "upload" feature, a .htaccess file preventing access to \files\ directory and the "if strpos" then is this safe?
Edit: bad code example
1) You would be vulnerable to ..\..\..\any\file\here
2) I would recommend you to get a list of all sub-files in files/. Then if the file requested does not match any of those files return a 404 Error code.
PHP read sub-directories and loop through files how to?

PHP force download corrupt PDF file

I have gone through all articles on Stack Overflow and can't fix my issue. I am using following code:
$file = $_GET['url'];
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
The above mention code is downloading the file from the directly above the root and Yes it is downloading a PDF file but the file is only of 1KB size and not the original size. The $_GET['url'] is receiving ../dir/dir/filename.pdf in it. the filename is space in it as well. For security reason I cannot share the file name.
Please let me know where am I going wrong.
Please make sure you are using the web server path to access the file - for instance your path could be: /home/yourusername/public/sitename/downloads/<filename>, you should check first - to help you can run this at the top of your PHP script to find out the full path for the current script:
echo '<pre>FILE PATH: '.print_r(__FILE__, true).'</pre>';
die();
Only send the filename with the url using urlencode() and on the receiving PHP script use urldecode() to handle any character encoding issues.
See here: http://php.net/manual/en/function.urlencode.php
and here: http://php.net/manual/en/function.urldecode.php
So where you create your url:
Download File
And in your php script:
$file_base_path = '/home/yourusername/public/sitename/downloads/';
$file = urldecode($_GET['url']);
$file = $file_base_path . $file;
$file = $_GET['url'];
if (file_exists($file))
{
if (FALSE!== ($handler = fopen($file, 'r')))
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: chunked'); //changed to chunked
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
//header('Content-Length: ' . filesize($file)); //Remove
//Send the content in chunks
while(false !== ($chunk = fread($handler,4096)))
{
echo $chunk;
}
}
exit;
}
echo "<h1>Content error</h1><p>The file does not exist!</p>";
I hope this helps you!

PHP Coding for downloading the image

In the website page contains many images with downloading options. If I click the download button it automatically downloaded on user system and it shows on browser downloadable page. I have PHP code like
$image = file_get_contents('http://website.com/images/logo.png');
file_put_contents('C:/Users/ASUS/Downloads/image.jpg', $image);
Above coding is working fine. But I need to provide the path name for image to save. In user side we don`t know the path.
I need the PHP code to use the browser download location and download images need to show the browser downloads.
not possible to store the image in particular user location due to security issues .you don't force user .you have to suggest him to store particular location .and also you don't know the what file system there in user system.and also downloading path can be setting up by user anywhere so your not able to get that.
$filename = '/images/'.basename($_POST['text']);
file_put_contents($filename, $content);
you have to save/download the image somewhere on your web serwer and next send the file to user using header function, for example:
$file = 'path_to_image';
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
else {
echo "file not exists";
}
manual
`<?php
$filename ='http://website.com/images/logo.png';
$size = #getimagesize($filename);
$fp = #fopen($filename, "rb");
if ($size && $fp)
{
header("Content-type: {$size['mime']}");
header("Content-Length: " . filesize($filename));
header("Content-Disposition: attachment; filename=$filename");
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
fpassthru($fp);
exit;
}
header("HTTP/1.0 404 Not Found");
?>`

Categories