5

I have created together a pretty simple Download Code redeemer in .php (thanks to help from here) and am having a hard time trying to figure out what the best way to serve a download is if the validation is successful. Basically -

User enters invalid code -> Page is refreshed with error message. User enters valid code -> Give download 'Save as' -> refresh page.

At the minute I'm using http://www.zubrag.com/scripts/download.php to serve the file but once it has started downloading, my form refreshes the page but only half loads the content?!

This is the form with the PHP script I did.

<div class="dcrForm">
    <p>Have a physical copy of this release? Claim your digital download by entering your Download Code below.</p>
    <form  action="index.php" method="post">
        <input type="text" name="code" class="dcrInput" value="">
        <input type="submit" name="harrisSubmit" class="dcrSubmit" value="Submit">
    </form>
<?php
    include("scripts/dcr_config.php");
    $code="";
    $log="";

    if (isset($_POST['harrisSubmit']))
    {
        $code=$_POST['code'];

        $link = mysql_connect($hostname, $dbusername, $dbpassword);
        mysql_select_db("$databasename");

        $query = "select count from $harris where code='$code'";
        if ($q=mysql_query($query))
            if ($r=mysql_fetch_array($q)){
                if ($r[0]<3)
                {
                    $subquery="update $tbname set count='".($r[0]+1)."' where code='$code'";
                    mysql_query($subquery);
                    ?><script>window.location.href="download.php?f=test.txt";</script><?php
                }
            }
        $log="<p>Invalid code. Try Again.</p>";
    }
    echo $log."";
?>
</div>

Does anyone have an ideas on what the best way to serve the download would be? I know that currently anyone who had the file location could download the file but I'm not sure how I could go about protecting i

Incognito
  • 20,537
  • 15
  • 80
  • 120
Bantros
  • 295
  • 8
  • 17

2 Answers2

4

I am glad you have made it this far!

If you are going to redirect the user to a download script, that script would need to have some sort of token attached to it as to prevent unauthorized downloads, basically re-verifying the code or token given.

In the above script, instead of outputting the javascript to redirect to the download script you could do this:

<?php

include "scripts/dcr_config.php";
$code = "";
$log  = "";

if (isset($_POST['harrisSubmit'])) {
    $code = trim($_POST['code']);

    $link = mysql_connect ( $hostname, $dbusername, $dbpassword );
    mysql_select_db ( "$databasename" );

    $code = mysql_real_escape_string($code); // very important! protects against exploits

    $query = "select count from $harris where code='$code'";
    if ($q = mysql_query ( $query )) {
        if ($r = mysql_fetch_array ( $q )) {
            if ($r [0] < 3) {
                $subquery = "update $tbname set count='" . ($r [0] + 1) . "' where code='$code'";
                mysql_query ( $subquery );

                $file = '/path/to/protecteddownload.txt';

                // send file to browser as a download dialog
                // no content can be output prior to these header() calls
                header('Content-type: application/octet-stream');
                header('Content-Disposition: attachment; filename="file.txt"');
                header('Content-Length: ' . filesize($file));
                header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
                header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");

                echo file_get_contents($file);
                exit; // terminate script
            } else {
                $log = 'Sorry, this code has already been redeemed.';
            }
        } else {
            $log = 'Invalid download code.  Try again.';
        }
    } else {
        // query failed
        $log = 'An error occurred validating your code, please try again later.';
    }

    $log = "<p>Invalid code. Try Again.</p>";
}

?>

<?php if (isset($log) && $log != ''): ?>
<strong class="error"><?php echo $log ?></strong>
<?php endif; ?>

<div class="dcrForm">
<p>Have a physical copy of this release? Claim your digital download by
entering your Download Code below.</p>
<form action="index.php" method="post"><input type="text" name="code"
    class="dcrInput" value=""> <input type="submit" name="harrisSubmit"
    class="dcrSubmit" value="Submit"></form>
</div>

The download script is probably similar to some of what I have above. The key thing about this example is that the file you are serving with file_get_contents, is not accessible from the web. You only send it when a valid code is entered.

drew010
  • 68,777
  • 11
  • 134
  • 162
  • Hi again drew, thanks, it was a pretty useful project. I've tried your above solution but even if I set the $file to a location that is accessible from the web and change the filename to the one I am pointing to, no download is served and the page refreshes and only loads half! – Bantros Sep 23 '11 at 00:25
  • Are you using absolute or relative paths? – dizas Sep 28 '11 at 13:24
  • I changed the structure of your code a bit, because if you want to send headers, no html or whitespace can be output prior to doing that, especially if we are to serve a download. Is there other HTML above that block of code that sends the headers that was not shown in the example? If so make sure the script that serves the download has no other output prior to serving up the file. Sorry for not responding before, I must have missed the inbox message. – drew010 Sep 28 '11 at 16:45
  • dizas I'm using absolute paths. @drew010 No problem, its great that you keep replying. The code is just on the index.php page, just below the form like so http://jsfiddle.net/jeUmJ/ but it doesn't sound like it is in the correct place? – Bantros Sep 29 '11 at 12:14
  • 1
    jsfiddle was slow so I posted an update on pastebin [here](http://pastebin.com/6fp7tDSY). What I did was move all the php and validation to the very beginning of the file. Now you should have no errors trying to serve the download. See if that works. It also sets the error at the beginning and will display it later if the code was invalid. – drew010 Sep 29 '11 at 15:10
  • My last comment sent yesterday has disappeared... Thanks drew, it's trying to serve the download now but it saves with a filesize of 0 bytes – Bantros Oct 01 '11 at 12:50
  • Are you still having that problem? Does it take long before a download completes or is it immediately finished and 0 bytes? You could try adding `set_time_limit(0);` to allow the download to complete if it is a big file. If you have a URL up I could check out maybe I can run wireshark and see what might be happening. – drew010 Oct 01 '11 at 19:16
1

I have just 1 quick question, how big is this file? Could this be a case that the php timeout is being experienced while reading the file to the browser?

You could play around with the php settings to confirm this (http://php.net/manual/en/function.set-time-limit.php).

Just my 2 cents

Richard Niemand
  • 161
  • 1
  • 7