feat: delete
This commit is contained in:
@@ -67,20 +67,110 @@ class CorunaArchive
|
||||
return $repaired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch an on-disk archive header without loading the file body.
|
||||
*/
|
||||
private function patchCorunaHeaderFile(string $path): void
|
||||
{
|
||||
$size = filesize($path);
|
||||
if (! is_int($size) || $size < 32) {
|
||||
return;
|
||||
}
|
||||
$fh = fopen($path, 'r+b');
|
||||
if (! is_resource($fh)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$head = fread($fh, 32);
|
||||
if (! is_string($head) || strlen($head) < 32 || str_starts_with($head, self::STANDARD_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
if (! $this->isCorunaHeader($head)) {
|
||||
return;
|
||||
}
|
||||
$nextHeaderOffset = unpack('P', substr($head, 0, 8))[1] ^ self::HEADER_XOR;
|
||||
$nextHeaderSize = unpack('P', substr($head, 8, 8))[1] ^ self::HEADER_XOR;
|
||||
$nextHeaderStart = 32 + $nextHeaderOffset;
|
||||
if ($nextHeaderSize <= 0 || $nextHeaderSize > 8_000_000
|
||||
|| $nextHeaderStart + $nextHeaderSize > $size) {
|
||||
return;
|
||||
}
|
||||
if (fseek($fh, $nextHeaderStart) !== 0) {
|
||||
return;
|
||||
}
|
||||
$nextHeader = fread($fh, (int) $nextHeaderSize);
|
||||
if (! is_string($nextHeader) || strlen($nextHeader) !== (int) $nextHeaderSize) {
|
||||
return;
|
||||
}
|
||||
$patched = substr_replace($head, self::STANDARD_PREFIX."\x00\x04", 0, 8);
|
||||
$patched = substr_replace($patched, pack('P', $nextHeaderOffset), 12, 8);
|
||||
$patched = substr_replace($patched, pack('P', $nextHeaderSize), 20, 8);
|
||||
$nextCrc = crc32($nextHeader) & 0xFFFFFFFF;
|
||||
$patched = substr_replace($patched, pack('V', $nextCrc), 28, 4);
|
||||
$startCrc = crc32(substr($patched, 12, 20)) & 0xFFFFFFFF;
|
||||
$patched = substr_replace($patched, pack('V', $startCrc), 8, 4);
|
||||
if (fseek($fh, 0) !== 0) {
|
||||
return;
|
||||
}
|
||||
fwrite($fh, $patched);
|
||||
} finally {
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
|
||||
public function extract(string $wireData, string $destDir, string $batchBase = '0'): array
|
||||
{
|
||||
if (! is_dir($destDir)) {
|
||||
mkdir($destDir, 0755, true);
|
||||
}
|
||||
|
||||
$archive = $destDir.'/capture.7z';
|
||||
file_put_contents($archive, $wireData);
|
||||
try {
|
||||
$repaired = $this->repairHeader($wireData);
|
||||
} catch (\Throwable $e) {
|
||||
$repaired = $wireData;
|
||||
$this->patchCorunaHeaderFile($archive);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
return $this->runSevenZip($archive, $destDir, $batchBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract from a path without loading the whole archive into PHP.
|
||||
*/
|
||||
public function extractPath(string $srcPath, string $destDir, string $batchBase = '0'): array
|
||||
{
|
||||
if (! is_file($srcPath)) {
|
||||
throw new RuntimeException('archive missing');
|
||||
}
|
||||
if (! is_dir($destDir)) {
|
||||
mkdir($destDir, 0755, true);
|
||||
}
|
||||
|
||||
$archive = $destDir.'/capture.7z';
|
||||
file_put_contents($archive, $repaired);
|
||||
$in = fopen($srcPath, 'rb');
|
||||
$out = fopen($archive, 'wb');
|
||||
if (! is_resource($in) || ! is_resource($out)) {
|
||||
throw new RuntimeException('cannot copy archive');
|
||||
}
|
||||
try {
|
||||
stream_copy_to_stream($in, $out);
|
||||
} finally {
|
||||
fclose($in);
|
||||
fclose($out);
|
||||
}
|
||||
try {
|
||||
$this->patchCorunaHeaderFile($archive);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
return $this->runSevenZip($archive, $destDir, $batchBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{ok: bool, stderr: string, files: list<string>, password_recipe: string}
|
||||
*/
|
||||
private function runSevenZip(string $archive, string $destDir, string $batchBase): array
|
||||
{
|
||||
$password = $this->crypto->archivePassword($batchBase);
|
||||
$membersDir = $destDir.'/members';
|
||||
@mkdir($membersDir, 0755, true);
|
||||
|
||||
Reference in New Issue
Block a user