You are here

function _uc_file_download_transfer in Ubercart 6.2

Same name and namespace in other branches
  1. 7.3 uc_file/uc_file.pages.inc \_uc_file_download_transfer()

Sends the file's binary data to a user via HTTP and updates the uc_file_users table.

Parameters

$file_user: The file_user object from the uc_file_users.

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

1 call to _uc_file_download_transfer()
_uc_file_download in uc_file/uc_file.pages.inc
Handles file downloading and error states.

File

uc_file/uc_file.pages.inc, line 265
File menu items.

Code

function _uc_file_download_transfer($file_user, $ip) {

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

  // This could get clobbered, so make a copy.
  $filename = $file_user->filename;

  // Gather relevant info about the file.
  $size = filesize($file_user->full_path);
  $mimetype = file_get_mimetype($filename);

  // 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', $filename, substr_count($filename, '.') - 1);
  }

  // Check if HTTP_RANGE is sent by browser (or download manager)
  $range = NULL;
  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);
    }
  }

  // Figure out download piece from range (if set)
  if (isset($range)) {
    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));

  // 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: ' . gmdate("D, d M Y H:i:s", filemtime($file_user->full_path)) . " GMT");

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

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

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

    // Reset time limit for large files
    set_time_limit(0);

    // Push the data to the client.
    print fread($fp, UC_FILE_BYTE_SIZE);
    flush();

    // Not sure why ob_flush() is required here, but leaving it out breaks
    // some systems (clicking on link doesn't download anything without it).
    ob_flush();
  }

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