Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/zip.php |
<?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; } }