파일 업로드에서 가장 중요하게 생각되는 부분이 확장자 추출이 아닐까 싶습니다.


다음 함수로 문제시 되는 파일명(또는 확장자)를 가진 파일을 필터링하여 보다 안전하게 관리할 수 있겠습니다.


다음 문자열을 포함한 파일이라면 false 를 반환하고, ".exe. .." 과 같이 부적절한 확장자도 실행가능한 파일로 인식하므로 정확하게 exe 파일 확장자를 추출하여 줍니다.


 \/:*?\"'<>|\n\t\r\x0\x0B



 <?php
 function 
cExtraction($extensions
 { 
    
$return false
    if(
strlen($extensions) === strcspn($extensions"\\/:*?\"'<>|\n\t\r\x0\x0B")) 
    { 
        if(
false !== strpbrk($extensions'.')) 
        { 
           
$return strtolower(trim(

                         substr($extensionsstrcspn($extensions'.')),

            '. ')); 


            if(
false !== strpbrk($return'.')) 
            { 
                return 
trim(strrchr($return'.'), '. '); 

            }  
        } 
    } 
    return 
$return
 } 
 ?> 


다음 예제에서 \n 을 포함한 파일명이므로 false 입니다.


예 1)

 <?php   
 $file 
" f.ile. \n  zip.zzzz.aswqww.dwq....wqe.w..ddd.cd.d....";   
 if(
false !== ($ext cExtraction($file)) 
 { 
    echo 
"'" $ext "'";
 } 
 ?> 


다음 예제에서는 확장자 d 를 반환합니다.


예 2)

 <?php   
 $file 
" f.ile.  zip.zzzz.aswqww.dwq....wqe.w..ddd.cd. . d ...";   
 if(
false !== ($ext cType_Extraction($file)) 
 { 
    echo 
"'" $ext "'"// 출력: d
 }
 ?> 


예 3)

 <?php   
 $file 
" f.ile";   
 if(
false !== ($ext cType_Extraction($file)) 
 { 
    echo 
"'" $ext "'"// 출력: ile
 }
 ?> 


블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

파일 업로드 크기가 제한되는 경우 .htaccess 파일을 만들어 아래 코드를 적절히 수정해서 사용하면 됩니다.


 php_value upload_max_filesize 16M
 php_value post_max_size 20M

#Ex. 1)

 <form action="" method="post" enctype="multipart/form-data"> 
<div> 
<label for="upload">Select file</label> 
<input name="upload" type="file" /> 
<input type="submit" name="Submit" value="Upload" /> 
</div> 
</form> 

<?php 
// FTP 아이디, 패스워드, 도메인 정보 
$ftp_url 'userid:password@example.com'

if (isset(
$_POST['Submit'])) { 
 if (!empty(
$_FILES['upload']['name'])) { 
     
$ch curl_init(); 
     
$localfile $_FILES['upload']['tmp_name']; 
     
$fp fopen($localfile'r'); 
     
curl_setopt($chCURLOPT_URL,

        'ftp://'.$ftp_url.'/'.$_FILES['upload']['name']); 
     
curl_setopt($chCURLOPT_UPLOAD1); 
     
curl_setopt($chCURLOPT_INFILE$fp); 
     
curl_setopt($chCURLOPT_INFILESIZE,filesize($localfile)); 
     
curl_exec ($ch); 
     
$error_no curl_errno($ch); 
     
curl_close ($ch); 
        if (
$error_no == 0) { 
            
$error 'File uploaded succesfully.'
        } else { 
            
$error 'File upload error.'
        } 
 } else { 
        
$error 'Please select a file.'
 } 

?>


출처: http://www.web-development-blog.com/archives/tutorial-ftp-upload-via-curl/



블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

파일을 다른 서버로 전송하기 위해 꼭 FTP 나 fopen 으로만 가능한 것은 아닙니다. cUrl 함수로도 FTP의 기능을 충분히 흉내낼 수 있는데, 파일을 일반 변수로 전송할 수 있습니다.

 

로컬에서 파일을 받아 http://example.com/test.php  로 파일을 전송하는데, $_FILES 변수가 아닌 $_POST 의 imagefile 변수로 받는다는 점에 주목해야 합니다.


ex #1)

<? 
/* 
test.php 의 내용 
print_r($_POST); 

// 내용을 임시 파일로 만들어서 uploaded 폴더에 저장합니다. 
$str = base64_decode($_POST['imagefile']); 
$tmpfname = tempnam("/tmp", "test_");  
$handle = fopen($tmpfname, "wb");  
fwrite($handle, $str);  
fclose($handle);  

move_uploaded_file($tmpfname, "uploaded/test.gif"); 
*/ 

if($_FILES

    
$filename $_FILES['userfile']['tmp_name']; 
    
$handle fopen($filename"r"); 
    
$data base64_encode(fread($handlefilesize($filename))); 

    
// $data is file data 
    
$post   = array('imagefile' => $data); 
    
$timeout 30
    
$curl    curl_init(); 

    
curl_setopt($curlCURLOPT_URL'http://example.com/test.php'); 
    
curl_setopt($curlCURLOPT_TIMEOUT$timeout); 
    
curl_setopt($curlCURLOPT_POST1); 
    
curl_setopt($curlCURLOPT_RETURNTRANSFER1); 
    
curl_setopt($curlCURLOPT_POSTFIELDS$post); 

    
$str curl_exec($curl); 

    
curl_close ($curl); 
    
print_r($str); 
    
/* 
    결과: 
    Array ( 
        [imagefile] => /9j/4AAQSkZJRgABAgEAYABgAAD/7gAOQWRv.... 
    )  
    */ 


?> 

<form enctype="multipart/form-data" action="" method="POST">  
    이 파일을 전송합니다: <input name="userfile" type="file" />  
    <input type="submit" value="파일 전송" />  
</form>



블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

HTML5 부터 지원하는 FileAPI 는 프로그래밍적으로 파일 객체 표현 뿐만 아니라 로컬 파일에 접근할 수 있는 API 를 제공하는데, 이제 파일 인터페이스로 이름, 크기, 타입 등 기본적인 데이터에 접근할 수 있게 되었습니다.


<input type="file"> 요소는 보통 하나의 파일만을 선택할 수 있었지만 'multiple' 속성을 이용하면 파일을 여러 개 선택할 수 있게 해줍니다.


파일 속성 중 accept 속성은 파일의 종류(MIME)를 지정할 수 있습니다. 이미지 파일만 선택하게 하려면 "image/*", 비디오만 선택하게 하려면 "video/*"로 설정하고, 모든 파일을 선택하게 하려면 설정을 비워두면 됩니다.



ex #1)

<!DOCTYPE html> 
<html> 
<head> 
  <title>File API Demo</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
</head>
<body> 

  <h1>File API Demo</h1>
  <h3>파일(들)을 선택하세요.</h3>

  <input id="files-upload" type="file" multiple accept="image/*">
  
  <h3>Uploaded files</h3> 
  <ul id="file-list"> 
    <li class="no-items">(파일이 선택되지 않음)</li> 
  </ul>

  <script>
    var filesUpload = document.getElementById("files-upload"),
        fileList = document.getElementById("file-list");
    
    function traverseFiles (files) {
      var li,
          file,
          fileInfo;
      fileList.innerHTML = "";
        
      for (var i=0, il=files.length; i<il; i++) {
        li = document.createElement("li");
        file = files[i];
        fileInfo = "<div><strong>Name:</strong> "

                     + file.name + "</div>";
        fileInfo += "<div><strong>Size:</strong> "

                      + file.size + " bytes</div>";
        fileInfo += "<div><strong>Type:</strong> "

                      + file.type + "</div>";
        li.innerHTML = fileInfo;
        fileList.appendChild(li);
      };
    };
    
    filesUpload.onchange = function () {
      traverseFiles(this.files);
    };
  </script>
</body> 
</html>


출처: http://www.w3.org/TR/FileAPI/

        http://html5.firejune.com/


블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

파일 업로드시 항상 문제되는 건 확장자를 제대로 체크하고 있느냐 라고 생각합니다. 그래서 다음과 같이 코드를 짜봤습니다. 참고로 파일길이 255길이 이상이면 다른 의도를 가진 파일이라고 생각해야 할것 같네요.

 

<?php
/*
*************************   확장자 추출   *************************

 파일: test.php.gif -> gif

 파일: test . bmp.  . -> bmp

 파일: test.php . jpg-. -> jpg-

 파일: test....bmp....   .. .     .. -> bmp
*/
function extExtract($filename)
{
    if(
strlen($filename) > 255)
    {
        return false
;
    }

    
$basename trim(basename($filename));
    
$resource explode("."$basename);
    
$i count($resource)-1;
    
$resource[$i] = trim($resource[$i]);

    if(
$resource[$i] === "")
    {
        while(
$i 0)
        {
            
$i--;
            
$resource[$i] = trim($resource[$i]);
            if(
!empty($resource[$i]))
            {
                return 
strtolower($resource[$i]);
            }
        }
        return 
false;
    }
    elseif(
!empty($resource[$i]))
    {
        return 
strtolower($resource[$i]);
    }
    else
    {
        return 
false;
    }
}
?>

관련 글: http://habony.tistory.com/219

블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

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 초보

댓글을 달아 주세요

간혹, 이미지업로드 권한만 주었는데, php파일 등 우회되어 실행파일이 업로드되는 경우가 있습니다. 그래서 이런 실수를 하더라도 업로드 폴더에 있는 .php나 .html 파일 등 실행권한이 없는 일반 텍스트 파일로 인식하도록 설정된다면, 보다 안전한 웹페이지 구성이 가능할 것입니다.

업로드 디렉토리에 다음 코드를 .htaccess 로 작성해서 업로드 해주면 해당 확장자를 가진 파일은 실행되지 않습니다.

 <FilesMatch "\.(phtml|html|htm|ph|php|php3|php4|txt|pl|cgi|inc)"> 
 Order allow,deny
 Deny from all
 </FilesMatch>
블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요


파일 업로드시 php 나 html 등 위험한 파일은 보통 제한하게 됩니다. 그래서 다양한 방법으로 허용가능 파일인지 체크 스크립트를 작성해 주는데, 문제는 잘못된 체크방법으로 개발자도 모르는 우회하여 파일을 업로드됩니다.

예제 (ex #1
 <?php 
 $filename 
"test.php."
;
 $ext array_pop(explode("."strtolower($filename
)));

 if(@ereg($ext"php|php3|php4|htm|inc|html")){
        echo 
"죄송합니다. php, html 파일은 업로드가 제한됩니다."
;
 }
 ?>

아무 문제가 없어 보이지만, 실은 php. 나 htm. gif. 등 모두 실행 가능한 파일임을 알아 둘 필요가 있습니다. 그러므로 다음 같이 한번더 체크해서 공격자가 우회하지 못하게 해주는게 좋습니다.

예제 (ex #2
 <?php 
 $filename 
"test.gif.bmp.php."
;
 $ext explode("."strtolower($filename
));

 $cnt count($ext)-1
;
 if(
$ext[$cnt] === ""
){
    if(@
ereg($ext[$cnt-1], "php|php3|php4|htm|inc|html"
)){
        echo 
"죄송합니다. php, html 파일은 업로드가 제한됩니다."
;
    }
 } else if(@
ereg($ext[$cnt], "php|php3|php4|htm|inc|html"
)){
        echo 
"죄송합니다. php, html 파일은 업로드가 제한됩니다."
;
 }
 ?>
블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

  • Favicon of http://Null BlogIcon torrious 2011.11.30 03:01  댓글주소  수정/삭제  댓글쓰기

    지나가다 오타가 있어 적고 갑니다.

    if($ext[$cnt] === "";){
    if(@ereg($ext[$cnt-1], "php|php3|php4|htm|inc|html";)){
    echo "죄송합니다. php, html 파일은 업로드가 제한됩니다.";
    }
    } else if(@ereg($ext[$cnt], "php|php3|php4|htm|inc|html";){
    echo "죄송합니다. php, html 파일은 업로드가 제한됩니다.";
    }


    에서
    else if(@ereg($ext[$cnt], "php|php3|php4|htm|inc|html";){ 를

    else if(@ereg($ext[$cnt], "php|php3|php4|htm|inc|html";)){ 로 정정 바랍니다.

    괄호가 안닫혔더라구요

  • Favicon of https://blog.habonyphp.com BlogIcon 하보니 2011.11.30 19:36 신고  댓글주소  수정/삭제  댓글쓰기

    ㅎㅎ 확인해보니 닫질 않았네요
    조은 지적 감솨합니당.... 수정되었습니다.

php.ini에서 allow_url_fopen 을 On으로 활성화하면 HTTP나 FTP를 통해 파일을 작성하거나 읽어올 수 있습니다. HTTP인 경우 include, include_once, require, require_once를 사용할 수 있고, FTP는 fopen 으로 접속가능합니다.

FTP 접속은 "ftp://아이디:패스워드@도메인" 으로 접속가능하나 동일 파일이 이미 존재하면 fopen함수 호출에 실패한다는 점에 주의해야 합니다.

예제 (ex #1
  <?php
 
// (ex #1
 
if($file = @fopen (
   
"ftp://user_id:user_passwd@example.com/public_html/test.php""w"
)){
      
// 여기에서 데이터를 씁니다.
      
fwrite ($file"원격 파일 쓰기<br />\n"
);
      
fclose ($file
);
 } else {
    echo 
"<p>원격 파일을 쓰도록 열 수 없습니다.\n"
;
    exit;
 }
 
// 결과: 원격 파일 쓰기<br />\n


 // 이미 (ex #1 에서 test.php파일을 작성하였으므로 (ex #2 는 원격파일 작성에 실패합니다.
 // (ex #2
 
if($file = @fopen (
   
"ftp://user_id:user_passwd@example.com/public_html/test.php""w"
)){
      
// 여기에서 데이터를 씁니다.
      
fwrite ($file"원격 파일 쓰기<br />\n"
);
      
fclose ($file
);
 } else {
    echo 
"<p>원격 파일을 쓰도록 열 수 없습니다.\n"
;
    exit;
 }

 
/*
 결과:
 Warning: fopen(ftp://...@example.com/public_html/test.php
[function.fopen]: failed to open stream: Remote file already exists
 and overwrite context option not specified
 <br /> FTP server reports  213 49 in C:\Server\public_html\test.php 
on line 4
 */
 
?>

원격 접속시 동일 파일이 존재하면 덮어 쓰기는 되지 않지만 데이터 추가는 가능합니다.

예제 (ex #2
 <?php
  
if($file = @fopen (
   
ftp://user_id:user_passwd@example.com/public_html/test.php
    "a"
)){
      
// 여기에서 데이터를 씁니다.
      
fwrite ($file"원격 파일 쓰기<br />\n"
);
      
fclose ($file
);
 } else {
    echo 
"<p>원격 파일을 쓰도록 열 수 없습니다.\n"
;
    exit;
 }

 
// 결과: 원격 파일 쓰기<br />\n원격 파일 쓰기<br />\n

 
?>

원격 접속시 읽어 오기도 가능합니다.

예제 (ex #3

 <?php
  
if($file = @fopen (
   
ftp://user_id:user_passwd@example.com/public_html/test.php,
     
"r"
)){
      
// 여기에서 데이터를 읽어 옵니다.
      
while(!feof($fp
)){
           
$file .= fread($fp,1024
);
      }
      
fclose ($fp
);
 } else {
    echo 
"<p>원격 파일을 열 수 없습니다.\n"
;
    exit;
 }

 echo 
$file
;

 
// 결과: 원격 파일 쓰기<br />\n원격 파일 쓰기<br />\n

 
?>


간단하게 원격파일 접속에 대해 설명은 되었지만, allow_url_fopen 를 활성화하는 것을 권하지는 않습니다.
블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

복수의 파일은 input에서 다른 name을 사용하거나 배열로 조직화한 정보를 얻을 수 있습니다. 다중 파일 업로드 폼은 다음의 조건으로 구성할 수 있습니다.

 <form action="file-upload.php" method="post" enctype="multipart/form-data"><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input type="submit" value="파일 전송" />
 </form> 

폼전송시 file-upload.php파일에서는 다음 표로 구성하여 정보를 받을 수 있습니다.

 <?php
 
foreach($_FILES['userfile']['name'] as $key => $val
){
      if((
$_FILES['userfile']['size'][$key] > 0
){
           if(
$_FILES['userfile']['error'][$key] === UPLOAD_ERR_OK
){
                if(
is_uploaded_file($_FILES['userfile']['tmp_name'][$key
])){
                     
$filename md5("habony_".$_FILES['userfile']['name'][$key
]);
                     if(
move_uploaded_file($_FILES['userfile']['tmp_name'][$key],"./upload/".$filename
)){
                           
mysql_query(
"insert into $db value(
                           '',
                           '"
.addslashes($_FILES['userfile']['name'][$key]).
"'
                           )"
);
                     }
                }
           }
      }
 }
 
?> 

복수 파일 업로드를 허용하더라도 최대 파일 크기를 제한하려면 다음 예와 같을 것입니다.

 <form action="" method="post" enctype="multipart/form-data"><br />
   <input type="hidden" name="MAX_FILE_SIZE" value="300000" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input name="userfile[]" type="file" /><br />
   <input type="submit" value="파일 전송" />
 </form> 

 
<?php
 
if(is_array($_FILES['userfile']['name'
])){
     
$filesize 0
;
     foreach(
$_FILES['userfile']['size'] as $key => $val
){
          if(
$_FILES['userfile']['size'][$key] > 0
){
               
$filesize += $_FILES['userfile']['size'][$key
];
          }
     }
     
// 사용자 입력필드 파일 제한 크기
     
$maxfilesize = (int)$_POST['MAX_FILE_SIZE'
];
     if(
$filesize $maxfilesize
){
          echo 
"허용 파일용량을 초과하였습니다."
;
     } else {
       foreach(
$_FILES['userfile']['name'] as $key => $val
){
         if(
$_FILES['userfile']['size'][$key] > 0
){
               if(
$_FILES['userfile']['error'][$key] === UPLOAD_ERR_OK
){
               if(
is_uploaded_file($_FILES['userfile']['tmp_name'][$key
])){
                
$filename md5("habony_".$_FILES['userfile']['name'][$key
]);
                
$tmpfilename $_FILES['userfile']['tmp_name'][$key
];
                 if(
move_uploaded_file($tmpfilename,"./upload/".$filename
)){
                  
mysql_query(
"insert into $db value (
                                '',
                                '"
.addslashes($_FILES['userfile']['name'][$key]).
"'
                                       )"
);
                                  }
                        }
                   }
              }
          }
     }
 }
 
?>


블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

php는 POST로 파일 업로드기능을 제공합니다. RFC-1867 호환 브라우저(넷스케이프 네비게이터 3 이상, 마이크로소프트 인터넷 익스플로러 3+패치나 패치 없이 그 이상 버전을 포함)라면 파일 업로드를 받을 수 있는 기능을 제공합니다.

다음 표의 내용을 php.ini에서 조작할 수 있습니다.

 옵션  의미
 file_uploads

 업로드 기능을 사용할지를 결정합니다. 기본값 On

 upload_tmp_dir  업로드시 임시 저장 될 디렉토리 경로
 upload_max_filesize  허용하는 최대 파일 크기, 기본값 100M
 max_file_uploads  허용하는 최대 업로드 수, 기본값 100개

파일 업로드 폼은 다음 표처럼 작성하되 폼 안에 enctype="multipart/form-data"값이 있는지 확인하여야 합니다. 그렇지 않으면 업로드 기능은 작동하지 않습니다.

 <form enctype="multipart/form-data" action="send_ok.php" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    이 파일을 전송합니다: <input name="userfile" type="file" />
    <input type="submit" value="파일 전송" />
 </form>

method는 post로 정의하며, MAX_FILE_SIZE는 php.ini에 지정된 upload_max_filesize 크기 보다 클 수 는 없습니다. 이 필드는 ini에 지정된 파일크기보다 크면 에러를 표시하는 용도로만 사용되어야 합니다.

보통 하나의 파일을 업로드하면 다음 표의 변수($_FILES['파일명']['name'])를 여러개 가지게 됩니다.

 변수명  의미
 name  클라이언트측 원래 이름
 type  "image/gif"와 같은 파일의 mime형식, 그러나 php에서 확인하지 않으므로 이 값을 무시하여야 합니다.
 size  업로드된 파일의 바이트로 표현한 크기
 tmp_name
 서버에 저장된 업로드된 파일의 임시 파일 이름
 error  파일 업로드에 대한 에러 코드를 표시

파일 업로드는 다음 예제와 같은 과정으로 작성하면 됩니다.

예제 (ex #1

  <form enctype="multipart/form-data" action="" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    이 파일을 전송합니다: <input name="userfile" type="file" />
    <input type="submit" value="파일 전송" />
 </form>

 
<?php
 
// uploads디렉토리에 파일을 업로드합니다.
 
$uploaddir './uploads/'
;
 
$uploadfile $uploaddir basename($_FILES['userfile']['name'
]);

 echo 
'<pre>'
;
 if(
$_POST['MAX_FILE_SIZE'] < $_FILES['userfile']['size'
]){
      echo 
"업로드 파일이 지정된 파일크기보다 큽니다.\n"
;
 } else {
     if((
$_FILES['userfile']['error'] > 0) || ($_FILES['userfile']['size'] <= 0
)){
          echo 
"파일 업로드에 실패하였습니다."
;
     } else {
          
// HTTP post로 전송된 것인지 체크합니다.
          
if(!is_uploaded_file($_FILES['userfile']['tmp_name'
])) {
                echo 
"HTTP로 전송된 파일이 아닙니다."
;
          } else {
                
// move_uploaded_file은 임시 저장되어 있는 파일을 ./uploads 디렉토리로 이동합니다.
                
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile
)) {
                     echo 
"성공적으로 업로드 되었습니다.\n"
;
                } else {
                     echo 
"파일 업로드 실패입니다.\n"
;
                }
          }
     }
 }

 
print_r($_FILES
);

 
/*
 결과:
 성공적으로 업로드 되었습니다.
 Array
 (
    [userfile] => Array
        (
            [name] => config.sys
            [type] => text/plain
            [tmp_name] => /tmp/phpXTtzBW
            [error] => 0
            [size] => 10
        )
 )
 */
 
?> 


파일 업로드가 성공적이면 에러코드는 0을 가집니다. 에러 코드는 다음 표의 상수로 대조해도 됩니다.

 상수명  의미
 UPLOAD_ERR_OK  값: 0; 오류 없이 파일 업로드가 성공했습니다
 UPLOAD_ERR_INI_SIZE 값: 1; 업로드한 파일이 php.ini upload_max_filesize 지시어보다 큽니다
 UPLOAD_ERR_FORM_SIZE 
값: 2; 업로드한 파일이 HTML 폼에서 지정한 MAX_FILE_SIZE 보다 큽니다. 
 UPLOAD_ERR_PARTIAL 
값: 3; 파일이 일부분만 전송되었습니다
 UPLOAD_ERR_NO_FILE  값: 4; 파일이 전송되지 않았습니다
 UPLOAD_ERR_NO_TMP_DIR  값: 6; 임시 폴더가 없습니다. PHP 4.3.10과 PHP 5.0.3에서 추가.
 UPLOAD_ERR_CANT_WRITE  값: 7; 디스크에 파일 쓰기를 실패했습니다. PHP 5.1.0에서 추가
 UPLOAD_ERR_EXTENSION  값: 8; 확장에 의해 파일 업로드가 중지되었습니다. PHP 5.2.0에서 추가.

파일 이름을 서버에 그대로 저장하는 것도 보안상 상당한 위험이 있으므로 md5로 암호화해서 저장하고, 디비에 원래 이름을 저장하도록 합니다.

예제 (ex #2

   <form enctype="multipart/form-data" action="" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    이 파일을 전송합니다: <input name="userfile" type="file" />
    <input type="submit" value="파일 전송" />
 </form>

 
<?php
 
function file_errmsg($code
){
     switch(
$code
){
         case(
UPLOAD_ERR_INI_SIZE
):
         return 
"업로드한 파일이 php.ini upload_max_filesize보다 큽니다."
;
     case(
UPLOAD_ERR_FORM_SIZE
):
         return 
"업로드한 파일이 MAX_FILE_SIZE 보다 큽니다. "
;
     case(
UPLOAD_ERR_PARTIAL
):
         return 
"파일이 일부분만 전송되었습니다. 다시 시도해 주십시요."
;
     case(
UPLOAD_ERR_NO_FILE
):
         return 
"파일이 전송되지 않았습니다."
;
     case(
UPLOAD_ERR_NO_TMP_DIR
):
         return 
"임시 폴더가 없습니다."
;
     case(
UPLOAD_ERR_CANT_WRITE
):
         return 
"디스크에 파일 쓰기를 실패했습니다. 다시 시도해 주십시요."
;
     default:
         return 
"확장에 의해 파일 업로드가 중지되었습니다."
;
     }
 }

 
// 서버에 저장될 디렉토리이름
 
$uploaddir './uploads/'
;
 
// 서버에 저장될 파일이름
 
$filename basename($_FILES['userfile']['name'
]);
 
$md5filename $uploaddir md5("habony_".$filename
);
 
$ext array_pop(explode(".","$filename"
);

 echo 
'<pre>'
;
 if(
$_FILES['userfile']['error'] === UPLOAD_ERR_OK
) {
      if(
strtolower($ext) == "php"
) {
           echo 
"확장자 php파일은 업로드 하실수 없습니다."
;
      }
      else if(
$_FILES['userfile']['size'] <= 0
){
          echo 
"파일 업로드에 실패하였습니다."
;
      } else {
          
// HTTP post로 전송된 것인지 체크합니다.
          
if(!is_uploaded_file($_FILES['userfile']['tmp_name'
])) {
               echo 
"HTTP로 전송된 파일이 아닙니다."
;
          } else {
               
// move_uploaded_file은 임시 저장되어 있는 파일을 ./uploads 디렉토리로 이동합니다.
               
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $md5filename
)) {
                    echo 
"성공적으로 업로드 되었습니다.\n"
;
               } else {
                    echo 
"파일 업로드 실패입니다.\n"
;
               }
               
mysql_query("insert into $db values  ('','$filename')"
);
          }
      }
 } else {
      echo 
file_errmsg($_FILES['userfile']['error'
]);
 }

 
print_r($_FILES
);

 
/*
 결과:
 성공적으로 업로드 되었습니다.
 Array
 (
    [userfile] => Array
        (
            [name] => config.sys
            [type] => text/plain
            [tmp_name] => /tmp/phpXTtzBW
            [error] => 0
            [size] => 10
        )
 )
 */
 
?>

블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요