Downloading a file without disclosing location

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Downloading a file without disclosing location

Post by jwalsh »

Hi,

I'm trying to download a file without the user knowing the existing location. But this code is actually downloading a blank file called "..-products-secretfiles-".

Code: Select all

$Download = new Download;
	$Download->GetById($fileid);
	$file = '../products/secretfiles/' . $Download->filename;
	
	$type = filetype($file);
	
	header("Content-type: $type");
	header("Content-Disposition: attachment;filename=$file");
	header("Content-Transfer-Encoding: binary");
	header('Pragma: no-cache');
	header('Expires: 0');
	
	readfile($file);
Any ideas??
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

What are you really trying to do? Because i would expect my (and others') to actually ask where to download a file (and which name to give it).
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Post by jwalsh »

The users are downloading a file they have purchased. I don't want a leak to the actual url so they can share it.

The download is now working ok... but I can't figure out how to rename the file. Right now it downloads as "products-secretfiles-filename.zip" when I only want it to say "filename.zip".

Thanks,

Josh

Code: Select all

// MAKE SURE YOU'RE ALLOWED TO DOWNLOAD FILE
if (in_array($fileid, $myfiles)) {
	// load content into var
	$Download = new Download;
	$Download->GetById($fileid);
	$file = "../products/secretfiles/" . $Download->filename;
	
	$type = filetype($file);
	
	header("Content-type: $type");
	header("Content-Disposition: attachment;filename=$file");
	header("Content-Transfer-Encoding: binary");
	header('Pragma: no-cache');
	header('Expires: 0');
	
	readfile($file);
	
} else {
	echo "DOWNLOAD NOT ALLOWED"; 
}
foobar
Forum Regular
Posts: 613
Joined: Wed Sep 28, 2005 10:08 am

Post by foobar »

Generate a random URL that expires after a certain amount of time.
You're best bet is probably to have a virtual "downloads" directory, and everything beyond that is passed to your file-serving script, which then determines if that URL is valid or if it has expired.
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Post by jwalsh »

I'm not sure I follow. The code I attached above won't work? It should be secure since it runs authentication before prompting the download.

Now that I check, it's not actually downloading the file, simply creating a blank file with that filename... so I'm lost. Can someone point me to an example, documentation, or something to get me started?

Thanks again.
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

Actually, jwalsh' strategy seems better (thats how we do it overhere too). At first sight it seems all ok (so you might want to check if the $file really exists (eg: print realpath($file)) and test if it's readable...
User avatar
shiznatix
DevNet Master
Posts: 2745
Joined: Tue Dec 28, 2004 5:57 pm
Location: Tallinn, Estonia
Contact:

Post by shiznatix »

how exactally would one go about making a random url that expires after some time?
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

use of mod-rewrite.

actual url will be something like:

Code: Select all

/downloads/file.php?fid=xqz1wesfh3
whilst the modrewrite constructs a url like:

Code: Select all

/downloads/xqz1wesfh3/
a DB table contains a date stamp and the fid, if the file is older than so many minutes/hours, it is invalid and the file.php script redirects to 401/404. You can also include the users login credentials on the table for further security.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

mod_rewrite isn't needed, but it makes it "nicer."

Generally, an entry into the database will be created when they select to download the file. This entry will map the random garble to the actual file and when it's supposed to expire. The entry could track how many times it's been downloaded (that particular entry) as well, to help you log whether you want to shorten the lifetime to help curb <span style='color:blue' title='I&#39;m naughty, are you naughty?'>smurf</span> .. requesting domain filtering can also help, but create false positives..
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Post by jwalsh »

Everyone,

I've played with this overnight, and still no luck. The print realpath($file) works fine, and shows the correct location.

It still creates a blank file with the name ..-products-secretfiles-filename.zip. 0kb.
[EDIT: I forgot to uncomment readfile()... LOL. The download does download the file now.... but how can I change the filename, so it doesn't show the "products-secretfiles"]
Here's the full code for the download page....

Code: Select all

<? 

require_once("config.php"); 
AllowedHere(array(1,9));

$fileid = $_REQUEST['id'];
$myfiles = array();

// COLLECT ARRAY OF USERS FILES
$DB = new DB;
  $DB->query("SELECT * FROM orders WHERE status = 3 AND memberid = {$ME->id}");
  foreach ($DB->all_rows AS $completeorders) {
	$DB2 = new DB;
	$DB2->query("SELECT * FROM orderitems WHERE `order` = '{$completeorders['id']}'");
	$myfiles[] = $DB2->row['fileid'];
  }
  
// MAKE SURE YOU'RE ALLOWED TO DOWNLOAD FILE
if (in_array($fileid, $myfiles)) {
	// load content into var
	$Download = new Download;
	$Download->GetById($fileid);
	$file = "../products/secretfiles/" . $Download->filename;
	
	$type = filetype($file);
	
	header("Content-type: $type");
	header("Content-Disposition: attachment;filename=$file");
	header("Content-Transfer-Encoding: binary");
	header('Pragma: no-cache');
	header('Expires: 0');
	
	
	readfile($file);
	
} else {
	echo "DOWNLOAD NOT ALLOWED"; 
}

?>
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Post by jwalsh »

As always, I rack my brain all night for a solution, and as soon as I post here I find it. Here it is for anyone curious.

What I did not understand is that...

Code: Select all

header("Content-Disposition: attachment;filename=$file2");


... determines the download filename, not the source of the download. Thus I needed a second variable with the filename without the location, and used readfile() to generate the real location.

Here's a full code drop.

Code: Select all

<? 

require_once("config.php"); 
AllowedHere(array(1,9));

$fileid = $_REQUEST['id'];
$myfiles = array();

// COLLECT ARRAY OF USERS FILES
$DB = new DB;
  $DB->query("SELECT * FROM orders WHERE status = 3 AND memberid = {$ME->id}");
  foreach ($DB->all_rows AS $completeorders) {
	$DB2 = new DB;
	$DB2->query("SELECT * FROM orderitems WHERE `order` = '{$completeorders['id']}'");
	$myfiles[] = $DB2->row['fileid'];
  }
  
// MAKE SURE YOU'RE ALLOWED TO DOWNLOAD FILE
if (in_array($fileid, $myfiles)) {
	// load content into var
	$Download = new Download;
	$Download->GetById($fileid);
	$file = "../products/secretfiles/" . $Download->filename;
	
	$type = filetype($file);
	
	$file2 = $Download->filename;
	
	header("Content-type: $type");
	header("Content-Disposition: attachment;filename=$file2");
	header("Content-Transfer-Encoding: binary");
	header('Pragma: no-cache');
	header('Expires: 0');
	
	
	readfile($file);
	
} else {
	echo "DOWNLOAD NOT ALLOWED"; 
}

?>
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

I must have looked over it... but my download version had it already :)
User avatar
jwalsh
Forum Contributor
Posts: 202
Joined: Sat Jan 03, 2004 4:55 pm
Location: Cleveland, OH

Post by jwalsh »

LOL... Oh well :) It got done!
Post Reply