PHP All-in-one Download page

Printer-friendly version
Articles: 

The long and short of it is that you can't push both HTML and a download file out through one HTTP response. You have to get the client to make two requests, and service them independently. The trick is to arrange your HTML response so that it triggers the client to make the download request without human intervention.

There are a couple of ways to do this, and I've chosen to use HTML iframes to cause the client browser to pull the download file. I've refined the technique a bit, and created a standalone PHP page, which I show below

=================== Source code below =================================


<?php

  function SendFile($file)
  {
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename=' . basename($file));
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . filesize ($file));
    readfile($file);
  }

  $msg = null;

  switch ($_SERVER['REQUEST_METHOD'])
  {
    case 'GET':
      if (isset($_GET['type']))
      {
        switch ($_GET['type'])
        {
          case 'HTML':
            SendFile($_SERVER['SCRIPT_FILENAME']);
            break;

          default:
            ?>
            <html><body><p>Sorry, I don't know how to send you a
            <?php print $_GET['type']; ?> file</p></body></html>
            <?php
        }
        exit(0);
      }
      break;

    case 'POST':
      switch ($_POST['Format'])
      {
        case 'HTML':
         $msg = "Downloading ".$_SERVER['SCRIPT_FILENAME']." to you";
         break;
      }
      break;
  }
?>
<html>
<head><title>Trial Sendfile</title></head>
<body>
<h1>Download Form</h1>
<form name="Download" method="post">
<dl>
<dt>Download</dt>
<dd><input type="radio" name="Format" value="HTML"/>webpage</dd>
<dd><input type="radio" name="Format" value="XYZ"/>unknown format</dd>
<dd><input type="submit"/></dd>
</dl>
</form>
<?php
  if (isset($msg))
    printf("<p><b>%s</b></p>\n",$msg);

  /****************************************************************
  ** In order for the page to render properly, we need to ensure **
  ** that the <iframe> appears as the last element of the page   **
  ** Otherwise, the render stops at the <iframe>, and does not   **
  ** show any of the page that follows it.                       **
  ****************************************************************/
  if ($_POST['Format'])
    printf("<iframe src=\"%s?type=%s\" border=\"0\" frameborder=\"0\" />\n",
           $_SERVER['SCRIPT_NAME'],
           $_POST['Format']);
?>
</body>
</html>

=================== Source code above =================================

This is a self-contained page:

  • GET with no (recognized) parameters will display the initial page
  • POST with required parameters will initiate download, and display a modified page
  • GET with valid type= parameter will download the script
  • GET with invalid type= parameter will generate error HTML

On initial entry (GET with no parameters), a web form is presented to the user. The user will select one of the two radio buttons, and click the Submit button to initiate a POST.

On POST, the script will update the page html to include an appropriate message (either "Downloading ..." or nothing) and emit the new page html.

The browser, will render the HTML, including the embedded <iframe>. The <iframe> will initiate a secondary GET (with type= parameter) to the PHP, which will determine the validity of the parameter. A valid parameter will cause the PHP (on this secondary GET) to initiate a file download, while an invalid parameter will cause the PHP to emit an error webpage, which will display in the original POSTs <iframe>.

Thus, the appearance of an updating webpage that simultaneously downloads a file the the client system.