isCorunaHeader($data)) { throw new RuntimeException('not a Coruna header-obfuscated 7z archive'); } $nextHeaderOffset = unpack('P', substr($data, 0, 8))[1] ^ self::HEADER_XOR; $nextHeaderSize = unpack('P', substr($data, 8, 8))[1] ^ self::HEADER_XOR; $nextHeaderStart = 32 + $nextHeaderOffset; $nextHeaderEnd = $nextHeaderStart + $nextHeaderSize; if ($nextHeaderSize === 0 || $nextHeaderEnd > strlen($data)) { throw new RuntimeException('invalid Coruna 7z bounds'); } $repaired = $data; $repaired = substr_replace($repaired, self::STANDARD_PREFIX."\x00\x04", 0, 8); $repaired = substr_replace($repaired, pack('P', $nextHeaderOffset), 12, 8); $repaired = substr_replace($repaired, pack('P', $nextHeaderSize), 20, 8); $nextCrc = crc32(substr($repaired, $nextHeaderStart, $nextHeaderSize)) & 0xFFFFFFFF; $repaired = substr_replace($repaired, pack('V', $nextCrc), 28, 4); $startCrc = crc32(substr($repaired, 12, 20)) & 0xFFFFFFFF; $repaired = substr_replace($repaired, pack('V', $startCrc), 8, 4); return $repaired; } public function extract(string $wireData, string $destDir, string $batchBase = '0'): array { if (! is_dir($destDir)) { mkdir($destDir, 0755, true); } try { $repaired = $this->repairHeader($wireData); } catch (\Throwable $e) { $repaired = $wireData; } $archive = $destDir.'/capture.7z'; file_put_contents($archive, $repaired); file_put_contents($destDir.'/wire.bin', $wireData); $password = $this->crypto->archivePassword($batchBase); $membersDir = $destDir.'/members'; @mkdir($membersDir, 0755, true); $bin = is_executable($this->sevenZip) ? $this->sevenZip : '7z'; $result = Process::timeout(120)->run([ $bin, 'x', '-y', '-p'.$password, '-o'.$membersDir, $archive, ]); $files = []; if (is_dir($membersDir)) { $it = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator( $membersDir, \FilesystemIterator::SKIP_DOTS )); foreach ($it as $file) { if ($file->isFile()) { $files[] = $file->getPathname(); } } } return [ 'ok' => $result->successful() && count($files) > 0, 'stderr' => $result->errorOutput(), 'files' => $files, 'password_recipe' => 'session_key||'.$batchBase, ]; } }