Unoffical empeg BBS

Quick Links: Empeg FAQ | RioCar.Org | Hijack | BigDisk Builder | jEmplode | emphatic
Repairs: Repairs

Topic Options
#298269 - 09/05/2007 12:35 PHP: Protecting/serving a download
hybrid8
carpal tunnel

Registered: 12/11/2001
Posts: 7738
Loc: Toronto, CANADA
I seem to have terrific luck when Googling answers for other people but never when trying to find something for myself.

I've got to create a download link so that the served file is only presented to specific people. I've already got access control in place whereby I can restrict visitors based on userID entries in a database, bt now I need to be able to serve them a file without having a static path to that file. Otherwise that path can be copied and pasted anywhere for anyone to download the same file.

I'm thinking I can put the file outside the path of the normal web hierarchy and then serve it up directly from a PHP landing page that can only be gotten to with the access control (user ID) mechanism I mentiond above. That should mean it's impossible to distribute a link directly to the file (no one would know where the file was stored) and a link to the landing page would only prompt for user ID credentials again.

Does anyone have any tips for generating this dynamic download?

There are other ways to accomplish this type of thing such as creating entries in an .htaccess file in the same path as the download file, but that can get pretty hairy pretty fast when dealing with a lot of users (about 5000) unless entries were periodically trimmed.

Anyway, I'm all ears...
_________________________
Bruno
Twisted Melon : Fine Mac OS Software

Top
#298270 - 09/05/2007 13:34 Re: PHP: Protecting/serving a download [Re: hybrid8]
JBjorgen
carpal tunnel

Registered: 19/01/2002
Posts: 3584
Loc: Columbus, OH
You can use a bit of code like the following example to check to see whether they're logged in to a valid session and then download the file from outside the web root.

Code:

<?php
session_start();
if ($SESSION_USERID) {
$browser = $_SERVER["HTTP_USER_AGENT"];
if (preg_match('/MSIE 5.5/', $browser) || preg_match('/MSIE 6.0/', $browser)) {
header('Content-Disposition: filename="myfile.zip"');
} else {
header('Content-Disposition: attachment; filename="myfile.zip"');
}
header('Content-Transfer-Encoding: binary');
header("Content-type: application/octet-stream");
header('Pragma: no-cache');
header('Expires: 0');
readfile("../protected/myfile.zip");
} else {
header("Location: ./");
}

?>



This bit has been tested with zip files in IE and firefox. You may have to mess with the headers a bit for other browsers / filetypes.
_________________________
~ John

Top
#298271 - 09/05/2007 13:57 Re: PHP: Protecting/serving a download [Re: JBjorgen]
DWallach
carpal tunnel

Registered: 30/04/2000
Posts: 3810
Quote:
readfile("../protected/myfile.zip");

Just make sure the "protected" subdirectory can't be directly reached from the web, and this should be the right answer.

Top
#298272 - 09/05/2007 14:51 Re: PHP: Protecting/serving a download [Re: JBjorgen]
hybrid8
carpal tunnel

Registered: 12/11/2001
Posts: 7738
Loc: Toronto, CANADA
This needs to be plugged into a part of the site that was being used to remail a bit of info based on the lookup of an input email address in a database.

Unfortunately these pages are not making use of session data at all (yet).

Can you provide a bit of setup code I might use to set up the session such that the example code can be used?

Currently everything lives in one re-entrant php file with a form. User types email address into form and submits which reloads same file but validates the email. If email validation fails, a warning is displayed and the form appears again. If it validates, alternate content is shown instead. This is where I'd like to display the download link. I'm fine with the download link being another php file, such as "download.php" containing the code above.
_________________________
Bruno
Twisted Melon : Fine Mac OS Software

Top
#298273 - 09/05/2007 15:30 Re: PHP: Protecting/serving a download [Re: hybrid8]
hybrid8
carpal tunnel

Registered: 12/11/2001
Posts: 7738
Loc: Toronto, CANADA
I seem to have cobbled together a solution without using session data. What I've done was to create the download.php within my web root with an include to a second download.php outside the web root. This second file contains a query to my database (which requires DB login and password - that's why I put it outside the web root) and the above header generation code.

To use a standard link I have to employ the dynamic header within its own file (the link destination). This means I have to validate the email address one more time to prevent outside linking to this file. A little but of duplicated effort I suppose.

The initial php validates the input email address and passes it to download.php on the URL. The email address is again checked against the DB and if it passes the file download headers are generated (as above) otherwise not (shown a generic page).
_________________________
Bruno
Twisted Melon : Fine Mac OS Software

Top
#298274 - 09/05/2007 19:06 Re: PHP: Protecting/serving a download [Re: hybrid8]
JBjorgen
carpal tunnel

Registered: 19/01/2002
Posts: 3584
Loc: Columbus, OH
In its very simplest form, the session code would look something like this:

Code:

if ($submit_x === '0' || $submit_x > 0) {
// form submitted

if (!$user || !$pass ) {
header("Location: $PHP_SELF?err=1");
exit;
}

$user = trim(stripslashes($user));
$pass = trim(stripslashes($pass));

$query = "SELECT `password` FROM `tUsers` WHERE userid = '$user' AND `isActive`=1";
$result = mysql_query($query);
if ($result) {
if (mysql_num_rows($result) > 0) {
$myrow = mysql_fetch_array($result);
if ($myrow["password"]) {
$dbpass = $myrow["password"];
} else {
header("Location: $PHP_SELF?err=2");
exit;
}
} else {
header("Location: $PHP_SELF?err=2");
exit;
}
} else {
header("Location: $PHP_SELF?err=3");
exit;
}


// compare given pass with real pass
if ($pass == $dbpass) {

// store the details in session variables
session_start();
session_register("SESSION");
session_register("SESSION_USERID");

// assign values to the session variables
$SESSION_USERID = $user;

// redirect user to the list page
header("Location: index.php");
} else {
header("Location: $PHP_SELF?err=2");
}
}
?>
<html>
<head>
</head>
<body>
<?php
if ($err) {
if ($err == 1) {
print "<div id="errMsg">You did not enter a username or password. Please try again.</div>";
}
if ($err == 2) {
print "<div id="errMsg">Username or password incorrect. Please try again.</div>";
}
if ($err == 3) {
print "<div id="errMsg">Database error. Please try again later.</div>";
}
}
?>

<div id="loginForm>
<form name="login" method="POST" action="<?php echo $PHP_SELF ?>">
<label for="user">Username</label><input type="text" name="user" tabindex="1" />
<label for="pass">Password</label><input type="password" name="pass" tabindex="2" />
<input type="image" src="submitbutton.gif" value="Submit" tabindex="3" name="submit" />
<input type="image" src="clearbutton.gif" value="Clear" tabindex="4" name="clear" />
</form>
</div>
</body>
</html>


Top
#298275 - 09/05/2007 20:08 Re: PHP: Protecting/serving a download [Re: JBjorgen]
hybrid8
carpal tunnel

Registered: 12/11/2001
Posts: 7738
Loc: Toronto, CANADA
I'm predicting, based on a number of pages I've read about sessions, that the session data will be lost when moving to a new page here:

Quote:

// redirect user to the list page
header("Location: index.php");



Am I wrong?

The issue I was presented with was that I needed to show intermediate content after the person types in their user ID, not jump immediately to a hidden download. Without that necessity I could replace that header with the full header from the first example to send the file.

The flow is like this...

Form page (remail.php) with email_address input -> submits back to itself
Form page (remail.php) calls different script to validate input address against DB and send an email, fail shows an error message and pass shows a success message along with a download button image.

At this point user can leave or optionally click to download using his validated address as the authentication criteria for the download.

I do have session variables used elsewhere on my site (shopping cart), but I started with some skeleton code for that and never dug deep into the mechanics of the session stuff. In that implementation the session data gets written to a session file on the server (it's used to hold the cart contents).

I've also read advice not to use session_register but instead to use the $_SESSION array. Example: $_SESSION["userid"] = $user; Not sure if it's as simple as a direct replacement though.
_________________________
Bruno
Twisted Melon : Fine Mac OS Software

Top
#298276 - 10/05/2007 04:52 Re: PHP: Protecting/serving a download [Re: hybrid8]
andy
carpal tunnel

Registered: 10/06/1999
Posts: 5916
Loc: Wivenhoe, Essex, UK
The whole point about session data is that it is not lost when you move to a new page. The session data is held on the server, with a cookie on in the browser (or in some cases by embedding the session id in the URL) to allow the server to find the right session data for each visitor to the site.
_________________________
Remind me to change my signature to something more interesting someday

Top
#298277 - 10/05/2007 10:36 Re: PHP: Protecting/serving a download [Re: hybrid8]
JBjorgen
carpal tunnel

Registered: 19/01/2002
Posts: 3584
Loc: Columbus, OH
Andy is correct. As long as you include session_start(); on a subsequent page, you can access your session variables until the browser (all windows) are closed or the session times out (generally about 20 minutes...check your php settings to be sure).

I believe that you are also correct about session_register. That was copied from some code that's at least 7 years old, so some things have changed a bit. For example, it also assumes that register_globals is on, which is clearly a no-no these days.
_________________________
~ John

Top
#298278 - 10/05/2007 11:39 Re: PHP: Protecting/serving a download [Re: JBjorgen]
Roger
carpal tunnel

Registered: 18/01/2000
Posts: 5683
Loc: London, UK
Quote:
if ($pass == $dbpass) {


Please, oh please, hash the frigging password before stashing it in the database.
_________________________
-- roger

Top
#298279 - 10/05/2007 12:25 Re: PHP: Protecting/serving a download [Re: Roger]
andy
carpal tunnel

Registered: 10/06/1999
Posts: 5916
Loc: Wivenhoe, Essex, UK
And concatenate the first few characters of the user name (or a random value) with the password before hashing it, to prevent dictionary attacks.

http://phpsec.org/articles/2005/password-hashing.html


Edited by andy (10/05/2007 12:32)
_________________________
Remind me to change my signature to something more interesting someday

Top
#298280 - 10/05/2007 16:03 Re: PHP: Protecting/serving a download [Re: JBjorgen]
JBjorgen
carpal tunnel

Registered: 19/01/2002
Posts: 3584
Loc: Columbus, OH
Note that I said "In its very simplest form." I use a significantly better authentication scheme for my real sites, but I didn't want to make the example overly complex.
_________________________
~ John

Top