iso9660.py
ofrak.core.iso9660
ElToritoISO9660Image (ISO9660Image)
dataclass
ElToritoISO9660Image()
ISO9660Entry (ResourceView)
dataclass
ISO9660Entry(name: str, path: str, is_dir: bool, is_file: bool, is_symlink: bool, is_dot: bool, is_dotdot: bool, iso_version: int)
ISO9660Image (GenericBinary, FilesystemRoot)
dataclass
ISO 9660 image. ISO 9660 is a file system for optical disc media.
ISO9660ImageAnalyzer (Analyzer)
Extracts metadata and structural attributes from ISO 9660 disc images including volume descriptors, volume labels, creation/modification timestamps, block sizes, volume size, path table information, and root directory location. ISO 9660 defines the standard CD/DVD filesystem format. Use to understand ISO structure before extraction, validate ISO integrity, check disc capacity and layout, extract metadata for cataloging, or locate specific filesystem structures. Helps understand disc organization.
analyze(self, resource, config=None)
async
Analyze a resource for to extract specific ResourceAttributes.
Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The resource that is being analyzed |
required |
config |
Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Returns:
| Type | Description |
|---|---|
The analysis results |
Source code in ofrak/core/iso9660.py
async def analyze(self, resource: Resource, config=None):
joliet_level = None
rockridge_version = None
udf_version = None
iso = PyCdlib()
iso.open_fp(BytesIO(await resource.get_data()))
interchange_level = iso.interchange_level
has_joliet = iso.has_joliet()
has_rockridge = iso.has_rock_ridge()
has_udf = iso.has_udf()
has_eltorito = iso.eltorito_boot_catalog is not None
vol_identifier = iso.pvd.volume_identifier.decode("utf-8").strip()
sys_identifier = iso.pvd.system_identifier.decode("utf-8").strip()
app_identifier = iso.pvd.application_identifier.text.decode("utf-8").strip()
xa = iso.xa
if has_joliet:
joliet_vd = iso.joliet_vd
assert isinstance(joliet_vd, PrimaryOrSupplementaryVD)
esc = joliet_vd.escape_sequences
if b"%/@" in esc:
joliet_level = 1
elif b"%/C" in esc:
joliet_level = 2
elif b"%/E" in esc:
joliet_level = 3
elif has_rockridge:
rockridge_version = iso.rock_ridge
elif has_udf:
udf_version = "2.60"
iso.close()
return ISO9660ImageAttributes(
interchange_level=interchange_level,
volume_identifier=vol_identifier,
system_identifier=sys_identifier,
app_identifier=app_identifier,
extended_attributes=xa,
has_joliet=has_joliet,
joliet_level=joliet_level,
has_rockridge=has_rockridge,
rockridge_version=rockridge_version,
has_udf=has_udf,
udf_version=udf_version,
has_eltorito=has_eltorito,
)
ISO9660ImageAttributes (ResourceAttributes)
dataclass
ISO9660ImageAttributes(interchange_level: int, volume_identifier: str, system_identifier: str, app_identifier: str, extended_attributes: bool, has_joliet: bool, has_rockridge: bool, has_udf: bool, has_eltorito: bool, joliet_level: Optional[int] = None, rockridge_version: Optional[str] = None, udf_version: Optional[str] = None)
ISO9660Packer (Packer)
Packages files into an ISO 9660 optical disc image suitable for CD/DVD burning or virtual machine use. The packer creates the Volume Descriptor Set, Path Tables, and directory structures required by the ISO 9660 standard. Use after modifying extracted ISO contents to recreate bootable installation media or software distribution images.
pack(self, resource, config=None)
async
Pack the given resource.
Users should not call this method directly; rather, they should run Resource.run or Resource.pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
required | |
config |
Optional config for packing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Source code in ofrak/core/iso9660.py
async def pack(self, resource: Resource, config=None) -> None:
iso_view = await resource.view_as(ISO9660Image)
try:
isolinux_bin = await resource.get_only_descendant_as_view(
ISO9660Entry,
r_filter=ResourceFilter(
attribute_filters=(
(ResourceAttributeValueFilter(ISO9660Entry.Name, "isolinux.bin"),)
),
),
)
isolinux_bin_cmd = [
"-b",
isolinux_bin.path.strip("/"),
] # The leading "/" is not needed in this CLI arg
except NotFoundError:
isolinux_bin_cmd = list()
try:
boot_cat = await resource.get_only_descendant_as_view(
ISO9660Entry,
r_filter=ResourceFilter(
attribute_filters=(
(ResourceAttributeValueFilter(ISO9660Entry.Name, "boot.cat"),)
),
),
)
boot_cat_cmd = [
"-c",
boot_cat.path.strip("/"),
] # The leading "/" is not needed in this CLI arg
except NotFoundError:
boot_cat_cmd = list()
iso_attrs = resource.get_attributes(ISO9660ImageAttributes)
temp_flush_dir = await iso_view.flush_to_disk()
with tempfile.NamedTemporaryFile(suffix=".iso", mode="rb", delete_on_close=False) as temp:
temp.close()
cmd = [
"mkisofs",
*(["-J"] if iso_attrs.has_joliet else []),
*(["-R"] if iso_attrs.has_rockridge else []),
*(["-V", iso_attrs.volume_identifier] if iso_attrs.volume_identifier else []),
*(["-sysid", iso_attrs.system_identifier] if iso_attrs.system_identifier else []),
*(["-A", iso_attrs.app_identifier] if iso_attrs.app_identifier else []),
*(
[
"-no-emul-boot",
*isolinux_bin_cmd,
*boot_cat_cmd,
"-boot-info-table",
"-no-emul-boot",
]
if iso_attrs.has_eltorito
else []
),
"-allow-multidot",
"-o",
temp.name,
temp_flush_dir,
]
proc = await asyncio.create_subprocess_exec(*cmd)
returncode = await proc.wait()
if proc.returncode:
raise CalledProcessError(returncode=returncode, cmd=cmd)
with open(temp.name, "rb") as new_fh:
new_data = new_fh.read()
# Passing in the original range effectively replaces the original data with the new data
resource.queue_patch(Range(0, await resource.get_data_length()), new_data)
ISO9660Unpacker (Unpacker)
Extracts files and directories from ISO 9660 optical disc images. Use when analyzing CD/DVD images, bootable ISOs, or software distributions packaged as disc images. The unpacker handles various ISO 9660 extensions like Rock Ridge (Unix permissions) and Joliet (long filenames).
unpack(self, resource, config=None)
async
Unpack the given resource.
Users should not call this method directly; rather, they should run Resource.run or Resource.unpack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The resource that is being unpacked |
required |
config |
Optional config for unpacking. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Source code in ofrak/core/iso9660.py
async def unpack(self, resource: Resource, config=None):
iso_data = await resource.get_data()
iso_attributes = await resource.analyze(ISO9660ImageAttributes)
resource.add_attributes(iso_attributes)
iso_resource = await resource.view_as(ISO9660Image)
iso = PyCdlib()
iso.open_fp(BytesIO(iso_data))
if iso_attributes.has_joliet:
facade: Union[
PyCdlibJoliet, PyCdlibUDF, PyCdlibRockRidge, PyCdlibISO9660
] = iso.get_joliet_facade()
path_var = "joliet_path"
elif iso_attributes.has_udf:
LOGGER.warning("UDF images are not currently supported")
facade = iso.get_udf_facade()
path_var = "udf_path"
elif iso_attributes.has_rockridge:
LOGGER.warning("Rock Ridge images are not currently supported")
facade = iso.get_rock_ridge_facade()
path_var = "rr_name"
else:
facade = iso.get_iso9660_facade()
path_var = "iso_path"
if iso_attributes.has_eltorito:
LOGGER.warning("El Torito images are not currently supported")
for root, dirs, files in iso.walk(**{path_var: "/"}):
for d in dirs:
path = posixpath.join(root, d)
folder_tags = (ISO9660Entry, Folder)
entry = ISO9660Entry(
name=d,
path=path,
is_dir=True,
is_file=False,
is_symlink=False,
is_dot=(str(d).startswith(".") and not str(d).startswith("..")),
is_dotdot=str(d).startswith(".."),
iso_version=-1,
)
await iso_resource.add_folder(
path, None, None, folder_tags, entry.get_attributes_instances().values()
)
for f in files:
path = posixpath.join(root, f)
file_tags = (ISO9660Entry, File)
fp = BytesIO()
facade.get_file_from_iso_fp(fp, **{path_var: path})
file_data = fp.getvalue()
if ";" in f:
f, iso_version = f.split(";")
iso_version = int(iso_version)
path = path.split(";")[0]
if f.endswith("."):
f = f[:-1]
path = path[:-1]
else:
iso_version = -1
entry = ISO9660Entry(
name=f,
path=path,
is_dir=False,
is_file=True,
is_symlink=False,
is_dot=(str(f).startswith(".") and not str(f).startswith("..")),
is_dotdot=str(f).startswith(".."),
iso_version=iso_version,
)
await iso_resource.add_file(
path,
file_data,
None,
None,
file_tags,
entry.get_attributes_instances().values(),
)
fp.close()
iso.close()
JolietISO9660Image (ISO9660Image)
dataclass
JolietISO9660Image()
RockRidgeISO9660Image (ISO9660Image)
dataclass
RockRidgeISO9660Image()
UdfISO9660Image (ISO9660Image)
dataclass
UdfISO9660Image()