<?php
/**
* @package MakeCAB
* @example /examplecab.php Example usage of this class.
* @category File Formats
* @author Sam Shull <samshull@samshull.com>
* @copyright Copyright (c) 2007, Sam Shull
* @license http://www.samshull.com/bsdlicense.txt BSD License
* @link http://samshull.com/examplecab.php
* @version 0.9
* @access public
*/
class MakeCAB{
/**
* Absolute / Relative Path to cab file
*
* @var string
* @access protected
*/
var $cabfile;
/**
* Array of files to be added to archive
*
* @var array
* @access protected
*/
var $cabfiles = array();
/**
* Array of data for CAB folder
*
* @var array
* @access protected
*/
var $cabfolder = array('offset' => 44, ## offset of data
'numblocks' => 0, ## number of blocks
'typecmp' => 1, ## 0 = no compression; 1 = MSZIP; LZX
'fileoffset' => 0
);
/**
* Array of data for CAB Header
*
* @var array
* @access protected
*/
var $cabheader = array('sig' => "MSCF", ## signature
'res1' => 0, ## reserved space
'size' => 44, ## total size of cab file
'res2' => 0,
'offset' => 44, ## offset of first data block
'res3' => 0,
'vmaj' => 1, ## cab file format major version number 1
'vmin' => 3, ## cab file format minor version number 3
'numfolders' => 1, ## must have at least one folder NewCABFolder()
'numfiles' => 0, ## number of files in cab file
'flags' => 0, ## not supported
'setid' => 1234, ## set ID (not supported)
'cabid' => 0, ##cab id not supported
);
/**
* Max data block size
*
* @var integer
* @access protected
*/
var $BLOCKSIZE = 32768;
/**
* Linux Seperator
*
* @var string
* @access protected
*/
var $LINSEP = '/';
/**
* Windows Seperator
*
* @var string
* @access protected
*/
var $WINSEP = '\\';
/**
* Method: MakeCAB __construct
* Construct
* @param string $file - file that will be written to
* @param int $typecmp - level of compression
*/
function MakeCAB($file,$typecmp=1){
$this->cabfile = $file;
$this->cabfolder['typecmp'] = ($typecmp > 0 && extension_loaded('zlib')) ? $typecmp : 0;
}
/**
* Method: WriteCab
* Write the data to a file
*/
function WriteCAB(){
$this->cabfolder['numblocks'] = ceil($this->cabfolder['fileoffset']/$this->BLOCKSIZE);
$this->cabheader['size'] += ($this->cabfolder['numblocks'] * 8);
if(false !== ($res = fopen( $this->cabfile, "w+b" ))){
$this->WriteHeader($res);
$this->WriteFolder($res);
$this->WriteFiles($res);
$this->WriteData($res);
$this->UpdateCABsize($res);
fclose( $res );
} else {
die("Failed to open ".$this->cabfile);
}
}
//Update the file size in the header (occassional errors with filesize caused by compression & using strlen as a byte counter)
function UpdateCABsize($res){
$t = ftell($res);
fseek($res,8);
$this->WriteDword( $res, $t );
}
/**
* Method: addfile
* Add a local file system file to your cab file
* @param string $file - path preferably absolute path
* @param bool|string $strip - if string is used parser will leave on the path what it is told to
* e.g.: $file="/usr/path/data/file" $strip="/path" will result in an archive entry="/path/data/file"
* a strip value of (bool) false then would result in archive entry= "/usr/path/data/file"
* @param int $atribs - e.g hidden, read-only - see sdk
*/
function addfile($file,$strip=true,$atribs=32){
$filename = $strip==false ? $this->MakeWinPath( $file ) : $this->StripPath($file,$strip);
$m=stat($file);
$newcabfile = array('ucomp' => $m['size'],
'offset' => $this->cabfolder['fileoffset'],
'date' => $this->MakeCabFileDate($m['mtime']),
'time' => $this->MakeCabFileTime($m['mtime']),
'atribs' => $atribs,
'name' => $this->MakeWinPath($filename)."\0",
'path' => $file,
'size' => $m['size'] );
$this->cabfolder['fileoffset'] += $m['size'];
$this->cabfolder['offset'] += 17 + strlen( $filename );
$this->cabheader['size'] += 17 + strlen( $filename ) + $m['size'];
$this->cabheader['numfiles']++;
$this->cabfiles[]=$newcabfile;
clearstatcache();
}
/**
* Method: addurl
* Add a file from a url using any stream that can be handled by PHP
* for parameter descriptions see addfile descriptions
* @param string $file
* @param bool|string $strip
* @param int $atribs
*/
function addurl($file,$strip=true,$atribs=32){
$x = file_get_contents($file);
$filename = $strip==false ? $this->MakeWinPath( preg_replace("@^.*://@","/",$file) ) : $this->StripPath($file,$strip);
$filen = dirname(__FILE__).$this->StripPath($file,true).".cabtemp";
file_put_contents($filen,$x);
$m=stat($filen);
$newcabfile = array('ucomp' => $m['size'],
'offset' => $this->cabfolder['fileoffset'],
'date' => $this->MakeCabFileDate($m['mtime']),
'time' => $this->MakeCabFileTime($m['mtime']),
'atribs' => $atribs,
'name' => $this->MakeWinPath($filename)."\0",
'path' => $filen,
'size' => $m['size'] );
$this->cabfolder['fileoffset'] += $m['size'];
$this->cabfolder['offset'] += 17 + strlen( $filename );
$this->cabheader['size'] += 17 + strlen( $filename ) + $m['size'];
$this->cabheader['numfiles']++;
$this->cabfiles[]=$newcabfile;
clearstatcache();
}
/**
* Method: adddir
* Add an entire file system directory to your cab file
* for parameter descriptions see addfile descriptions
* @param string $dir
* @param bool|string $strip
* @param int $atribs
*/
function adddir($dir,$strip=true,$atribs=32){
$dir = preg_match("/(\/|\\\\)$/",$dir) ? $dir : $dir.$this->LINSEP;
if($d = opendir($dir) ){
while(false !== ($file = readdir($d)) ){
if(is_file($dir.$file)){
$filename = $strip==false ? $this->MakeWinPath( $dir.$file ) : $this->StripPath($dir.$file,$strip);
$m=stat($dir.$file);
$newcabfile = array('ucomp' => $m['size'],
'offset' => $this->cabfolder['fileoffset'],
'date' => $this->MakeCabFileDate($m['mtime']),
'time' => $this->MakeCabFileTime($m['mtime']),
'atribs' => $atribs,
'name' => $this->MakeWinPath($filename)."\0",
'path' => $dir.$file,
'size' => $m['size'] );
$this->cabfolder['fileoffset'] += $m['size'];
$this->cabfolder['offset'] += 17 + strlen( $filename );
$this->cabheader['size'] += 17 + strlen( $filename ) + $m['size']; ## + 1 for \0 !
$this->cabheader['numfiles']++;
$this->cabfiles[]=$newcabfile;
}//end if
clearstatcache();
}//end while
closedir($d);
}
}
## strip the path
function StripPath($path,$strip){
$one = preg_match("@".$this->LINSEP."@x", $path) ? 1 : 0;
$path = $strip === true ? substr($path,strrpos($path,$this->LINSEP)+$one) : ($strip != false ? substr($path,strpos($path,$strip)) : $path);
return $path;
}
## make win path
function MakeWinPath ($path){
if(preg_match("@".$this->LINSEP."@x", $path) )
$path = preg_replace("'".$this->LINSEP."'s", $this->WINSEP, $path);
return $path;
}
## make cabinet file date from certain file
function MakeCabFileDate($seconds){
$s = localtime($seconds, true);
$res = ( ($s['tm_year'] - 80 ) << 9 ) + ( ($s['tm_mon']+1) << 5 ) + $s['tm_mday'];
return $res;
}
## make cabinet file date
function MakeCabFileTime($seconds){
$s = localtime($seconds, true);
$res = ( $s['tm_hour'] << 11 ) + ( $s['tm_min'] << 5 ) + ( $s['tm_sec'] / 2 );
return $res;
}
################# WRITING #################
## write the header
function WriteHeader($res){
$this->WriteByteBuffer( $res, $this->cabheader['sig'] );
$this->WriteDword( $res, $this->cabheader['res1'] );
$this->WriteDword( $res, $this->cabheader['size'] );
$this->WriteDword( $res, $this->cabheader['res2'] );
$this->WriteDword( $res, $this->cabheader['offset'] );
$this->WriteDword( $res, $this->cabheader['res3'] );
$this->WriteByte( $res, $this->cabheader['vmin'] );
$this->WriteByte( $res, $this->cabheader['vmaj'] );
$this->WriteWord( $res, $this->cabheader['numfolders'] );
$this->WriteWord( $res, $this->cabheader['numfiles'] );
$this->WriteWord( $res, $this->cabheader['flags'] );
$this->WriteWord( $res, $this->cabheader['setid'] );
$this->WriteWord( $res, $this->cabheader['cabid'] );
}
function WriteByteBuffer($res, $scalar){
fwrite( $res, $scalar );
}
## write (unsigned long int) dword;
function WriteDword($res, $scalar){
if( preg_match("/[ \/a-zA-Z]/", $scalar) )
{
$f = array();
for($i=0; $i<strlen($scalar); ++$i )
{
$f[$i] = ord( substr($scalar,$i,1) );
}
$fmt = "L".strlen($scalar);
fwrite( $res, pack( "$fmt", $f ) );
return;
}
fwrite( $res, pack("L",$scalar) );
}
## write (unsigned char) single byte with pack;
function WriteByte($res, $scalar){
fwrite( $res, pack("C",$scalar) );
}
## write (unsigned short int) word;
function WriteWord($res, $scalar){
if( preg_match("/[ \/a-zA-Z]/", $scalar) )
{
$f = array();
for($i=0; $i<strlen($scalar); ++$i )
{
$f[$i] = ord( substr($scalar,$i,1) );
}
$fmt = "S".strlen($scalar);
fwrite( $res, pack( "$fmt", $f ) );
return;
}
fwrite( $res, pack("S",$scalar) );
}
## write the folder
function WriteFolder($res){
$this->WriteDword( $res, $this->cabfolder['offset'] );
$this->WriteWord( $res, $this->cabfolder['numblocks'] );
$this->WriteWord( $res, $this->cabfolder['typecmp'] );
}
## write the files
function WriteFiles($res){
foreach ( $this->cabfiles as $file )
{
$this->WriteDword( $res, $file['ucomp'] );
$this->WriteDword( $res, $file['offset'] );
$this->WriteWord( $res, $file['index'] );
$this->WriteWord( $res, $file['date'] );
$this->WriteWord( $res, $file['time'] );
$this->WriteWord( $res, $file['fileattr'] );
$this->WriteByteBuffer( $res, $file['name'] );
}
}
function WriteData($res){
$datasize = $this->cabfolder['fileoffset'];
$blocksize = $datasize > $this->BLOCKSIZE ? $this->BLOCKSIZE : $datasize;
$block = $blocksize;
$newblock = false;
foreach ( $this->cabfiles as $file ){
if(false !== ($res1 = @fopen( $file['path'], "rb" ))){
$read_this_time = 0;
$read_last_time = 0;
while(!feof($res1)){
$buffer .= fread($res1,$block);
$read = ftell($res1);
$read_this_time = $read - $read_last_time;
$read_last_time = $read;
$datasize -= $read_this_time;
if($read_this_time == $blocksize){
$block = $this->BLOCKSIZE;
$newblock = true;
} else {
$block -= $read_this_time;
$newblock = false;
if($block <= 0){
$block = $this->BLOCKSIZE;
$newblock = true;
}
}
if( $newblock ){
$data = $this->cabfolder['typecmp'] == 1 ? $this->compressdata($buffer) : array('cbytes'=>$blocksize,'data'=>$buffer);
$this->WriteDword( $res, 0 );
$this->WriteWord( $res, $data['cbytes'] );
$this->WriteWord( $res, $blocksize );
$this->WriteByteBuffer( $res, $data['data'] );
$blocksize = $datasize > $this->BLOCKSIZE ? $this->BLOCKSIZE : $datasize;
$buffer = "";
}
}
## close input file
fclose( $res1 );
if(preg_match("'\.cabtemp$'",$file['path'])) unlink($file['path']);
} else {
die("Failed to open ".$file);
}
}
}
//*/
function compressdata($buffer){
$data['data'] = pack("C",0x43) . pack("C", 0x4B) . gzdeflate($buffer,$this->cabfolder['typecmp']);
$data['cbytes']=$this->bytelen($data['data']);
return $data;
}
function bytelen($data) {
return strlen($data."A") - 1;
}
/*
An unused method to compute the csum, not finished
function CSUMCompute($pv, $cb, $seed=0){
//$csumpartial = $this->CSUMCompute(0,$data['cbytes'],0);
//$csum = $this->CSUMCompute($data['cbytes'],sizeof(CFDATA) – sizeof($csum),$csumpartial);
//int cUlong; // Number of ULONGs in block
//CHECKSUM csum; // Checksum accumulator
//BYTE *pb;
//ULONG ul;
$cUlong = $cb / 4; // Number of ULONGs
$csum = $seed; // Init checksum
$pb = $pv; // Start at front of data block
//** Checksum integral multiple of ULONGs
while ($cUlong-- > 0) {
//** NOTE: Build ULONG in big/little-endian independent manner
$ul = $pb++; // Get low-order byte
$ul |= (($pb++) << 8); // Add 2nd byte
$ul |= (($pb++) << 16); // Add 3nd byte
$ul |= (($pb++) << 24); // Add 4th byte
$csum ^= $ul; // Update checksum
}
//** Checksum remainder bytes
$ul = 0;
switch ($cb % 4) {
case 3:
$ul |= (($pb++) << 16); // Add 3nd byte
case 2:
$ul |= (($pb++) << 8); // Add 2nd byte
case 1:
$ul |= $pb++; // Get low-order byte
default:
break;
}
$csum ^= $ul; // Update checksum
//** Return computed checksum
return $csum;
}
//*/
}
?>
|