home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / HTTP / Download.php next >
Encoding:
PHP Script  |  2008-07-02  |  29.5 KB  |  1,037 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3.  
  4. /**
  5.  * HTTP::Download
  6.  * 
  7.  * PHP versions 4 and 5
  8.  *
  9.  * @category   HTTP
  10.  * @package    HTTP_Download
  11.  * @author     Michael Wallner <mike@php.net>
  12.  * @copyright  2003-2005 Michael Wallner
  13.  * @license    BSD, revised
  14.  * @version    CVS: $Id: Download.php,v 1.79 2007/10/05 07:02:03 mike Exp $
  15.  * @link       http://pear.php.net/package/HTTP_Download
  16.  */
  17.  
  18. // {{{ includes
  19. /**
  20.  * Requires PEAR
  21.  */
  22. require_once 'PEAR.php';
  23.  
  24. /**
  25.  * Requires HTTP_Header
  26.  */
  27. require_once 'HTTP/Header.php';
  28. // }}}
  29.  
  30. // {{{ constants
  31. /**#@+ Use with HTTP_Download::setContentDisposition() **/
  32. /**
  33.  * Send data as attachment
  34.  */
  35. define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment');
  36. /**
  37.  * Send data inline
  38.  */
  39. define('HTTP_DOWNLOAD_INLINE', 'inline');
  40. /**#@-**/
  41.  
  42. /**#@+ Use with HTTP_Download::sendArchive() **/
  43. /**
  44.  * Send as uncompressed tar archive
  45.  */
  46. define('HTTP_DOWNLOAD_TAR', 'TAR');
  47. /**
  48.  * Send as gzipped tar archive
  49.  */
  50. define('HTTP_DOWNLOAD_TGZ', 'TGZ');
  51. /**
  52.  * Send as bzip2 compressed tar archive
  53.  */
  54. define('HTTP_DOWNLOAD_BZ2', 'BZ2');
  55. /**
  56.  * Send as zip archive
  57.  */
  58. define('HTTP_DOWNLOAD_ZIP', 'ZIP');
  59. /**#@-**/
  60.  
  61. /**#@+
  62.  * Error constants
  63.  */
  64. define('HTTP_DOWNLOAD_E_HEADERS_SENT',          -1);
  65. define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB',           -2);
  66. define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC',         -3);
  67. define('HTTP_DOWNLOAD_E_INVALID_FILE',          -4);
  68. define('HTTP_DOWNLOAD_E_INVALID_PARAM',         -5);
  69. define('HTTP_DOWNLOAD_E_INVALID_RESOURCE',      -6);
  70. define('HTTP_DOWNLOAD_E_INVALID_REQUEST',       -7);
  71. define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE',  -8);
  72. define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE',  -9);
  73. /**#@-**/
  74. // }}}
  75.  
  76. /** 
  77.  * Send HTTP Downloads/Responses.
  78.  *
  79.  * With this package you can handle (hidden) downloads.
  80.  * It supports partial downloads, resuming and sending 
  81.  * raw data ie. from database BLOBs.
  82.  * 
  83.  * <i>ATTENTION:</i>
  84.  * You shouldn't use this package together with ob_gzhandler or 
  85.  * zlib.output_compression enabled in your php.ini, especially 
  86.  * if you want to send already gzipped data!
  87.  * 
  88.  * @access   public
  89.  * @version  $Revision: 1.79 $
  90.  */
  91. class HTTP_Download
  92. {
  93.     // {{{ protected member variables
  94.     /**
  95.      * Path to file for download
  96.      *
  97.      * @see     HTTP_Download::setFile()
  98.      * @access  protected
  99.      * @var     string
  100.      */
  101.     var $file = '';
  102.     
  103.     /**
  104.      * Data for download
  105.      *
  106.      * @see     HTTP_Download::setData()
  107.      * @access  protected
  108.      * @var     string
  109.      */
  110.     var $data = null;
  111.     
  112.     /**
  113.      * Resource handle for download
  114.      *
  115.      * @see     HTTP_Download::setResource()
  116.      * @access  protected
  117.      * @var     int
  118.      */
  119.     var $handle = null;
  120.     
  121.     /**
  122.      * Whether to gzip the download
  123.      *
  124.      * @access  protected
  125.      * @var     bool
  126.      */
  127.     var $gzip = false;
  128.     
  129.     /**
  130.      * Whether to allow caching of the download on the clients side
  131.      * 
  132.      * @access  protected
  133.      * @var     bool
  134.      */
  135.     var $cache = true;
  136.     
  137.     /**
  138.      * Size of download
  139.      *
  140.      * @access  protected
  141.      * @var     int
  142.      */
  143.     var $size = 0;
  144.     
  145.     /**
  146.      * Last modified
  147.      *
  148.      * @access  protected
  149.      * @var     int
  150.      */
  151.     var $lastModified = 0;
  152.     
  153.     /**
  154.      * HTTP headers
  155.      *
  156.      * @access  protected
  157.      * @var     array
  158.      */
  159.     var $headers   = array(
  160.         'Content-Type'  => 'application/x-octetstream',
  161.         'Pragma'        => 'cache',
  162.         'Cache-Control' => 'public, must-revalidate, max-age=0',
  163.         'Accept-Ranges' => 'bytes',
  164.         'X-Sent-By'     => 'PEAR::HTTP::Download'
  165.     );
  166.  
  167.     /**
  168.      * HTTP_Header
  169.      * 
  170.      * @access  protected
  171.      * @var     object
  172.      */
  173.     var $HTTP = null;
  174.     
  175.     /**
  176.      * ETag
  177.      * 
  178.      * @access  protected
  179.      * @var     string
  180.      */
  181.     var $etag = '';
  182.     
  183.     /**
  184.      * Buffer Size
  185.      * 
  186.      * @access  protected
  187.      * @var     int
  188.      */
  189.     var $bufferSize = 2097152;
  190.     
  191.     /**
  192.      * Throttle Delay
  193.      * 
  194.      * @access  protected
  195.      * @var     float
  196.      */
  197.     var $throttleDelay = 0;
  198.     
  199.     /**
  200.      * Sent Bytes
  201.      * 
  202.      * @access  public
  203.      * @var     int
  204.      */
  205.     var $sentBytes = 0;
  206.     // }}}
  207.     
  208.     // {{{ constructor
  209.     /**
  210.      * Constructor
  211.      *
  212.      * Set supplied parameters.
  213.      * 
  214.      * @access  public
  215.      * @param   array   $params     associative array of parameters
  216.      * 
  217.      *          <b>one of:</b>
  218.      *                  o 'file'                => path to file for download
  219.      *                  o 'data'                => raw data for download
  220.      *                  o 'resource'            => resource handle for download
  221.      * <br/>
  222.      *          <b>and any of:</b>
  223.      *                  o 'cache'               => whether to allow cs caching
  224.      *                  o 'gzip'                => whether to gzip the download
  225.      *                  o 'lastmodified'        => unix timestamp
  226.      *                  o 'contenttype'         => content type of download
  227.      *                  o 'contentdisposition'  => content disposition
  228.      *                  o 'buffersize'          => amount of bytes to buffer
  229.      *                  o 'throttledelay'       => amount of secs to sleep
  230.      *                  o 'cachecontrol'        => cache privacy and validity
  231.      * 
  232.      * <br />
  233.      * 'Content-Disposition' is not HTTP compliant, but most browsers 
  234.      * follow this header, so it was borrowed from MIME standard.
  235.      * 
  236.      * It looks like this: <br />
  237.      * "Content-Disposition: attachment; filename=example.tgz".
  238.      * 
  239.      * @see HTTP_Download::setContentDisposition()
  240.      */
  241.     function HTTP_Download($params = array())
  242.     {
  243.         $this->HTTP = &new HTTP_Header;
  244.         $this->setParams($params);
  245.     }
  246.     // }}}
  247.     
  248.     // {{{ public methods
  249.     /**
  250.      * Set parameters
  251.      * 
  252.      * Set supplied parameters through its accessor methods.
  253.      *
  254.      * @access  public
  255.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  256.      * @param   array   $params     associative array of parameters
  257.      * 
  258.      * @see     HTTP_Download::HTTP_Download()
  259.      */
  260.     function setParams($params)
  261.     {
  262.         foreach((array) $params as $param => $value){
  263.             $method = 'set'. $param;
  264.             
  265.             if (!method_exists($this, $method)) {
  266.                 return PEAR::raiseError(
  267.                     "Method '$method' doesn't exist.",
  268.                     HTTP_DOWNLOAD_E_INVALID_PARAM
  269.                 );
  270.             }
  271.             
  272.             $e = call_user_func_array(array(&$this, $method), (array) $value);
  273.             
  274.             if (PEAR::isError($e)) {
  275.                 return $e;
  276.             }
  277.         }
  278.         return true;
  279.     }
  280.     
  281.     /**
  282.      * Set path to file for download
  283.      *
  284.      * The Last-Modified header will be set to files filemtime(), actually.
  285.      * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist.
  286.      * Sends HTTP 404 status if $send_404 is set to true.
  287.      * 
  288.      * @access  public
  289.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  290.      * @param   string  $file       path to file for download
  291.      * @param   bool    $send_404   whether to send HTTP/404 if
  292.      *                              the file wasn't found
  293.      */
  294.     function setFile($file, $send_404 = true)
  295.     {
  296.         $file = realpath($file);
  297.         if (!is_file($file)) {
  298.             if ($send_404) {
  299.                 $this->HTTP->sendStatusCode(404);
  300.             }
  301.             return PEAR::raiseError(
  302.                 "File '$file' not found.",
  303.                 HTTP_DOWNLOAD_E_INVALID_FILE
  304.             );
  305.         }
  306.         $this->setLastModified(filemtime($file));
  307.         $this->file = $file;
  308.         $this->size = filesize($file);
  309.         return true;
  310.     }
  311.     
  312.     /**
  313.      * Set data for download
  314.      *
  315.      * Set $data to null if you want to unset this.
  316.      * 
  317.      * @access  public
  318.      * @return  void
  319.      * @param   $data   raw data to send
  320.      */
  321.     function setData($data = null)
  322.     {
  323.         $this->data = $data;
  324.         $this->size = strlen($data);
  325.     }
  326.     
  327.     /**
  328.      * Set resource for download
  329.      *
  330.      * The resource handle supplied will be closed after sending the download.
  331.      * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle 
  332.      * is no valid resource. Set $handle to null if you want to unset this.
  333.      * 
  334.      * @access  public
  335.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  336.      * @param   int     $handle     resource handle
  337.      */
  338.     function setResource($handle = null)
  339.     {
  340.         if (!isset($handle)) {
  341.             $this->handle = null;
  342.             $this->size = 0;
  343.             return true;
  344.         }
  345.         
  346.         if (is_resource($handle)) {
  347.             $this->handle = $handle;
  348.             $filestats    = fstat($handle);
  349.             $this->size   = $filestats['size'];
  350.             return true;
  351.         }
  352.  
  353.         return PEAR::raiseError(
  354.             "Handle '$handle' is no valid resource.",
  355.             HTTP_DOWNLOAD_E_INVALID_RESOURCE
  356.         );
  357.     }
  358.     
  359.     /**
  360.      * Whether to gzip the download
  361.      *
  362.      * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB)
  363.      * if ext/zlib is not available/loadable.
  364.      * 
  365.      * @access  public
  366.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  367.      * @param   bool    $gzip   whether to gzip the download
  368.      */
  369.     function setGzip($gzip = false)
  370.     {
  371.         if ($gzip && !PEAR::loadExtension('zlib')){
  372.             return PEAR::raiseError(
  373.                 'GZIP compression (ext/zlib) not available.',
  374.                 HTTP_DOWNLOAD_E_NO_EXT_ZLIB
  375.             );
  376.         }
  377.         $this->gzip = (bool) $gzip;
  378.         return true;
  379.     }
  380.  
  381.     /**
  382.      * Whether to allow caching
  383.      * 
  384.      * If set to true (default) we'll send some headers that are commonly
  385.      * used for caching purposes like ETag, Cache-Control and Last-Modified.
  386.      * 
  387.      * If caching is disabled, we'll send the download no matter if it
  388.      * would actually be cached at the client side.
  389.      *
  390.      * @access  public
  391.      * @return  void
  392.      * @param   bool    $cache  whether to allow caching
  393.      */
  394.     function setCache($cache = true)
  395.     {
  396.         $this->cache = (bool) $cache;
  397.     }
  398.     
  399.     /**
  400.      * Whether to allow proxies to cache
  401.      * 
  402.      * If set to 'private' proxies shouldn't cache the response.
  403.      * This setting defaults to 'public' and affects only cached responses.
  404.      * 
  405.      * @access  public
  406.      * @return  bool
  407.      * @param   string  $cache  private or public
  408.      * @param   int     $maxage maximum age of the client cache entry
  409.      */
  410.     function setCacheControl($cache = 'public', $maxage = 0)
  411.     {
  412.         switch ($cache = strToLower($cache))
  413.         {
  414.             case 'private':
  415.             case 'public':
  416.                 $this->headers['Cache-Control'] = 
  417.                     $cache .', must-revalidate, max-age='. abs($maxage);
  418.                 return true;
  419.             break;
  420.         }
  421.         return false;
  422.     }
  423.     
  424.     /**
  425.      * Set ETag
  426.      * 
  427.      * Sets a user-defined ETag for cache-validation.  The ETag is usually
  428.      * generated by HTTP_Download through its payload information.
  429.      * 
  430.      * @access  public
  431.      * @return  void
  432.      * @param   string  $etag Entity tag used for strong cache validation.
  433.      */
  434.     function setETag($etag = null)
  435.     {
  436.         $this->etag = (string) $etag;
  437.     }
  438.     
  439.     /**
  440.      * Set Size of Buffer
  441.      * 
  442.      * The amount of bytes specified as buffer size is the maximum amount
  443.      * of data read at once from resources or files.  The default size is 2M
  444.      * (2097152 bytes).  Be aware that if you enable gzip compression and
  445.      * you set a very low buffer size that the actual file size may grow
  446.      * due to added gzip headers for each sent chunk of the specified size.
  447.      * 
  448.      * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not
  449.      * greater than 0 bytes.
  450.      * 
  451.      * @access  public
  452.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  453.      * @param   int     $bytes Amount of bytes to use as buffer.
  454.      */
  455.     function setBufferSize($bytes = 2097152)
  456.     {
  457.         if (0 >= $bytes) {
  458.             return PEAR::raiseError(
  459.                 'Buffer size must be greater than 0 bytes ('. $bytes .' given)',
  460.                 HTTP_DOWNLOAD_E_INVALID_PARAM);
  461.         }
  462.         $this->bufferSize = abs($bytes);
  463.         return true;
  464.     }
  465.     
  466.     /**
  467.      * Set Throttle Delay
  468.      * 
  469.      * Set the amount of seconds to sleep after each chunck that has been
  470.      * sent.  One can implement some sort of throttle through adjusting the
  471.      * buffer size and the throttle delay.  With the following settings
  472.      * HTTP_Download will sleep a second after each 25 K of data sent.
  473.      * 
  474.      * <code>
  475.      *  Array(
  476.      *      'throttledelay' => 1,
  477.      *      'buffersize'    => 1024 * 25,
  478.      *  )
  479.      * </code>
  480.      * 
  481.      * Just be aware that if gzipp'ing is enabled, decreasing the chunk size 
  482.      * too much leads to proportionally increased network traffic due to added
  483.      * gzip header and bottom bytes around each chunk.
  484.      * 
  485.      * @access  public
  486.      * @return  void
  487.      * @param   float   $seconds    Amount of seconds to sleep after each 
  488.      *                              chunk that has been sent.
  489.      */
  490.     function setThrottleDelay($seconds = 0)
  491.     {
  492.         $this->throttleDelay = abs($seconds) * 1000;
  493.     }
  494.     
  495.     /**
  496.      * Set "Last-Modified"
  497.      *
  498.      * This is usually determined by filemtime() in HTTP_Download::setFile()
  499.      * If you set raw data for download with HTTP_Download::setData() and you
  500.      * want do send an appropiate "Last-Modified" header, you should call this
  501.      * method.
  502.      * 
  503.      * @access  public
  504.      * @return  void
  505.      * @param   int     unix timestamp
  506.      */
  507.     function setLastModified($last_modified)
  508.     {
  509.         $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified;
  510.     }
  511.     
  512.     /**
  513.      * Set Content-Disposition header
  514.      * 
  515.      * @see HTTP_Download::HTTP_Download
  516.      *
  517.      * @access  public
  518.      * @return  void
  519.      * @param   string  $disposition    whether to send the download
  520.      *                                  inline or as attachment
  521.      * @param   string  $file_name      the filename to display in
  522.      *                                  the browser's download window
  523.      * 
  524.      * <b>Example:</b>
  525.      * <code>
  526.      * $HTTP_Download->setContentDisposition(
  527.      *   HTTP_DOWNLOAD_ATTACHMENT,
  528.      *   'download.tgz'
  529.      * );
  530.      * </code>
  531.      */
  532.     function setContentDisposition( $disposition    = HTTP_DOWNLOAD_ATTACHMENT, 
  533.                                     $file_name      = null)
  534.     {
  535.         $cd = $disposition;
  536.         if (isset($file_name)) {
  537.             $cd .= '; filename="' . $file_name . '"';
  538.         } elseif ($this->file) {
  539.             $cd .= '; filename="' . basename($this->file) . '"';
  540.         }
  541.         $this->headers['Content-Disposition'] = $cd;
  542.     }
  543.     
  544.     /**
  545.      * Set content type of the download
  546.      *
  547.      * Default content type of the download will be 'application/x-octetstream'.
  548.      * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if 
  549.      * $content_type doesn't seem to be valid.
  550.      * 
  551.      * @access  public
  552.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  553.      * @param   string  $content_type   content type of file for download
  554.      */
  555.     function setContentType($content_type = 'application/x-octetstream')
  556.     {
  557.         if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) {
  558.             return PEAR::raiseError(
  559.                 "Invalid content type '$content_type' supplied.",
  560.                 HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
  561.             );
  562.         }
  563.         $this->headers['Content-Type'] = $content_type;
  564.         return true;
  565.     }
  566.     
  567.     /**
  568.      * Guess content type of file
  569.      * 
  570.      * First we try to use PEAR::MIME_Type, if installed, to detect the content 
  571.      * type, else we check if ext/mime_magic is loaded and properly configured.
  572.      *
  573.      * Returns PEAR_Error if:
  574.      *      o if PEAR::MIME_Type failed to detect a proper content type
  575.      *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
  576.      *      o ext/magic.mime is not installed, or not properly configured
  577.      *        (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC)
  578.      *      o mime_content_type() couldn't guess content type or returned
  579.      *        a content type considered to be bogus by setContentType()
  580.      *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
  581.      * 
  582.      * @access  public
  583.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  584.      */
  585.     function guessContentType()
  586.     {
  587.         if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') {
  588.             if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) {
  589.                 return PEAR::raiseError($mime_type->getMessage(),
  590.                     HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE);
  591.             }
  592.             return $this->setContentType($mime_type);
  593.         }
  594.         if (!function_exists('mime_content_type')) {
  595.             return PEAR::raiseError(
  596.                 'This feature requires ext/mime_magic!',
  597.                 HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
  598.             );
  599.         }
  600.         if (!is_file(ini_get('mime_magic.magicfile'))) {
  601.             return PEAR::raiseError(
  602.                 'ext/mime_magic is loaded but not properly configured!',
  603.                 HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
  604.             );
  605.         }
  606.         if (!$content_type = @mime_content_type($this->file)) {
  607.             return PEAR::raiseError(
  608.                 'Couldn\'t guess content type with mime_content_type().',
  609.                 HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
  610.             );
  611.         }
  612.         return $this->setContentType($content_type);
  613.     }
  614.  
  615.     /**
  616.      * Send
  617.      *
  618.      * Returns PEAR_Error if:
  619.      *   o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT)
  620.      *   o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST)
  621.      * 
  622.      * @access  public
  623.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  624.      * @param   bool    $autoSetContentDisposition Whether to set the
  625.      *                  Content-Disposition header if it isn't already.
  626.      */
  627.     function send($autoSetContentDisposition = true)
  628.     {
  629.         if (headers_sent()) {
  630.             return PEAR::raiseError(
  631.                 'Headers already sent.',
  632.                 HTTP_DOWNLOAD_E_HEADERS_SENT
  633.             );
  634.         }
  635.         
  636.         if (!ini_get('safe_mode')) {
  637.             @set_time_limit(0);
  638.         }
  639.         
  640.         if ($autoSetContentDisposition && 
  641.             !isset($this->headers['Content-Disposition'])) {
  642.             $this->setContentDisposition();
  643.         }
  644.         
  645.         if ($this->cache) {
  646.             $this->headers['ETag'] = $this->generateETag();
  647.             if ($this->isCached()) {
  648.                 $this->HTTP->sendStatusCode(304);
  649.                 $this->sendHeaders();
  650.                 return true;
  651.             }
  652.         } else {
  653.             unset($this->headers['Last-Modified']);
  654.         }
  655.         
  656.         if (ob_get_level()) {
  657.             while (@ob_end_clean());
  658.         }
  659.         
  660.         if ($this->gzip) {
  661.             @ob_start('ob_gzhandler');
  662.         } else {
  663.             ob_start();
  664.         }
  665.         
  666.         $this->sentBytes = 0;
  667.         
  668.         if ($this->isRangeRequest()) {
  669.             $this->HTTP->sendStatusCode(206);
  670.             $chunks = $this->getChunks();
  671.         } else {
  672.             $this->HTTP->sendStatusCode(200);
  673.             $chunks = array(array(0, $this->size));
  674.             if (!$this->gzip && count(ob_list_handlers()) < 2) {
  675.                 $this->headers['Content-Length'] = $this->size;
  676.             }
  677.         }
  678.  
  679.         if (PEAR::isError($e = $this->sendChunks($chunks))) {
  680.             ob_end_clean();
  681.             $this->HTTP->sendStatusCode(416);
  682.             return $e;
  683.         }
  684.         
  685.         ob_end_flush();
  686.         flush();
  687.         return true;
  688.     }    
  689.  
  690.     /**
  691.      * Static send
  692.      *
  693.      * @see     HTTP_Download::HTTP_Download()
  694.      * @see     HTTP_Download::send()
  695.      * 
  696.      * @static
  697.      * @access  public
  698.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  699.      * @param   array   $params     associative array of parameters
  700.      * @param   bool    $guess      whether HTTP_Download::guessContentType()
  701.      *                               should be called
  702.      */
  703.     function staticSend($params, $guess = false)
  704.     {
  705.         $d = &new HTTP_Download();
  706.         $e = $d->setParams($params);
  707.         if (PEAR::isError($e)) {
  708.             return $e;
  709.         }
  710.         if ($guess) {
  711.             $e = $d->guessContentType();
  712.             if (PEAR::isError($e)) {
  713.                 return $e;
  714.             }
  715.         }
  716.         return $d->send();
  717.     }
  718.     
  719.     /**
  720.      * Send a bunch of files or directories as an archive
  721.      * 
  722.      * Example:
  723.      * <code>
  724.      *  require_once 'HTTP/Download.php';
  725.      *  HTTP_Download::sendArchive(
  726.      *      'myArchive.tgz',
  727.      *      '/var/ftp/pub/mike',
  728.      *      HTTP_DOWNLOAD_TGZ,
  729.      *      '',
  730.      *      '/var/ftp/pub'
  731.      *  );
  732.      * </code>
  733.      *
  734.      * @see         Archive_Tar::createModify()
  735.      * @deprecated  use HTTP_Download_Archive::send()
  736.      * @static
  737.      * @access  public
  738.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  739.      * @param   string  $name       name the sent archive should have
  740.      * @param   mixed   $files      files/directories
  741.      * @param   string  $type       archive type
  742.      * @param   string  $add_path   path that should be prepended to the files
  743.      * @param   string  $strip_path path that should be stripped from the files
  744.      */
  745.     function sendArchive(   $name, 
  746.                             $files, 
  747.                             $type       = HTTP_DOWNLOAD_TGZ, 
  748.                             $add_path   = '', 
  749.                             $strip_path = '')
  750.     {
  751.         require_once 'HTTP/Download/Archive.php';
  752.         return HTTP_Download_Archive::send($name, $files, $type, 
  753.             $add_path, $strip_path);
  754.     }
  755.     // }}}
  756.     
  757.     // {{{ protected methods
  758.     /** 
  759.      * Generate ETag
  760.      * 
  761.      * @access  protected
  762.      * @return  string
  763.      */
  764.     function generateETag()
  765.     {
  766.         if (!$this->etag) {
  767.             if ($this->data) {
  768.                 $md5 = md5($this->data);
  769.             } else {
  770.                 $fst = is_resource($this->handle) ? 
  771.                     fstat($this->handle) : stat($this->file);
  772.                 $md5 = md5($fst['mtime'] .'='. $fst['ino'] .'='. $fst['size']);
  773.             }
  774.             $this->etag = '"' . $md5 . '-' . crc32($md5) . '"';
  775.         }
  776.         return $this->etag;
  777.     }
  778.     
  779.     /** 
  780.      * Send multiple chunks
  781.      * 
  782.      * @access  protected
  783.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  784.      * @param   array   $chunks
  785.      */
  786.     function sendChunks($chunks)
  787.     {
  788.         if (count($chunks) == 1) {
  789.             return $this->sendChunk(current($chunks));
  790.         }
  791.  
  792.         $bound = uniqid('HTTP_DOWNLOAD-', true);
  793.         $cType = $this->headers['Content-Type'];
  794.         $this->headers['Content-Type'] =
  795.             'multipart/byteranges; boundary=' . $bound;
  796.         $this->sendHeaders();
  797.         foreach ($chunks as $chunk){
  798.             if (PEAR::isError($e = $this->sendChunk($chunk, $cType, $bound))) {
  799.                 return $e;
  800.             }
  801.         }
  802.         #echo "\r\n--$bound--\r\n";
  803.         return true;
  804.     }
  805.     
  806.     /**
  807.      * Send chunk of data
  808.      * 
  809.      * @access  protected
  810.      * @return  mixed   Returns true on success or PEAR_Error on failure.
  811.      * @param   array   $chunk  start and end offset of the chunk to send
  812.      * @param   string  $cType  actual content type
  813.      * @param   string  $bound  boundary for multipart/byteranges
  814.      */
  815.     function sendChunk($chunk, $cType = null, $bound = null)
  816.     {
  817.         list($offset, $lastbyte) = $chunk;
  818.         $length = ($lastbyte - $offset) + 1;
  819.         
  820.         if ($length < 1) {
  821.             return PEAR::raiseError(
  822.                 "Error processing range request: $offset-$lastbyte/$length",
  823.                 HTTP_DOWNLOAD_E_INVALID_REQUEST
  824.             );
  825.         }
  826.         
  827.         $range = $offset . '-' . $lastbyte . '/' . $this->size;
  828.         
  829.         if (isset($cType, $bound)) {
  830.             echo    "\r\n--$bound\r\n",
  831.                     "Content-Type: $cType\r\n",
  832.                     "Content-Range: bytes $range\r\n\r\n";
  833.         } else {
  834.             if ($this->isRangeRequest()) {
  835.                 $this->headers['Content-Length'] = $length;
  836.                 $this->headers['Content-Range'] = 'bytes '. $range;
  837.             }
  838.             $this->sendHeaders();
  839.         }
  840.  
  841.         if ($this->data) {
  842.             while (($length -= $this->bufferSize) > 0) {
  843.                 $this->flush(substr($this->data, $offset, $this->bufferSize));
  844.                 $this->throttleDelay and $this->sleep();
  845.                 $offset += $this->bufferSize;
  846.             }
  847.             if ($length) {
  848.                 $this->flush(substr($this->data, $offset, $this->bufferSize + $length));
  849.             }
  850.         } else {
  851.             if (!is_resource($this->handle)) {
  852.                 $this->handle = fopen($this->file, 'rb');
  853.             }
  854.             fseek($this->handle, $offset);
  855.             while (($length -= $this->bufferSize) > 0) {
  856.                 $this->flush(fread($this->handle, $this->bufferSize));
  857.                 $this->throttleDelay and $this->sleep();
  858.             }
  859.             if ($length) {
  860.                 $this->flush(fread($this->handle, $this->bufferSize + $length));
  861.             }
  862.         }
  863.         return true;
  864.     }
  865.     
  866.     /** 
  867.      * Get chunks to send
  868.      * 
  869.      * @access  protected
  870.      * @return  array
  871.      */
  872.     function getChunks()
  873.     {
  874.         $parts = array();
  875.         foreach (explode(',', $this->getRanges()) as $chunk){
  876.             list($o, $e) = explode('-', $chunk);
  877.             if ($e >= $this->size || (empty($e) && $e !== 0 && $e !== '0')) {
  878.                 $e = $this->size - 1;
  879.             }
  880.             if (empty($o) && $o !== 0 && $o !== '0') {
  881.                 $o = $this->size - $e;
  882.                 $e = $this->size - 1;
  883.             }
  884.             $parts[] = array($o, $e);
  885.         }
  886.         return $parts;
  887.     }
  888.     
  889.     /** 
  890.      * Check if range is requested
  891.      * 
  892.      * @access  protected
  893.      * @return  bool
  894.      */
  895.     function isRangeRequest()
  896.     {
  897.         if (!isset($_SERVER['HTTP_RANGE'])) {
  898.             return false;
  899.         }
  900.         return $this->isValidRange();
  901.     }
  902.     
  903.     /** 
  904.      * Get range request
  905.      * 
  906.      * @access  protected
  907.      * @return  array
  908.      */
  909.     function getRanges()
  910.     {
  911.         return preg_match('/^bytes=((\d*-\d*,? ?)+)$/', 
  912.             @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array();
  913.     }
  914.     
  915.     /** 
  916.      * Check if entity is cached
  917.      * 
  918.      * @access  protected
  919.      * @return  bool
  920.      */
  921.     function isCached()
  922.     {
  923.         return (
  924.             (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
  925.             $this->lastModified == strtotime(current($a = explode(
  926.                 ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) ||
  927.             (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
  928.             $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag))
  929.         );
  930.     }
  931.     
  932.     /** 
  933.      * Check if entity hasn't changed
  934.      * 
  935.      * @access  protected
  936.      * @return  bool
  937.      */
  938.     function isValidRange()
  939.     {
  940.         if (isset($_SERVER['HTTP_IF_MATCH']) &&
  941.             !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) {
  942.             return false;
  943.         }
  944.         if (isset($_SERVER['HTTP_IF_RANGE']) &&
  945.                   $_SERVER['HTTP_IF_RANGE'] !== $this->etag &&
  946.                   strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) {
  947.             return false;
  948.         }
  949.         if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
  950.             $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']));
  951.             if (strtotime($lm) !== $this->lastModified) {
  952.                 return false;
  953.             }
  954.         }
  955.         if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) {
  956.             $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE']));
  957.             if (strtotime($lm) !== $this->lastModified) {
  958.                 return false;
  959.             }
  960.         }
  961.         return true;
  962.     }
  963.     
  964.     /** 
  965.      * Compare against an asterisk or check for equality
  966.      * 
  967.      * @access  protected
  968.      * @return  bool
  969.      * @param   string  key for the $_SERVER array
  970.      * @param   string  string to compare
  971.      */
  972.     function compareAsterisk($svar, $compare)
  973.     {
  974.         foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) {
  975.             if ($request === '*' || $request === $compare) {
  976.                 return true;
  977.             }
  978.         }
  979.         return false;
  980.     }
  981.     
  982.     /**
  983.      * Send HTTP headers
  984.      *
  985.      * @access  protected
  986.      * @return  void
  987.      */
  988.     function sendHeaders()
  989.     {
  990.         foreach ($this->headers as $header => $value) {
  991.             $this->HTTP->setHeader($header, $value);
  992.         }
  993.         $this->HTTP->sendHeaders();
  994.         /* NSAPI won't output anything if we did this */
  995.         if (strncasecmp(PHP_SAPI, 'nsapi', 5)) {
  996.             if (ob_get_level()) {
  997.                 ob_flush();
  998.             }
  999.             flush();
  1000.         }
  1001.     }
  1002.     
  1003.     /**
  1004.      * Flush
  1005.      * 
  1006.      * @access  protected
  1007.      * @return  void
  1008.      * @param   string  $data
  1009.      */
  1010.     function flush($data = '')
  1011.     {
  1012.         if ($dlen = strlen($data)) {
  1013.             $this->sentBytes += $dlen;
  1014.             echo $data;
  1015.         }
  1016.         ob_flush();
  1017.         flush();
  1018.     }
  1019.     
  1020.     /**
  1021.      * Sleep
  1022.      * 
  1023.      * @access  protected
  1024.      * @return  void
  1025.      */
  1026.     function sleep()
  1027.     {
  1028.         if (OS_WINDOWS) {
  1029.             com_message_pump($this->throttleDelay);
  1030.         } else {
  1031.             usleep($this->throttleDelay * 1000);
  1032.         }
  1033.     }
  1034.     // }}}
  1035. }
  1036. ?>
  1037.