Your IP : 3.145.99.55
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2023 Bitrix
*/
IncludeModuleLangFile(__FILE__);
class CZip implements IBXArchive
{
const ReadBlockSize = 2048;
public $zipname = '';
public $zipfile = 0;
private CBXVirtualIo $io;
private $arErrors = [];
private $fileSystemEncoding;
private $startFile;
private $arHeaders;
//should be changed via SetOptions
private $compress = true;
private $remove_path = "";
private $add_path = "";
private $replaceExistentFiles = false;
private $checkPermissions = true;
private $rule = [];
private $step_time = 30;
private $arPackedFiles = [];
private $arPackedFilesData = [];
public function __construct($pzipname)
{
$this->io = CBXVirtualIo::GetInstance();
$this->zipname = $this->_convertWinPath($pzipname, false);
$this->_errorReset();
$this->fileSystemEncoding = $this->_getfileSystemEncoding();
}
/**
* Packs files and folders into archive
* @param array $arFileList containing files and folders to be packed into archive
* @param string $startFile - if specified then all files before it won't be packed during the traversing of $arFileList. Can be used for multistep archivation
* @return mixed 0 or false if error, 1 if success, 2 if the next step should be performed. Errors can be seen using GetErrors() method
*/
public function Pack($arFileList, $startFile = "")
{
$this->_errorReset();
$this->startFile = $this->io->GetPhysicalName($startFile);
$this->arPackedFiles = [];
$this->arHeaders = [];
$arCentralDirInfo = [];
$zipfile_tmp = $zipname_tmp = '';
$isNewArchive = true;
if ($startFile != "" && is_file($this->io->GetPhysicalName($this->zipname)))
{
$isNewArchive = false;
}
if ($isNewArchive)
{
if (!$this->_openFile("wb"))
{
return false;
}
}
else
{
if (!$this->_openFile("rb"))
{
return false;
}
// read the central directory
if (($res = $this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
$this->_closeFile();
return $res;
}
@rewind($this->zipfile);
//creating tmp file
$zipname_tmp = GetDirPath($this->zipname) . uniqid('ziparc') . '.tmp';
if (($zipfile_tmp = @fopen($this->io->GetPhysicalName($zipname_tmp), 'wb')) == 0)
{
$this->_closeFile();
$this->_errorLog("ERR_READ_TMP", str_replace("#FILE_NAME#", removeDocRoot($zipname_tmp), GetMessage("MAIN_ZIP_ERR_READ_TMP")));
return $this->arErrors;
}
//copy files from the archive to the tmp file
$size = $arCentralDirInfo['offset'];
while ($size != 0)
{
$length = ($size < self::ReadBlockSize ? $size : self::ReadBlockSize);
$buffer = fread($this->zipfile, $length);
@fwrite($zipfile_tmp, $buffer, $length);
$size -= $length;
}
//swapping file handle to use methods on the temporary file, not the real archive
$tmp_id = $this->zipfile;
$this->zipfile = $zipfile_tmp;
$zipfile_tmp = $tmp_id;
}
//starting measuring start time from here (only packing time)
define("ZIP_START_TIME", microtime(true));
$this->tempres = null;
$arFileList = $this->_parseFileParams($arFileList);
$arConvertedFileList = [];
foreach ($arFileList as $fullpath)
{
$arConvertedFileList[] = $this->io->GetPhysicalName($fullpath);
}
$packRes = null;
if (is_array($arFileList) && !empty($arFileList))
{
$packRes = $this->_processFiles($arConvertedFileList, $this->add_path, $this->remove_path);
}
if ($isNewArchive)
{
//writing Central Directory
//save central directory offset
$offset = @ftell($this->zipfile);
//make central dir files header
for ($i = 0, $counter = 0; $i < sizeof($this->arPackedFiles); $i++)
{
//write file header
if ($this->arHeaders[$i]['status'] == 'ok')
{
if (($res = $this->_writeCentralFileHeader($this->arHeaders[$i])) != 1)
{
return $res;
}
$counter++;
}
$this->_convertHeader2FileInfo($this->arHeaders[$i], $this->arPackedFilesData[$i]);
}
$zip_comment = '';
//calculate the size of the central header
$size = @ftell($this->zipfile) - $offset;
//make central dir footer
if (($res = $this->_writeCentralHeader($counter, $size, $offset, $zip_comment)) != 1)
{
$this->arHeaders = null;
return $res;
}
}
else
{
//save the offset of the central dir
$offset = @ftell($this->zipfile);
//copy file headers block from the old archive
$size = $arCentralDirInfo['size'];
while ($size != 0)
{
$length = ($size < self::ReadBlockSize ? $size : self::ReadBlockSize);
$buffer = @fread($zipfile_tmp, $length);
@fwrite($this->zipfile, $buffer, $length);
$size -= $length;
}
//add central dir files header
for ($i = 0, $counter = 0; $i < sizeof($this->arHeaders); $i++)
{
//create the file header
if ($this->arHeaders[$i]['status'] == 'ok')
{
if (($res = $this->_writeCentralFileHeader($this->arHeaders[$i])) != 1)
{
fclose($zipfile_tmp);
$this->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
$counter++;
}
//convert header to the usable format
$this->_convertHeader2FileInfo($this->arHeaders[$i], $this->arPackedFilesData[$i]);
}
$zip_comment = '';
//find the central header size
$size = @ftell($this->zipfile) - $offset;
//make central directory footer
if (($res = $this->_writeCentralHeader($counter + $arCentralDirInfo['entries'], $size, $offset, $zip_comment)) != 1)
{
//clear file list
$this->arHeaders = null;
return $res;
}
//changing file handler back
$tmp_id = $this->zipfile;
$this->zipfile = $zipfile_tmp;
$zipfile_tmp = $tmp_id;
$this->_closeFile();
@fclose($zipfile_tmp);
// @unlink($this->zipname);
//probably test the result @rename($zipname_tmp, $this->zipname);
$this->_renameTmpFile($zipname_tmp, $this->zipname);
}
if ($isNewArchive && ($res === false))
{
$this->_cleanFile();
}
else
{
$this->_closeFile();
}
//if packing is not completed, remember last file
if ($packRes === 'continue')
{
$this->startFile = $this->io->GetLogicalName(array_pop($this->arPackedFiles));
}
if ($packRes === false)
{
return IBXArchive::StatusError;
}
elseif ($packRes && $this->startFile == "")
{
return IBXArchive::StatusSuccess;
}
elseif ($packRes && $this->startFile != "")
{
//call Pack() with $this->GetStartFile() next time to continue
return IBXArchive::StatusContinue;
}
return null;
}
private function _haveTime()
{
return microtime(true) - ZIP_START_TIME < $this->step_time;
}
private function _processFiles($arFileList, $addPath, $removePath)
{
$addPath = str_replace("\\", "/", $addPath);
$removePath = str_replace("\\", "/", $removePath);
if (!$this->zipfile)
{
$this->arErrors[] = ["ERR_DFILE", GetMessage("MAIN_ZIP_ERR_DFILE")];
return false;
}
if (!is_array($arFileList) || empty($arFileList))
{
return true;
}
$j = -1;
if (!isset($this->tempres))
{
$this->tempres = "started";
}
//files and directory scan
while ($j++ < count($arFileList) && ($this->tempres === "started"))
{
$filename = $arFileList[$j] ?? '';
if ($filename == '')
{
continue;
}
if (!file_exists($filename))
{
$this->arErrors[] = ["ERR_NO_FILE", str_replace("#FILE_NAME#", $filename, GetMessage("MAIN_ZIP_ERR_NO_FILE"))];
continue;
}
//is a file
if (!@is_dir($filename))
{
$filename = str_replace("//", "/", $filename);
//jumping to startFile, if it's specified
if ($this->startFile <> '')
{
if ($filename != $this->startFile)
{
//don't pack - jump to the next file
continue;
}
else
{
//if startFile is found, continue to pack files and folders without startFile, starting from next
$this->startFile = null;
continue;
}
}
//check product permissions
if ($this->checkPermissions)
{
if (!CBXArchive::HasAccess($filename, true))
{
continue;
}
}
if ($this->_haveTime())
{
if (!$this->_addFile($filename, $arFileHeaders, $this->add_path, $this->remove_path))
{
//$arErrors is filled in the _addFile method
$this->tempres = false;
}
else
{
//remember last file
$this->arPackedFiles[] = $filename;
$this->arHeaders[] = $arFileHeaders;
}
}
else
{
$this->tempres = 'continue';
return $this->tempres;
}
}
//if directory
else
{
if (!($handle = opendir($filename)))
{
$this->arErrors[] = ["ERR_DIR_OPEN_FAIL", str_replace("#DIR_NAME#", $filename, GetMessage("MAIN_ZIP_ERR_DIR_OPEN_FAIL"))];
continue;
}
if ($this->checkPermissions)
{
if (!CBXArchive::HasAccess($filename, false))
{
continue;
}
}
while (false !== ($dir = readdir($handle)))
{
if ($dir != "." && $dir != "..")
{
$arFileList_tmp = [];
if ($filename != ".")
{
$arFileList_tmp[] = $filename . '/' . $dir;
}
else
{
$arFileList_tmp[] = $dir;
}
$this->_processFiles($arFileList_tmp, $addPath, $removePath);
}
}
unset($arFileList_tmp);
unset($dir);
unset($handle);
}
}
return $this->tempres;
}
/**
* Called from the archive object it returns the name of the file for the next step during multistep archivation. Call if Pack method returned 2
* @return string path to file
*/
public function GetStartFile()
{
return $this->startFile;
}
/**
* Unpacks archive into specified folder
* @param string $strPath - path to the directory to unpack archive to
* @return mixed 0 or false if error, 1 if success. Errors can be seen using GetErrors() method
*/
public function Unpack($strPath)
{
$this->SetOptions(["ADD_PATH" => $strPath]);
$rule = $this->GetOptions()['RULE'];
$arParams = [
"add_path" => $this->add_path,
"remove_path" => $this->remove_path,
"extract_as_string" => false,
"remove_all_path" => false,
"callback_pre_extract" => "",
"callback_post_extract" => "",
"set_chmod" => 0,
"by_name" => "",
"by_index" => array_key_exists("by_index", $rule) ? $rule['by_index'] : "",
"by_preg" => "",
];
@set_time_limit(0);
$result = $this->Extract($arParams);
if ($result === 0)
{
return false;
}
//if there was no error, but we didn't extract any file ($this->replaceExistentFile = false)
else
{
if ($result == [])
{
return true;
}
else
{
return $result;
}
}
}
/**
* Lets the user define packing/unpacking options
* @param array $arOptions an array with the options' names and their values
* @return void
*/
public function SetOptions($arOptions)
{
if (array_key_exists("COMPRESS", $arOptions))
{
$this->compress = $arOptions["COMPRESS"] === true;
}
if (array_key_exists("ADD_PATH", $arOptions))
{
$this->add_path = $this->io->GetPhysicalName(str_replace("\\", "/", strval($arOptions["ADD_PATH"])));
}
if (array_key_exists("REMOVE_PATH", $arOptions))
{
$this->remove_path = $this->io->GetPhysicalName(str_replace("\\", "/", strval($arOptions["REMOVE_PATH"])));
}
if (array_key_exists("STEP_TIME", $arOptions))
{
$this->step_time = floatval($arOptions["STEP_TIME"]);
}
if (array_key_exists("UNPACK_REPLACE", $arOptions))
{
$this->replaceExistentFiles = $arOptions["UNPACK_REPLACE"] === true;
}
if (array_key_exists("CHECK_PERMISSIONS", $arOptions))
{
$this->checkPermissions = $arOptions["CHECK_PERMISSIONS"] === true;
}
if (array_key_exists("RULE", $arOptions))
{
$this->rule = $arOptions["RULE"];
}
}
/**
* Returns an array of packing/unpacking options and their current values
* @return array
*/
public function GetOptions()
{
$arOptions = [
"COMPRESS" => $this->compress,
"ADD_PATH" => $this->add_path,
"REMOVE_PATH" => $this->remove_path,
"STEP_TIME" => $this->step_time,
"UNPACK_REPLACE" => $this->replaceExistentFiles,
"CHECK_PERMISSIONS" => $this->checkPermissions,
"RULE" => $this->rule,
];
return $arOptions;
}
/**
* Returns an array containing error codes and messages. Call this method after Pack or Unpack
* @return array
*/
public function GetErrors()
{
return $this->arErrors;
}
/**
* Creates an archive
* @param array $arFileList containing files and folders to be added to the archive
* @param array|int $arParams an array of parameters
* @return array|int 0 if error, array $arResultList with packed files if success
*/
public function Create($arFileList, $arParams = 0)
{
$this->_errorReset();
if ($arParams === 0)
{
$arParams = [];
}
if ($this->_checkParams($arParams,
['no_compression' => false,
'add_path' => "",
'remove_path' => "",
'remove_all_path' => false]) != 1)
{
return 0;
}
$arResultList = [];
if (is_array($arFileList))
{
$res = $this->_createArchive($arFileList, $arResultList, $arParams);
}
else
{
if (is_string($arFileList))
{
$arTmpList = explode(",", $arFileList);
$res = $this->_createArchive($arTmpList, $arResultList, $arParams);
}
else
{
$this->_errorLog("ERR_PARAM", GetMessage("MAIN_ZIP_ERR_PARAM"));
$res = "ERR_PARAM";
}
}
if ($res != 1)
{
return 0;
}
return $arResultList;
}
/**
* Archives files and folders
* @param array $arFileList containing files and folders to be packed into archive
* @param array|int $arParams - if specified contains options to use for archivation
* @return array|int 0 or false if error, array with the list of packed files and folders if success. Errors can be seen using GetErrors() method
*/
public function Add($arFileList, $arParams = 0)
{
$this->_errorReset();
if ($arParams === 0)
{
$arParams = [];
}
if ($this->_checkParams($arParams,
['no_compression' => false,
'add_path' => '',
'remove_path' => '',
'remove_all_path' => false,
'callback_pre_add' => '',
'callback_post_add' => '']) != 1)
{
return 0;
}
$arResultList = [];
if (is_array($arFileList))
{
$res = $this->_addData($arFileList, $arResultList, $arParams);
}
else
{
if (is_string($arFileList))
{
$arTmpList = explode(",", $arFileList);
$res = $this->_addData($arTmpList, $arResultList, $arParams);
}
else
{
$this->_errorLog("ERR_PARAM_LIST", GetMessage("MAIN_ZIP_ERR_PARAM_LIST"));
$res = "ERR_PARAM_LIST";
}
}
if ($res != 1)
{
return 0;
}
return $arResultList;
}
/**
* Returns the list of files and folders in the archive
* @return array|int 0 if error, array of results if success
*/
public function GetContent()
{
$this->_errorReset();
if (!$this->_checkFormat())
{
return (0);
}
$arTmpList = [];
if ($this->_getFileList($arTmpList) != 1)
{
unset($arTmpList);
return (0);
}
return $arTmpList;
}
/**
* Extracts archive content
* @param array|int $arParams an array of parameters
* @return array|int 0 or false if error, array of extracted files and folders if success. Errors can be seen using GetErrors() method
*/
public function Extract($arParams = 0)
{
$this->_errorReset();
if (!$this->_checkFormat())
{
return (0);
}
if ($arParams === 0)
{
$arParams = [];
}
if ($this->_checkParams($arParams,
['extract_as_string' => false,
'add_path' => '',
'remove_path' => '',
'remove_all_path' => false,
'callback_pre_extract' => '',
'callback_post_extract' => '',
'set_chmod' => 0,
'by_name' => '',
'by_index' => '',
'by_preg' => '']) != 1)
{
return 0;
}
$arTmpList = [];
if ($this->_extractByRule($arTmpList, $arParams) != 1)
{
unset($arTmpList);
return (0);
}
return $arTmpList;
}
/**
* Deletes a file from the archive
* @param array $arParams rules defining which files should be deleted
* @return array|int 0 if error, array $arResultList with deleted files if success
*/
public function Delete($arParams)
{
$this->_errorReset();
if (!$this->_checkFormat())
{
return (0);
}
if ($this->_checkParams($arParams, ['by_name' => '', 'by_index' => '', 'by_preg' => '']) != 1)
{
return 0;
}
//at least one rule should be set
if (($arParams['by_name'] == '') && ($arParams['by_index'] == '') && ($arParams['by_preg'] == ''))
{
$this->_errorLog("ERR_PARAM_RULE", GetMessage("MAIN_ZIP_ERR_PARAM_RULE"));
return 0;
}
$arTmpList = [];
if ($this->_deleteByRule($arTmpList, $arParams) != 1)
{
unset($arTmpList);
return (0);
}
return $arTmpList;
}
/**
* Returns archive properties
* @return array|int 0 if error, array $arProperties if success
*/
public function GetProperties()
{
$this->_errorReset();
if (!$this->_checkFormat())
{
return (0);
}
$arProperties = [];
$arProperties['comment'] = '';
$arProperties['nb'] = 0;
$arProperties['status'] = 'not_exist';
if (@is_file($this->io->GetPhysicalName($this->zipname)))
{
if (($this->zipfile = @fopen($this->io->GetPhysicalName($this->zipname), 'rb')) == 0)
{
$this->_errorLog("ERR_READ", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_READ")));
return 0;
}
//read central directory info
$arCentralDirInfo = [];
if (($this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
return 0;
}
$this->_closeFile();
//set user attributes
$arProperties['comment'] = $arCentralDirInfo['comment'];
$arProperties['nb'] = $arCentralDirInfo['entries'];
$arProperties['status'] = 'ok';
}
return $arProperties;
}
private function _checkFormat()
{
$this->_errorReset();
if (!is_file($this->io->GetPhysicalName($this->zipname)))
{
$this->_errorLog("ERR_MISSING_FILE", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_MISSING_FILE")));
return (false);
}
if (!is_readable($this->io->GetPhysicalName($this->zipname)))
{
$this->_errorLog("ERR_READ", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_READ")));
return (false);
}
//possible checks: magic code, central header, each file header
return true;
}
private function _createArchive($arFilesList, &$arResultList, &$arParams)
{
$addDir = $arParams['add_path'];
$removeDir = $arParams['remove_path'];
$removeAllDir = $arParams['remove_all_path'];
if (($res = $this->_openFile('wb')) != 1)
{
return $res;
}
$res = $this->_addList($arFilesList, $arResultList, $addDir, $removeDir, $removeAllDir, $arParams);
$this->_closeFile();
return $res;
}
private function _addData($arFilesList, &$arResultList, &$arParams)
{
$addDir = $arParams['add_path'];
$removeDir = $arParams['remove_path'];
$removeAllDir = $arParams['remove_all_path'];
if ((!is_file($this->io->GetPhysicalName($this->zipname))) || (filesize($this->io->GetPhysicalName($this->zipname)) == 0))
{
$res = $this->_createArchive($arFilesList, $arResultList, $arParams);
return $res;
}
if (($res = $this->_openFile('rb')) != 1)
{
return $res;
}
$arCentralDirInfo = [];
if (($res = $this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
$this->_closeFile();
return $res;
}
@rewind($this->zipfile);
$zipname_tmp = GetDirPath($this->zipname) . uniqid('ziparc') . '.tmp';
if (($zipfile_tmp = @fopen($this->io->GetPhysicalName($zipname_tmp), 'wb')) == 0)
{
$this->_closeFile();
$this->_errorLog("ERR_READ_TMP", str_replace("#FILE_NAME#", removeDocRoot($zipname_tmp), GetMessage("MAIN_ZIP_ERR_READ_TMP")));
return $this->arErrors;
}
//copy files from archive to the tmp file
$size = $arCentralDirInfo['offset'];
while ($size != 0)
{
$length = ($size < self::ReadBlockSize ? $size : self::ReadBlockSize);
$buffer = fread($this->zipfile, $length);
@fwrite($zipfile_tmp, $buffer, $length);
$size -= $length;
}
//changing file handles to use methods on the temporary file, not the real archive
$tmp_id = $this->zipfile;
$this->zipfile = $zipfile_tmp;
$zipfile_tmp = $tmp_id;
$arHeaders = [];
if (($res = $this->_addFileList($arFilesList, $arHeaders,
$addDir, $removeDir,
$removeAllDir, $arParams)) != 1)
{
fclose($zipfile_tmp);
$this->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
//save central dir offset
$offset = @ftell($this->zipfile);
//copy file headers block from the old archive
$size = $arCentralDirInfo['size'];
while ($size != 0)
{
$length = ($size < self::ReadBlockSize ? $size : self::ReadBlockSize);
$buffer = @fread($zipfile_tmp, $length);
@fwrite($this->zipfile, $buffer, $length);
$size -= $length;
}
//write central dir files header
for ($i = 0, $counter = 0; $i < sizeof($arHeaders); $i++)
{
//add the file header
if ($arHeaders[$i]['status'] == 'ok')
{
if (($res = $this->_writeCentralFileHeader($arHeaders[$i])) != 1)
{
fclose($zipfile_tmp);
$this->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
$counter++;
}
$this->_convertHeader2FileInfo($arHeaders[$i], $arResultList[$i]);
}
$zip_comment = '';
//size of the central header
$size = @ftell($this->zipfile) - $offset;
//make central dir footer
if (($res = $this->_writeCentralHeader($counter + $arCentralDirInfo['entries'], $size, $offset, $zip_comment)) != 1)
{
//reset files list
unset($arHeaders);
return $res;
}
//change back file handler
$tmp_id = $this->zipfile;
$this->zipfile = $zipfile_tmp;
$zipfile_tmp = $tmp_id;
$this->_closeFile();
@fclose($zipfile_tmp);
@unlink($this->io->GetPhysicalName($this->zipname));
//possibly test the result @rename($zipname_tmp, $this->zipname);
$this->_renameTmpFile($zipname_tmp, $this->zipname);
return $res;
}
private function _openFile($mode)
{
$res = 1;
if ($this->zipfile != 0)
{
$this->_errorLog("ERR_OPEN", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_READ_OPEN")));
return $this->arErrors;
}
$this->_checkDirPath($this->zipname);
if (($this->zipfile = @fopen($this->io->GetPhysicalName($this->zipname), $mode)) == 0)
{
$this->_errorLog("ERR_READ_MODE", str_replace(["#FILE_NAME#", "#MODE#"], [removeDocRoot($this->zipname), $mode], GetMessage("MAIN_ZIP_ERR_READ_MODE")));
return $this->arErrors;
}
return $res;
}
private function _closeFile()
{
if ($this->zipfile != 0)
{
@fclose($this->zipfile);
}
$this->zipfile = 0;
}
private function _addList($arFilesList, &$arResultList, $addDir, $removeDir, $removeAllDir, &$arParams)
{
$arHeaders = [];
if (($res = $this->_addFileList($arFilesList, $arHeaders, $addDir, $removeDir, $removeAllDir, $arParams)) != 1)
{
return $res;
}
//save the offset of the central dir
$offset = @ftell($this->zipfile);
//make central dir files header
for ($i = 0, $counter = 0; $i < sizeof($arHeaders); $i++)
{
if ($arHeaders[$i]['status'] == 'ok')
{
if (($res = $this->_writeCentralFileHeader($arHeaders[$i])) != 1)
{
return $res;
}
$counter++;
}
$this->_convertHeader2FileInfo($arHeaders[$i], $arResultList[$i]);
}
$zip_comment = '';
//the size of the central header
$size = @ftell($this->zipfile) - $offset;
//add central dir footer
if (($res = $this->_writeCentralHeader($counter, $size, $offset, $zip_comment)) != 1)
{
unset($arHeaders);
return $res;
}
return $res;
}
private function _addFileList($arFilesList, &$arResultList, $addDir, $removeDir, $removeAllDir, &$arParams)
{
$res = 1;
$header = [];
//save the current number of elements in the result list
$count = sizeof($arResultList);
$filesListCount = count($arFilesList);
for ($j = 0; ($j < $filesListCount) && ($res == 1); $j++)
{
$filename = $this->_convertWinPath($arFilesList[$j], false);
//if empty - skip
if ($filename == "")
{
continue;
}
if (!file_exists($this->io->GetPhysicalName($filename)))
{
$this->_errorLog("ERR_MISSING_FILE", str_replace("#FILE_NAME#", removeDocRoot($filename), GetMessage("MAIN_ZIP_ERR_MISSING_FILE")));
return $this->arErrors;
}
if ((is_file($this->io->GetPhysicalName($filename))) || ((is_dir($this->io->GetPhysicalName($filename))) && !$removeAllDir))
{
if (($res = $this->_addFile($filename, $header, $addDir, $removeDir, $removeAllDir, $arParams)) != 1)
{
return $res;
}
//save file info
$arResultList[$count++] = $header;
}
if (is_dir($this->io->GetPhysicalName($filename)))
{
if ($filename != ".")
{
$path = $filename . "/";
}
else
{
$path = "";
}
//read the folder for files and subfolders
$hdir = opendir($this->io->GetPhysicalName($filename));
while ($hitem = readdir($hdir))
{
if ($hitem == '.' || $hitem == '..')
{
continue;
}
if (is_file($this->io->GetPhysicalName($path . $hitem)))
{
if (($res = $this->_addFile($path . $hitem, $header, $addDir, $removeDir, $removeAllDir, $arParams)) != 1)
{
return $res;
}
//save file info
$arResultList[$count++] = $header;
}
else
{
//should be ana array as a parameter
$arTmpList[0] = $path . $hitem;
$res = $this->_addFileList($arTmpList, $arResultList, $addDir, $removeDir, $removeAllDir, $arParams);
$count = sizeof($arResultList);
}
}
//unset variables for the recursive call
unset($arTmpList);
unset($hdir);
unset($hitem);
}
}
return $res;
}
private function _addFile($filename, &$arHeader, $addDir, $removeDir, $removeAllDir = false, $arParams = [])
{
$res = 1;
if ($filename == "")
{
$this->_errorLog("ERR_PARAM_LIST", GetMessage("MAIN_ZIP_ERR_PARAM_LIST"));
return $this->arErrors;
}
//saved filename
$storedFilename = $filename;
//remove the path
if ($removeAllDir)
{
$storedFilename = basename($filename);
}
else
{
if ($removeDir != "")
{
if (mb_substr($removeDir, -1) != '/')
{
$removeDir .= "/";
}
if ((mb_substr($filename, 0, 2) == "./") || (mb_substr($removeDir, 0, 2) == "./"))
{
if ((mb_substr($filename, 0, 2) == "./") && (mb_substr($removeDir, 0, 2) != "./"))
{
$removeDir = "./" . $removeDir;
}
if ((mb_substr($filename, 0, 2) != "./") && (mb_substr($removeDir, 0, 2) == "./"))
{
$removeDir = mb_substr($removeDir, 2);
}
}
$incl = $this->_containsPath($removeDir, $filename);
if ($incl > 0)
{
if ($incl == 2)
{
$storedFilename = "";
}
else
{
$storedFilename = mb_substr($filename, mb_strlen($removeDir));
}
}
}
}
if ($addDir != "")
{
if (mb_substr($addDir, -1) == "/")
{
$storedFilename = $addDir . $storedFilename;
}
else
{
$storedFilename = $addDir . "/" . $storedFilename;
}
}
//make the filename
$storedFilename = $this->_reducePath($storedFilename);
//save file properties
clearstatcache();
$arHeader['comment'] = '';
$arHeader['comment_len'] = 0;
$arHeader['compressed_size'] = 0;
$arHeader['compression'] = 0;
$arHeader['crc'] = 0;
$arHeader['disk'] = 0;
$arHeader['external'] = (is_file($filename) ? 0xFE49FFE0 : 0x41FF0010);
$arHeader['extra'] = '';
$arHeader['extra_len'] = 0;
$arHeader['filename'] = \Bitrix\Main\Text\Encoding::convertEncoding($filename, $this->fileSystemEncoding, "cp866");
$arHeader['filename_len'] = strlen(\Bitrix\Main\Text\Encoding::convertEncoding($filename, $this->fileSystemEncoding, "cp866"));
$arHeader['flag'] = 0;
$arHeader['index'] = -1;
$arHeader['internal'] = 0;
$arHeader['mtime'] = filemtime($filename);
$arHeader['offset'] = 0;
$arHeader['size'] = filesize($filename);
$arHeader['status'] = 'ok';
$arHeader['stored_filename'] = \Bitrix\Main\Text\Encoding::convertEncoding($storedFilename, $this->fileSystemEncoding, "cp866");
$arHeader['version'] = 20;
$arHeader['version_extracted'] = 10;
//pre-add callback
if ((isset($arParams['callback_pre_add'])) && ($arParams['callback_pre_add'] != ''))
{
//generate local information
$arLocalHeader = [];
$this->_convertHeader2FileInfo($arHeader, $arLocalHeader);
//callback call
eval('$res = ' . $arParams['callback_pre_add'] . '(\'callback_pre_add\', $arLocalHeader);');
//if res == 0 change the file status
if ($res == 0)
{
$arHeader['status'] = "skipped";
$res = 1;
}
//update the info, only some fields can be modified
if ($arHeader['stored_filename'] != $arLocalHeader['stored_filename'])
{
$arHeader['stored_filename'] = $this->_reducePath($arLocalHeader['stored_filename']);
}
}
//if stored filename is empty - filter
if ($arHeader['stored_filename'] == "")
{
$arHeader['status'] = "filtered";
}
//check path length
if (mb_strlen($arHeader['stored_filename']) > 0xFF)
{
$arHeader['status'] = 'filename_too_long';
}
//if no error
if ($arHeader['status'] == 'ok')
{
if (is_file($filename))
{
//reading source
if (($file = @fopen($filename, "rb")) == 0)
{
$this->_errorLog("ERR_READ", str_replace("#FILE_NAME#", removeDocRoot($filename), GetMessage("MAIN_ZIP_ERR_READ")));
return $this->arErrors;
}
//reading the file content
$content = $arHeader['size'] > 0 ? fread($file, $arHeader['size']) : '';
//calculating crc
$arHeader['crc'] = crc32($content);
//compress the file
$compressedContent = empty($arParams['no_compression']) ? gzdeflate($content) : $content;
//set header params
$arHeader['compressed_size'] = strlen($compressedContent);
$arHeader['compression'] = 8;
//generate header
if (($res = $this->_writeFileHeader($arHeader)) != 1)
{
@fclose($file);
return $res;
}
//writing the compressed content
$binary_data = pack('a' . $arHeader['compressed_size'], $compressedContent);
@fwrite($this->zipfile, $binary_data, $arHeader['compressed_size']);
@fclose($file);
}
//if directory
else
{
//set file properties
$arHeader['filename'] .= '/';
$arHeader['filename_len']++;
$arHeader['size'] = 0;
//folder value. to be checked
$arHeader['external'] = 0x41FF0010;
//generate header
if (($res = $this->_writeFileHeader($arHeader)) != 1)
{
return $res;
}
}
}
//pre-add callack
if ((isset($arParams['callback_post_add'])) && ($arParams['callback_post_add'] != ''))
{
//make local info
$arLocalHeader = [];
$this->_convertHeader2FileInfo($arHeader, $arLocalHeader);
//callback call
eval('$res = ' . $arParams['callback_post_add'] . '(\'callback_post_add\', $arLocalHeader);');
if ($res == 0)
{
$res = 1;
} //ignored
}
return $res;
}
private function _writeFileHeader(&$arHeader)
{
$res = 1;
//to be checked: for(reset($arHeader); $key = key($arHeader); next($arHeader))
//save offset position of the file
$arHeader['offset'] = ftell($this->zipfile);
//transform unix modification time to the dos mdate/mtime format
$date = getdate($arHeader['mtime']);
$mtime = ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;
$mdate = (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];
// $arHeader["stored_filename"] = "12345678.gif";
//pack data
$binary_data = pack("VvvvvvVVVvv",
0x04034b50,
$arHeader['version'],
$arHeader['flag'],
$arHeader['compression'],
$mtime,
$mdate,
$arHeader['crc'],
$arHeader['compressed_size'],
$arHeader['size'],
strlen($arHeader['stored_filename']),
$arHeader['extra_len']
);
//write first 148 bytes of the header in the archive
fputs($this->zipfile, $binary_data, 30);
//write the variable fields
if ($arHeader['stored_filename'] <> '')
{
fputs($this->zipfile, $arHeader['stored_filename'], strlen($arHeader['stored_filename']));
}
if ($arHeader['extra_len'] != 0)
{
fputs($this->zipfile, $arHeader['extra'], $arHeader['extra_len']);
}
return $res;
}
private function _writeCentralFileHeader($arHeader)
{
$res = 1;
//to be checked: for(reset($arHeader); $key = key($arHeader); next($arHeader)) {}
//convert unix mtime to dos mdate/mtime
$date = getdate($arHeader['mtime']);
$mtime = ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;
$mdate = (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];
//pack data
$binary_data = pack("VvvvvvvVVVvvvvvVV",
0x02014b50,
$arHeader['version'],
$arHeader['version_extracted'],
$arHeader['flag'],
$arHeader['compression'],
$mtime,
$mdate,
$arHeader['crc'],
$arHeader['compressed_size'],
$arHeader['size'],
strlen($arHeader['stored_filename']),
$arHeader['extra_len'],
$arHeader['comment_len'],
$arHeader['disk'],
$arHeader['internal'],
$arHeader['external'],
$arHeader['offset']);
//write 42 bytes of the header in the zip file
fputs($this->zipfile, $binary_data, 46);
//variable fields
if ($arHeader['stored_filename'] <> '')
{
fputs($this->zipfile, $arHeader['stored_filename'], strlen($arHeader['stored_filename']));
}
if ($arHeader['extra_len'] != 0)
{
fputs($this->zipfile, $arHeader['extra'], $arHeader['extra_len']);
}
if ($arHeader['comment_len'] != 0)
{
fputs($this->zipfile, $arHeader['comment'], $arHeader['comment_len']);
}
return $res;
}
private function _writeCentralHeader($entriesNumber, $blockSize, $offset, $comment)
{
$res = 1;
//packed data
$binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $entriesNumber, $entriesNumber, $blockSize, $offset, mb_strlen($comment));
//22 bytes of the header in the zip file
fputs($this->zipfile, $binary_data, 22);
//variable fields
if ($comment <> '')
{
fputs($this->zipfile, $comment, mb_strlen($comment));
}
return $res;
}
private function _getFileList(&$arFilesList)
{
if (($this->zipfile = @fopen($this->io->GetPhysicalName($this->zipname), 'rb')) == 0)
{
$this->_errorLog("ERR_READ", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_READ")));
return $this->arErrors;
}
//get central directory information
$arCentralDirInfo = [];
if (($res = $this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
return $res;
}
//go to the beginning of the central directory
@rewind($this->zipfile);
if (@fseek($this->zipfile, $arCentralDirInfo['offset']))
{
$this->_errorLog("ERR_INVALID_ARCHIVE_ZIP", GetMessage("MAIN_ZIP_ERR_INVALID_ARCHIVE_ZIP"));
return $this->arErrors;
}
//read each entry
for ($i = 0; $i < $arCentralDirInfo['entries']; $i++)
{
//read the file header
if (($res = $this->_readCentralFileHeader($header)) != 1)
{
return $res;
}
$header['index'] = $i;
//get only interesting attributes
$this->_convertHeader2FileInfo($header, $arFilesList[$i]);
unset($header);
}
$this->_closeFile();
return $res;
}
private function _convertHeader2FileInfo($arHeader, &$arInfo)
{
$res = 1;
$arInfo = [];
//get necessary attributes
$arInfo['filename'] = $arHeader['filename'];
$arInfo['stored_filename'] = $arHeader['stored_filename'];
$arInfo['size'] = $arHeader['size'];
$arInfo['compressed_size'] = $arHeader['compressed_size'];
$arInfo['mtime'] = $arHeader['mtime'];
$arInfo['comment'] = $arHeader['comment'];
$arInfo['folder'] = (($arHeader['external'] & 0x00000010) == 0x00000010);
$arInfo['index'] = $arHeader['index'];
$arInfo['status'] = $arHeader['status'];
return $res;
}
private function _extractByRule(&$arFileList, &$arParams)
{
$path = $arParams['add_path'];
$removePath = $arParams['remove_path'];
$removeAllPath = $arParams['remove_all_path'];
//path checking
if (($path == "") || ((mb_substr($path, 0, 1) != "/") && (mb_substr($path, 0, 3) != "../") && (mb_substr($path, 1, 2) != ":/")))
{
$path = "./" . $path;
}
//reduce the path last (and duplicated) '/'
if (($path != "./") && ($path != "/"))
{
// checking path end '/'
while (mb_substr($path, -1) == "/")
{
$path = mb_substr($path, 0, mb_strlen($path) - 1);
}
}
//path should end with the /
if (($removePath != "") && (mb_substr($removePath, -1) != '/'))
{
$removePath .= '/';
}
if (($res = $this->_openFile('rb')) != 1)
{
return $res;
}
//reading central directory information
$arCentralDirInfo = [];
if (($res = $this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
$this->_closeFile();
return $res;
}
//starting from the beginning of the central directory
$entryPos = $arCentralDirInfo['offset'];
//reading each entry
$j_start = 0;
for ($i = 0, $extractedCounter = 0; $i < $arCentralDirInfo['entries']; $i++)
{
//reading next central directory record
@rewind($this->zipfile);
if (@fseek($this->zipfile, $entryPos))
{
$this->_closeFile();
$this->_errorLog("ERR_INVALID_ARCHIVE_ZIP", GetMessage("MAIN_ZIP_ERR_MISSING_FILE"));
return $this->arErrors;
}
//reading the file header
$header = [];
if (($res = $this->_readCentralFileHeader($header)) != 1)
{
$this->_closeFile();
return $res;
}
//saving the index
$header['index'] = $i;
//saving the file pos
$entryPos = ftell($this->zipfile);
$extract = false;
//look for the specific extract rules
if ((isset($arParams['by_name'])) && is_array($arParams['by_name']))
{
//is filename in the list
$count = count($arParams['by_name']);
for ($j = 0; $j < $count && !$extract; $j++)
{
//is directory
if (mb_substr($arParams['by_name'][$j], -1) == "/")
{
//is dir in the filename path
if ((mb_strlen($header['stored_filename']) > mb_strlen($arParams['by_name'][$j]))
&& (mb_substr($header['stored_filename'], 0, mb_strlen($arParams['by_name'][$j])) == $arParams['by_name'][$j]))
{
$extract = true;
}
}
else
{
if ($header['stored_filename'] == $arParams['by_name'][$j])
{
$extract = true;
}
}
}
}
else
{
if ((isset($arParams['by_preg'])) && ($arParams['by_preg'] != ""))
{
//extract by preg rule
if (preg_match($arParams['by_preg'], $header['stored_filename']))
{
$extract = true;
}
}
else
{
if ((isset($arParams['by_index'])) && is_array($arParams['by_index']))
{
//extract by index rule (if index is in the list)
for ($j = $j_start, $n = count($arParams['by_index']); $j < $n && !$extract; $j++)
{
if (($i >= $arParams['by_index'][$j]['start']) && ($i <= $arParams['by_index'][$j]['end']))
{
$extract = true;
}
if ($i >= $arParams['by_index'][$j]['end'])
{
$j_start = $j + 1;
}
if ($arParams['by_index'][$j]['start'] > $i)
{
break;
}
}
}
else
{
$extract = true;
}
}
}
// extract file
if ($extract)
{
@rewind($this->zipfile);
if (@fseek($this->zipfile, $header['offset']))
{
$this->_closeFile();
$this->_errorLog("ERR_INVALID_ARCHIVE_ZIP", GetMessage("MAIN_ZIP_ERR_INVALID_ARCHIVE_ZIP"));
return $this->arErrors;
}
//extract as a string
if ($arParams['extract_as_string'])
{
//extract the file
if (($res = $this->_extractFileAsString($header, $string)) != 1)
{
$this->_closeFile();
return $res;
}
//get attributes
if (($res = $this->_convertHeader2FileInfo($header, $arFileList[$extractedCounter])) != 1)
{
$this->_closeFile();
return $res;
}
//set file content
$arFileList[$extractedCounter]['content'] = $string;
//next extracted file
$extractedCounter++;
}
else
{
if (($res = $this->_extractFile($header, $path, $removePath, $removeAllPath, $arParams)) != 1)
{
$this->_closeFile();
return $res;
}
//get attributes
if (($res = $this->_convertHeader2FileInfo($header, $arFileList[$extractedCounter++])) != 1)
{
$this->_closeFile();
return $res;
}
}
}
}
$this->_closeFile();
return $res;
}
private function _extractFile(&$arEntry, $path, $removePath, $removeAllPath, $arParams)
{
if (($res = $this->_readFileHeader($header)) != 1)
{
return $res;
}
//to be checked: file header should be coherent with $arEntry info
$arEntry["filename"] = \Bitrix\Main\Text\Encoding::convertEncoding($arEntry["filename"], "cp866", $this->fileSystemEncoding);
$arEntry["stored_filename"] = \Bitrix\Main\Text\Encoding::convertEncoding($arEntry["stored_filename"], "cp866", $this->fileSystemEncoding);
//protecting against ../ etc. in file path
//only absolute path should be in the $arEntry
$arEntry['filename'] = _normalizePath($arEntry['filename']);
$arEntry['stored_filename'] = _normalizePath($arEntry['stored_filename']);
if ($removeAllPath)
{
$arEntry['filename'] = basename($arEntry['filename']);
}
else
{
if ($removePath != "")
{
if ($this->_containsPath($removePath, $arEntry['filename']) == 2)
{
//change file status
$arEntry['status'] = "filtered";
return $res;
}
$removePath_size = mb_strlen($removePath);
if (mb_substr($arEntry['filename'], 0, $removePath_size) == $removePath)
{
//remove path
$arEntry['filename'] = mb_substr($arEntry['filename'], $removePath_size);
}
}
}
//making absolute path to the extracted file out of filename stored in the zip header and passed extracting path
if ($path != '')
{
$arEntry['filename'] = $path . "/" . $arEntry['filename'];
}
//pre-extract callback
if ((isset($arParams['callback_pre_extract']))
&& ($arParams['callback_pre_extract'] != ''))
{
//generate local info
$arLocalHeader = [];
$this->_convertHeader2FileInfo($arEntry, $arLocalHeader);
//callback call
eval('$res = ' . $arParams['callback_pre_extract'] . '(\'callback_pre_extract\', $arLocalHeader);');
//change file status
if ($res == 0)
{
$arEntry['status'] = "skipped";
$res = 1;
}
//update the info, only some fields can be modified
$arEntry['filename'] = $arLocalHeader['filename'];
}
//check if extraction should be done
if ($arEntry['status'] == 'ok')
{
if ($this->checkPermissions && !CBXArchive::IsFileSafe($arEntry['filename']))
{
$arEntry['status'] = "no_permissions";
}
else
{
//if the file exists, change status
if (file_exists($arEntry['filename']))
{
if (is_dir($arEntry['filename']))
{
$arEntry['status'] = "already_a_directory";
}
else
{
if (!is_writeable($arEntry['filename']))
{
$arEntry['status'] = "write_protected";
}
else
{
if ((filemtime($arEntry['filename']) > $arEntry['mtime']) && (!$this->replaceExistentFiles))
{
$arEntry['status'] = "newer_exist";
}
}
}
}
else
{
//check the directory availability and create it if necessary
if ((($arEntry['external'] & 0x00000010) == 0x00000010) || (mb_substr($arEntry['filename'], -1) == '/'))
{
$checkDir = $arEntry['filename'];
}
else
{
if (!mb_strstr($arEntry['filename'], "/"))
{
$checkDir = "";
}
else
{
$checkDir = dirname($arEntry['filename']);
}
}
if (($res = $this->_checkDir($checkDir, (($arEntry['external'] & 0x00000010) == 0x00000010))) != 1)
{
//change file status
$arEntry['status'] = "path_creation_fail";
//return $res;
$res = 1;
}
}
}
}
//check if extraction should be done
if ($arEntry['status'] == 'ok')
{
//if not a folder - extract
if (!(($arEntry['external'] & 0x00000010) == 0x00000010))
{
//if zip file with 0 compression
if (($arEntry['compression'] == 0) && ($arEntry['compressed_size'] == $arEntry['size']))
{
if (($destFile = @fopen($arEntry['filename'], 'wb')) == 0)
{
$arEntry['status'] = "write_error";
return $res;
}
//reading the fileby by self::ReadBlockSize octets blocks
$size = $arEntry['compressed_size'];
while ($size != 0)
{
$length = ($size < self::ReadBlockSize ? $size : self::ReadBlockSize);
$buffer = fread($this->zipfile, $length);
$binary_data = pack('a' . $length, $buffer);
@fwrite($destFile, $binary_data, $length);
$size -= $length;
}
//close the destination file
fclose($destFile);
//changing file modification time
touch($arEntry['filename'], $arEntry['mtime']);
}
else
{
if (($destFile = @fopen($arEntry['filename'], 'wb')) == 0)
{
//change file status
$arEntry['status'] = "write_error";
return $res;
}
//read the compressed file in a buffer (one shot)
$buffer = @fread($this->zipfile, $arEntry['compressed_size']);
//decompress the file
$fileContent = gzinflate($buffer);
unset($buffer);
//write uncompressed data
@fwrite($destFile, $fileContent, $arEntry['size']);
unset($fileContent);
@fclose($destFile);
touch($arEntry['filename'], $arEntry['mtime']);
}
if ((isset($arParams['set_chmod'])) && ($arParams['set_chmod'] != 0))
{
chmod($arEntry['filename'], $arParams['set_chmod']);
}
}
}
//post-extract callback
if ((isset($arParams['callback_post_extract'])) && ($arParams['callback_post_extract'] != ''))
{
//make local info
$arLocalHeader = [];
$this->_convertHeader2FileInfo($arEntry, $arLocalHeader);
//callback call
eval('$res = ' . $arParams['callback_post_extract'] . '(\'callback_post_extract\', $arLocalHeader);');
}
return $res;
}
private function _extractFileAsString($arEntry, &$string)
{
//reading file header
$header = [];
if (($res = $this->_readFileHeader($header)) != 1)
{
return $res;
}
//to be checked: file header should be coherent with the $arEntry info
//extract if not a folder
if (!(($arEntry['external'] & 0x00000010) == 0x00000010))
{
//if not compressed
if ($arEntry['compressed_size'] == $arEntry['size'])
{
$string = fread($this->zipfile, $arEntry['compressed_size']);
}
else
{
$data = fread($this->zipfile, $arEntry['compressed_size']);
$string = gzinflate($data);
}
}
else
{
$this->_errorLog("ERR_EXTRACT", GetMessage("MAIN_ZIP_ERR_EXTRACT"));
return $this->arErrors;
}
return $res;
}
private function _readFileHeader(&$arHeader)
{
$res = 1;
//read 4 bytes signature
$binary_data = @fread($this->zipfile, 4);
$data = unpack('Vid', $binary_data);
//check signature
if ($data['id'] != 0x04034b50)
{
$this->_errorLog("ERR_BAD_FORMAT", GetMessage("MAIN_ZIP_ERR_STRUCT"));
return $this->arErrors;
}
//reading first 42 bytes of the header
$binary_data = fread($this->zipfile, 26);
//look for invalid block size
if (strlen($binary_data) != 26)
{
$arHeader['filename'] = "";
$arHeader['status'] = "invalid_header";
$this->_errorLog("ERR_BAD_BLOCK_SIZE", str_replace("#BLOCK_SIZE#", $binary_data, GetMessage("MAIN_ZIP_ERR_BLOCK_SIZE")));
return $this->arErrors;
}
//extract values
$data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $binary_data);
$arHeader['filename'] = fread($this->zipfile, $data['filename_len']);
//extra fields
if ($data['extra_len'] != 0)
{
$arHeader['extra'] = fread($this->zipfile, $data['extra_len']);
}
else
{
$arHeader['extra'] = '';
}
//extract properties
$arHeader['compression'] = $data['compression'];
$arHeader['size'] = $data['size'];
$arHeader['compressed_size'] = $data['compressed_size'];
$arHeader['crc'] = $data['crc'];
$arHeader['flag'] = $data['flag'];
//save date in unix format
$arHeader['mdate'] = $data['mdate'];
$arHeader['mtime'] = $data['mtime'];
if ($arHeader['mdate'] && $arHeader['mtime'])
{
//extract time
$hour = ($arHeader['mtime'] & 0xF800) >> 11;
$min = ($arHeader['mtime'] & 0x07E0) >> 5;
$sec = ($arHeader['mtime'] & 0x001F) * 2;
//...and date
$year = (($arHeader['mdate'] & 0xFE00) >> 9) + 1980;
$month = ($arHeader['mdate'] & 0x01E0) >> 5;
$day = $arHeader['mdate'] & 0x001F;
//unix date format
$arHeader['mtime'] = mktime($hour, $min, $sec, $month, $day, $year);
}
else
{
$arHeader['mtime'] = time();
}
//to be checked: for(reset($data); $key = key($data); next($data)) { }
$arHeader['stored_filename'] = $arHeader['filename'];
$arHeader['status'] = "ok";
return $res;
}
private function _readCentralFileHeader(&$arHeader)
{
$res = 1;
//reading 4 bytes signature
$binary_data = @fread($this->zipfile, 4);
$data = unpack('Vid', $binary_data);
//checking signature
if ($data['id'] != 0x02014b50)
{
$this->_errorLog("ERR_BAD_FORMAT", GetMessage("MAIN_ZIP_ERR_STRUCT"));
return $this->arErrors;
}
//reading first header 42 bytes
$binary_data = fread($this->zipfile, 42);
//if block size is not valid
if (strlen($binary_data) != 42)
{
$arHeader['filename'] = "";
$arHeader['status'] = "invalid_header";
$this->_errorLog("ERR_BAD_BLOCK_SIZE", str_replace("#SIZE#", $binary_data, GetMessage("MAIN_ZIP_ERR_BLOCK_SIZE")));
return $this->arErrors;
}
//extract values
$arHeader = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $binary_data);
//getting filename
if ($arHeader['filename_len'] != 0)
{
$arHeader['filename'] = fread($this->zipfile, $arHeader['filename_len']);
}
else
{
$arHeader['filename'] = '';
}
//getting extra
if ($arHeader['extra_len'] != 0)
{
$arHeader['extra'] = fread($this->zipfile, $arHeader['extra_len']);
}
else
{
$arHeader['extra'] = '';
}
//getting comments
if ($arHeader['comment_len'] != 0)
{
$arHeader['comment'] = fread($this->zipfile, $arHeader['comment_len']);
}
else
{
$arHeader['comment'] = '';
}
//extracting properties
//saving date in unix format
if ($arHeader['mdate'] && $arHeader['mtime'])
{
//extracting time
$hour = ($arHeader['mtime'] & 0xF800) >> 11;
$min = ($arHeader['mtime'] & 0x07E0) >> 5;
$sec = ($arHeader['mtime'] & 0x001F) * 2;
//...and date
$year = (($arHeader['mdate'] & 0xFE00) >> 9) + 1980;
$month = ($arHeader['mdate'] & 0x01E0) >> 5;
$day = $arHeader['mdate'] & 0x001F;
//in unix date format
$arHeader['mtime'] = mktime($hour, $min, $sec, $month, $day, $year);
}
else
{
$arHeader['mtime'] = time();
}
//set stored filename
$arHeader['stored_filename'] = $arHeader['filename'];
//default status is 'ok'
$arHeader['status'] = 'ok';
//is directory?
if (mb_substr($arHeader['filename'], -1) == '/')
{
$arHeader['external'] = 0x41FF0010;
}
return $res;
}
private function _readEndCentralDir(&$arCentralDir)
{
$res = 1;
//going to the end of the file
$size = filesize($this->io->GetPhysicalName($this->zipname));
@fseek($this->zipfile, $size);
if (@ftell($this->zipfile) != $size)
{
$this->_errorLog("ERR_ARC_END", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_ARC_END")));
return $this->arErrors;
}
//if archive is without comments (usually), the end of central dir is at 22 bytes of the file end
$isFound = 0;
$pos = 0;
if ($size > 26)
{
@fseek($this->zipfile, $size - 22);
if (@ftell($this->zipfile) != ($size - 22))
{
$this->_errorLog("ERR_ARC_MID", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_ARC_MID")));
return $this->arErrors;
}
//read 4 bytes
$binary_data = @fread($this->zipfile, 4);
$data = unpack('Vid', $binary_data);
//signature check
if ($data['id'] == 0x06054b50)
{
$isFound = 1;
}
$pos = ftell($this->zipfile);
}
//going back to the max possible size of the Central Dir End Record
if (!$isFound)
{
$maxSize = 65557; // 0xFFFF + 22;
if ($maxSize > $size)
{
$maxSize = $size;
}
@fseek($this->zipfile, $size - $maxSize);
if (@ftell($this->zipfile) != ($size - $maxSize))
{
$this->_errorLog("ERR_ARC_MID", str_replace("#FILE_NAME#", removeDocRoot($this->zipname), GetMessage("MAIN_ZIP_ERR_ARC_MID")));
return $this->arErrors;
}
//reading byte per byte to find the signature
$pos = ftell($this->zipfile);
$bytes = 0x00000000;
while ($pos < $size)
{
//reading 1 byte
$byte = @fread($this->zipfile, 1);
//0x03000000504b0506 -> 0x504b0506
$bytes = ($bytes << (8 * (PHP_INT_SIZE - 3))) >> (8 * (PHP_INT_SIZE - 4));
//adding the byte
$bytes = $bytes | ord($byte);
//compare bytes
if ($bytes == 0x504b0506)
{
$pos++;
break;
}
$pos++;
}
//if end of the central dir is not found
if ($pos == $size)
{
$this->_errorLog("ERR_ARC_MID_END", GetMessage("MAIN_ZIP_ERR_ARC_MID_END"));
return $this->arErrors;
}
}
//reading first 18 bytes of the header
$binary_data = fread($this->zipfile, 18);
//if block size is not valid
if (strlen($binary_data) != 18)
{
$this->_errorLog("ERR_ARC_END_SIZE", str_replace("#SIZE#", mb_strlen($binary_data), GetMessage("MAIN_ZIP_ERR_ARC_END_SIZE")));
return $this->arErrors;
}
//extracting values
$data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $binary_data);
//checking global size
if (($pos + $data['comment_size'] + 18) != $size)
{
$this->_errorLog("ERR_SIGNATURE", GetMessage("MAIN_ZIP_ERR_SIGNATURE"));
return $this->arErrors;
}
//reading comments
if ($data['comment_size'] != 0)
{
$arCentralDir['comment'] = fread($this->zipfile, $data['comment_size']);
}
else
{
$arCentralDir['comment'] = '';
}
$arCentralDir['entries'] = $data['entries'];
$arCentralDir['disk_entries'] = $data['disk_entries'];
$arCentralDir['offset'] = $data['offset'];
$arCentralDir['size'] = $data['size'];
$arCentralDir['disk'] = $data['disk'];
$arCentralDir['disk_start'] = $data['disk_start'];
return $res;
}
private function _deleteByRule(&$arResultList, $arParams)
{
$arCentralDirInfo = [];
$arHeaders = [];
if (($res = $this->_openFile('rb')) != 1)
{
return $res;
}
if (($res = $this->_readEndCentralDir($arCentralDirInfo)) != 1)
{
$this->_closeFile();
return $res;
}
//scanning all the files, starting at the beginning of Central Dir
$entryPos = $arCentralDirInfo['offset'];
@rewind($this->zipfile);
if (@fseek($this->zipfile, $entryPos))
{
//clean file
$this->_closeFile();
$this->_errorLog("ERR_INVALID_ARCHIVE_ZIP", GetMessage("MAIN_ZIP_ERR_INVALID_ARCHIVE_ZIP"));
return $this->arErrors;
}
$j_start = 0;
//reading each entry
for ($i = 0, $extractedCounter = 0; $i < $arCentralDirInfo['entries']; $i++)
{
//reading file header
$arHeaders[$extractedCounter] = [];
$res = $this->_readCentralFileHeader($arHeaders[$extractedCounter]);
if ($res != 1)
{
$this->_closeFile();
return $res;
}
//saving index
$arHeaders[$extractedCounter]['index'] = $i;
//check specific extract rules
$isFound = false;
//name rule
if ((isset($arParams['by_name'])) && is_array($arParams['by_name']))
{
//if the filename is in the list
for ($j = 0, $n = count($arParams['by_name']); $j < $n && !$isFound; $j++)
{
if (mb_substr($arParams['by_name'][$j], -1) == "/")
{
//if the directory is in the filename path
if ((mb_strlen($arHeaders[$extractedCounter]['stored_filename']) > mb_strlen($arParams['by_name'][$j]))
&& (mb_substr($arHeaders[$extractedCounter]['stored_filename'], 0, mb_strlen($arParams['by_name'][$j])) == $arParams['by_name'][$j]))
{
$isFound = true;
}
elseif ((($arHeaders[$extractedCounter]['external'] & 0x00000010) == 0x00000010) /* Indicates a folder */
&& ($arHeaders[$extractedCounter]['stored_filename'] . '/' == $arParams['by_name'][$j]))
{
$isFound = true;
}
}
elseif ($arHeaders[$extractedCounter]['stored_filename'] == $arParams['by_name'][$j])
{
//check filename
$isFound = true;
}
}
}
else
{
if ((isset($arParams['by_preg'])) && ($arParams['by_preg'] != ""))
{
if (preg_match($arParams['by_preg'], $arHeaders[$extractedCounter]['stored_filename']))
{
$isFound = true;
}
}
else
{
if ((isset($arParams['by_index'])) && is_array($arParams['by_index']))
{
//index rule: if index is in the list
for ($j = $j_start, $n = count($arParams['by_index']); $j < $n && !$isFound; $j++)
{
if (($i >= $arParams['by_index'][$j]['start'])
&& ($i <= $arParams['by_index'][$j]['end']))
{
$isFound = true;
}
if ($i >= $arParams['by_index'][$j]['end'])
{
$j_start = $j + 1;
}
if ($arParams['by_index'][$j]['start'] > $i)
{
break;
}
}
}
}
}
//delete?
if ($isFound)
{
unset($arHeaders[$extractedCounter]);
}
else
{
$extractedCounter++;
}
}
//if something should be deleted
if ($extractedCounter > 0)
{
//create tmp file
$zipname_tmp = GetDirPath($this->zipname) . uniqid('ziparc') . '.tmp';
//create tmp zip archive
$tmpzip = new CZip($zipname_tmp);
if (($res = $tmpzip->_openFile('wb')) != 1)
{
$this->_closeFile();
return $res;
}
//check which file should be kept
for ($i = 0; $i < sizeof($arHeaders); $i++)
{
//calculate the position of the header
@rewind($this->zipfile);
if (@fseek($this->zipfile, $arHeaders[$i]['offset']))
{
$this->_closeFile();
$tmpzip->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
$this->_errorLog("ERR_INVALID_ARCHIVE_ZIP", GetMessage("MAIN_ZIP_ERR_INVALID_ARCHIVE_ZIP"));
return $this->arErrors;
}
if (($res = $this->_readFileHeader($arHeaders[$i])) != 1)
{
$this->_closeFile();
$tmpzip->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
//writing file header
$res = $tmpzip->_writeFileHeader($arHeaders[$i]);
if ($res != 1)
{
$this->_closeFile();
$tmpzip->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
//reading/writing data block
$res = $this->_copyBlocks($this->zipfile, $tmpzip->zipfile, $arHeaders[$i]['compressed_size']);
if ($res != 1)
{
$this->_closeFile();
$tmpzip->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
}
//save central dir offset
$offset = @ftell($tmpzip->zipfile);
//re-write central dir files header
for ($i = 0; $i < sizeof($arHeaders); $i++)
{
$res = $tmpzip->_writeCentralFileHeader($arHeaders[$i]);
if ($res != 1)
{
$tmpzip->_closeFile();
$this->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
//convert header to the 'usable' format
$tmpzip->_convertHeader2FileInfo($arHeaders[$i], $arResultList[$i]);
}
$zip_comment = '';
$size = @ftell($tmpzip->zipfile) - $offset;
$res = $tmpzip->_writeCentralHeader(sizeof($arHeaders), $size, $offset, $zip_comment);
if ($res != 1)
{
unset($arHeaders);
$tmpzip->_closeFile();
$this->_closeFile();
@unlink($this->io->GetPhysicalName($zipname_tmp));
return $res;
}
$tmpzip->_closeFile();
$this->_closeFile();
//deleting zip file (result should be checked)
@unlink($this->io->GetPhysicalName($this->zipname));
//result should be checked
$this->_renameTmpFile($zipname_tmp, $this->zipname);
unset($tmpzip);
}
return $res;
}
private function _checkDir($dir, $isDir = false)
{
$res = 1;
//remove '/' at the end
if (($isDir) && (mb_substr($dir, -1) == '/'))
{
$dir = mb_substr($dir, 0, mb_strlen($dir) - 1);
}
//check if dir is available
if ((is_dir($dir)) || ($dir == ""))
{
return 1;
}
//get parent directory
$parentDir = dirname($dir);
if ($parentDir != $dir)
{
//find the parent dir
if ($parentDir != "")
{
if (($res = $this->_checkDir($parentDir)) != 1)
{
return $res;
}
}
}
//creating a directory
if (!@mkdir($dir))
{
$this->_errorLog("ERR_DIR_CREATE_FAIL", str_replace("#DIR_NAME#", $dir, GetMessage("MAIN_ZIP_ERR_DIR_CREATE_FAIL")));
return $this->arErrors;
}
return $res;
}
private function _checkParams(&$arParams, $arDefaultValues)
{
if (!is_array($arParams))
{
$this->_errorLog("ERR_PARAM", GetMessage("MAIN_ZIP_ERR_PARAM"));
return $this->arErrors;
}
//all params should be valid
foreach ($arParams as $key => $dummy)
{
if (!isset($arDefaultValues[$key]))
{
$this->_errorLog("ERR_PARAM_KEY", str_replace("#KEY#", $key, GetMessage("MAIN_ZIP_ERR_PARAM_KEY")));
return $this->arErrors;
}
}
//set default values
foreach ($arDefaultValues as $key => $value)
{
if (!isset($arParams[$key]))
{
$arParams[$key] = $value;
}
}
//check specific parameters
$arCallbacks = ['callback_pre_add', 'callback_post_add', 'callback_pre_extract', 'callback_post_extract'];
for ($i = 0; $i < sizeof($arCallbacks); $i++)
{
$key = $arCallbacks[$i];
if ((isset($arParams[$key])) && ($arParams[$key] != ''))
{
if (!function_exists($arParams[$key]))
{
$this->_errorLog("ERR_PARAM_CALLBACK", str_replace(["#CALLBACK#", "#PARAM_NAME#"], [$arParams[$key], $key], GetMessage("MAIN_ZIP_ERR_PARAM_CALLBACK")));
return $this->arErrors;
}
}
}
return (1);
}
private function _errorLog($errorName, $errorString = '')
{
$this->arErrors[] = "[" . $errorName . "] " . $errorString;
}
private function _errorReset()
{
$this->arErrors = [];
}
private function _reducePath($dir)
{
$res = "";
if ($dir != "")
{
//get directory names
$arTmpList = explode("/", $dir);
//check from last to first
for ($i = sizeof($arTmpList) - 1; $i >= 0; $i--)
{
//is current path
if ($arTmpList[$i] == ".")
{
//just ignore. the first $i should be = 0, but no check is done
}
else
{
if ($arTmpList[$i] == "..")
{
//ignore this and ignore the $i-1
$i--;
}
else
{
if (($arTmpList[$i] == "") && ($i != (sizeof($arTmpList) - 1)) && ($i != 0))
{
//ignore only the double '//' in path, but not the first and last '/'
}
else
{
$res = $arTmpList[$i] . ($i != (sizeof($arTmpList) - 1) ? "/" . $res : "");
}
}
}
}
}
return $res;
}
private function _containsPath($dir, $path)
{
$res = 1;
//explode dir and path by directory separator
$arTmpDirList = explode("/", $dir);
$arTmpPathList = explode("/", $path);
$arTmpDirListSize = sizeof($arTmpDirList);
$arTmpPathListSize = sizeof($arTmpPathList);
//check dir paths
$i = 0;
$j = 0;
while (($i < $arTmpDirListSize) && ($j < $arTmpPathListSize) && ($res))
{
//check if is empty
if ($arTmpDirList[$i] == '')
{
$i++;
continue;
}
if ($arTmpPathList[$j] == '')
{
$j++;
continue;
}
//compare items
if ($arTmpDirList[$i] != $arTmpPathList[$j])
{
$res = 0;
}
$i++;
$j++;
}
//check if the same
if ($res)
{
//skip empty items
while (($j < $arTmpPathListSize) && ($arTmpPathList[$j] == ''))
{
$j++;
}
while (($i < $arTmpDirListSize) && ($arTmpDirList[$i] == ''))
{
$i++;
}
if (($i >= $arTmpDirListSize) && ($j >= $arTmpPathListSize))
{
//exactly the same
$res = 2;
}
else
{
if ($i < $arTmpDirListSize)
{
//path is shorter than the dir
$res = 0;
}
}
}
return $res;
}
private function _copyBlocks($source, $dest, $blockSize, $mode = 0)
{
$res = 1;
if ($mode == 0)
{
while ($blockSize != 0)
{
$length = ($blockSize < self::ReadBlockSize ? $blockSize : self::ReadBlockSize);
$buffer = @fread($source, $length);
@fwrite($dest, $buffer, $length);
$blockSize -= $length;
}
}
else
{
if ($mode == 1)
{
while ($blockSize != 0)
{
$length = ($blockSize < self::ReadBlockSize ? $blockSize : self::ReadBlockSize);
$buffer = @gzread($source, $length);
@fwrite($dest, $buffer, $length);
$blockSize -= $length;
}
}
else
{
if ($mode == 2)
{
while ($blockSize != 0)
{
$length = ($blockSize < self::ReadBlockSize ? $blockSize : self::ReadBlockSize);
$buffer = @fread($source, $length);
@gzwrite($dest, $buffer, $length);
$blockSize -= $length;
}
}
else
{
if ($mode == 3)
{
while ($blockSize != 0)
{
$length = ($blockSize < self::ReadBlockSize ? $blockSize : self::ReadBlockSize);
$buffer = @gzread($source, $length);
@gzwrite($dest, $buffer, $length);
$blockSize -= $length;
}
}
}
}
}
return $res;
}
private function _renameTmpFile($source, $dest)
{
$res = 1;
if (!@rename($this->io->GetPhysicalName($source), $this->io->GetPhysicalName($dest)))
{
if (!@copy($this->io->GetPhysicalName($source), $this->io->GetPhysicalName($dest)))
{
$res = 0;
}
else
{
if (!@unlink($this->io->GetPhysicalName($source)))
{
$res = 0;
}
}
}
return $res;
}
private function _convertWinPath($path, $removeDiskLetter = true)
{
if (mb_stristr(php_uname(), 'windows'))
{
//disk letter?
if ($removeDiskLetter && ($position = mb_strpos($path, ':')) !== false)
{
$path = mb_substr($path, $position + 1);
}
//change windows directory separator
if ((mb_strpos($path, '\\') > 0) || (mb_substr($path, 0, 1) == '\\'))
{
$path = strtr($path, '\\', '/');
}
}
return $path;
}
private function _parseFileParams($arFileList)
{
if (isset($arFileList) && is_array($arFileList))
{
return $arFileList;
}
if (isset($arFileList) && $arFileList <> '')
{
if (mb_strpos($arFileList, "\"") === 0)
{
return [trim($arFileList, "\"")];
}
return explode(" ", $arFileList);
}
return [];
}
private function _cleanFile()
{
$this->_closeFile();
@unlink($this->io->GetPhysicalName($this->zipname));
}
private function _checkDirPath($path)
{
$path = str_replace(["\\", "//"], "/", $path);
//remove file name
if (mb_substr($path, -1) != "/")
{
$p = mb_strrpos($path, "/");
$path = mb_substr($path, 0, $p);
}
$path = rtrim($path, "/");
if (!file_exists($this->io->GetPhysicalName($path)))
{
return mkdir($this->io->GetPhysicalName($path), BX_DIR_PERMISSIONS, true);
}
else
{
return is_dir($this->io->GetPhysicalName($path));
}
}
private function _getfileSystemEncoding()
{
$fileSystemEncoding = mb_strtolower(defined("BX_FILE_SYSTEM_ENCODING") ? BX_FILE_SYSTEM_ENCODING : "");
if (empty($fileSystemEncoding))
{
if (mb_strtoupper(mb_substr(PHP_OS, 0, 3)) === "WIN")
{
$fileSystemEncoding = "windows-1251";
}
else
{
$fileSystemEncoding = "utf-8";
}
}
return $fileSystemEncoding;
}
}