You are here

function _file_download_transfer in Ubercart 5

Send the file's binary data to a user via HTTP and update the uc_file_users table.

Supports resume and download managers.

Parameters

$file_user: The file_user object from the uc_file_users

$ip: The string containing the ip address the download is going to

$fid: The file id of the file to transfer

1 call to _file_download_transfer()
_file_download in uc_file/uc_file.module
Perform first-pass authorization. Call authorization hooks afterwards.

File

uc_file/uc_file.module, line 1125
Allows products to be associated with downloadable files.

Code

function _file_download_transfer($file_user, $ip, $fid) {
  $file = db_result(db_query("SELECT filename FROM {uc_files} WHERE fid = %d", $fid));
  $file_path = variable_get('uc_file_base_dir', NULL) . '/' . $file;
  if (!is_file($file_path)) {
    drupal_set_message(t('The file %filename could not be found. Please contact the site administrator.', array(
      '%filename' => basename($file),
    )), 'error');
    watchdog('uc_file', t('%username failed to download the file %filename.', array(
      '%username' => $message_user,
      '%filename' => basename($file),
    )), WATCHDOG_NOTICE);
    drupal_not_found();
    exit;
  }
  else {

    //Check any if any hook_file_transfer_alter calls alter the download
    foreach (module_implements('file_transfer_alter') as $module) {
      $name = $module . '_file_transfer_alter';
      $file_path = $name($file_user, $ip, $fid, $file_path);
    }

    //Gather relevent info about file
    $size = filesize($file_path);
    $fileinfo = pathinfo($file_path);

    // Workaround for IE filename bug with multiple periods / multiple dots in filename
    // that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
    if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
      $filename = preg_replace('/\\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1);
    }
    else {
      $filename = $fileinfo['basename'];
    }

    // Compatibility workaround for older versions of Drupal 5
    if (function_exists('file_get_mimetype')) {
      $mimetype = file_get_mimetype($filename);
    }
    else {

      // Set the Content-Type based on file extension.
      $file_extension = strtolower($fileinfo['extension']);
      switch ($file_extension) {
        case 'exe':
          $mimetype = 'application/octet-stream';
          break;
        case 'zip':
          $mimetype = 'application/zip';
          break;
        case 'mp3':
          $mimetype = 'audio/mpeg';
          break;
        case 'mpg':
          $mimetype = 'video/mpeg';
          break;
        case 'avi':
          $mimetype = 'video/x-msvideo';
          break;
        default:
          $mimetype = 'application/force-download';
      }
    }

    // Check if HTTP_RANGE is sent by browser (or download manager)
    if (isset($_SERVER['HTTP_RANGE'])) {
      list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
      if ($size_unit == 'bytes') {

        // Multiple ranges could be specified at the same time, but for simplicity only serve the first range
        // See http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
        list($range, $extra_ranges) = explode(',', $range_orig, 2);
      }
      else {
        $range = '';
      }
    }
    else {
      $range = '';
    }

    // Figure out download piece from range (if set)
    list($seek_start, $seek_end) = explode('-', $range, 2);

    // Set start and end based on range (if set), else set defaults and check for invalid ranges.
    $seek_end = intval(empty($seek_end) ? $size - 1 : min(abs(intval($seek_end)), $size - 1));
    $seek_start = intval(empty($seek_start) || $seek_end < abs(intval($seek_start)) ? 0 : max(abs(intval($seek_start)), 0));
    ob_end_clean();

    // Start building the array of headers
    $http_headers = array();

    //Only send partial content header if downloading a piece of the file (IE workaround)
    if ($seek_start > 0 || $seek_end < $size - 1) {
      drupal_set_header('HTTP/1.1 206 Partial Content');
    }

    // Standard headers, including content-range and length
    drupal_set_header('Pragma: public');
    drupal_set_header('Cache-Control: cache, must-revalidate');
    drupal_set_header('Accept-Ranges: bytes');
    drupal_set_header('Content-Range: bytes ' . $seek_start . '-' . $seek_end . '/' . $size);
    drupal_set_header('Content-Type: ' . $mimetype);
    drupal_set_header('Content-Disposition: attachment; filename="' . $filename . '"');
    drupal_set_header('Content-Length: ' . ($seek_end - $seek_start + 1));

    // Last-modified is required for content served dynamically
    drupal_set_header('Last-modified: ' . format_date(filemtime($file_path), 'large'));

    // Etag header is required for Firefox3 and other managers
    drupal_set_header('ETag: ' . md5($file_path));

    // Open the file and seek to starting byte
    $fp = fopen($file_path, 'rb');
    fseek($fp, $seek_start);

    // Start buffered download
    while (!feof($fp)) {

      // Reset time limit for large files
      set_time_limit(0);
      print fread($fp, 1024 * 8);
      flush();
      ob_flush();
    }

    // Finished serving the file, close the stream and log the download to the user table
    fclose($fp);
    _user_table_action('download', $file_user, $ip);
    exit;
  }
}