14

I want to serve invoices for download. Currently I'm using a simple numbering scheme (invoice-01.pdf, invoice-02.pdf, and so on). I know that I could use hashes instead to obscure the data.

Is it also possible to use PHP and serve the invoices by not directly having the user point to them?

  • Yes. How about "http://invoices.invalid/request.php?id=baeba7e7b9ec453e23b5bac313219285d6292358" ? – mailq Sep 29 '11 at 13:19

5 Answers5

28

There is even an example of this on php.net

<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');

// It will be called downloaded.pdf header('Content-Disposition: attachment; filename="downloaded.pdf"');

// The PDF source is in original.pdf readfile('original.pdf'); ?>

Or expand that a bit with

<?php
if ( can_this_file_be_downloaded() ) {
  header('Content-type: application/pdf');
  header('Content-Disposition: attachment; filename="invoice.pdf"');
  readfile("{$_GET['filename']}.pdf");
} else {
  die("None shall pass");
}
?>
5

Sam has the answer. Also put them in a directory with .htaccess:

Authname Private
AuthType basic
require user noadmittance

That will keep out direct access if they know the url. You can still read it from your PHP script with readfile().

Charlie
  • 181
  • 2
4

I found for this excellent guide: How to serve big files through PHP.

Especially useful is the lighttpd trick - If your PHP happens to run under lighhtpd, script only needs to set "X-Sendfile" header, and lighttpd will read and send the file for you (and it well knows how to send files).

UPDATE:

Lighttpd has this feature and there is a mod_xsendfile for Apache2.

(Quoted from NginX documentation)

Sandman4
  • 4,095
  • 2
  • 22
  • 27
1

My function with automatic MIME type detection :

function serve_file($filepath, $new_filename=null) {
    $filename = basename($filepath);
    if (!$new_filename) {
        $new_filename = $filename;
    }
    $mime_type = mime_content_type($filepath);
    header('Content-type: '.$mime_type);
    header('Content-Disposition: attachment; filename="'.$new_filename.'"');
    readfile($filepath);
}

usage :

serve_file("/no_apache/invoice27342.pdf");

Pay attention to not send anything else with PHP (no echo).

0

In general, you should clean the output buffer before streaming the file, otherwise it may not open. If you're downloading an EXE, the code signing certificate may be corrupted. This may not matter on EXEs that are not code signed, but this is the cleanest method to ensure code signing still is intact by delivering only the file and no 'extra' headers/output.

This is close to another answer, but ob_clean(); has been inserted before the headers.

function serve_file($filepath, $new_filename = null) {
    $filename = basename($filepath);
    if (!$new_filename) {
        $new_filename = $filename;
    }
    $mime_type = mime_content_type($filepath);
    ob_clean();
    header('Content-type: ' . $mime_type);
    header('Content-Disposition: attachment; filename="' . $new_filename . '"');
    readfile($filepath);
}

Usage:

serve_file($url, $filename);

Example:

$url = "example.exe";
$filename = "Example123.exe";
serve_file($url, $filename);