Skip to content

Commit

Permalink
Added extract files support
Browse files Browse the repository at this point in the history
  • Loading branch information
indy2kro committed Jan 19, 2025
1 parent 83900ba commit 3b39936
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 24 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![codecov](https://codecov.io/gh/indy2kro/php-iso/graph/badge.svg?token=NBj76nYtmB)](https://codecov.io/gh/indy2kro/php-iso) [![Tests](https://github.com/indy2kro/php-iso/actions/workflows/tests.yml/badge.svg)](https://github.com/indy2kro/php-iso/actions/workflows/tests.yml)

PHP Library used to read metadata from ISO files based on [php-iso-file](https://github.com/php-classes/php-iso-file)
PHP Library used to read metadata and extract information from ISO files based on [php-iso-file](https://github.com/php-classes/php-iso-file)

This library follows the [ISO 9660 / ECMA-119](https://www.ecma-international.org/wp-content/uploads/ECMA-119_4th_edition_june_2019.pdf) standard.

Expand Down Expand Up @@ -45,10 +45,11 @@ Description:
Tool to process ISO files
Usage:
isotool --file=<path>
isotool [options] --file=<path>
Options:
-f, --file Path for the ISO file (mandatory)
-f, --file Path for the ISO file (mandatory)
-x, --extract=<extract_path> Extract files in the given location
```

Sample usage:
Expand Down
108 changes: 102 additions & 6 deletions src/Cli/IsoTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use PhpIso\Descriptor;
use PhpIso\Descriptor\Boot;
use PhpIso\Descriptor\PrimaryVolume;
use PhpIso\Descriptor\SupplementaryVolume;
use PhpIso\Descriptor\Type;
use PhpIso\Descriptor\Volume;
use PhpIso\Exception;
use PhpIso\FileDirectory;
Expand Down Expand Up @@ -39,11 +42,23 @@ public function run(): void
exit(2);
}

$extractPath = '';
if (isset($options['extract']) && is_string($options['extract'])) {
$extractPath = $options['extract'];
} elseif (isset($options['x']) && is_string($options['x'])) {
$extractPath = $options['x'];
}

echo 'Input ISO file: ' . $file . PHP_EOL;

try {
$this->checkIsoFile($file);
$this->infoAction($file);

if ($extractPath !== '') {
$this->extractAction($file, $extractPath);
} else {
$this->infoAction($file);
}
} catch (Throwable $ex) {
$this->displayError($ex->getMessage());
exit(3);
Expand Down Expand Up @@ -84,6 +99,74 @@ protected function infoAction(string $file): void
}
}

protected function extractAction(string $file, string $extractPath): void
{
if (! is_dir($extractPath)) {
$mkdirResult = mkdir($extractPath, 0777, true);

if ($mkdirResult === false) {
throw new Exception('Failed to create extract output directory: ' . $extractPath);
}
}

$isoFile = new IsoFile($file);

echo 'Extract ISO file to: ' . $extractPath . PHP_EOL;

if (isset($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC]) && $isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC] instanceof SupplementaryVolume) {
$this->extractFiles($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC], $isoFile, $extractPath);
} elseif (isset($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC]) && $isoFile->descriptors[Type::PRIMARY_VOLUME_DESC] instanceof PrimaryVolume) {
$this->extractFiles($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC], $isoFile, $extractPath);
}

echo 'Extract finished!' . PHP_EOL;
}

protected function extractFiles(Volume $primaryVolume, IsoFile $isoFile, string $destinationDir): void
{
$pathTable = $primaryVolume->loadTable($isoFile);

if ($pathTable === null) {
return;
}

$destinationDir = rtrim($destinationDir, DIRECTORY_SEPARATOR);

/** @var PathTableRecord $pathRecord */
foreach ($pathTable as $pathRecord) {
// check extents
$extents = $pathRecord->loadExtents($isoFile, $primaryVolume->blockSize);

if ($extents !== false) {
/** @var FileDirectory $extentRecord */
foreach ($extents as $extentRecord) {
$path = $extentRecord->fileId;

if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
$fullPath = $destinationDir . $pathRecord->getFullPath($pathTable) . $path;
if ($extentRecord->isDirectory()) {
$fullPath .= DIRECTORY_SEPARATOR;
}

if (! $extentRecord->isDirectory()) {
$location = $extentRecord->location;
$dataLength = $extentRecord->dataLength;
echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')' . PHP_EOL;

$pathRecord->extractFile($isoFile, $primaryVolume->blockSize, $location, $dataLength, $fullPath);
} else {
if (! is_dir($fullPath)) {
if (mkdir($fullPath) === false) {
throw new Exception('Failed to create directory: ' . $fullPath);
}
}
}
}
}
}
}
}

protected function infoVolume(Volume $volumeDescriptor): void
{
echo ' - System ID: ' . $volumeDescriptor->systemId . PHP_EOL;
Expand Down Expand Up @@ -124,10 +207,21 @@ protected function displayFiles(Volume $volumeDescriptor, IsoFile $isoFile): voi
/** @var FileDirectory $extentRecord */
foreach ($extents as $extentRecord) {
$path = $extentRecord->fileId;
if ($extentRecord->isDirectory() && ! $extentRecord->isThis() && ! $extentRecord->isParent()) {
$path .= '/';

if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
$fullPath = $pathRecord->getFullPath($pathTable) . $path;
if ($extentRecord->isDirectory()) {
$fullPath .= DIRECTORY_SEPARATOR;
}

if (! $extentRecord->isDirectory()) {
$location = $extentRecord->location;
$dataLength = $extentRecord->dataLength;
echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')' . PHP_EOL;
} else {
echo $fullPath . PHP_EOL;
}
}
echo $path . PHP_EOL;
}
}
}
Expand All @@ -145,9 +239,10 @@ protected function infoBoot(Boot $bootDescriptor): void
*/
protected function parseCliArgs(): array
{
$shortopts = 'f:';
$shortopts = 'f:x::';
$longopts = [
'file:',
'extract::',
];
$options = getopt($shortopts, $longopts, $restIndex);

Expand All @@ -173,7 +268,8 @@ protected function displayHelp(): void
isotool [options] --file=<path>
Options:
-f, --file Path for the ISO file (mandatory)
-f, --file Path for the ISO file (mandatory)
-x, --extract=<extract_path> Extract files in the given location
';
echo $help;
}
Expand Down
49 changes: 46 additions & 3 deletions src/PathTableRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,45 @@ public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementa
return FileDirectory::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary);
}

public function extractFile(IsoFile &$isoFile, int $blockSize, int $location, int $dataLength, string $destinationFile): void
{
$seekLocation = $location * $blockSize;

if ($isoFile->seek($seekLocation, SEEK_SET) === -1) {
throw new Exception('Failed to seek to location');
}

$writeHandle = fopen($destinationFile, 'wb');

if ($writeHandle === false) {
throw new Exception('Failed to open file for writing: ' . $destinationFile);
}

do {
$readLength = 1024;

if ($dataLength < $readLength) {
$readLength = $dataLength;
}

$readResult = $isoFile->read($readLength);

if ($readResult === false) {
break;
}

$writeResult = fwrite($writeHandle, $readResult);

if ($writeResult === false) {
throw new Exception('Failed to write to file: ' . $destinationFile);
}

$dataLength -= $readLength;
} while ($dataLength > 0);

fclose($writeHandle);
}

/**
* Build the full path of a PathTableRecord object based on it's parent(s)
*
Expand All @@ -96,7 +135,11 @@ public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementa
public function getFullPath(array $pathTable): string
{
if ($this->parentDirNum === 1) {
return '/' . $this->dirIdentifier;
if ($this->dirIdentifier === '') {
return DIRECTORY_SEPARATOR;
}

return DIRECTORY_SEPARATOR . $this->dirIdentifier . DIRECTORY_SEPARATOR;
}

$path = $this->dirIdentifier;
Expand All @@ -111,7 +154,7 @@ public function getFullPath(array $pathTable): string
throw new Exception('Maximum depth of 1000 reached');
}

$path = $used->dirIdentifier . '/' . $path;
$path = $used->dirIdentifier . DIRECTORY_SEPARATOR . $path;

if ($used->parentDirNum === 1) {
break;
Expand All @@ -120,6 +163,6 @@ public function getFullPath(array $pathTable): string
$used = $pathTable[$used->parentDirNum];
}

return $path;
return DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR;
}
}
12 changes: 6 additions & 6 deletions tests/IsoFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ public function testDescriptorsTestDirIso(): void
}

$pathsExpected = [
'/' => [
DIRECTORY_SEPARATOR => [
'./',
'../',
'CONTRIBU.MD',
'LICENSE.',
'SUB_DIR/',
'TEST_FIL.TXT',
],
'/SUB_DIR' => [
DIRECTORY_SEPARATOR . 'SUB_DIR' . DIRECTORY_SEPARATOR => [
'./',
'../',
'TEST_000.TXT',
Expand Down Expand Up @@ -225,25 +225,25 @@ public function testDescriptorsSubdirIso(): void
}

$pathsExpected = [
'/' => [
DIRECTORY_SEPARATOR => [
'./',
'../',
'DIR1/',
'TEST1.TXT',
],
'/DIR1' => [
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR => [
'./',
'../',
'DIR2/',
'TEST2.TXT',
],
'DIR1/DIR2' => [
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR . 'DIR2' . DIRECTORY_SEPARATOR => [
'./',
'../',
'DIR3/',
'TEST3.TXT',
],
'DIR1/DIR2/DIR3' => [
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR . 'DIR2' . DIRECTORY_SEPARATOR . 'DIR3' . DIRECTORY_SEPARATOR => [
'./',
'../',
'TEST4.TXT',
Expand Down
6 changes: 3 additions & 3 deletions tests/IsoFileTestUdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ public function testDescriptorsTestIso(): void
}

$pathsExpected = [
'/' => [
DIRECTORY_SEPARATOR => [
'./',
'../',
'CLASSES/',
'COMPOSER.JSO',
'EXAMPLES/',
],
'/CLASSES' => [
DIRECTORY_SEPARATOR . 'CLASSES' . DIRECTORY_SEPARATOR => [
'./',
'../',
'BOOT_CA2.PHP',
Expand All @@ -133,7 +133,7 @@ public function testDescriptorsTestIso(): void
'ISO_INCL.PHP',
'PATH_TAB.PHP',
],
'/EXAMPLES' => [
DIRECTORY_SEPARATOR . 'EXAMPLES' . DIRECTORY_SEPARATOR => [
'./',
'../',
'BOOTCATA.PHP',
Expand Down
6 changes: 3 additions & 3 deletions tests/PathTableRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public function testGetFullPath(): void
3 => $record3,
];

$this->assertSame('/root', $record1->getFullPath($pathTable));
$this->assertSame('/subdir', $record2->getFullPath($pathTable));
$this->assertSame('subdir/subsubdir', $record3->getFullPath($pathTable));
$this->assertSame(DIRECTORY_SEPARATOR . 'root' . DIRECTORY_SEPARATOR, $record1->getFullPath($pathTable));
$this->assertSame(DIRECTORY_SEPARATOR . 'subdir' . DIRECTORY_SEPARATOR, $record2->getFullPath($pathTable));
$this->assertSame(DIRECTORY_SEPARATOR . 'subdir' . DIRECTORY_SEPARATOR . 'subsubdir' . DIRECTORY_SEPARATOR, $record3->getFullPath($pathTable));
}

public function testGetFullPathWithMaxDepth(): void
Expand Down

0 comments on commit 3b39936

Please sign in to comment.