2015-08-30 15:01:12 +02:00
< ? php
/**
* Class to create zip file , aimed at large files , or even large target zip file .
* This class will stream the generated zip file directly to the HTTP client as the content is added .
*
* If you need the Zip file data on the server , for storage in a database of the server file system , look at
* the Zip class at http :// www . phpclasses . org / browse / package / 6110. html
*
* License : GNU LGPL , Attribution required for commercial implementations , requested for everything else .
*
* Inspired on CreateZipFile by Rochak Chauhan www . rochakchauhan . com ( http :// www . phpclasses . org / browse / package / 2322. html )
* and
* http :// www . pkware . com / documents / casestudies / APPNOTE . TXT Zip file specification .
*
* @ author A . Grandt < php @ grandt . com >
* @ copyright 2009 - 2013 A . Grandt
* @ license GNU LGPL , Attribution required for commercial implementations , requested for everything else .
* @ link http :// www . phpclasses . org / package / 6116
* @ link https :// github . com / Grandt / PHPZip
* @ version 1.37
*/
2016-09-30 00:26:31 +02:00
class ZipStream
{
2015-10-25 03:35:37 +01:00
const VERSION = 1.37 ;
const ZIP_LOCAL_FILE_HEADER = " \x50 \x4b \x03 \x04 " ; // Local file header signature
const ZIP_CENTRAL_FILE_HEADER = " \x50 \x4b \x01 \x02 " ; // Central file header signature
const ZIP_END_OF_CENTRAL_DIRECTORY = " \x50 \x4b \x05 \x06 \x00 \x00 \x00 \x00 " ; //end of Central directory record
const EXT_FILE_ATTR_DIR = " \x10 \x00 \xFF \x41 " ;
const EXT_FILE_ATTR_FILE = " \x00 \x00 \xFF \x81 " ;
const ATTR_VERSION_TO_EXTRACT = " \x14 \x00 " ; // Version needed to extract
const ATTR_MADE_BY_VERSION = " \x1E \x03 " ; // Made By Version
private $zipMemoryThreshold = 1048576 ; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB)
private $zipComment = null ;
2016-09-30 00:26:31 +02:00
private $cdRec = []; // central directory
2015-10-25 03:35:37 +01:00
private $offset = 0 ;
2016-09-30 00:26:31 +02:00
private $isFinalized = false ;
private $addExtraField = true ;
2015-10-25 03:35:37 +01:00
private $streamChunkSize = 16384 ; // 65536;
private $streamFilePath = null ;
private $streamTimeStamp = null ;
private $streamComment = null ;
private $streamFile = null ;
private $streamData = null ;
private $streamFileLength = 0 ;
/**
* Constructor .
*
2021-02-14 03:39:15 +01:00
* @ param string $archiveName Name to send to the HTTP client .
* @ param string $contentType Content mime type . Optional , defaults to " application/zip " .
2015-10-25 03:35:37 +01:00
*/
2021-02-14 03:39:15 +01:00
public function __construct ( $archiveName = '' , $contentType = 'application/zip' )
2016-09-30 00:26:31 +02:00
{
2021-02-14 03:39:15 +01:00
if ( ! function_exists ( 'sys_get_temp_dir' )) {
die ( 'ERROR: ZipStream ' . self :: VERSION . ' requires PHP version 5.2.1 or above.' );
2015-10-25 03:35:37 +01:00
}
$headerFile = null ;
$headerLine = null ;
2021-02-14 03:39:15 +01:00
if ( ! headers_sent ( $headerFile , $headerLine ) or die ( " <p><strong>Error:</strong> Unable to send file $archiveName . HTML Headers have already been sent from <strong> $headerFile </strong> in line <strong> $headerLine </strong></p> " )) {
if (( ob_get_contents () === false || ob_get_contents () == '' ) or die ( " \n <p><strong>Error:</strong> Unable to send file <strong> $archiveName .epub</strong>. Output buffer contains the following text (typically warnings or errors):<br> " . ob_get_contents () . '</p>' )) {
2015-10-25 03:35:37 +01:00
if ( ini_get ( 'zlib.output_compression' )) {
ini_set ( 'zlib.output_compression' , 'Off' );
}
header ( 'Pragma: public' );
2021-02-14 03:39:15 +01:00
header ( 'Last-Modified: ' . gmdate ( 'D, d M Y H:i:s T' ));
header ( 'Expires: 0' );
header ( 'Accept-Ranges: bytes' );
2015-10-25 03:35:37 +01:00
//header("Connection: Keep-Alive");
2021-02-14 03:39:15 +01:00
header ( 'Content-Type: ' . $contentType );
header ( 'Content-Disposition: attachment; filename="' . $archiveName . '";' );
header ( 'Content-Transfer-Encoding: binary' );
2015-10-25 03:35:37 +01:00
flush ();
}
}
}
2021-02-14 03:39:15 +01:00
public function __destruct ()
2016-09-30 00:26:31 +02:00
{
$this -> isFinalized = true ;
2015-10-25 03:35:37 +01:00
$this -> cdRec = null ;
exit ;
}
/**
* Extra fields on the Zip directory records are Unix time codes needed for compatibility on the default Mac zip archive tool .
* These are enabled as default , as they do no harm elsewhere and only add 26 bytes per file added .
*
* @ param bool $setExtraField TRUE ( default ) will enable adding of extra fields , anything else will disable it .
*/
2021-02-14 03:39:15 +01:00
public function setExtraField ( $setExtraField = true )
2016-09-30 00:26:31 +02:00
{
$this -> addExtraField = ( $setExtraField === true );
2015-10-25 03:35:37 +01:00
}
/**
* Set Zip archive comment .
*
* @ param string $newComment New comment . null to clear .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function setComment ( $newComment = null )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
$this -> zipComment = $newComment ;
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
/**
* Add an empty directory entry to the zip archive .
* Basically this is only used if an empty directory is added .
*
* @ param string $directoryPath Directory Path and name to be added to the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added directory , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this directory . To use fileComment , timestamp must be given .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function addDirectory ( $directoryPath , $timestamp = 0 , $fileComment = null )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
$directoryPath = str_replace ( '\\' , '/' , $directoryPath );
$directoryPath = rtrim ( $directoryPath , '/' );
2015-10-25 03:35:37 +01:00
if ( strlen ( $directoryPath ) > 0 ) {
$this -> buildZipEntry ( $directoryPath . '/' , $fileComment , " \x00 \x00 " , " \x00 \x00 " , $timestamp , " \x00 \x00 \x00 \x00 " , 0 , 0 , self :: EXT_FILE_ATTR_DIR );
2021-02-14 03:39:15 +01:00
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
/**
* Add a file to the archive at the specified location and file name .
*
* @ param string $data File data .
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function addFile ( $data , $filePath , $timestamp = 0 , $fileComment = null )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
if ( is_resource ( $data ) && get_resource_type ( $data ) == 'stream' ) {
2015-10-25 03:35:37 +01:00
$this -> addLargeFile ( $data , $filePath , $timestamp , $fileComment );
2021-02-14 03:39:15 +01:00
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
$gzType = " \x08 \x00 " ; // Compression type 8 = deflate
$gpFlags = " \x00 \x00 " ; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression.
$dataLength = strlen ( $data );
2021-02-14 03:39:15 +01:00
$fileCRC32 = pack ( 'V' , crc32 ( $data ));
2015-10-25 03:35:37 +01:00
$gzData = gzcompress ( $data );
$gzData = substr ( substr ( $gzData , 0 , strlen ( $gzData ) - 4 ), 2 ); // gzcompress adds a 2 byte header and 4 byte CRC we can't use.
// The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag.
$gzLength = strlen ( $gzData );
if ( $gzLength >= $dataLength ) {
$gzLength = $dataLength ;
$gzData = $data ;
$gzType = " \x00 \x00 " ; // Compression type 0 = stored
$gpFlags = " \x00 \x00 " ; // Compression type 0 = stored
}
$this -> buildZipEntry ( $filePath , $fileComment , $gpFlags , $gzType , $timestamp , $fileCRC32 , $gzLength , $dataLength , self :: EXT_FILE_ATTR_FILE );
2021-02-14 03:39:15 +01:00
echo $gzData ;
2015-10-25 03:35:37 +01:00
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
/**
* Add the content to a directory .
*
* @ author Adam Schmalhofer < Adam . Schmalhofer @ gmx . de >
* @ author A . Grandt
*
2021-02-14 03:39:15 +01:00
* @ param string $realPath Path on the file system .
* @ param string $zipPath Filepath and name to be used in the archive .
2015-10-25 03:35:37 +01:00
* @ param bool $recursive Add content recursively , default is TRUE .
* @ param bool $followSymlinks Follow and add symbolic links , if they are accessible , default is TRUE .
* @ param array & $addedFiles Reference to the added files , this is used to prevent duplicates , efault is an empty array .
* If you start the function by parsing an array , the array will be populated with the realPath
* and zipPath kay / value pairs added to the archive by the function .
*/
2016-09-30 00:26:31 +02:00
public function addDirectoryContent ( $realPath , $zipPath , $recursive = true , $followSymlinks = true , & $addedFiles = [])
{
2021-02-14 03:39:15 +01:00
if ( file_exists ( $realPath ) && ! isset ( $addedFiles [ realpath ( $realPath )])) {
2015-10-25 03:35:37 +01:00
if ( is_dir ( $realPath )) {
$this -> addDirectory ( $zipPath );
}
$addedFiles [ realpath ( $realPath )] = $zipPath ;
$iter = new DirectoryIterator ( $realPath );
foreach ( $iter as $file ) {
if ( $file -> isDot ()) {
continue ;
}
$newRealPath = $file -> getPathname ();
$newZipPath = self :: pathJoin ( $zipPath , $file -> getFilename ());
2021-02-14 03:39:15 +01:00
if ( file_exists ( $newRealPath ) && ( $followSymlinks === true || ! is_link ( $newRealPath ))) {
2015-10-25 03:35:37 +01:00
if ( $file -> isFile ()) {
$addedFiles [ realpath ( $newRealPath )] = $newZipPath ;
$this -> addLargeFile ( $newRealPath , $newZipPath );
2021-02-14 03:39:15 +01:00
} elseif ( $recursive === true ) {
2015-10-25 03:35:37 +01:00
$this -> addDirectoryContent ( $newRealPath , $newZipPath , $recursive );
} else {
$this -> addDirectory ( $zipPath );
}
}
}
}
}
/**
* Add a file to the archive at the specified location and file name .
*
* @ param string $dataFile File name / path .
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function addLargeFile ( $dataFile , $filePath , $timestamp = 0 , $fileComment = null )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
if ( is_string ( $dataFile ) && is_file ( $dataFile )) {
$this -> processFile ( $dataFile , $filePath , $timestamp , $fileComment );
2021-02-14 03:39:15 +01:00
} elseif ( is_resource ( $dataFile ) && get_resource_type ( $dataFile ) == 'stream' ) {
2015-10-25 03:35:37 +01:00
$fh = $dataFile ;
$this -> openStream ( $filePath , $timestamp , $fileComment );
2021-02-14 03:39:15 +01:00
while ( ! feof ( $fh )) {
2015-10-25 03:35:37 +01:00
$this -> addStreamData ( fread ( $fh , $this -> streamChunkSize ));
}
$this -> closeStream ( $this -> addExtraField );
}
2021-02-14 03:39:15 +01:00
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
/**
* Create a stream to be used for large entries .
*
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function openStream ( $filePath , $timestamp = 0 , $fileComment = null )
{
2021-02-14 03:39:15 +01:00
if ( ! function_exists ( 'sys_get_temp_dir' )) {
die ( 'ERROR: Zip ' . self :: VERSION . ' requires PHP version 5.2.1 or above if large files are used.' );
2015-10-25 03:35:37 +01:00
}
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
if ( strlen ( $this -> streamFilePath ) > 0 ) {
closeStream ();
}
$this -> streamFile = tempnam ( sys_get_temp_dir (), 'ZipStream' );
2021-02-14 03:39:15 +01:00
$this -> streamData = fopen ( $this -> streamFile , 'wb' );
2015-10-25 03:35:37 +01:00
$this -> streamFilePath = $filePath ;
$this -> streamTimestamp = $timestamp ;
$this -> streamFileComment = $fileComment ;
$this -> streamFileLength = 0 ;
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
/**
* Add data to the open stream .
*
2021-02-14 03:39:15 +01:00
* @ param string $data
2015-10-25 03:35:37 +01:00
* @ return $length bytes added or FALSE if the archive is finalized or there are no open stream .
*/
2016-09-30 00:26:31 +02:00
public function addStreamData ( $data )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized || strlen ( $this -> streamFilePath ) == 0 ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
$length = fwrite ( $this -> streamData , $data , strlen ( $data ));
if ( $length != strlen ( $data )) {
2016-09-30 00:26:31 +02:00
die ( " <p>Length mismatch</p> \n " );
2015-10-25 03:35:37 +01:00
}
$this -> streamFileLength += $length ;
return $length ;
}
/**
* Close the current stream .
*
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function closeStream ()
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized || strlen ( $this -> streamFilePath ) == 0 ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
fflush ( $this -> streamData );
fclose ( $this -> streamData );
$this -> processFile ( $this -> streamFile , $this -> streamFilePath , $this -> streamTimestamp , $this -> streamFileComment );
$this -> streamData = null ;
$this -> streamFilePath = null ;
$this -> streamTimestamp = null ;
$this -> streamFileComment = null ;
$this -> streamFileLength = 0 ;
// Windows is a little slow at times, so a millisecond later, we can unlink this.
unlink ( $this -> streamFile );
$this -> streamFile = null ;
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
2016-09-30 00:26:31 +02:00
private function processFile ( $dataFile , $filePath , $timestamp = 0 , $fileComment = null )
{
2015-10-25 03:35:37 +01:00
if ( $this -> isFinalized ) {
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
$tempzip = tempnam ( sys_get_temp_dir (), 'ZipStream' );
$zip = new ZipArchive ;
2016-09-30 00:26:31 +02:00
if ( $zip -> open ( $tempzip ) === true ) {
2015-10-25 03:35:37 +01:00
$zip -> addFile ( $dataFile , 'file' );
$zip -> close ();
}
2021-02-14 03:39:15 +01:00
$file_handle = fopen ( $tempzip , 'rb' );
2015-10-25 03:35:37 +01:00
$stats = fstat ( $file_handle );
2021-02-14 03:39:15 +01:00
$eof = $stats [ 'size' ] - 72 ;
2015-10-25 03:35:37 +01:00
fseek ( $file_handle , 6 );
$gpFlags = fread ( $file_handle , 2 );
$gzType = fread ( $file_handle , 2 );
fread ( $file_handle , 4 );
$fileCRC32 = fread ( $file_handle , 4 );
2021-02-14 03:39:15 +01:00
$v = unpack ( 'Vval' , fread ( $file_handle , 4 ));
2015-10-25 03:35:37 +01:00
$gzLength = $v [ 'val' ];
2021-02-14 03:39:15 +01:00
$v = unpack ( 'Vval' , fread ( $file_handle , 4 ));
2015-10-25 03:35:37 +01:00
$dataLength = $v [ 'val' ];
$this -> buildZipEntry ( $filePath , $fileComment , $gpFlags , $gzType , $timestamp , $fileCRC32 , $gzLength , $dataLength , self :: EXT_FILE_ATTR_FILE );
fseek ( $file_handle , 34 );
$pos = 34 ;
2021-02-14 03:39:15 +01:00
while ( ! feof ( $file_handle ) && $pos < $eof ) {
2015-10-25 03:35:37 +01:00
$datalen = $this -> streamChunkSize ;
if ( $pos + $this -> streamChunkSize > $eof ) {
2021-02-14 03:39:15 +01:00
$datalen = $eof - $pos ;
2015-10-25 03:35:37 +01:00
}
echo fread ( $file_handle , $datalen );
$pos += $datalen ;
flush ();
}
fclose ( $file_handle );
unlink ( $tempzip );
}
/**
* Close the archive .
* A closed archive can no longer have new files added to it .
* @ return bool $success
*/
2016-09-30 00:26:31 +02:00
public function finalize ()
{
2021-02-14 03:39:15 +01:00
if ( ! $this -> isFinalized ) {
2015-10-25 03:35:37 +01:00
if ( strlen ( $this -> streamFilePath ) > 0 ) {
$this -> closeStream ();
}
2021-02-14 03:39:15 +01:00
$cdRecSize = pack ( 'v' , count ( $this -> cdRec ));
2015-10-25 03:35:37 +01:00
2021-02-14 03:39:15 +01:00
$cd = implode ( '' , $this -> cdRec );
echo $cd ;
echo self :: ZIP_END_OF_CENTRAL_DIRECTORY ;
echo $cdRecSize . $cdRecSize ;
echo pack ( 'VV' , strlen ( $cd ), $this -> offset );
if ( ! empty ( $this -> zipComment )) {
echo pack ( 'v' , strlen ( $this -> zipComment ));
echo $this -> zipComment ;
2015-10-25 03:35:37 +01:00
} else {
2021-02-14 03:39:15 +01:00
echo " \x00 \x00 " ;
2015-10-25 03:35:37 +01:00
}
flush ();
2016-09-30 00:26:31 +02:00
$this -> isFinalized = true ;
2015-10-25 03:35:37 +01:00
$cd = null ;
$this -> cdRec = null ;
2016-09-30 00:26:31 +02:00
return true ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
2016-09-30 00:26:31 +02:00
return false ;
2015-10-25 03:35:37 +01:00
}
/**
* Calculate the 2 byte dostime used in the zip entries .
*
* @ param int $timestamp
* @ return 2 - byte encoded DOS Date
*/
2016-09-30 00:26:31 +02:00
private function getDosTime ( $timestamp = 0 )
{
2021-02-14 03:39:15 +01:00
$timestamp = ( int ) $timestamp ;
2015-10-25 03:35:37 +01:00
$oldTZ = @ date_default_timezone_get ();
date_default_timezone_set ( 'UTC' );
$date = ( $timestamp == 0 ? getdate () : getdate ( $timestamp ));
date_default_timezone_set ( $oldTZ );
2021-02-14 03:39:15 +01:00
if ( $date [ 'year' ] >= 1980 ) {
return pack ( 'V' , (( $date [ 'mday' ] + ( $date [ 'mon' ] << 5 ) + (( $date [ 'year' ] - 1980 ) << 9 )) << 16 ) |
(( $date [ 'seconds' ] >> 1 ) + ( $date [ 'minutes' ] << 5 ) + ( $date [ 'hours' ] << 11 )));
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
2015-10-25 03:35:37 +01:00
return " \x00 \x00 \x00 \x00 " ;
}
/**
2021-02-14 03:39:15 +01:00
* Build the Zip file structures .
2015-10-25 03:35:37 +01:00
*
2021-02-14 03:39:15 +01:00
* @ param string $filePath
* @ param string $fileComment
* @ param string $gpFlags
* @ param string $gzType
2015-10-25 03:35:37 +01:00
* @ param int $timestamp
* @ param string $fileCRC32
* @ param int $gzLength
* @ param int $dataLength
2021-02-14 03:39:15 +01:00
* @ param int $extFileAttr Use self :: EXT_FILE_ATTR_FILE for files , self :: EXT_FILE_ATTR_DIR for Directories .
2015-10-25 03:35:37 +01:00
*/
2016-09-30 00:26:31 +02:00
private function buildZipEntry ( $filePath , $fileComment , $gpFlags , $gzType , $timestamp , $fileCRC32 , $gzLength , $dataLength , $extFileAttr )
{
2021-02-14 03:39:15 +01:00
$filePath = str_replace ( '\\' , '/' , $filePath );
2015-10-25 03:35:37 +01:00
$fileCommentLength = ( empty ( $fileComment ) ? 0 : strlen ( $fileComment ));
2021-02-14 03:39:15 +01:00
$timestamp = ( int ) $timestamp ;
2015-10-25 03:35:37 +01:00
$timestamp = ( $timestamp == 0 ? time () : $timestamp );
$dosTime = $this -> getDosTime ( $timestamp );
2021-02-14 03:39:15 +01:00
$tsPack = pack ( 'V' , $timestamp );
2015-10-25 03:35:37 +01:00
$ux = " \x75 \x78 \x0B \x00 \x01 \x04 \xE8 \x03 \x00 \x00 \x04 \x00 \x00 \x00 \x00 " ;
2021-02-14 03:39:15 +01:00
if ( ! isset ( $gpFlags ) || strlen ( $gpFlags ) != 2 ) {
2015-10-25 03:35:37 +01:00
$gpFlags = " \x00 \x00 " ;
}
2021-02-14 03:39:15 +01:00
$isFileUTF8 = mb_check_encoding ( $filePath , 'UTF-8' ) && ! mb_check_encoding ( $filePath , 'ASCII' );
$isCommentUTF8 = ! empty ( $fileComment ) && mb_check_encoding ( $fileComment , 'UTF-8' ) && ! mb_check_encoding ( $fileComment , 'ASCII' );
2015-10-25 03:35:37 +01:00
if ( $isFileUTF8 || $isCommentUTF8 ) {
$flag = 0 ;
2021-02-14 03:39:15 +01:00
$gpFlagsV = unpack ( 'vflags' , $gpFlags );
2015-10-25 03:35:37 +01:00
if ( isset ( $gpFlagsV [ 'flags' ])) {
$flag = $gpFlagsV [ 'flags' ];
}
2021-02-14 03:39:15 +01:00
$gpFlags = pack ( 'v' , $flag | ( 1 << 11 ));
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
$header = $gpFlags . $gzType . $dosTime . $fileCRC32
. pack ( 'VVv' , $gzLength , $dataLength , strlen ( $filePath )); // File name length
2015-10-25 03:35:37 +01:00
2021-02-14 03:39:15 +01:00
$zipEntry = self :: ZIP_LOCAL_FILE_HEADER ;
2015-10-25 03:35:37 +01:00
$zipEntry .= self :: ATTR_VERSION_TO_EXTRACT ;
$zipEntry .= $header ;
$zipEntry .= $this -> addExtraField ? " \x1C \x00 " : " \x00 \x00 " ; // Extra field length
$zipEntry .= $filePath ; // FileName
// Extra fields
if ( $this -> addExtraField ) {
2021-02-14 03:39:15 +01:00
$zipEntry .= " \x55 \x54 \x09 \x00 \x03 " . $tsPack . $tsPack . $ux ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
echo $zipEntry ;
2015-10-25 03:35:37 +01:00
2021-02-14 03:39:15 +01:00
$cdEntry = self :: ZIP_CENTRAL_FILE_HEADER ;
2015-10-25 03:35:37 +01:00
$cdEntry .= self :: ATTR_MADE_BY_VERSION ;
$cdEntry .= ( $dataLength === 0 ? " \x0A \x00 " : self :: ATTR_VERSION_TO_EXTRACT );
$cdEntry .= $header ;
$cdEntry .= $this -> addExtraField ? " \x18 \x00 " : " \x00 \x00 " ; // Extra field length
2021-02-14 03:39:15 +01:00
$cdEntry .= pack ( 'v' , $fileCommentLength ); // File comment length
2015-10-25 03:35:37 +01:00
$cdEntry .= " \x00 \x00 " ; // Disk number start
$cdEntry .= " \x00 \x00 " ; // internal file attributes
$cdEntry .= $extFileAttr ; // External file attributes
2021-02-14 03:39:15 +01:00
$cdEntry .= pack ( 'V' , $this -> offset ); // Relative offset of local header
2015-10-25 03:35:37 +01:00
$cdEntry .= $filePath ; // FileName
// Extra fields
if ( $this -> addExtraField ) {
2021-02-14 03:39:15 +01:00
$cdEntry .= " \x55 \x54 \x05 \x00 \x03 " . $tsPack . $ux ;
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
if ( ! empty ( $fileComment )) {
2015-10-25 03:35:37 +01:00
$cdEntry .= $fileComment ; // Comment
}
$this -> cdRec [] = $cdEntry ;
$this -> offset += strlen ( $zipEntry ) + $gzLength ;
}
/**
* Join $file to $dir path , and clean up any excess slashes .
*
2021-02-14 03:39:15 +01:00
* @ param string $dir
* @ param string $file
2015-10-25 03:35:37 +01:00
*/
2016-09-30 00:26:31 +02:00
public static function pathJoin ( $dir , $file )
{
2015-10-25 03:35:37 +01:00
if ( empty ( $dir ) || empty ( $file )) {
2021-02-14 03:39:15 +01:00
return self :: getRelativePath ( $dir . $file );
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
return self :: getRelativePath ( $dir . '/' . $file );
2015-10-25 03:35:37 +01:00
}
/**
* Clean up a path , removing any unnecessary elements such as /./ , // or redundant ../ segments.
* If the path starts with a " / " , it is deemed an absolute path and any /../ in the beginning is stripped off .
* The returned path will not end in a " / " .
*
2021-02-14 03:39:15 +01:00
* @ param string $path The path to clean up
* @ return string the clean path
2015-10-25 03:35:37 +01:00
*/
2016-09-30 00:26:31 +02:00
public static function getRelativePath ( $path )
{
2021-02-14 03:39:15 +01:00
$path = preg_replace ( " #/+ \ .?/+# " , '/' , str_replace ( '\\' , '/' , $path ));
$dirs = explode ( '/' , rtrim ( preg_replace ( '#^(?:\./)+#' , '' , $path ), '/' ));
2015-10-25 03:35:37 +01:00
$offset = 0 ;
$sub = 0 ;
$subOffset = 0 ;
2021-02-14 03:39:15 +01:00
$root = '' ;
2015-10-25 03:35:37 +01:00
if ( empty ( $dirs [ 0 ])) {
2021-02-14 03:39:15 +01:00
$root = '/' ;
2015-10-25 03:35:37 +01:00
$dirs = array_splice ( $dirs , 1 );
2021-02-14 03:39:15 +01:00
} elseif ( preg_match ( '#[A-Za-z]:#' , $dirs [ 0 ])) {
$root = strtoupper ( $dirs [ 0 ]) . '/' ;
2015-10-25 03:35:37 +01:00
$dirs = array_splice ( $dirs , 1 );
}
2016-09-30 00:26:31 +02:00
$newDirs = [];
2015-10-25 03:35:37 +01:00
foreach ( $dirs as $dir ) {
2021-02-14 03:39:15 +01:00
if ( $dir !== '..' ) {
2015-10-25 03:35:37 +01:00
$subOffset -- ;
$newDirs [ ++ $offset ] = $dir ;
} else {
$subOffset ++ ;
if ( -- $offset < 0 ) {
$offset = 0 ;
if ( $subOffset > $sub ) {
$sub ++ ;
}
}
}
}
if ( empty ( $root )) {
2021-02-14 03:39:15 +01:00
$root = str_repeat ( '../' , $sub );
2015-10-25 03:35:37 +01:00
}
2021-02-14 03:39:15 +01:00
return $root . implode ( '/' , array_slice ( $newDirs , 0 , $offset ));
2015-10-25 03:35:37 +01:00
}
2015-08-30 15:01:12 +02:00
}