Protecting Downloads - php

I was just testing this download script below. The downloading works fine but the downloaded zip or rar archive is always corrupt and cannot be opened. I tested it on local development server as well as my hosting account.
I am just trying to learn how this works but I don't really understand it.
All help is appreciated!
Test Code:
<?php
$is_logged_in = 1;
$path_to_file = 'downloads/tes.zip';
$file_name = 'test.zip';
if ($is_logged_in == 1)
{
header("X-Sendfile: $path_to_file");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$file_name\"");
exit;
}
?>
<h1>Permission denied</h1>
<p>Please Login first!</p>

It mostly probable that you have something appended/prepended to the file. Try to use buffering and cleaning.
<?php
ob_start();
$is_logged_in = 1;
$path_to_file = 'downloads/tes.zip';
$file_name = 'test.zip';
if ($is_logged_in == 1)
{
$fp = fopen($path_to_file, 'rb');
if(is_resource($fp))
{
ob_clean();
header("Content-Type: application/force-download");
header("Content-Length: " . filesize($path_to_file));
header("Cache-Control: max_age=0");
header("Content-Disposition: attachment; filename=\"$file_name\"");
header("Pragma: public");
fpassthru($fp);
die;
}
} else {
echo "<h1>Permission denied</h1>";
echo "<p>Please Login first!</p>";
}

Have you configured your webserver (Apache in particular) to load mod_xsendfile? Without that module installed, your script essentially does nothing.

Related

readfile returns a corrupt zip download

I have the following code in my Phalcon application.
public function downloadAction($key = NULL) {
if ($key == NULL) {
return $this->respondDownloadFailed();
}
$url = Urls::findFirst("token = '".$key."'");
if ($url) {
$path = $url->getUrl();
$ext = pathinfo($path, PATHINFO_EXTENSION);
header("Content-type: application/".$ext);
header("Content-Disposition: attachment; filename=".$key.".".$ext);
header("Content-length: " . filesize($path));
header("Pragma: no-cache");
header("Expires: 0");
ob_clean();
flush();
readfile($path);
unlink($path);
die;
}
return $this->respondDownloadFailed();
}
Now, when the .zip is created on the server I can open and unzip it. However, when I download it through the code above, I get a zip file which then unarchives to a .cpgz file. When unarchiving that one, I get a .zip again. Any ideas as to what is going wrong?

PHP Download MP3 files from directory on server

I am attempting to download MP3 files to the user computer located in a directory named "songs" on the server. I have been able to run a script which downloads these files through the browser. However, these files download strictly as text with .mp3 extension. I want these files to be playable mp3 files upon downloading from the server.
Here is my PHP script.
<?php
$link = mysqli_connect("...","...","....","...") or die("Error ".mysqli_error($link));
if(mysqli_connect_errno())
{echo nl2br("Failed to connect to MySQL:". mysqli_connect_error() . "\n");}
else
{echo nl2br("Established Database Connection \n");}
//Script currently downloads a list of all songs found on server
$target = "songs/";
if ($handle = opendir($target)) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
echo $entry."'>".$entry."</a>\n";
}
}
closedir($handle);
}
$file = basename($_GET['file']);
$file = 'Songs_From_Server'.$file;
if(!$file){ // file does not exist
die('file not found');
} else {
header("Cache-Control: private");
header("Content-type: audio/mpeg3");
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=".basename($file));
readfile($file);
}
?>
Here is an example of the result I get in a txt file.
Established Database Connection
01 In 1983 He Loved To Fly.mp3'>01 In 1983 He Loved To Fly.mp3
First, header()should be sent before any output which includes echo, print_r, any html, a blank space before the opening tag(eg. <?php). Refer
to the manual
Second, if you want to response with the content of a file to the browser, your script should not output anything else. Any output besides the content will be considered a part of the content. Unless you send it as multipart and your client is able to handle it.
So, an example
<?php
$fileName = $_GET['file'];
$path = '/directory/contains/mp3/';
$file = $path.$fileName;
if (!file_exists($file)) {
http_response_code(404);
die();
}
header("Cache-Control: private");
header("Content-type: audio/mpeg3");
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=".$fileName);
//So the browser can display the download progress
header("Content-Length: ".filesize($file));
readfile($file);
This worked for me
header("Content-type: application/mp3");
header("Content-Disposition: attachment; filename=".$s['song_name']);
header('Pragma: no-cache');
header('Expires: 0');
//So the browser can display the download progress
readfile('uploads/'.$s['song_mp3']);
exit;

Offering file download within a session

I am trying to make a file download dependent inside a session. Here is the code:
<?php>
session_name("My-Download");
session_start();
$_SESSION['Download-Authorized'] = 1;
echo "<a class='invlink' rel='nofollow' download target='_blank' href='download.php?download_file=file.to.download.pdf'>Name of File</a><br /><br />";
?>
The download script ('download.php') comes next:
<?php
session_start();
if(!isset($_SESSION['Download-Authorized'])) {
exit;
}
$path = $_SERVER['DOCUMENT_ROOT']."/downdir/";
$fullPath = $path.$_GET['download_file'];
if ($fd = fopen ($fullPath, "r")) {
$fsize = filesize($fullPath);
$path_parts = pathinfo($fullPath);
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: application/pdf");
header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . $fsize);
while(!feof($fd)) {
$buffer = fread($fd, 2048);
print($buffer);
flush();
}
fclose ($fd);
} else {
die("File does not exist. Make sure you specified correct file name.");
}
exit;
?>
All works fine as long as the verification of '$_SESSION['Download-Authorized'] ist commented out.
When I check the session-variable $_SESSION['Download-Authorized'] is set
the download will fail.
What's wrong with my code?
Any help appreciated.
After adding session_start() to the beginning of download.php the script still does not work.
It appears to be the case that the Session-ID as well as the Session-Name changes when "download.php" is called. Additionally $_SESSION['Downlad-Autorized'] is reset.
Your initial script stores the flag in a session explicitely renamed (session_name("My-Download");), but the download script uses the default session name (no session_name()).
Therefore your download script starts with another (possibly empty) session.

PHP downloads at 0 bytes

I have ZIP files in a directory that is one step up from the root directory (to prevent hotlinking etc...). Here is the code:
<?php
$filename = $_GET['id'];
if(!$filename){
header("Location: index.html");
} else {
function send_download($filename){
$file_path = '../../../../downloads/' . $filename . '.zip';
$file_size=#filesize($file_path);
header("Content-Type: application/x-zip-compressed");
header("Content-disposition: attachment; filename=$filename");
header("Content-Length: $file_size");
readfile($file_path);
exit;
}
send_download($filename);
}
?>
All the ZIP files are fine but using this method on an 'a' tag causes the downloads to be 0 bytes in file size! :(
Any ideas as to why?
Many thanks!
Can you try changing the Content-type to the following:
Content-type: application/x-zip;
And before you do, check if the file exists.
<?php
$filename = $_GET['id'];
if(!$filename){
header("Location: index.html");
} else{
function send_download($filename){
$file_path = '../../../../downloads/' . $filename . '.zip';
if (file_exists($file_path))
{
$file_size=#filesize($file_path);
header("Content-Type: application/x-zip-compressed");
header("Content-disposition: attachment; filename=$filename");
header("Content-Length: $file_size");
readfile($file_path);
exit;
}
send_download($filename);
}
else
die("Error 404!");
}
?>
header("Location: http://www.fqdn.tld/file.ext");
Based on _GET["id"] being > 0 and not null, you've created a function which has been used directly after, so you've just added more lines of code for no required purpose.
I see you have added an "#" sign as people have previously commented, however, the only way for you to begin fixing this is by:
Removing the # sign as you should never use it, very bad practice, you should cater for all exceptions in your code, it makes for a really good programmer. (Scripting in PHP)
error_reporting(E_ALL);
Place point b) on it's own line but after the <?php - so we can see a full list of errors.
Your code is fine, the problem you have is permissions, ensure that apache has access to your "files" repository, the fact you are able to check that the file is there suggest slight permissions and the files exists, the point that 0 bytes are being returned suggest reading privileges are denied at Apache level.
Right, after a bit of Googling and some blood, sweat and tears I have finally found a solution!
<?php
$filename = $_GET['id'];
header('Content-type: application/zip');
header("Content-Disposition: attachment; filename=" . $filename);
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
readfile('../../downloads/' . $filename . ".zip");
exit;
?>
Many thanks to all of whom shed some light on this!
And here is the code with the filesize:
<?php
$filename = $_GET['id'];
$file = "../../downloads/" . $filename . ".zip";
header('Content-type: application/zip');
header("Content-Disposition: attachment; filename=" . $file);
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-length: " . filesize($file));
readfile($file);
//echo filesize($file);
exit;
?>
Only one mistake we all are doing check below.
readfile($file); //$file should be relative not absolute. That's it.
Thanks,
Anirudh Sood.

Joomla component + forcedownload

i have coded a component (Gallery). And i want to intergrate forcedownload for all items.
But will not work :(, just getting corrupted files. I have tried to use exact same code and run it outside of joomla and its works fine. I have created a template thats totaly empty just
have but its not help.
My (com_component/views/forcedownload/tmpl/default.php)
<?php
if(!isset($_GET["id"]) || empty($this->item)) {
echo "Invalid request";
die();
}
$path = "images/randomtest/catid".$this->item->cat_id."/";
$filename = $path.$this->item->filename;
$file = $this->item->filename;
if(!file_exists($filename)) {
echo "Error: File not found";
die();
}
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$file");
header("Content-Type: Content-type: application/octet-stream");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile($filename);
?>
The first thing is: Are you using $_GET['format'] = raw? If not in your controller do
if(!isset($_GET["format"]) || $_GET["format"]!='raw') {
$this->setRedirect( JURI::current(). '?format=raw' );
return false;
}
also in your header you are declared content type as: application/octet-stream, are you sure you want this?
also here:
$path = "images/randomtest/catid".$this->item->cat_id."/";
you need, whole SYSTEM PATH not only local one, try using:
$path = JPATH_SITE."/images/randomtest/catid".$this->item->cat_id."/";

Categories