123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\Archive;
- use Magento\Framework\Archive\Helper\File;
- /**
- * Class to work with tar archives
- *
- * @author Magento Core Team <core@magentocommerce.com>
- */
- class Tar extends \Magento\Framework\Archive\AbstractArchive implements \Magento\Framework\Archive\ArchiveInterface
- {
- /**
- * Tar block size
- *
- * @const int
- */
- const TAR_BLOCK_SIZE = 512;
- /**
- * Keep file or directory for packing.
- *
- * @var string
- */
- protected $_currentFile;
- /**
- * Keep path to file or directory for packing.
- *
- * @var string
- */
- protected $_currentPath;
- /**
- * Skip first level parent directory. Example:
- * use test/fip.php instead test/test/fip.php;
- *
- * @var bool
- */
- protected $_skipRoot;
- /**
- * Tarball data writer
- *
- * @var File
- */
- protected $_writer;
- /**
- * Tarball data reader
- *
- * @var File
- */
- protected $_reader;
- /**
- * Path to file where tarball should be placed
- *
- * @var string
- */
- protected $_destinationFilePath;
- /**
- * Initialize tarball writer
- *
- * @return $this
- */
- protected function _initWriter()
- {
- $this->_writer = new File($this->_destinationFilePath);
- $this->_writer->open('w');
- return $this;
- }
- /**
- * Returns string that is used for tar's header parsing
- *
- * @return string
- */
- protected static function _getFormatParseHeader()
- {
- return 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/' .
- 'a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer';
- }
- /**
- * Destroy tarball writer
- *
- * @return $this
- */
- protected function _destroyWriter()
- {
- if ($this->_writer instanceof File) {
- $this->_writer->close();
- $this->_writer = null;
- }
- return $this;
- }
- /**
- * Get tarball writer
- *
- * @return File
- */
- protected function _getWriter()
- {
- if (!$this->_writer) {
- $this->_initWriter();
- }
- return $this->_writer;
- }
- /**
- * Initialize tarball reader
- *
- * @return $this
- */
- protected function _initReader()
- {
- $this->_reader = new File($this->_getCurrentFile());
- $this->_reader->open('r');
- return $this;
- }
- /**
- * Destroy tarball reader
- *
- * @return $this
- */
- protected function _destroyReader()
- {
- if ($this->_reader instanceof File) {
- $this->_reader->close();
- $this->_reader = null;
- }
- return $this;
- }
- /**
- * Get tarball reader
- *
- * @return File
- */
- protected function _getReader()
- {
- if (!$this->_reader) {
- $this->_initReader();
- }
- return $this->_reader;
- }
- /**
- * Set option that define ability skip first catalog level.
- *
- * @param bool $skipRoot
- * @return $this
- */
- protected function _setSkipRoot($skipRoot)
- {
- $this->_skipRoot = $skipRoot;
- return $this;
- }
- /**
- * Set file which is packing.
- *
- * @param string $file
- * @return $this
- */
- protected function _setCurrentFile($file)
- {
- $file = str_replace('\\', '/', $file);
- $this->_currentFile = $file . (!is_link($file) && is_dir($file) && substr($file, -1) != '/' ? '/' : '');
- return $this;
- }
- /**
- * Set path to file where tarball should be placed
- *
- * @param string $destinationFilePath
- * @return $this
- */
- protected function _setDestinationFilePath($destinationFilePath)
- {
- $this->_destinationFilePath = $destinationFilePath;
- return $this;
- }
- /**
- * Retrieve file which is packing.
- *
- * @return string
- */
- protected function _getCurrentFile()
- {
- return $this->_currentFile;
- }
- /**
- * Set path to file which is packing.
- *
- * @param string $path
- * @return $this
- */
- protected function _setCurrentPath($path)
- {
- $path = str_replace('\\', '/', $path);
- if ($this->_skipRoot && is_dir($path)) {
- $this->_currentPath = $path . (substr($path, -1) != '/' ? '/' : '');
- } else {
- $this->_currentPath = dirname($path) . '/';
- }
- return $this;
- }
- /**
- * Retrieve path to file which is packing.
- *
- * @return string
- */
- protected function _getCurrentPath()
- {
- return $this->_currentPath;
- }
- /**
- * Recursively walk through file tree and create tarball
- *
- * @param bool $skipRoot
- * @param bool $finalize
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- protected function _createTar($skipRoot = false, $finalize = false)
- {
- if (!$skipRoot) {
- $this->_packAndWriteCurrentFile();
- }
- $file = $this->_getCurrentFile();
- if (is_dir($file)) {
- $dirFiles = scandir($file, SCANDIR_SORT_NONE);
- if (false === $dirFiles) {
- throw new \Magento\Framework\Exception\LocalizedException(
- new \Magento\Framework\Phrase('Can\'t scan dir: %1', [$file])
- );
- }
- $dirFiles = array_diff($dirFiles, ['..', '.']);
- foreach ($dirFiles as $item) {
- $this->_setCurrentFile($file . $item)->_createTar();
- }
- }
- if ($finalize) {
- $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12));
- }
- }
- /**
- * Write current file to tarball
- *
- * @return void
- */
- protected function _packAndWriteCurrentFile()
- {
- $archiveWriter = $this->_getWriter();
- $archiveWriter->write($this->_composeHeader());
- $currentFile = $this->_getCurrentFile();
- $fileSize = 0;
- if (is_file($currentFile) && !is_link($currentFile)) {
- $fileReader = new File($currentFile);
- $fileReader->open('r');
- while (!$fileReader->eof()) {
- $archiveWriter->write($fileReader->read());
- }
- $fileReader->close();
- $fileSize = filesize($currentFile);
- }
- $appendZerosCount = (self::TAR_BLOCK_SIZE - $fileSize % self::TAR_BLOCK_SIZE) % self::TAR_BLOCK_SIZE;
- $archiveWriter->write(str_repeat("\0", $appendZerosCount));
- }
- /**
- * Compose header for current file in TAR format.
- * If length of file's name greater 100 characters,
- * method breaks header into two pieces. First contains
- * header and data with long name. Second contain only header.
- *
- * @param bool $long
- * @return string
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- protected function _composeHeader($long = false)
- {
- $file = $this->_getCurrentFile();
- $path = $this->_getCurrentPath();
- $infoFile = stat($file);
- $nameFile = str_replace($path, '', $file);
- $nameFile = str_replace('\\', '/', $nameFile);
- $packedHeader = '';
- $longHeader = '';
- if (!$long && strlen($nameFile) > 100) {
- $longHeader = $this->_composeHeader(true);
- $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0");
- }
- $header = [];
- $header['100-name'] = $long ? '././@LongLink' : substr($nameFile, 0, 100);
- $header['8-mode'] = $long ? ' ' : str_pad(
- substr(sprintf("%07o", $infoFile['mode']), -4),
- 6,
- '0',
- STR_PAD_LEFT
- );
- $header['8-uid'] = $long || $infoFile['uid'] == 0 ? "\0\0\0\0\0\0\0" : sprintf("%07o", $infoFile['uid']);
- $header['8-gid'] = $long || $infoFile['gid'] == 0 ? "\0\0\0\0\0\0\0" : sprintf("%07o", $infoFile['gid']);
- $header['12-size'] = $long ? sprintf(
- "%011o",
- strlen($nameFile)
- ) : sprintf(
- "%011o",
- is_dir($file) ? 0 : filesize($file)
- );
- $header['12-mtime'] = $long ? '00000000000' : sprintf("%011o", $infoFile['mtime']);
- $header['8-check'] = sprintf('% 8s', '');
- $header['1-type'] = $long ? 'L' : (is_link($file) ? 2 : (is_dir($file) ? 5 : 0));
- $header['100-symlink'] = is_link($file) ? readlink($file) : '';
- $header['6-magic'] = 'ustar ';
- $header['2-version'] = ' ';
- $a = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner($file)) : ['name' => ''];
- $header['32-uname'] = $a['name'];
- $a = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup($file)) : ['name' => ''];
- $header['32-gname'] = $a['name'];
- $header['8-devmajor'] = '';
- $header['8-devminor'] = '';
- $header['155-prefix'] = '';
- $header['12-closer'] = '';
- $packedHeader = '';
- foreach ($header as $key => $element) {
- $length = explode('-', $key);
- $packedHeader .= pack('a' . $length[0], $element);
- }
- $checksum = 0;
- for ($i = 0; $i < 512; $i++) {
- $checksum += ord(substr($packedHeader, $i, 1));
- }
- $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum) . "\0", 148, 8);
- return $longHeader . $packedHeader;
- }
- /**
- * Read TAR string from file, and unpacked it.
- * Create files and directories information about described
- * in the string.
- *
- * @param string $destination path to file is unpacked
- * @return string[] list of files
- * @throws \Magento\Framework\Exception\LocalizedException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- protected function _unpackCurrentTar($destination)
- {
- $archiveReader = $this->_getReader();
- $list = [];
- while (!$archiveReader->eof()) {
- $header = $this->_extractFileHeader();
- if (!$header) {
- continue;
- }
- $currentFile = $destination . $header['name'];
- $dirname = dirname($currentFile);
- if (in_array($header['type'], ["0", chr(0), ''])) {
- if (!file_exists($dirname)) {
- $mkdirResult = @mkdir($dirname, 0777, true);
- if (false === $mkdirResult) {
- throw new \Magento\Framework\Exception\LocalizedException(
- new \Magento\Framework\Phrase('Failed to create directory %1', [$dirname])
- );
- }
- }
- $this->_extractAndWriteFile($header, $currentFile);
- $list[] = $currentFile;
- } elseif ($header['type'] == '5') {
- if (!file_exists($dirname)) {
- $mkdirResult = @mkdir($currentFile, $header['mode'], true);
- if (false === $mkdirResult) {
- throw new \Magento\Framework\Exception\LocalizedException(
- new \Magento\Framework\Phrase('Failed to create directory %1', [$currentFile])
- );
- }
- }
- $list[] = $currentFile . '/';
- } elseif ($header['type'] == '2') {
- //we do not interrupt unpack process if symlink creation failed as symlinks are not so important
- @symlink($header['symlink'], $currentFile);
- }
- }
- return $list;
- }
- /**
- * Read and decode file header information from tarball
- *
- * @return array|bool
- */
- protected function _extractFileHeader()
- {
- $archiveReader = $this->_getReader();
- $headerBlock = $archiveReader->read(self::TAR_BLOCK_SIZE);
- if (strlen($headerBlock) < self::TAR_BLOCK_SIZE) {
- return false;
- }
- $header = unpack(self::_getFormatParseHeader(), $headerBlock);
- $header['mode'] = octdec($header['mode']);
- $header['uid'] = octdec($header['uid']);
- $header['gid'] = octdec($header['gid']);
- $header['size'] = octdec($header['size']);
- $header['mtime'] = octdec($header['mtime']);
- $header['checksum'] = octdec($header['checksum']);
- if ($header['type'] == "5") {
- $header['size'] = 0;
- }
- $checksum = 0;
- $headerBlock = substr_replace($headerBlock, ' ', 148, 8);
- for ($i = 0; $i < 512; $i++) {
- $checksum += ord(substr($headerBlock, $i, 1));
- }
- $checksumOk = $header['checksum'] == $checksum;
- if (isset($header['name']) && $checksumOk) {
- $header['name'] = trim($header['name']);
- if (!($header['name'] == '././@LongLink' && $header['type'] == 'L')) {
- return $header;
- }
- $realNameBlockSize = floor(
- ($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE
- ) * self::TAR_BLOCK_SIZE;
- $realNameBlock = $archiveReader->read($realNameBlockSize);
- $realName = substr($realNameBlock, 0, $header['size']);
- $headerMain = $this->_extractFileHeader();
- $headerMain['name'] = trim($realName);
- return $headerMain;
- }
- return false;
- }
- /**
- * Extract next file from tarball by its $header information and save it to $destination
- *
- * @param array $fileHeader
- * @param string $destination
- * @return void
- */
- protected function _extractAndWriteFile($fileHeader, $destination)
- {
- $fileWriter = new File($destination);
- $fileWriter->open('w', $fileHeader['mode']);
- $archiveReader = $this->_getReader();
- $filesize = $fileHeader['size'];
- $bytesExtracted = 0;
- while ($filesize > $bytesExtracted && !$archiveReader->eof()) {
- $block = $archiveReader->read(self::TAR_BLOCK_SIZE);
- $nonExtractedBytesCount = $filesize - $bytesExtracted;
- $data = substr($block, 0, $nonExtractedBytesCount);
- $fileWriter->write($data);
- $bytesExtracted += strlen($block);
- }
- }
- /**
- * Pack file to TAR (Tape Archiver).
- *
- * @param string $source
- * @param string $destination
- * @param bool $skipRoot
- * @return string
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
- */
- public function pack($source, $destination, $skipRoot = false)
- {
- $this->_setSkipRoot($skipRoot);
- $source = realpath($source);
- $tarData = $this->_setCurrentPath($source)->_setDestinationFilePath($destination)->_setCurrentFile($source);
- $this->_initWriter();
- $this->_createTar($skipRoot, true);
- $this->_destroyWriter();
- return $destination;
- }
- /**
- * Unpack file from TAR (Tape Archiver).
- *
- * @param string $source
- * @param string $destination
- * @return string
- */
- public function unpack($source, $destination)
- {
- $this->_setCurrentFile($source)->_setCurrentPath($source);
- $this->_initReader();
- $this->_unpackCurrentTar($destination);
- $this->_destroyReader();
- return $destination;
- }
- /**
- * Extract one file from TAR (Tape Archiver).
- *
- * @param string $file
- * @param string $source
- * @param string $destination
- * @return string
- */
- public function extract($file, $source, $destination)
- {
- $this->_setCurrentFile($source);
- $this->_initReader();
- $archiveReader = $this->_getReader();
- $extractedFile = '';
- while (!$archiveReader->eof()) {
- $header = $this->_extractFileHeader();
- if ($header['name'] == $file) {
- $extractedFile = $destination . basename($header['name']);
- $this->_extractAndWriteFile($header, $extractedFile);
- break;
- }
- if ($header['type'] != 5) {
- $skipBytes = floor(
- ($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE
- ) * self::TAR_BLOCK_SIZE;
- $archiveReader->read($skipBytes);
- }
- }
- $this->_destroyReader();
- return $extractedFile;
- }
- }
|