1244 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1244 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 | 
						|
 | 
						|
/**
 | 
						|
 * HTTP::Download
 | 
						|
 *
 | 
						|
 * PHP versions 4 and 5
 | 
						|
 *
 | 
						|
 * @category   HTTP
 | 
						|
 * @package    HTTP_Download
 | 
						|
 * @author     Michael Wallner <mike@php.net>
 | 
						|
 * @copyright  2003-2005 Michael Wallner
 | 
						|
 * @license    BSD, revised
 | 
						|
 * @version    CVS: $Id: Download.php 304423 2010-10-15 13:36:46Z clockwerx $
 | 
						|
 * @link       http://pear.php.net/package/HTTP_Download
 | 
						|
 */
 | 
						|
 | 
						|
// {{{ includes
 | 
						|
/**
 | 
						|
 * Requires PEAR
 | 
						|
 */
 | 
						|
require_once 'PEAR.php';
 | 
						|
 | 
						|
/**
 | 
						|
 * Requires HTTP_Header
 | 
						|
 */
 | 
						|
require_once 'HTTP/Header.php';
 | 
						|
// }}}
 | 
						|
 | 
						|
// {{{ constants
 | 
						|
/**#@+ Use with HTTP_Download::setContentDisposition() **/
 | 
						|
/**
 | 
						|
 * Send data as attachment
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment');
 | 
						|
/**
 | 
						|
 * Send data inline
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_INLINE', 'inline');
 | 
						|
/**#@-**/
 | 
						|
 | 
						|
/**#@+ Use with HTTP_Download::sendArchive() **/
 | 
						|
/**
 | 
						|
 * Send as uncompressed tar archive
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_TAR', 'TAR');
 | 
						|
/**
 | 
						|
 * Send as gzipped tar archive
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_TGZ', 'TGZ');
 | 
						|
/**
 | 
						|
 * Send as bzip2 compressed tar archive
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_BZ2', 'BZ2');
 | 
						|
/**
 | 
						|
 * Send as zip archive
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_ZIP', 'ZIP');
 | 
						|
/**#@-**/
 | 
						|
 | 
						|
/**#@+
 | 
						|
 * Error constants
 | 
						|
 */
 | 
						|
define('HTTP_DOWNLOAD_E_HEADERS_SENT',          -1);
 | 
						|
define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB',           -2);
 | 
						|
define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC',         -3);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_FILE',          -4);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_PARAM',         -5);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_RESOURCE',      -6);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_REQUEST',       -7);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE',  -8);
 | 
						|
define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE',  -9);
 | 
						|
/**#@-**/
 | 
						|
// }}}
 | 
						|
 | 
						|
/**
 | 
						|
 * Send HTTP Downloads/Responses.
 | 
						|
 *
 | 
						|
 * With this package you can handle (hidden) downloads.
 | 
						|
 * It supports partial downloads, resuming and sending
 | 
						|
 * raw data ie. from database BLOBs.
 | 
						|
 *
 | 
						|
 * <i>ATTENTION:</i>
 | 
						|
 * You shouldn't use this package together with ob_gzhandler or
 | 
						|
 * zlib.output_compression enabled in your php.ini, especially
 | 
						|
 * if you want to send already gzipped data!
 | 
						|
 *
 | 
						|
 * @access   public
 | 
						|
 * @version  $Revision: 304423 $
 | 
						|
 */
 | 
						|
class HTTP_Download
 | 
						|
{
 | 
						|
    // {{{ protected member variables
 | 
						|
    /**
 | 
						|
     * Path to file for download
 | 
						|
     *
 | 
						|
     * @see     HTTP_Download::setFile()
 | 
						|
     * @access  protected
 | 
						|
     * @var     string
 | 
						|
     */
 | 
						|
    var $file = '';
 | 
						|
 | 
						|
    /**
 | 
						|
     * Data for download
 | 
						|
     *
 | 
						|
     * @see     HTTP_Download::setData()
 | 
						|
     * @access  protected
 | 
						|
     * @var     string
 | 
						|
     */
 | 
						|
    var $data = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Resource handle for download
 | 
						|
     *
 | 
						|
     * @see     HTTP_Download::setResource()
 | 
						|
     * @access  protected
 | 
						|
     * @var     int
 | 
						|
     */
 | 
						|
    var $handle = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether to gzip the download
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     bool
 | 
						|
     */
 | 
						|
    var $gzip = false;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether to allow caching of the download on the clients side
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     bool
 | 
						|
     */
 | 
						|
    var $cache = true;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Size of download
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     int
 | 
						|
     */
 | 
						|
    var $size = 0;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Last modified
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     int
 | 
						|
     */
 | 
						|
    var $lastModified = 0;
 | 
						|
 | 
						|
    /**
 | 
						|
     * HTTP headers
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     array
 | 
						|
     */
 | 
						|
    var $headers   = array(
 | 
						|
        'Content-Type'  => 'application/x-octetstream',
 | 
						|
        'Pragma'        => 'cache',
 | 
						|
        'Cache-Control' => 'public, must-revalidate, max-age=0',
 | 
						|
        'Accept-Ranges' => 'bytes',
 | 
						|
        //'X-Sent-By'     => 'PEAR::HTTP::Download'
 | 
						|
    );
 | 
						|
 | 
						|
    /**
 | 
						|
     * HTTP_Header
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     object
 | 
						|
     */
 | 
						|
    var $HTTP = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * ETag
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     string
 | 
						|
     */
 | 
						|
    var $etag = '';
 | 
						|
 | 
						|
    /**
 | 
						|
     * Buffer Size
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     int
 | 
						|
     */
 | 
						|
    var $bufferSize = 2097152;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Throttle Delay
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @var     float
 | 
						|
     */
 | 
						|
    var $throttleDelay = 0;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sent Bytes
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @var     int
 | 
						|
     */
 | 
						|
    var $sentBytes = 0;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Startup error
 | 
						|
     *
 | 
						|
     * @var    PEAR_Error
 | 
						|
     * @access protected
 | 
						|
     */
 | 
						|
    var $_error = null;
 | 
						|
    // }}}
 | 
						|
 | 
						|
    // {{{ constructor
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     *
 | 
						|
     * Set supplied parameters.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @param   array   $params     associative array of parameters
 | 
						|
     *  <strong>one of:</strong>
 | 
						|
     *  <ul>
 | 
						|
     *    <li>'file'               => path to file for download</li>
 | 
						|
     *    <li>'data'               => raw data for download</li>
 | 
						|
     *    <li>'resource'           => resource handle for download</li>
 | 
						|
     *  </ul>
 | 
						|
     *  <strong>and any of:</strong>
 | 
						|
     *  <ul>
 | 
						|
     *    <li>'cache'              => whether to allow cs caching</li>
 | 
						|
     *    <li>'gzip'               => whether to gzip the download</li>
 | 
						|
     *    <li>'lastmodified'       => unix timestamp</li>
 | 
						|
     *    <li>'contenttype'        => content type of download</li>
 | 
						|
     *    <li>'contentdisposition' => content disposition</li>
 | 
						|
     *    <li>'buffersize'         => amount of bytes to buffer</li>
 | 
						|
     *    <li>'throttledelay'      => amount of secs to sleep</li>
 | 
						|
     *    <li>'cachecontrol'       => cache privacy and validity</li>
 | 
						|
     *  </ul>
 | 
						|
     *
 | 
						|
     * 'Content-Disposition' is not HTTP compliant, but most browsers
 | 
						|
     * follow this header, so it was borrowed from MIME standard.
 | 
						|
     *
 | 
						|
     * It looks like this:
 | 
						|
     * "Content-Disposition: attachment; filename=example.tgz".
 | 
						|
     *
 | 
						|
     * @see HTTP_Download::setContentDisposition()
 | 
						|
     */
 | 
						|
    function __construct($params = array())
 | 
						|
    {
 | 
						|
        $this->HTTP = new HTTP_Header;
 | 
						|
        $this->_error = $this->setParams($params);
 | 
						|
    }
 | 
						|
    // }}}
 | 
						|
 | 
						|
    // {{{ public methods
 | 
						|
    /**
 | 
						|
     * Set parameters
 | 
						|
     *
 | 
						|
     * Set supplied parameters through its accessor methods.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   array   $params     associative array of parameters
 | 
						|
     *
 | 
						|
     * @see     HTTP_Download::HTTP_Download()
 | 
						|
     */
 | 
						|
    function setParams($params)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        foreach((array) $params as $param => $value){
 | 
						|
            $method = 'set'. $param;
 | 
						|
 | 
						|
            if (!method_exists($this, $method)) {
 | 
						|
                return PEAR::raiseError(
 | 
						|
                    "Method '$method' doesn't exist.",
 | 
						|
                    HTTP_DOWNLOAD_E_INVALID_PARAM
 | 
						|
                );
 | 
						|
            }
 | 
						|
 | 
						|
            $e = call_user_func_array(array(&$this, $method), (array) $value);
 | 
						|
 | 
						|
            if (PEAR::isError($e)) {
 | 
						|
                return $e;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set path to file for download
 | 
						|
     *
 | 
						|
     * The Last-Modified header will be set to files filemtime(), actually.
 | 
						|
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist.
 | 
						|
     * Sends HTTP 404 or 403 status if $send_error is set to true.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   string  $file       path to file for download
 | 
						|
     * @param   bool    $send_error whether to send HTTP/404 or 403 if
 | 
						|
     *                              the file wasn't found or is not readable
 | 
						|
     */
 | 
						|
    function setFile($file, $send_error = true)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        $file = realpath($file);
 | 
						|
        if (!is_file($file)) {
 | 
						|
            if ($send_error) {
 | 
						|
                $this->HTTP->sendStatusCode(404);
 | 
						|
            }
 | 
						|
            return PEAR::raiseError(
 | 
						|
                "File '$file' not found.",
 | 
						|
                HTTP_DOWNLOAD_E_INVALID_FILE
 | 
						|
            );
 | 
						|
        }
 | 
						|
        if (!is_readable($file)) {
 | 
						|
            if ($send_error) {
 | 
						|
                $this->HTTP->sendStatusCode(403);
 | 
						|
            }
 | 
						|
            return PEAR::raiseError(
 | 
						|
                "Cannot read file '$file'.",
 | 
						|
                HTTP_DOWNLOAD_E_INVALID_FILE
 | 
						|
            );
 | 
						|
        }
 | 
						|
        $this->setLastModified(filemtime($file));
 | 
						|
        $this->file = $file;
 | 
						|
        $this->size = filesize($file);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set data for download
 | 
						|
     *
 | 
						|
     * Set $data to null if you want to unset this.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   $data   raw data to send
 | 
						|
     */
 | 
						|
    function setData($data = null)
 | 
						|
    {
 | 
						|
        $this->data = $data;
 | 
						|
        $this->size = strlen($data);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set resource for download
 | 
						|
     *
 | 
						|
     * The resource handle supplied will be closed after sending the download.
 | 
						|
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle
 | 
						|
     * is no valid resource. Set $handle to null if you want to unset this.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   int     $handle     resource handle
 | 
						|
     */
 | 
						|
    function setResource($handle = null)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if (!isset($handle)) {
 | 
						|
            $this->handle = null;
 | 
						|
            $this->size = 0;
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        if (is_resource($handle)) {
 | 
						|
            $this->handle = $handle;
 | 
						|
            $filestats    = fstat($handle);
 | 
						|
            $this->size   = isset($filestats['size']) ? $filestats['size']
 | 
						|
                                                      : -1;
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        return PEAR::raiseError(
 | 
						|
            "Handle '$handle' is no valid resource.",
 | 
						|
            HTTP_DOWNLOAD_E_INVALID_RESOURCE
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether to gzip the download
 | 
						|
     *
 | 
						|
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB)
 | 
						|
     * if ext/zlib is not available/loadable.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   bool    $gzip   whether to gzip the download
 | 
						|
     */
 | 
						|
    function setGzip($gzip = false)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if ($gzip && !PEAR::loadExtension('zlib')){
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'GZIP compression (ext/zlib) not available.',
 | 
						|
                HTTP_DOWNLOAD_E_NO_EXT_ZLIB
 | 
						|
            );
 | 
						|
        }
 | 
						|
        $this->gzip = (bool) $gzip;
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether to allow caching
 | 
						|
     *
 | 
						|
     * If set to true (default) we'll send some headers that are commonly
 | 
						|
     * used for caching purposes like ETag, Cache-Control and Last-Modified.
 | 
						|
     *
 | 
						|
     * If caching is disabled, we'll send the download no matter if it
 | 
						|
     * would actually be cached at the client side.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   bool    $cache  whether to allow caching
 | 
						|
     */
 | 
						|
    function setCache($cache = true)
 | 
						|
    {
 | 
						|
        $this->cache = (bool) $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether to allow proxies to cache
 | 
						|
     *
 | 
						|
     * If set to 'private' proxies shouldn't cache the response.
 | 
						|
     * This setting defaults to 'public' and affects only cached responses.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  bool
 | 
						|
     * @param   string  $cache  private or public
 | 
						|
     * @param   int     $maxage maximum age of the client cache entry
 | 
						|
     */
 | 
						|
    function setCacheControl($cache = 'public', $maxage = 0)
 | 
						|
    {
 | 
						|
        switch ($cache = strToLower($cache))
 | 
						|
        {
 | 
						|
            case 'private':
 | 
						|
            case 'public':
 | 
						|
                $this->headers['Cache-Control'] =
 | 
						|
                    $cache .', must-revalidate, max-age='. abs($maxage);
 | 
						|
                return true;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set ETag
 | 
						|
     *
 | 
						|
     * Sets a user-defined ETag for cache-validation.  The ETag is usually
 | 
						|
     * generated by HTTP_Download through its payload information.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   string  $etag Entity tag used for strong cache validation.
 | 
						|
     */
 | 
						|
    function setETag($etag = null)
 | 
						|
    {
 | 
						|
        $this->etag = (string) $etag;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set Size of Buffer
 | 
						|
     *
 | 
						|
     * The amount of bytes specified as buffer size is the maximum amount
 | 
						|
     * of data read at once from resources or files.  The default size is 2M
 | 
						|
     * (2097152 bytes).  Be aware that if you enable gzip compression and
 | 
						|
     * you set a very low buffer size that the actual file size may grow
 | 
						|
     * due to added gzip headers for each sent chunk of the specified size.
 | 
						|
     *
 | 
						|
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not
 | 
						|
     * greater than 0 bytes.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   int     $bytes Amount of bytes to use as buffer.
 | 
						|
     */
 | 
						|
    function setBufferSize($bytes = 2097152)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if (0 >= $bytes) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'Buffer size must be greater than 0 bytes ('. $bytes .' given)',
 | 
						|
                HTTP_DOWNLOAD_E_INVALID_PARAM);
 | 
						|
        }
 | 
						|
        $this->bufferSize = abs($bytes);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set Throttle Delay
 | 
						|
     *
 | 
						|
     * Set the amount of seconds to sleep after each chunck that has been
 | 
						|
     * sent.  One can implement some sort of throttle through adjusting the
 | 
						|
     * buffer size and the throttle delay.  With the following settings
 | 
						|
     * HTTP_Download will sleep a second after each 25 K of data sent.
 | 
						|
     *
 | 
						|
     * <code>
 | 
						|
     *  Array(
 | 
						|
     *      'throttledelay' => 1,
 | 
						|
     *      'buffersize'    => 1024 * 25,
 | 
						|
     *  )
 | 
						|
     * </code>
 | 
						|
     *
 | 
						|
     * Just be aware that if gzipp'ing is enabled, decreasing the chunk size
 | 
						|
     * too much leads to proportionally increased network traffic due to added
 | 
						|
     * gzip header and bottom bytes around each chunk.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   float   $seconds    Amount of seconds to sleep after each
 | 
						|
     *                              chunk that has been sent.
 | 
						|
     */
 | 
						|
    function setThrottleDelay($seconds = 0)
 | 
						|
    {
 | 
						|
        $this->throttleDelay = abs($seconds) * 1000;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set "Last-Modified"
 | 
						|
     *
 | 
						|
     * This is usually determined by filemtime() in HTTP_Download::setFile()
 | 
						|
     * If you set raw data for download with HTTP_Download::setData() and you
 | 
						|
     * want do send an appropiate "Last-Modified" header, you should call this
 | 
						|
     * method.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   int     unix timestamp
 | 
						|
     */
 | 
						|
    function setLastModified($last_modified)
 | 
						|
    {
 | 
						|
        $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set Content-Disposition header
 | 
						|
     *
 | 
						|
     * @see HTTP_Download::HTTP_Download
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  void
 | 
						|
     * @param   string  $disposition    whether to send the download
 | 
						|
     *                                  inline or as attachment
 | 
						|
     * @param   string  $file_name      the filename to display in
 | 
						|
     *                                  the browser's download window
 | 
						|
     *
 | 
						|
     * <b>Example:</b>
 | 
						|
     * <code>
 | 
						|
     * $HTTP_Download->setContentDisposition(
 | 
						|
     *   HTTP_DOWNLOAD_ATTACHMENT,
 | 
						|
     *   'download.tgz'
 | 
						|
     * );
 | 
						|
     * </code>
 | 
						|
     */
 | 
						|
    function setContentDisposition( $disposition    = HTTP_DOWNLOAD_ATTACHMENT,
 | 
						|
                                    $file_name      = null)
 | 
						|
    {
 | 
						|
        $cd = $disposition;
 | 
						|
        if (isset($file_name)) {
 | 
						|
            $cd .= '; filename="' . $file_name . '"';
 | 
						|
        } elseif ($this->file) {
 | 
						|
            $cd .= '; filename="' . basename($this->file) . '"';
 | 
						|
        }
 | 
						|
        $this->headers['Content-Disposition'] = $cd;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set content type of the download
 | 
						|
     *
 | 
						|
     * Default content type of the download will be 'application/x-octetstream'.
 | 
						|
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if
 | 
						|
     * $content_type doesn't seem to be valid.
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   string  $content_type   content type of file for download
 | 
						|
     */
 | 
						|
    function setContentType($content_type = 'application/x-octetstream')
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                "Invalid content type '$content_type' supplied.",
 | 
						|
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
 | 
						|
            );
 | 
						|
        }
 | 
						|
        $this->headers['Content-Type'] = $content_type;
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Guess content type of file
 | 
						|
     *
 | 
						|
     * First we try to use PEAR::MIME_Type, if installed, to detect the content
 | 
						|
     * type, else we check if ext/mime_magic is loaded and properly configured.
 | 
						|
     *
 | 
						|
     * Returns PEAR_Error if:
 | 
						|
     *      o if PEAR::MIME_Type failed to detect a proper content type
 | 
						|
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
 | 
						|
     *      o ext/magic.mime is not installed, or not properly configured
 | 
						|
     *        (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC)
 | 
						|
     *      o mime_content_type() couldn't guess content type or returned
 | 
						|
     *        a content type considered to be bogus by setContentType()
 | 
						|
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     */
 | 
						|
    function guessContentType()
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') {
 | 
						|
            if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) {
 | 
						|
                return PEAR::raiseError($mime_type->getMessage(),
 | 
						|
                    HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE);
 | 
						|
            }
 | 
						|
            return $this->setContentType($mime_type);
 | 
						|
        }
 | 
						|
        if (!function_exists('mime_content_type')) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'This feature requires ext/mime_magic!',
 | 
						|
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
 | 
						|
            );
 | 
						|
        }
 | 
						|
        if (!is_file(ini_get('mime_magic.magicfile'))) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'ext/mime_magic is loaded but not properly configured!',
 | 
						|
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
 | 
						|
            );
 | 
						|
        }
 | 
						|
        if (!$content_type = @mime_content_type($this->file)) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'Couldn\'t guess content type with mime_content_type().',
 | 
						|
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
 | 
						|
            );
 | 
						|
        }
 | 
						|
        return $this->setContentType($content_type);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send
 | 
						|
     *
 | 
						|
     * Returns PEAR_Error if:
 | 
						|
     *   o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT)
 | 
						|
     *   o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST)
 | 
						|
     *
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   bool    $autoSetContentDisposition Whether to set the
 | 
						|
     *                  Content-Disposition header if it isn't already.
 | 
						|
     */
 | 
						|
    function send($autoSetContentDisposition = true)
 | 
						|
    {
 | 
						|
        $error = $this->_getError();
 | 
						|
        if ($error !== null) {
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        if (headers_sent()) {
 | 
						|
            return PEAR::raiseError(
 | 
						|
                'Headers already sent.',
 | 
						|
                HTTP_DOWNLOAD_E_HEADERS_SENT
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        if (!ini_get('safe_mode')) {
 | 
						|
            @set_time_limit(0);
 | 
						|
        }
 | 
						|
 | 
						|
        if ($autoSetContentDisposition &&
 | 
						|
            !isset($this->headers['Content-Disposition'])) {
 | 
						|
            $this->setContentDisposition();
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->cache) {
 | 
						|
            $this->headers['ETag'] = $this->generateETag();
 | 
						|
            if ($this->isCached()) {
 | 
						|
                $this->HTTP->sendStatusCode(304);
 | 
						|
                $this->sendHeaders();
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            unset($this->headers['Last-Modified']);
 | 
						|
        }
 | 
						|
 | 
						|
        if (ob_get_level()) {
 | 
						|
            while (@ob_end_clean());
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->gzip) {
 | 
						|
            @ob_start('ob_gzhandler');
 | 
						|
        } else {
 | 
						|
            ob_start();
 | 
						|
        }
 | 
						|
 | 
						|
        $this->sentBytes = 0;
 | 
						|
 | 
						|
        // Known content length?
 | 
						|
        $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*';
 | 
						|
 | 
						|
        if ($end != '*' && $this->isRangeRequest()) {
 | 
						|
             $chunks = $this->getChunks();
 | 
						|
            if (empty($chunks)) {
 | 
						|
                $this->HTTP->sendStatusCode(200);
 | 
						|
                $chunks = array(array(0, $end));
 | 
						|
 | 
						|
            } elseif (PEAR::isError($chunks)) {
 | 
						|
                ob_end_clean();
 | 
						|
                $this->HTTP->sendStatusCode(416);
 | 
						|
                return $chunks;
 | 
						|
 | 
						|
            } else {
 | 
						|
                $this->HTTP->sendStatusCode(206);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            $this->HTTP->sendStatusCode(200);
 | 
						|
            $chunks = array(array(0, $end));
 | 
						|
            if (!$this->gzip && count(ob_list_handlers()) < 2 && $end != '*') {
 | 
						|
                $this->headers['Content-Length'] = $this->size;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $this->sendChunks($chunks);
 | 
						|
 | 
						|
        ob_end_flush();
 | 
						|
        flush();
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Static send
 | 
						|
     *
 | 
						|
     * @see     HTTP_Download::HTTP_Download()
 | 
						|
     * @see     HTTP_Download::send()
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   array   $params     associative array of parameters
 | 
						|
     * @param   bool    $guess      whether HTTP_Download::guessContentType()
 | 
						|
     *                               should be called
 | 
						|
     */
 | 
						|
    static function staticSend($params, $guess = false)
 | 
						|
    {
 | 
						|
        $d = new HTTP_Download();
 | 
						|
        $e = $d->setParams($params);
 | 
						|
        if (PEAR::isError($e)) {
 | 
						|
            return $e;
 | 
						|
        }
 | 
						|
        if ($guess) {
 | 
						|
            $e = $d->guessContentType();
 | 
						|
            if (PEAR::isError($e)) {
 | 
						|
                return $e;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return $d->send();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send a bunch of files or directories as an archive
 | 
						|
     *
 | 
						|
     * Example:
 | 
						|
     * <code>
 | 
						|
     *  require_once 'HTTP/Download.php';
 | 
						|
     *  HTTP_Download::sendArchive(
 | 
						|
     *      'myArchive.tgz',
 | 
						|
     *      '/var/ftp/pub/mike',
 | 
						|
     *      HTTP_DOWNLOAD_TGZ,
 | 
						|
     *      '',
 | 
						|
     *      '/var/ftp/pub'
 | 
						|
     *  );
 | 
						|
     * </code>
 | 
						|
     *
 | 
						|
     * @see         Archive_Tar::createModify()
 | 
						|
     * @deprecated  use HTTP_Download_Archive::send()
 | 
						|
     * @static
 | 
						|
     * @access  public
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   string  $name       name the sent archive should have
 | 
						|
     * @param   mixed   $files      files/directories
 | 
						|
     * @param   string  $type       archive type
 | 
						|
     * @param   string  $add_path   path that should be prepended to the files
 | 
						|
     * @param   string  $strip_path path that should be stripped from the files
 | 
						|
     */
 | 
						|
    static function sendArchive(   $name,
 | 
						|
                            $files,
 | 
						|
                            $type       = HTTP_DOWNLOAD_TGZ,
 | 
						|
                            $add_path   = '',
 | 
						|
                            $strip_path = '')
 | 
						|
    {
 | 
						|
        require_once 'HTTP/Download/Archive.php';
 | 
						|
        return HTTP_Download_Archive::send($name, $files, $type,
 | 
						|
            $add_path, $strip_path);
 | 
						|
    }
 | 
						|
    // }}}
 | 
						|
 | 
						|
    // {{{ protected methods
 | 
						|
    /**
 | 
						|
     * Generate ETag
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  string
 | 
						|
     */
 | 
						|
    function generateETag()
 | 
						|
    {
 | 
						|
        if (!$this->etag) {
 | 
						|
            if ($this->data) {
 | 
						|
                $md5 = md5($this->data);
 | 
						|
            } else {
 | 
						|
                $mtime = time();
 | 
						|
                $ino   = 0;
 | 
						|
                $size  = mt_rand();
 | 
						|
                extract(is_resource($this->handle) ? fstat($this->handle)
 | 
						|
                                                   : stat($this->file));
 | 
						|
                $md5 = md5($mtime .'='. $ino .'='. $size);
 | 
						|
            }
 | 
						|
            $this->etag = '"' . $md5 . '-' . crc32($md5) . '"';
 | 
						|
        }
 | 
						|
        return $this->etag;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send multiple chunks
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   array   $chunks
 | 
						|
     */
 | 
						|
    function sendChunks($chunks)
 | 
						|
    {
 | 
						|
        if (count($chunks) == 1) {
 | 
						|
            return $this->sendChunk(current($chunks));
 | 
						|
        }
 | 
						|
 | 
						|
        $bound = uniqid('HTTP_DOWNLOAD-', true);
 | 
						|
        $cType = $this->headers['Content-Type'];
 | 
						|
        $this->headers['Content-Type'] =
 | 
						|
            'multipart/byteranges; boundary=' . $bound;
 | 
						|
        $this->sendHeaders();
 | 
						|
        foreach ($chunks as $chunk){
 | 
						|
            $this->sendChunk($chunk, $cType, $bound);
 | 
						|
        }
 | 
						|
        #echo "\r\n--$bound--\r\n";
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send chunk of data
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  mixed   Returns true on success or PEAR_Error on failure.
 | 
						|
     * @param   array   $chunk  start and end offset of the chunk to send
 | 
						|
     * @param   string  $cType  actual content type
 | 
						|
     * @param   string  $bound  boundary for multipart/byteranges
 | 
						|
     */
 | 
						|
    function sendChunk($chunk, $cType = null, $bound = null)
 | 
						|
    {
 | 
						|
        list($offset, $lastbyte) = $chunk;
 | 
						|
        $length = ($lastbyte - $offset) + 1;
 | 
						|
 | 
						|
        $range = $offset . '-' . $lastbyte . '/'
 | 
						|
                 . (($this->size >= 0) ? $this->size : '*');
 | 
						|
 | 
						|
        if (isset($cType, $bound)) {
 | 
						|
            echo    "\r\n--$bound\r\n",
 | 
						|
                    "Content-Type: $cType\r\n",
 | 
						|
                    "Content-Range: bytes $range\r\n\r\n";
 | 
						|
        } else {
 | 
						|
            if ($lastbyte != '*' && $this->isRangeRequest()) {
 | 
						|
                $this->headers['Content-Length'] = $length;
 | 
						|
                $this->headers['Content-Range'] = 'bytes '. $range;
 | 
						|
            }
 | 
						|
            $this->sendHeaders();
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->data) {
 | 
						|
            while (($length -= $this->bufferSize) > 0) {
 | 
						|
                $this->flush(substr($this->data, $offset, $this->bufferSize));
 | 
						|
                $this->throttleDelay and $this->sleep();
 | 
						|
                $offset += $this->bufferSize;
 | 
						|
            }
 | 
						|
            if ($length) {
 | 
						|
                $this->flush(substr($this->data, $offset, $this->bufferSize + $length));
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            if (!is_resource($this->handle)) {
 | 
						|
                $this->handle = fopen($this->file, 'rb');
 | 
						|
            }
 | 
						|
            fseek($this->handle, $offset);
 | 
						|
            if ($lastbyte == '*') {
 | 
						|
                while (!feof($this->handle)) {
 | 
						|
                    $this->flush(fread($this->handle, $this->bufferSize));
 | 
						|
                    $this->throttleDelay and $this->sleep();
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                while (($length -= $this->bufferSize) > 0) {
 | 
						|
                    $this->flush(fread($this->handle, $this->bufferSize));
 | 
						|
                    $this->throttleDelay and $this->sleep();
 | 
						|
                }
 | 
						|
                if ($length) {
 | 
						|
                    $this->flush(fread($this->handle, $this->bufferSize + $length));
 | 
						|
                }
 | 
						|
             }
 | 
						|
         }
 | 
						|
         return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get chunks to send
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  array Chunk list or PEAR_Error on invalid range request
 | 
						|
     * @link    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
 | 
						|
     */
 | 
						|
    function getChunks()
 | 
						|
    {
 | 
						|
        $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*';
 | 
						|
 | 
						|
        // Trying to handle ranges on content with unknown length is too
 | 
						|
        // big of a mess (impossible to determine if a range is valid)
 | 
						|
        if ($end == '*') {
 | 
						|
            return array();
 | 
						|
        }
 | 
						|
 | 
						|
        $ranges = $this->getRanges();
 | 
						|
        if (empty($ranges)) {
 | 
						|
            return array();
 | 
						|
        }
 | 
						|
 | 
						|
        $parts = array();
 | 
						|
        $satisfiable = false;
 | 
						|
        foreach (explode(',', $ranges) as $chunk){
 | 
						|
            list($o, $e) = explode('-', trim($chunk));
 | 
						|
 | 
						|
            // If the last-byte-pos value is present, it MUST be greater than
 | 
						|
            // or equal to the first-byte-pos in that byte-range-spec, or the
 | 
						|
            // byte- range-spec is syntactically invalid. The recipient of a
 | 
						|
            // byte-range- set that includes one or more syntactically invalid
 | 
						|
            // byte-range-spec values MUST ignore the header field that
 | 
						|
            // includes that byte-range- set.
 | 
						|
            if ($e !== '' && $o !== '' && $e < $o) {
 | 
						|
                return array();
 | 
						|
            }
 | 
						|
 | 
						|
            // If the last-byte-pos value is absent, or if the value is
 | 
						|
            // greater than or equal to the current length of the entity-body,
 | 
						|
            // last-byte-pos is taken to be equal to one less than the current
 | 
						|
            // length of the entity- body in bytes.
 | 
						|
            if ($e === '' || $e > $end) {
 | 
						|
                $e = $end;
 | 
						|
            }
 | 
						|
 | 
						|
            // A suffix-byte-range-spec is used to specify the suffix of the
 | 
						|
            // entity-body, of a length given by the suffix-length value. (That
 | 
						|
            // is, this form specifies the last N bytes of an entity-body.) If
 | 
						|
            // the entity is shorter than the specified suffix-length, the
 | 
						|
            // entire entity-body is used.
 | 
						|
            if ($o === '') {
 | 
						|
                // If a syntactically valid byte-range-set includes at least
 | 
						|
                // one suffix-byte-range-spec with a non-zero suffix-length,
 | 
						|
                // then the byte-range-set is satisfiable.
 | 
						|
                $satisfiable |= ($e != 0);
 | 
						|
 | 
						|
                $o = max($this->size - $e, 0);
 | 
						|
                $e = $end;
 | 
						|
 | 
						|
            } elseif ($o <= $end) {
 | 
						|
                // If a syntactically valid byte-range-set includes at least
 | 
						|
                // one byte- range-spec whose first-byte-pos is less than the
 | 
						|
                // current length of the entity-body, then the byte-range-set
 | 
						|
                // is satisfiable.
 | 
						|
                $satisfiable = true;
 | 
						|
            } else {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            $parts[] = array($o, $e);
 | 
						|
        }
 | 
						|
 | 
						|
        // If the byte-range-set is unsatisfiable, the server SHOULD return a
 | 
						|
        // response with a status of 416 (Requested range not satisfiable).
 | 
						|
        if (!$satisfiable) {
 | 
						|
            $error = PEAR::raiseError('Error processing range request',
 | 
						|
                                      HTTP_DOWNLOAD_E_INVALID_REQUEST);
 | 
						|
            return $error;
 | 
						|
        }
 | 
						|
        //$this->sortChunks($parts);
 | 
						|
        return $this->mergeChunks($parts);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sorts the ranges to be in ascending order
 | 
						|
     *
 | 
						|
     * @param array &$chunks ranges to sort
 | 
						|
     *
 | 
						|
     * @return void
 | 
						|
     * @access protected
 | 
						|
     * @static
 | 
						|
     * @author Philippe Jausions <jausions@php.net>
 | 
						|
     */
 | 
						|
    static function sortChunks(&$chunks)
 | 
						|
    {
 | 
						|
        $sortFunc = create_function('$a,$b',
 | 
						|
            'if ($a[0] == $b[0]) {
 | 
						|
                if ($a[1] == $b[1]) {
 | 
						|
                    return 0;
 | 
						|
                }
 | 
						|
                return (($a[1] != "*" && $a[1] < $b[1])
 | 
						|
                        || $b[1] == "*") ? -1 : 1;
 | 
						|
             }
 | 
						|
 | 
						|
             return ($a[0] < $b[0]) ? -1 : 1;');
 | 
						|
 | 
						|
        usort($chunks, $sortFunc);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Merges consecutive chunks to avoid overlaps
 | 
						|
     *
 | 
						|
     * @param array $chunks Ranges to merge
 | 
						|
     *
 | 
						|
     * @return array merged ranges
 | 
						|
     * @access protected
 | 
						|
     * @static
 | 
						|
     * @author Philippe Jausions <jausions@php.net>
 | 
						|
     */
 | 
						|
    static function mergeChunks($chunks)
 | 
						|
    {
 | 
						|
        do {
 | 
						|
            $count = count($chunks);
 | 
						|
            $merged = array(current($chunks));
 | 
						|
            $j = 0;
 | 
						|
            for ($i = 1; $i < count($chunks); ++$i) {
 | 
						|
                list($o, $e) = $chunks[$i];
 | 
						|
                if ($merged[$j][1] == '*') {
 | 
						|
                    if ($merged[$j][0] <= $o) {
 | 
						|
                        continue;
 | 
						|
                    } elseif ($e == '*' || $merged[$j][0] <= $e) {
 | 
						|
                        $merged[$j][0] = min($merged[$j][0], $o);
 | 
						|
                    } else {
 | 
						|
                        $merged[++$j] = $chunks[$i];
 | 
						|
                    }
 | 
						|
                } elseif ($merged[$j][0] <= $o && $o <= $merged[$j][1]) {
 | 
						|
                    $merged[$j][1] = ($e == '*') ? '*' : max($e, $merged[$j][1]);
 | 
						|
                } elseif ($merged[$j][0] <= $e && $e <= $merged[$j][1]) {
 | 
						|
                    $merged[$j][0] = min($o, $merged[$j][0]);
 | 
						|
                } else {
 | 
						|
                    $merged[++$j] = $chunks[$i];
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if ($count == count($merged)) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            $chunks = $merged;
 | 
						|
        } while (true);
 | 
						|
        return $merged;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if range is requested
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  bool
 | 
						|
     */
 | 
						|
    function isRangeRequest()
 | 
						|
    {
 | 
						|
        if (!isset($_SERVER['HTTP_RANGE']) || $this->getRanges() == '' ) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        return $this->isValidRange();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get range request
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  array
 | 
						|
     */
 | 
						|
    function getRanges()
 | 
						|
    {
 | 
						|
        return preg_match('/^bytes=((\d+-|\d+-\d+|-\d+)(, ?(\d+-|\d+-\d+|-\d+))*)$/',
 | 
						|
            @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if entity is cached
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  bool
 | 
						|
     */
 | 
						|
    function isCached()
 | 
						|
    {
 | 
						|
        return (
 | 
						|
            (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
 | 
						|
            $this->lastModified == strtotime(current($a = explode(
 | 
						|
                ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) ||
 | 
						|
            (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
 | 
						|
            $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag))
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if entity hasn't changed
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  bool
 | 
						|
     */
 | 
						|
    function isValidRange()
 | 
						|
    {
 | 
						|
        if (isset($_SERVER['HTTP_IF_MATCH']) &&
 | 
						|
            !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        if (isset($_SERVER['HTTP_IF_RANGE']) &&
 | 
						|
                  $_SERVER['HTTP_IF_RANGE'] !== $this->etag &&
 | 
						|
                  strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
 | 
						|
            $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']));
 | 
						|
            if (strtotime($lm) !== $this->lastModified) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) {
 | 
						|
            $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE']));
 | 
						|
            if (strtotime($lm) !== $this->lastModified) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Compare against an asterisk or check for equality
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  bool
 | 
						|
     * @param   string  key for the $_SERVER array
 | 
						|
     * @param   string  string to compare
 | 
						|
     */
 | 
						|
    function compareAsterisk($svar, $compare)
 | 
						|
    {
 | 
						|
        foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) {
 | 
						|
            if ($request === '*' || $request === $compare) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send HTTP headers
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  void
 | 
						|
     */
 | 
						|
    function sendHeaders()
 | 
						|
    {
 | 
						|
        foreach ($this->headers as $header => $value) {
 | 
						|
            $this->HTTP->setHeader($header, $value);
 | 
						|
        }
 | 
						|
        $this->HTTP->sendHeaders();
 | 
						|
        /* NSAPI won't output anything if we did this */
 | 
						|
        if (strncasecmp(PHP_SAPI, 'nsapi', 5)) {
 | 
						|
            if (ob_get_level()) {
 | 
						|
                ob_flush();
 | 
						|
            }
 | 
						|
            flush();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Flush
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  void
 | 
						|
     * @param   string  $data
 | 
						|
     */
 | 
						|
    function flush($data = '')
 | 
						|
    {
 | 
						|
        if ($dlen = strlen($data)) {
 | 
						|
            $this->sentBytes += $dlen;
 | 
						|
            echo $data;
 | 
						|
        }
 | 
						|
        ob_flush();
 | 
						|
        flush();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sleep
 | 
						|
     *
 | 
						|
     * @access  protected
 | 
						|
     * @return  void
 | 
						|
     */
 | 
						|
    function sleep()
 | 
						|
    {
 | 
						|
        if (OS_WINDOWS) {
 | 
						|
            com_message_pump($this->throttleDelay);
 | 
						|
        } else {
 | 
						|
            usleep($this->throttleDelay * 1000);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns and clears startup error
 | 
						|
     *
 | 
						|
     * @return NULL|PEAR_Error startup error if one exists
 | 
						|
     * @access protected
 | 
						|
     */
 | 
						|
    function _getError()
 | 
						|
    {
 | 
						|
        $error = null;
 | 
						|
        if (PEAR::isError($this->_error)) {
 | 
						|
            $error = $this->_error;
 | 
						|
            $this->_error = null;
 | 
						|
        }
 | 
						|
        return $error;
 | 
						|
    }
 | 
						|
    // }}}
 | 
						|
}
 | 
						|
?>
 |