You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
9.3 KiB
286 lines
9.3 KiB
<?php
|
|
|
|
namespace Duplicator\Libs\DupArchive;
|
|
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderDirectoryHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderHeader;
|
|
use Error;
|
|
use Exception;
|
|
|
|
class DupArchive
|
|
{
|
|
const DUPARCHIVE_VERSION = '1.0.0';
|
|
const INDEX_FILE_NAME = '__dup__archive__index.json';
|
|
const INDEX_FILE_SIZE = 2000; // reserver 2K
|
|
const EXTRA_FILES_POS_KEY = 'extraPos';
|
|
|
|
const HEADER_TYPE_NONE = 0;
|
|
const HEADER_TYPE_FILE = 1;
|
|
const HEADER_TYPE_DIR = 2;
|
|
const HEADER_TYPE_GLOB = 3;
|
|
|
|
/**
|
|
* Get header type enum
|
|
*
|
|
* @param resource $archiveHandle archive resource
|
|
*
|
|
* @return int
|
|
*/
|
|
protected static function getNextHeaderType($archiveHandle)
|
|
{
|
|
$retVal = self::HEADER_TYPE_NONE;
|
|
$marker = fgets($archiveHandle, 4);
|
|
|
|
if (feof($archiveHandle) === false) {
|
|
switch ($marker) {
|
|
case '<D>':
|
|
$retVal = self::HEADER_TYPE_DIR;
|
|
break;
|
|
case '<F>':
|
|
$retVal = self::HEADER_TYPE_FILE;
|
|
break;
|
|
case '<G>':
|
|
$retVal = self::HEADER_TYPE_GLOB;
|
|
break;
|
|
default:
|
|
throw new Exception("Invalid header marker {$marker}. Location:" . ftell($archiveHandle));
|
|
}
|
|
}
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
/**
|
|
* Get archive index data
|
|
*
|
|
* @param string $archivePath archive path
|
|
*
|
|
* @return bool|array return index data, false if don't exists
|
|
*/
|
|
public static function getIndexData($archivePath)
|
|
{
|
|
try {
|
|
$indexContent = self::getSrcFile($archivePath, self::INDEX_FILE_NAME, 0, 3000, false);
|
|
if ($indexContent === false) {
|
|
return false;
|
|
}
|
|
$indexData = json_decode(rtrim($indexContent, "\0"), true);
|
|
|
|
if (!is_array($indexData)) {
|
|
return false;
|
|
}
|
|
} catch (Exception $e) {
|
|
return false;
|
|
} catch (Error $e) {
|
|
return false;
|
|
}
|
|
|
|
return $indexData;
|
|
}
|
|
|
|
/**
|
|
* Get extra files offset if set or 0
|
|
*
|
|
* @param string $archivePath archive path
|
|
*
|
|
* @return int
|
|
*/
|
|
public static function getExtraOffset($archivePath)
|
|
{
|
|
if (($indexData = self::getIndexData($archivePath)) === false) {
|
|
return 0;
|
|
}
|
|
return (isset($indexData[self::EXTRA_FILES_POS_KEY]) ? $indexData[self::EXTRA_FILES_POS_KEY] : 0);
|
|
}
|
|
|
|
/**
|
|
* Add file in archive from src
|
|
*
|
|
* @param string $archivePath archive path
|
|
* @param string $relativePath relative path
|
|
* @param int $offset start search location
|
|
* @param int $sizeToSearch max size where search
|
|
*
|
|
* @return bool|int false if file not found of path position
|
|
*/
|
|
public static function seachPathInArchive($archivePath, $relativePath, $offset = 0, $sizeToSearch = 0)
|
|
{
|
|
if (($archiveHandle = fopen($archivePath, 'rb')) === false) {
|
|
throw new Exception("Can’t open archive at $archivePath!");
|
|
}
|
|
$result = self::searchPath($archivePath, $relativePath, $offset, $sizeToSearch);
|
|
@fclose($archiveHandle);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Search path, if found set and return position
|
|
*
|
|
* @param resource $archiveHandle dup archive resource
|
|
* @param string $relativePath relative path to extract
|
|
* @param int $offset start search location
|
|
* @param int $sizeToSearch max size where search
|
|
*
|
|
* @return bool|int false if file not found of path position
|
|
*/
|
|
public static function searchPath($archiveHandle, $relativePath, $offset = 0, $sizeToSearch = 0)
|
|
{
|
|
if (!is_resource($archiveHandle)) {
|
|
throw new Exception('Archive handle must be a resource');
|
|
}
|
|
|
|
if (fseek($archiveHandle, $offset, SEEK_SET) < 0) {
|
|
return false;
|
|
}
|
|
|
|
if ($offset == 0) {
|
|
DupArchiveReaderHeader::readFromArchive($archiveHandle);
|
|
}
|
|
|
|
$result = false;
|
|
$position = ftell($archiveHandle);
|
|
$continue = true;
|
|
|
|
do {
|
|
switch (($type = self::getNextHeaderType($archiveHandle))) {
|
|
case self::HEADER_TYPE_FILE:
|
|
$currentFileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, true, true);
|
|
if ($currentFileHeader->relativePath == $relativePath) {
|
|
$continue = false;
|
|
$result = $position;
|
|
}
|
|
break;
|
|
case self::HEADER_TYPE_DIR:
|
|
$directoryHeader = DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true);
|
|
if ($directoryHeader->relativePath == $relativePath) {
|
|
$continue = false;
|
|
$result = $position;
|
|
}
|
|
break;
|
|
case self::HEADER_TYPE_NONE:
|
|
$continue = false;
|
|
break;
|
|
default:
|
|
throw new Exception('Invali header type "' . $type . '"');
|
|
}
|
|
$position = ftell($archiveHandle);
|
|
if ($sizeToSearch > 0 && ($position - $offset) >= $sizeToSearch) {
|
|
break;
|
|
}
|
|
} while ($continue);
|
|
|
|
if ($result !== false) {
|
|
if (fseek($archiveHandle, $result, SEEK_SET) < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get file content
|
|
*
|
|
* @param string $archivePath archvie path
|
|
* @param string $relativePath relative path to extract
|
|
* @param int $offset start search location
|
|
* @param int $sizeToSearch max size where search
|
|
* @param bool $isCompressed true if is compressed
|
|
*
|
|
* @return bool|string false if file not found
|
|
*/
|
|
public static function getSrcFile($archivePath, $relativePath, $offset = 0, $sizeToSearch = 0, $isCompressed = null)
|
|
{
|
|
if (($archiveHandle = fopen($archivePath, 'rb')) === false) {
|
|
throw new Exception("Can’t open archive at $archivePath!");
|
|
}
|
|
$archiveHeader = DupArchiveReaderHeader::readFromArchive($archiveHandle);
|
|
if (is_null($isCompressed)) {
|
|
$isCompressed = $archiveHeader->isCompressed;
|
|
}
|
|
|
|
if (self::searchPath($archiveHandle, $relativePath, $offset, $sizeToSearch) === false) {
|
|
return false;
|
|
}
|
|
|
|
if (self::getNextHeaderType($archiveHandle) != self::HEADER_TYPE_FILE) {
|
|
return false;
|
|
}
|
|
|
|
$header = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true);
|
|
$result = self::getSrcFromHeader($archiveHandle, $header, $isCompressed);
|
|
@fclose($archiveHandle);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get src file form header
|
|
*
|
|
* @param resource $archiveHandle archive handle
|
|
* @param DupArchiveReaderFileHeader $fileHeader file header
|
|
* @param bool $isCompressed true if is compressed
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function getSrcFromHeader($archiveHandle, DupArchiveReaderFileHeader $fileHeader, $isCompressed)
|
|
{
|
|
if ($fileHeader->fileSize == 0) {
|
|
return '';
|
|
}
|
|
$dataSize = 0;
|
|
$result = '';
|
|
|
|
do {
|
|
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle);
|
|
$result .= DupArchiveReaderGlobHeader::readContent($archiveHandle, $globHeader, $isCompressed);
|
|
$dataSize += $globHeader->originalSize;
|
|
} while ($dataSize < $fileHeader->fileSize);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Skip file in archive
|
|
*
|
|
* @param resource $archiveHandle dup archive resource
|
|
* @param DupArchiveFileHeader $fileHeader file header
|
|
*
|
|
* @return void
|
|
*/
|
|
protected static function skipFileInArchive($archiveHandle, DupArchiveReaderFileHeader $fileHeader)
|
|
{
|
|
if ($fileHeader->fileSize == 0) {
|
|
return;
|
|
}
|
|
$dataSize = 0;
|
|
|
|
do {
|
|
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle, true);
|
|
$dataSize += $globHeader->originalSize;
|
|
} while ($dataSize < $fileHeader->fileSize);
|
|
}
|
|
|
|
/**
|
|
* Assumes we are on one header and just need to get to the next
|
|
*
|
|
* @param resource $archiveHandle dup archive resource
|
|
*
|
|
* @return void
|
|
*/
|
|
protected static function skipToNextHeader($archiveHandle)
|
|
{
|
|
$headerType = self::getNextHeaderType($archiveHandle);
|
|
switch ($headerType) {
|
|
case self::HEADER_TYPE_FILE:
|
|
$fileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true);
|
|
self::skipFileInArchive($archiveHandle, $fileHeader);
|
|
break;
|
|
case self::HEADER_TYPE_DIR:
|
|
DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true);
|
|
break;
|
|
case self::HEADER_TYPE_NONE:
|
|
false;
|
|
}
|
|
}
|
|
}
|
|
|