Content-Range 헤더는 파일 다운로드 이어받기할 때 필요한 부분입니다. 동시에 $_SERVER['HTTP_RANGE'] 변수를 같이 사용하고 이로서 이어받기가 가능해집니다. 헤더부분은 Content-Range: bytes 다운받은 크기/전체크기 로 표현합니다. 다음을 참고하면 이해되리라 봅니다. (참고 문서)

 

 Actual result:
--------------

$ telnet dev.conduit-it.com 80
Trying 10.42.84.2...
Connected to dev.conduit-it.com.
Escape character is '^]'.
GET /test.php HTTP/1.1
Host:dev.conduit-it.com
Range:bytes=0-24
Connection:close

HTTP/1.1 206 Partial Content
Date: Fri, 29 Aug 2008 03:43:20 GMT
Content-Range: bytes 0-24/8000
Content-Length: 25
Connection: close
Content-Type: text/html

.........................Connection closed by foreign host.

$ telnet dev.conduit-it.com 80
Trying 10.42.84.2...
Connected to dev.conduit-it.com.
Escape character is '^]'.
GET /test.php HTTP/1.1
Host:dev.conduit-it.com
Range:bytes=0-24,50-74
Connection:close

HTTP/1.1 206 Partial Content
Date: Fri, 29 Aug 2008 03:45:44 GMT
Content-Length: 240
Connection: close
Content-Type: multipart/byteranges; boundary=455911696d6f354a2


--455911696d6f354a2
Content-type: text/html
Content-range: bytes 0-24/8000

.........................
--455911696d6f354a2
Content-type: text/html
Content-range: bytes 50-74/8000

.........................
--455911696d6f354a2--
Connection closed by foreign host.

 

헤더부분은 다음과 같이 표현됩니다.

 Accept-Ranges: bytes  

 Content-Length: {filesize} 
 Content-Range: bytes 10-{filesize-1}/{filesize}

 

파일 이어받기는 다음과 같이 구현할 수 있겠습니다.

 

예제(ex #1

 <?php 
function download_file($file_name
) { 

    if (!
file_exists($file_name)) { die("<b>404 File not found!</b>"
); } 
    
    
$file_extension strtolower(substr(strrchr($file_name,"."),1
)); 
    
$file_size filesize($file_name
); 
    
$md5_sum md5_file($file_name
); 
    
   
//This will set the Content-Type to the appropriate setting for 
the file 
    
switch($file_extension
) { 
        case 
"exe"$ctype="application/octet-stream"
; break; 
        case 
"zip"$ctype="application/zip"
; break; 
        case 
"mp3"$ctype="audio/mpeg"
; break; 
        case 
"mpg":$ctype="video/mpeg"
; break; 
        case 
"avi"$ctype="video/x-msvideo"
; break; 

        
//The following are for extensions that shouldn't be downloaded
 (sensitive stuff, like php files) 
        
case "php"

        case 
"htm"

        case 
"html"

        case 
"txt": die("<b>Cannot be used for "$file_extension
 
." files!</b>"
); break; 

        default: 
$ctype="application/force-download"

    } 
    
    if (isset(
$_SERVER['HTTP_RANGE'
])) { 
        
$partial_content true

        
$range explode("-"$_SERVER['HTTP_RANGE'
]); 
        
$offset intval($range[0
]); 
        
$length intval($range[1]) - $offset

    } 
    else { 
        
$partial_content false

        
$offset 0

        
$length $file_size

    } 
    
    
//read the data from the file 
    
$handle fopen($file_name'r'
); 
    
$buffer ''

    
fseek($handle$offset
); 
    
$buffer fread($handle$length
); 
    
$md5_sum md5($buffer
); 
    if (
$partial_content$data_size intval($range[1]) - 
intval($range[0
]); 
    else 
$data_size $file_size

    
fclose($handle
); 
    
    
// send the headers and data 
    
header("Content-Length: " $data_size
); 
    
header("Content-md5: " $md5_sum
); 
    
header("Accept-Ranges: bytes"
);    
    if (
$partial_contentheader('Content-Range: bytes ' $offset '-' . ($offset $length) . '/' $file_size
); 
    
header("Connection: close"
); 
    
header("Content-type: " $ctype
); 
    
header('Content-Disposition: attachment; filename=' $file_name
); 
    echo 
$buffer

    
flush
(); 

?>

출처: http://php.net/manual/en/function.fread.php

 

예제(ex #2

<?php
function dl_file_resume($file){

   
//First, see if the file exists
   
if (!is_file($file)) { die("<b>404 File not found!</b>"); }

   
//Gather relevent info about file
   
$len filesize($file);
   
$filename basename($file);
   
$file_extension strtolower(substr(strrchr($filename,"."),1));

   
//This will set the Content-Type to the appropriate setting for the file
   
switch( $file_extension ) {
     case 
"exe"$ctype="application/octet-stream"; break;
     case 
"zip"$ctype="application/zip"; break;
     case 
"mp3"$ctype="audio/mpeg"; break;
     case 
"mpg":$ctype="video/mpeg"; break;
     case 
"avi"$ctype="video/x-msvideo"; break;

     
//The following are for extensions that shouldn't be downloaded
 (sensitive stuff, like php files)
     
case "php":
     case 
"htm":
     case 
"html":
     case 
"txt": die("<b>Cannot be used for "$file_extension ." files!</b>"); break;

     default: 
$ctype="application/force-download";
   }

   
//Begin writing headers
   
header("Pragma: public");
   
header("Expires: 0");
   
header("Cache-Control:");
   
header("Cache-Control: public"); 
   
header("Content-Description: File Transfer");
   
   
//Use the switch-generated Content-Type
   
header("Content-Type: $ctype");
$filespaces str_replace("_"" "$filename);

//if your filename contains underscores, you can replace them with
 spaces
  
$header='Content-Disposition: attachment; filename='.$filespaces.';';
   
header($header );
   
header("Content-Transfer-Encoding: binary");

  
$size=filesize($file);
//check if http_range is sent by browser (or download manager)
   
if(isset($_ENV['HTTP_RANGE'])) {
 list(
$a$range)=explode("=",$_ENV['HTTP_RANGE']);
//if yes, download missing part
 
str_replace($range"-"$range);
 
$size2=$size-1;
 
header("Content-Range: $range$size2/$size");
 
$new_length=$size2-$range;
 
header("Content-Length: $new_length");
//if 
notdownload whole file
} else {
 
$size2=$size-1;
 
header("Content-Range: bytes 0-$size2/$size");
 
header("Content-Length: ".$size2);
}
//open the file
$fp=fopen("$file","r");
//seek to start of missing part
fseek($fp,$range);
//start buffered download
while(!feof($fp))
{
//reset time limit for big files
set_time_limit();
print(
fread($fp,1024*8));
 
flush();
}
fclose($fp); 
   
   exit;
     

?>

EXAMPLE
<?php
dl_file_resume
("somefile.mp3");
?>

출처: http://nc.php.net/manual/vote-note.php?id=50904&page=function.fread&vote=up

 

예제(ex #3 

<?php
function smartReadFile($location$filename$mimeType
='application/octet-stream'
)
{ if(!
file_exists($location
))
  { 
header ("HTTP/1.0 404 Not Found"
);
    return;
  }
  
  
$size=filesize($location
);
  
$time=date('r',filemtime($location
));
  
  
$fm=@fopen($location,'rb'
);
  if(!
$fm
)
  { 
header ("HTTP/1.0 505 Internal server error"
);
    return;
  }
  
  
$begin=0
;
  
$end=$size
;
  
  if(isset(
$_SERVER['HTTP_RANGE'
]))
  { if(
preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i'$_SERVER['HTTP_RANGE'], $matches
))
    { 
$begin=intval($matches[0
]);
      if(!empty(
$matches[1
]))
        
$end=intval($matches[1
]);
    }
  }
  
  if(
$begin>0||$end<$size
)
    
header('HTTP/1.0 206 Partial Content'
);
  else
    
header('HTTP/1.0 200 OK'
);  
  
  
header("Content-Type: $mimeType"
); 
  
header('Cache-Control: public, must-revalidate, max-age=0'
);
  
header('Pragma: no-cache'
);  
  
header('Accept-Ranges: bytes'
);
  
header('Content-Length:'.($end-$begin
));
  
header("Content-Range: bytes $begin-$end/$size"
);
  
header("Content-Disposition: inline; filename=$filename"
);
  
header("Content-Transfer-Encoding: binary\n"
);
  
header("Last-Modified: $time"
);
  
header('Connection: close'
);  
  
  
$cur=$begin
;
  
fseek($fm,$begin,0
);

  while(!
feof($fm)&&$cur<$end&&(connection_status()==0
))
  { print 
fread($fm,min(1024*16,$end-$cur
));
    
$cur+=1024*16
;
  }
}
?>

Usage:

<?php
smartReadFile
("/tmp/filename","myfile.mp3","audio/mpeg"
)
?>
 

출처: http://php.net/manual/ja/function.readfile.php

 

예제(ex #4

<?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 HTTP_Download($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 (
>= $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   $secondsAmount 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)
     *  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 10) : '*'
;
 
        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
     */
    
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
     */
    
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 10) : '*'
;
 
        
// 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 $e0
);
                
$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>
     */
    
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>
     */
    
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'])
             || !
count($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
]:array();
    }
 
    
/**
     * 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_Errorstartup error if one exists
     * @access protected
     */
    
function _getError
()
    {
        
$error null
;
        if (
PEAR::isError($this->_error
)) {
            
$error $this->_error
;
            
$this->_error null
;
        }
        return 
$error
;
    }
    
// }}}
}
?>
 

출처: http://pear.php.net/package/HTTP_Download/docs/latest/__filesource/fsource_HTTP_Download__HTTP_Download-1.1.4HTTPDownload.php.html

블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요