Skip to content

Commit 3b39936

Browse files
committed
Added extract files support
1 parent 83900ba commit 3b39936

File tree

6 files changed

+164
-24
lines changed

6 files changed

+164
-24
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![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)
44

5-
PHP Library used to read metadata from ISO files based on [php-iso-file](https://github.com/php-classes/php-iso-file)
5+
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)
66

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

@@ -45,10 +45,11 @@ Description:
4545
Tool to process ISO files
4646
4747
Usage:
48-
isotool --file=<path>
48+
isotool [options] --file=<path>
4949
5050
Options:
51-
-f, --file Path for the ISO file (mandatory)
51+
-f, --file Path for the ISO file (mandatory)
52+
-x, --extract=<extract_path> Extract files in the given location
5253
```
5354

5455
Sample usage:

src/Cli/IsoTool.php

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use PhpIso\Descriptor;
88
use PhpIso\Descriptor\Boot;
9+
use PhpIso\Descriptor\PrimaryVolume;
10+
use PhpIso\Descriptor\SupplementaryVolume;
11+
use PhpIso\Descriptor\Type;
912
use PhpIso\Descriptor\Volume;
1013
use PhpIso\Exception;
1114
use PhpIso\FileDirectory;
@@ -39,11 +42,23 @@ public function run(): void
3942
exit(2);
4043
}
4144

45+
$extractPath = '';
46+
if (isset($options['extract']) && is_string($options['extract'])) {
47+
$extractPath = $options['extract'];
48+
} elseif (isset($options['x']) && is_string($options['x'])) {
49+
$extractPath = $options['x'];
50+
}
51+
4252
echo 'Input ISO file: ' . $file . PHP_EOL;
4353

4454
try {
4555
$this->checkIsoFile($file);
46-
$this->infoAction($file);
56+
57+
if ($extractPath !== '') {
58+
$this->extractAction($file, $extractPath);
59+
} else {
60+
$this->infoAction($file);
61+
}
4762
} catch (Throwable $ex) {
4863
$this->displayError($ex->getMessage());
4964
exit(3);
@@ -84,6 +99,74 @@ protected function infoAction(string $file): void
8499
}
85100
}
86101

102+
protected function extractAction(string $file, string $extractPath): void
103+
{
104+
if (! is_dir($extractPath)) {
105+
$mkdirResult = mkdir($extractPath, 0777, true);
106+
107+
if ($mkdirResult === false) {
108+
throw new Exception('Failed to create extract output directory: ' . $extractPath);
109+
}
110+
}
111+
112+
$isoFile = new IsoFile($file);
113+
114+
echo 'Extract ISO file to: ' . $extractPath . PHP_EOL;
115+
116+
if (isset($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC]) && $isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC] instanceof SupplementaryVolume) {
117+
$this->extractFiles($isoFile->descriptors[Type::SUPPLEMENTARY_VOLUME_DESC], $isoFile, $extractPath);
118+
} elseif (isset($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC]) && $isoFile->descriptors[Type::PRIMARY_VOLUME_DESC] instanceof PrimaryVolume) {
119+
$this->extractFiles($isoFile->descriptors[Type::PRIMARY_VOLUME_DESC], $isoFile, $extractPath);
120+
}
121+
122+
echo 'Extract finished!' . PHP_EOL;
123+
}
124+
125+
protected function extractFiles(Volume $primaryVolume, IsoFile $isoFile, string $destinationDir): void
126+
{
127+
$pathTable = $primaryVolume->loadTable($isoFile);
128+
129+
if ($pathTable === null) {
130+
return;
131+
}
132+
133+
$destinationDir = rtrim($destinationDir, DIRECTORY_SEPARATOR);
134+
135+
/** @var PathTableRecord $pathRecord */
136+
foreach ($pathTable as $pathRecord) {
137+
// check extents
138+
$extents = $pathRecord->loadExtents($isoFile, $primaryVolume->blockSize);
139+
140+
if ($extents !== false) {
141+
/** @var FileDirectory $extentRecord */
142+
foreach ($extents as $extentRecord) {
143+
$path = $extentRecord->fileId;
144+
145+
if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
146+
$fullPath = $destinationDir . $pathRecord->getFullPath($pathTable) . $path;
147+
if ($extentRecord->isDirectory()) {
148+
$fullPath .= DIRECTORY_SEPARATOR;
149+
}
150+
151+
if (! $extentRecord->isDirectory()) {
152+
$location = $extentRecord->location;
153+
$dataLength = $extentRecord->dataLength;
154+
echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')' . PHP_EOL;
155+
156+
$pathRecord->extractFile($isoFile, $primaryVolume->blockSize, $location, $dataLength, $fullPath);
157+
} else {
158+
if (! is_dir($fullPath)) {
159+
if (mkdir($fullPath) === false) {
160+
throw new Exception('Failed to create directory: ' . $fullPath);
161+
}
162+
}
163+
}
164+
}
165+
}
166+
}
167+
}
168+
}
169+
87170
protected function infoVolume(Volume $volumeDescriptor): void
88171
{
89172
echo ' - System ID: ' . $volumeDescriptor->systemId . PHP_EOL;
@@ -124,10 +207,21 @@ protected function displayFiles(Volume $volumeDescriptor, IsoFile $isoFile): voi
124207
/** @var FileDirectory $extentRecord */
125208
foreach ($extents as $extentRecord) {
126209
$path = $extentRecord->fileId;
127-
if ($extentRecord->isDirectory() && ! $extentRecord->isThis() && ! $extentRecord->isParent()) {
128-
$path .= '/';
210+
211+
if (! $extentRecord->isThis() && ! $extentRecord->isParent()) {
212+
$fullPath = $pathRecord->getFullPath($pathTable) . $path;
213+
if ($extentRecord->isDirectory()) {
214+
$fullPath .= DIRECTORY_SEPARATOR;
215+
}
216+
217+
if (! $extentRecord->isDirectory()) {
218+
$location = $extentRecord->location;
219+
$dataLength = $extentRecord->dataLength;
220+
echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')' . PHP_EOL;
221+
} else {
222+
echo $fullPath . PHP_EOL;
223+
}
129224
}
130-
echo $path . PHP_EOL;
131225
}
132226
}
133227
}
@@ -145,9 +239,10 @@ protected function infoBoot(Boot $bootDescriptor): void
145239
*/
146240
protected function parseCliArgs(): array
147241
{
148-
$shortopts = 'f:';
242+
$shortopts = 'f:x::';
149243
$longopts = [
150244
'file:',
245+
'extract::',
151246
];
152247
$options = getopt($shortopts, $longopts, $restIndex);
153248

@@ -173,7 +268,8 @@ protected function displayHelp(): void
173268
isotool [options] --file=<path>
174269
175270
Options:
176-
-f, --file Path for the ISO file (mandatory)
271+
-f, --file Path for the ISO file (mandatory)
272+
-x, --extract=<extract_path> Extract files in the given location
177273
';
178274
echo $help;
179275
}

src/PathTableRecord.php

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,45 @@ public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementa
8686
return FileDirectory::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary);
8787
}
8888

89+
public function extractFile(IsoFile &$isoFile, int $blockSize, int $location, int $dataLength, string $destinationFile): void
90+
{
91+
$seekLocation = $location * $blockSize;
92+
93+
if ($isoFile->seek($seekLocation, SEEK_SET) === -1) {
94+
throw new Exception('Failed to seek to location');
95+
}
96+
97+
$writeHandle = fopen($destinationFile, 'wb');
98+
99+
if ($writeHandle === false) {
100+
throw new Exception('Failed to open file for writing: ' . $destinationFile);
101+
}
102+
103+
do {
104+
$readLength = 1024;
105+
106+
if ($dataLength < $readLength) {
107+
$readLength = $dataLength;
108+
}
109+
110+
$readResult = $isoFile->read($readLength);
111+
112+
if ($readResult === false) {
113+
break;
114+
}
115+
116+
$writeResult = fwrite($writeHandle, $readResult);
117+
118+
if ($writeResult === false) {
119+
throw new Exception('Failed to write to file: ' . $destinationFile);
120+
}
121+
122+
$dataLength -= $readLength;
123+
} while ($dataLength > 0);
124+
125+
fclose($writeHandle);
126+
}
127+
89128
/**
90129
* Build the full path of a PathTableRecord object based on it's parent(s)
91130
*
@@ -96,7 +135,11 @@ public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementa
96135
public function getFullPath(array $pathTable): string
97136
{
98137
if ($this->parentDirNum === 1) {
99-
return '/' . $this->dirIdentifier;
138+
if ($this->dirIdentifier === '') {
139+
return DIRECTORY_SEPARATOR;
140+
}
141+
142+
return DIRECTORY_SEPARATOR . $this->dirIdentifier . DIRECTORY_SEPARATOR;
100143
}
101144

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

114-
$path = $used->dirIdentifier . '/' . $path;
157+
$path = $used->dirIdentifier . DIRECTORY_SEPARATOR . $path;
115158

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

123-
return $path;
166+
return DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR;
124167
}
125168
}

tests/IsoFileTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,15 @@ public function testDescriptorsTestDirIso(): void
132132
}
133133

134134
$pathsExpected = [
135-
'/' => [
135+
DIRECTORY_SEPARATOR => [
136136
'./',
137137
'../',
138138
'CONTRIBU.MD',
139139
'LICENSE.',
140140
'SUB_DIR/',
141141
'TEST_FIL.TXT',
142142
],
143-
'/SUB_DIR' => [
143+
DIRECTORY_SEPARATOR . 'SUB_DIR' . DIRECTORY_SEPARATOR => [
144144
'./',
145145
'../',
146146
'TEST_000.TXT',
@@ -225,25 +225,25 @@ public function testDescriptorsSubdirIso(): void
225225
}
226226

227227
$pathsExpected = [
228-
'/' => [
228+
DIRECTORY_SEPARATOR => [
229229
'./',
230230
'../',
231231
'DIR1/',
232232
'TEST1.TXT',
233233
],
234-
'/DIR1' => [
234+
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR => [
235235
'./',
236236
'../',
237237
'DIR2/',
238238
'TEST2.TXT',
239239
],
240-
'DIR1/DIR2' => [
240+
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR . 'DIR2' . DIRECTORY_SEPARATOR => [
241241
'./',
242242
'../',
243243
'DIR3/',
244244
'TEST3.TXT',
245245
],
246-
'DIR1/DIR2/DIR3' => [
246+
DIRECTORY_SEPARATOR . 'DIR1' . DIRECTORY_SEPARATOR . 'DIR2' . DIRECTORY_SEPARATOR . 'DIR3' . DIRECTORY_SEPARATOR => [
247247
'./',
248248
'../',
249249
'TEST4.TXT',

tests/IsoFileTestUdf.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ public function testDescriptorsTestIso(): void
109109
}
110110

111111
$pathsExpected = [
112-
'/' => [
112+
DIRECTORY_SEPARATOR => [
113113
'./',
114114
'../',
115115
'CLASSES/',
116116
'COMPOSER.JSO',
117117
'EXAMPLES/',
118118
],
119-
'/CLASSES' => [
119+
DIRECTORY_SEPARATOR . 'CLASSES' . DIRECTORY_SEPARATOR => [
120120
'./',
121121
'../',
122122
'BOOT_CA2.PHP',
@@ -133,7 +133,7 @@ public function testDescriptorsTestIso(): void
133133
'ISO_INCL.PHP',
134134
'PATH_TAB.PHP',
135135
],
136-
'/EXAMPLES' => [
136+
DIRECTORY_SEPARATOR . 'EXAMPLES' . DIRECTORY_SEPARATOR => [
137137
'./',
138138
'../',
139139
'BOOTCATA.PHP',

tests/PathTableRecordTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ public function testGetFullPath(): void
4848
3 => $record3,
4949
];
5050

51-
$this->assertSame('/root', $record1->getFullPath($pathTable));
52-
$this->assertSame('/subdir', $record2->getFullPath($pathTable));
53-
$this->assertSame('subdir/subsubdir', $record3->getFullPath($pathTable));
51+
$this->assertSame(DIRECTORY_SEPARATOR . 'root' . DIRECTORY_SEPARATOR, $record1->getFullPath($pathTable));
52+
$this->assertSame(DIRECTORY_SEPARATOR . 'subdir' . DIRECTORY_SEPARATOR, $record2->getFullPath($pathTable));
53+
$this->assertSame(DIRECTORY_SEPARATOR . 'subdir' . DIRECTORY_SEPARATOR . 'subsubdir' . DIRECTORY_SEPARATOR, $record3->getFullPath($pathTable));
5454
}
5555

5656
public function testGetFullPathWithMaxDepth(): void

0 commit comments

Comments
 (0)