<?php
/**
 * download header_php.php
 *
 * @package page
 * @copyright Copyright 2003-2009 Zen Cart Development Team
 * @copyright Portions Copyright 2003 osCommerce
 * @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0
 * @version $Id: header_php.php 14448 2009-09-21 18:45:09Z drbyte $
 */
// This should be first line of the script:
$zco_notifier->notify('NOTIFY_HEADER_START_DOWNLOAD');

require(DIR_WS_MODULES . zen_get_module_directory('require_languages.php'));

// if the customer is not logged on, redirect them to the time out page
if (!$_SESSION['customer_id']) {
  zen_redirect(zen_href_link(FILENAME_TIME_OUT));
}

// Check download.php was called with proper GET parameters
if ((isset($_GET['order']) && !is_numeric($_GET['order'])) || (isset($_GET['id']) && !is_numeric($_GET['id'])) ) {
  // if the paramaters are wrong, redirect them to the time out page
  zen_redirect(zen_href_link(FILENAME_TIME_OUT));
}

// Check that order_id, customer_id and filename match
$sql = "SELECT date_format(o.date_purchased, '%Y-%m-%d')
          AS date_purchased_day, opd.download_maxdays, opd.download_count, opd.download_maxdays, opd.orders_products_filename
          FROM " . TABLE_ORDERS . " o, " . TABLE_ORDERS_PRODUCTS . " op, " . TABLE_ORDERS_PRODUCTS_DOWNLOAD . " opd
          WHERE o.customers_id = customersID
          AND o.orders_id = ordersID
          AND o.orders_id = op.orders_id
          AND op.orders_products_id = opd.orders_products_id
          AND opd.orders_products_download_id = downloadID
          AND opd.orders_products_filename != ''";

$sql = $db->bindVars($sql, 'customersID', $_SESSION['customer_id'], 'integer');
$sql = $db->bindVars($sql, 'downloadID', $_GET['id'], 'integer');
$sql = $db->bindVars($sql, 'ordersID', $_GET['order'], 'integer');
$downloads = $db->Execute($sql);
if ($downloads->RecordCount() <= 0 ) die;
// MySQL 3.22 does not have INTERVAL
list($dt_year, $dt_month, $dt_day) = explode('-', $downloads->fields['date_purchased_day']);
$download_timestamp = mktime(23, 59, 59, $dt_month, $dt_day + $downloads->fields['download_maxdays'], $dt_year);

// Die if time expired (maxdays = 0 means no time limit)
if (($downloads->fields['download_maxdays'] != 0) && ($download_timestamp <= time())) {
  zen_redirect(zen_href_link(FILENAME_DOWNLOAD_TIME_OUT));
}
// Die if remaining count is <=0 (maxdays = 0 means no time limit)
if ($downloads->fields['download_count'] <= 0 and $downloads->fields['download_maxdays'] != 0) {
  zen_redirect(zen_href_link(FILENAME_DOWNLOAD_TIME_OUT));
}

// FIX HERE AND GIVE ERROR PAGE FOR MISSING FILE
// Die if file is not there
if (!file_exists(DIR_FS_DOWNLOAD . $downloads->fields['orders_products_filename'])) die('Sorry. File not found. Please contact the webmaster to report this error.<br />c/f: ' . $downloads->fields['orders_products_filename']);

// Now decrement counter (probably should skip this if download_maxdays = 0, ie: unlimited) -- move it up to lines 48-54?
$sql = "UPDATE " . TABLE_ORDERS_PRODUCTS_DOWNLOAD . "
          SET download_count = download_count-1
          WHERE orders_products_download_id = :downloadID";

$sql = $db->bindVars($sql, ':downloadID', $_GET['id'], 'integer');
$db->Execute($sql);

// Returns a random name, 16 to 20 characters long
// There are more than 10^28 combinations
// The directory is "hidden", i.e. starts with '.'
function zen_random_name()
{
  $letters = 'abcdefghijklmnopqrstuvwxyz';
  $dirname = '.';
  $length = floor(zen_rand(16,20));
  for ($i = 1; $i <= $length; $i++) {
    $q = floor(zen_rand(1,26));
    $dirname .= $letters[$q];
  }
  return $dirname;
}

// Unlinks all subdirectories and files in $dir
// Works only on one subdir level, will not recurse
function zen_unlink_temp_dir($dir)
{
  $h1 = opendir($dir);
  while ($subdir = readdir($h1)) {
    // Ignore non directories
    if (!is_dir($dir . $subdir)) continue;
    // Ignore . and .. and CVS
    if ($subdir == '.' || $subdir == '..' || $subdir == 'CVS') continue;
    // Loop and unlink files in subdirectory
    $h2 = opendir($dir . $subdir);
    while ($file = readdir($h2)) {
      if ($file == '.' || $file == '..') continue;
      @unlink($dir . $subdir . '/' . $file);
    }
    closedir($h2);
    @rmdir($dir . $subdir);
  }
  closedir($h1);
}

// disable gzip output buffering if active:
@ob_end_clean();
@ini_set('zlib.output_compression', 'Off');

// determine filename for download
$origin_filename = $downloads->fields['orders_products_filename'];
$browser_filename = str_replace(' ', '_', $origin_filename);
if (strstr($browser_filename, '/')) $browser_filename = substr($browser_filename, strrpos($browser_filename, '/')+1);
if (strstr($browser_filename, '\\')) $browser_filename = substr($browser_filename, strrpos($browser_filename, '\\')+1);

    $downloadFilesize = filesize(DIR_FS_DOWNLOAD . $originFilename);


    /**
     * Browser detection
     * Presently only checks for IE-specific cases.
     */
    $detectedBrowser = '';
    if (preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT']))
    {
      $version = explode(' ', stristr($_SERVER['HTTP_USER_AGENT'], 'msie'));
      if ((int)$version[1] == 5) $detectedBrowser = 'IE5';
      if ((int)$version[1] == 6) $detectedBrowser = 'IE6';
      if ((int)$version[1] == 7) $detectedBrowser = 'IE7';
      if ((int)$version[1] == 8) $detectedBrowser = 'IE8';
    }


    /**
     * set notifier point ... we are ready to begin the actual download
     */
    $zco_notifier->notify('NOTIFY_DOWNLOAD_READY_TO_START', $origin_filename, $browser_filename, $downloadFilesize, $_SESSION['customers_host_address']);


    /**
     * Check whether any headers have already been set, because that will cause download problems:
     */
    $hfile = $hline = '';
    if (headers_sent($hfile, $hline)) {
      $msg = 'DOWNLOAD PROBLEM: Cannot begin download for ' . $originFilename . ' because HTTP headers were already sent. This indicates a PHP error, probably in a language file.  Start by checking ' . $hfile . ' on line ' . $hline . '.';
      zen_mail('', STORE_OWNER_EMAIL_ADDRESS, ERROR_CUSTOMER_DOWNLOAD_FAILURE, $msg, STORE_NAME, EMAIL_FROM);
    }

    /**
     * Now send the file with header() magic
     * The "must-revalidate" and expiry times are used to prevent caching and fraudulent re-acquiring of files w/o redownloading.
     * Certain browsers require certain header combinations, especially when related to SSL mode and caching
     */
    switch($detectedBrowser)
    {
      case 'IE5':
      case 'IE6':
        header("Expires: Mon, 22 Jan 2002 00:00:00 GMT");
        header("Last-Modified: " . gmdate("D,d M Y H:i:s") . " GMT");
        header("Pragma: public");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private", FALSE);
        header("Cache-Control: max-age=1");  // stores for only 1 second, which helps allow SSL downloads to work more reliably in IE
      break;
      case 'IE7':
      case 'IE8':
        header("Expires: Mon, 22 Jan 2002 00:00:00 GMT");
        header("Last-Modified: " . gmdate("D,d M Y H:i:s") . " GMT");
        header("Pragma: public");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private", FALSE);
        header("Cache-Control: max-age=1");  // stores for only 1 second, which helps allow SSL downloads to work more reliably in IE
        break;
      default:
        header("Expires: Mon, 22 Jan 2002 00:00:00 GMT");
        header("Last-Modified: " . gmdate("D,d M Y H:i:s") . " GMT");
        header("Cache-Control: no-cache, must-revalidate");
        header("Pragma: no-cache");
      break;
    }

    /**
     * Set mime type
     * These content-type headers should cause the downloaded file to trigger a "save as" dialog, instead of opening directly
     * However, some browsers and some software already installed on the PC may cause this to be overridden simply based on the filename. In these cases, the user will have to manually choose "Save As" themselves.
     */
    header("Content-Type: application/x-octet-stream");
    header("Content-Type: application/force-download");
    header("Content-Type: application/octet-stream");
    header("Content-Type: application/download");


    header("Content-Transfer-Encoding: binary");
    header('Content-Disposition: attachment; filename="' . urlencode($browser_filename) . '"');
    header("Content-Length: " . (string) $downloadFilesize);


// Redirect usually will work only on Unix/Linux hosts since Windows hosts can't do symlinking in PHP versions older than 5.3.0
if (DOWNLOAD_BY_REDIRECT == 'true') {
  zen_unlink_temp_dir(DIR_FS_DOWNLOAD_PUBLIC);
  $tempdir = zen_random_name();
  umask(0000);
  mkdir(DIR_FS_DOWNLOAD_PUBLIC . $tempdir, 0777);
  $download_link = str_replace(array('/','\\'),'_',$browser_filename);
  $link_create_status = @symlink(DIR_FS_DOWNLOAD . $origin_filename, DIR_FS_DOWNLOAD_PUBLIC . $tempdir . '/' . $download_link);

  if ($link_create_status==true) {
    $zco_notifier->notify('NOTIFY_DOWNLOAD_VIA_SYMLINK___BEGINS', $download_link, $origin_filename, $tempdir);
    header("HTTP/1.1 303 See Other");
    zen_redirect(DIR_WS_DOWNLOAD_PUBLIC . $tempdir . '/' . $download_link, 303);
  }
}

if (DOWNLOAD_BY_REDIRECT != 'true' or $link_create_status==false ) {
  // not downloading by redirect; instead, we stream it to the browser.
  // This happens if the symlink couldn't happen, or if set as default in Admin
  if (DOWNLOAD_IN_CHUNKS != 'true') {
    $zco_notifier->notify('NOTIFY_DOWNLOAD_WITHOUT_REDIRECT___COMPLETED', $origin_filename);
    // This will work on all systems, but will need considerable resources
    readfile(DIR_FS_DOWNLOAD . $origin_filename);
  } else {
    // override PHP timeout to 25 minutes, if allowed
    @set_time_limit(1500);
    $zc_notifier->notify('NOTIFY_DOWNLOAD_IN_CHUNKS___COMPLETED', $origin_filename);
    // loop with fread($fp, xxxx) to allow streaming in chunk sizes below the PHP memory_limit
    $handle = @fopen(DIR_FS_DOWNLOAD . $origin_filename, "rb");
    if ($handle) {
      while (!@feof($handle)) {
        echo(fread($handle, 4096));
        @flush();
      }
      fclose($handle);
    } else {
      // Throw error condition -- this should never happen!
      $messageStack->add_session('default', 'Please contact store owner.  ERROR: Cannot read file: ' . $origin_filename, 'error');
      zen_mail('', STORE_OWNER_EMAIL_ADDRESS, ERROR_CUSTOMER_DOWNLOAD_FAILURE, "Unable to open file '" . $origin_filename . " for reading.  Check the file permissions.", STORE_NAME, EMAIL_FROM);
    }
    $zco_notifier->notify('NOTIFY_DOWNLOAD_WITHOUT_REDIRECT_VIA_CHUNKS___COMPLETED');
  }
}

// This should be last line of the script:
$zco_notifier->notify('NOTIFY_HEADER_END_DOWNLOAD');

// finally, upon completion of the download, the script should end here and not attempt to display any template components etc.
zen_exit();
