0

As the title suggests. I would like to give users the option to download invoices for purchases that have been made in the past. I would not like to store these files so that they are easily accessible via http, e.g. http://www.site.com/inv/567.pdf.

I have thought of one way of doing this however i do not think that it is the best solution for this.

So in essence, I would have a folder on my server out of the public root and within this folder would be folders each with say a unique ID that relates to the customers account ID (MySQL row ID). Within each of these folders I could store the invoices or (.pdf files) relating to each customer.

I could build into the CMS for the admin a section that would deal with uploading these files to the various user accounts.

For the customer, when they click on the download button for any specific invoice, the server will copy the invoice to a temp directory in the public root but renaming it in the process to some obscure hash. The header is then directed to this location.

I could then run a cron job to clear this temp folder every x amount of time.

Given that I have never really attempted to accomplish this task before, I have thought it best to ask for any suggestions / feedback / advice or comments in relation to this as I am sure that you guys will have some good ideas on how to do this too. Thank you in advance for any assistance provided!

Craig van Tonder
  • 7,497
  • 18
  • 64
  • 109
  • 1
    Why not have a php file that handles downloading of sensitive files? `download.php?file=` for example, could check if the user is logged in and authorized to download such file. – Dave Chen Jan 16 '14 at 04:29
  • @DaveChen Thanks for your input, that makes sense however in my point of view and unless i am mistaken that would require the server to initiate a download, would that not be a security concern? e.g. how does the browser know that the file is legit and has been requested by the user and is not malware? – Craig van Tonder Jan 16 '14 at 04:46
  • 1
    `click on the download button for any specific invoice, the server will copy the invoice to a temp directory` instead of placing it into a temp directory within the document root of the web server, why not just output it as is? For example, [readfile](http://php.net/readfile)`('/home/not_in_doc_root/customer_invoice_123.pdf'); exit;` – Dave Chen Jan 16 '14 at 04:51
  • @DaveChen that is pretty logical to me... the user could then print it / use a pdf printer. The least amount of effort too. Will give this a bit more thought, thanks a lot! :) – Craig van Tonder Jan 16 '14 at 05:15
  • @DaveChen Thanks for your input, I really appreciate it! Take a look at my comment in the chosen answer as the solution provided was perfect for me and inline with what you had mentioned! – Craig van Tonder Jan 16 '14 at 08:42

4 Answers4

3

You have to implement the process in a web script page say download_invoice.php.

  1. Provide a link to download_invoice.php.
  2. Form query string parameters to pass to that page.
    2.1. query string may be like: ?invoice_id=24&prod_id=63
  3. Generate code for the download page:
    3.1. Read query parameters.
    3.2. Process access authentication.
    3.3. On failure redirect to a 403 page.
    3.4. On success continue.
    3.5. Generate content for the PDF file to download and store into byte buffer.
    3.6. Set response headers like attachment and content-type.
    3.7. Flush the response to client.

On the client user either will be prompted to store the file or an associated application for the content-type will open the downloaded file.

Also refer to a similar posting at:
How to make PDF file downloadable in HTML link?

Community
  • 1
  • 1
Ravinder Reddy
  • 23,692
  • 6
  • 52
  • 82
0

I have dealt with this situation and handled it in two different ways.

1) Base64 encode the pdf and store it in the database. 2) Move the pdf to another directory outside of the webserver's directory structure. You of course could use a cron job to accomplish this. If the user needs the pdf in the future then you need a system to move the file back.

I recommend base64 encoding the pdf and storing it in the database.

You wrote the following: http://www.site.com/inv/567.pdf

I hope that is not sensitive data, because pdfs stored as your link shows are accessible to everyone (ie no authentication to see that pdf).

ken koehler
  • 464
  • 4
  • 12
  • Hi there, thank you for your input. I would assume that your second solution relates in part to what I already had in mind. I would like to point out too that I am trying to avoid using a link as you had stated as the document is sensitive. Moving on though, I believe that doing a bit of research on how I would go about storing this information in my database instead and your input will surely help with that. I am a bit confused as to how the download would be initiated in this process though. – Craig van Tonder Jan 16 '14 at 04:52
0

If you are generating a pdf and keeping it out of the root, you can use readfile() to deliver the file and have a function to pass a hash based on the filename to prevent url hacking. For example:

<?php
function getFile( $file, $hv){
    // prevent url hacking - $hv is passed as the valid hash based on the filename
    // it is double checked here
    // create a gethash() function and use your choice of hashing e.g. md5, base64 encoding, etc. to generate the hash
    $hash = gethash(basename($file));

    // if hash correct 
    if ($hv == $hash) {      

        // File Exists?
        if (file_exists($file)){
            // Parse Info / Get Extension
            $fsize = filesize($file);
            $path_parts = pathinfo($file);
            $ext = strtolower($path_parts["extension"]);

            // Determine Content Type
            switch ($ext) {
                case "pdf": $ctype="application/pdf"; break;
                case "exe": $ctype="application/octet-stream"; break;
                case "zip": $ctype="application/zip"; break;
                case "doc": $ctype="application/msword"; break;
                case "xls": $ctype="application/vnd.ms-excel"; break;
                case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
                case "gif": $ctype="image/gif"; break;
                case "png": $ctype="image/png"; break;
                case "jpeg":
                case "jpg": $ctype="image/jpg"; break;
                default: $ctype="application/force-download";
            }
            header("Pragma: public"); // required
            header("Expires: 0");
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
            header("Cache-Control: private",false); // required for certain browsers
            header("Content-Type: $ctype");
            // Download file
            header('Content-Disposition: attachment; filename='.basename($file));
            header("Content-Transfer-Encoding: binary");
            header("Content-Length: ".$fsize);

            if (ob_get_length() > 0 ) {
             //ob_end_clean();
             ob_clean();
             flush();
            }
            readfile( $file);
        } 
        else {
            echo('File Not Found: ' . $file);
        }
    }
    else {
        echo ('Invalid file request');
    }
} 
?>
mseifert
  • 5,390
  • 9
  • 38
  • 100
0

move you pdf to a location that is not accessible via browser, say /path/to/pdf/doc.pdf then have a script.php :

//check if he has access
if ($has_access) {

   $id = $_GET['id'] + 0; //just numbers
   $file = '/path/to/pdf/'.$id.'.pdf'; 

   if (!file_exists($file)) die('cant find it');

   header('Content-type: application/pdf');
   header("Content-Disposition: attachment; filename=\"what_the_user_will see.pdf\"");

   $fp = fopen($file, 'r');

   while(!feof($fp)) {
echo fread($fp, 4096);
    flush();
   }
fclose($fp);

}

and link to it

<a href="download.php?id=567">download</a>
aconrad
  • 556
  • 5
  • 12