/**
  * Sends a file to the user.
  *
  * We’re overriding this from {@link \CHttpRequest::sendFile()} so we can have more control over the headers.
  *
  * @param string     $path      The path to the file on the server.
  * @param string     $content   The contents of the file.
  * @param array|null $options   An array of optional options. Possible keys include 'forceDownload', 'mimeType',
  *                              and 'cache'.
  * @param bool|null  $terminate Whether the request should be terminated after the file has been sent.
  *                              Defaults to `true`.
  *
  * @throws HttpException
  * @return null
  */
 public function sendFile($path, $content, $options = array(), $terminate = true)
 {
     $fileName = IOHelper::getFileName($path, true);
     // Clear the output buffer to prevent corrupt downloads. Need to check the OB status first, or else some PHP
     // versions will throw an E_NOTICE since we have a custom error handler
     // (http://pear.php.net/bugs/bug.php?id=9670)
     if (ob_get_length() !== false) {
         // If zlib.output_compression is enabled, then ob_clean() will corrupt the results of output buffering.
         // ob_end_clean is what we want.
         ob_end_clean();
     }
     // Default to disposition to 'download'
     $forceDownload = !isset($options['forceDownload']) || $options['forceDownload'];
     if ($forceDownload) {
         HeaderHelper::setDownload($fileName);
     }
     if (empty($options['mimeType'])) {
         if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($fileName)) === null) {
             $options['mimeType'] = 'text/plain';
         }
     }
     HeaderHelper::setHeader(array('Content-Type' => $options['mimeType'] . '; charset=utf-8'));
     $fileSize = mb_strlen($content, '8bit');
     $contentStart = 0;
     $contentEnd = $fileSize - 1;
     $httpVersion = $this->getHttpVersion();
     if (isset($_SERVER['HTTP_RANGE'])) {
         HeaderHelper::setHeader(array('Accept-Ranges' => 'bytes'));
         // Client sent us a multibyte range, can not hold this one for now
         if (mb_strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
             HeaderHelper::setHeader(array('Content-Range' => 'bytes ' . $contentStart - $contentEnd / $fileSize));
             throw new HttpException(416, 'Requested Range Not Satisfiable');
         }
         $range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']);
         // range requests starts from "-", so it means that data must be dumped the end point.
         if ($range[0] === '-') {
             $contentStart = $fileSize - mb_substr($range, 1);
         } else {
             $range = explode('-', $range);
             $contentStart = $range[0];
             // check if the last-byte-pos presents in header
             if (isset($range[1]) && is_numeric($range[1])) {
                 $contentEnd = $range[1];
             }
         }
         // Check the range and make sure it's treated according to the specs.
         // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
         // End bytes can not be larger than $end.
         $contentEnd = $contentEnd > $fileSize ? $fileSize - 1 : $contentEnd;
         // Validate the requested range and return an error if it's not correct.
         $wrongContentStart = $contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0;
         if ($wrongContentStart) {
             HeaderHelper::setHeader(array('Content-Range' => 'bytes ' . $contentStart - $contentEnd / $fileSize));
             throw new HttpException(416, 'Requested Range Not Satisfiable');
         }
         HeaderHelper::setHeader("HTTP/{$httpVersion} 206 Partial Content");
         HeaderHelper::setHeader(array('Content-Range' => 'bytes ' . $contentStart - $contentEnd / $fileSize));
     } else {
         HeaderHelper::setHeader("HTTP/{$httpVersion} 200 OK");
     }
     // Calculate new content length
     $length = $contentEnd - $contentStart + 1;
     if (!empty($options['cache'])) {
         $cacheTime = 31536000;
         // 1 year
         HeaderHelper::setHeader(array('Expires' => gmdate('D, d M Y H:i:s', time() + $cacheTime) . ' GMT'));
         HeaderHelper::setHeader(array('Pragma' => 'cache'));
         HeaderHelper::setHeader(array('Cache-Control' => 'max-age=' . $cacheTime));
         $modifiedTime = IOHelper::getLastTimeModified($path);
         HeaderHelper::setHeader(array('Last-Modified' => gmdate("D, d M Y H:i:s", $modifiedTime->getTimestamp()) . ' GMT'));
     } else {
         if (!$forceDownload) {
             HeaderHelper::setNoCache();
         } else {
             // Fixes a bug in IE 6, 7 and 8 when trying to force download a file over SSL:
             // https://stackoverflow.com/questions/1218925/php-script-to-download-file-not-working-in-ie
             HeaderHelper::setHeader(array('Pragma' => '', 'Cache-Control' => ''));
         }
     }
     if ($options['mimeType'] == 'application/x-javascript' || $options['mimeType'] == 'text/css') {
         HeaderHelper::setHeader(array('Vary' => 'Accept-Encoding'));
     }
     if (!ob_get_length()) {
         HeaderHelper::setLength($length);
     }
     $content = mb_substr($content, $contentStart, $length);
     if ($terminate) {
         // Clean up the application first because the file downloading could take long time which may cause timeout
         // of some resources (such as DB connection)
         ob_start();
         Craft::app()->end(0, false);
         ob_end_clean();
         echo $content;
         exit(0);
     } else {
         echo $content;
     }
 }
Пример #2
0
 /**
  * A wrapper for {@link FileHelper::getMimeTypeByExtension()}.
  *
  * @param  string $path The path to test.
  *
  * @return string       The mime type.
  */
 public static function getMimeTypeByExtension($path)
 {
     return FileHelper::getMimeTypeByExtension($path);
 }