Module refinery.lib.iso

Library for parsing ISO 9660 and UDF disk images.

Expand source code Browse git
"""
Library for parsing ISO 9660 and UDF disk images.
"""
from __future__ import annotations

import datetime
import enum

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from refinery.lib.iso.iso9660 import ISORef
    from refinery.lib.iso.udf import UDFRef


class FileSystemType(enum.Enum):
    AUTO = 'auto'
    ISO = 'iso'
    JOLIET = 'joliet'
    RR = 'rr'
    UDF = 'udf'


class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key


class ISOArchive:
    """
    Unified ISO/UDF archive reader. Tries UDF first, falls back to ISO 9660.
    """

    def __init__(self, data):
        from refinery.lib.iso.udf import ANCHOR_SECTOR, UDFArchive, _verify_tag

        self._data = data
        self._iso = None
        self._udf = None
        self._type = FileSystemType.ISO

        udf_found = False

        dv = memoryview(self._data)
        for ss in (2048, 512, 4096):
            anchor_pos = ANCHOR_SECTOR * ss
            if anchor_pos + 16 <= len(dv):
                tag_id = _verify_tag(dv, anchor_pos)
                if tag_id == 2:
                    udf_found = True
                    break

        if udf_found:
            udf = UDFArchive()
            try:
                udf.open(self._data)
            except Exception:
                udf = None
            if udf is not None and udf.refs:
                self._udf = udf
                self._type = FileSystemType.UDF
                return

        from refinery.lib.iso.iso9660 import ISO9660Archive
        iso = ISO9660Archive()
        iso.open(self._data)
        self._iso = iso
        self._type = iso.filesystem_type

    @property
    def filesystem_type(self) -> str:
        return self._type.value

    def select_filesystem(self, fs: FileSystemType) -> None:
        if fs is FileSystemType.UDF:
            if self._udf is not None:
                self._type = FileSystemType.UDF
                return
            from refinery.lib.iso.udf import UDFArchive
            udf = UDFArchive()
            try:
                udf.open(self._data)
            except Exception:
                return
            if udf.refs:
                self._udf = udf
                self._type = FileSystemType.UDF
        elif fs in (FileSystemType.JOLIET, FileSystemType.RR, FileSystemType.ISO):
            if self._iso is None:
                from refinery.lib.iso.iso9660 import ISO9660Archive
                self._iso = ISO9660Archive()
                self._iso.open(self._data)
            self._iso.select_filesystem(fs)
            self._type = self._iso.filesystem_type

    def entries(self):
        if self._type is FileSystemType.UDF and self._udf is not None:
            for ref in self._udf.entries():
                yield ISOFile(
                    path=ref.path,
                    size=ref.total_size,
                    date=ref.date,
                    is_dir=ref.is_dir,
                    extents=ref.extents,
                    inline=ref.inline_data,
                    key=(FileSystemType.UDF, ref),
                )
        elif self._iso is not None:
            for ref in self._iso.entries():
                yield ISOFile(
                    path=ref.path,
                    size=ref.total_size,
                    date=ref.date,
                    is_dir=ref.is_dir,
                    extents=ref.extents,
                    key=(FileSystemType.ISO, ref),
                )

    def extract(self, entry: ISOFile) -> bytes | bytearray:
        from refinery.lib.iso.iso9660 import ISORef
        from refinery.lib.iso.udf import UDFRef
        backend_id, ref = entry.key
        if isinstance(ref, ISORef) and (iso := self._iso):
            if backend_id != FileSystemType.ISO:
                raise ValueError(F'File System Inconsistency; expected ISO, got {backend_id.name}.')
            return iso.extract(ref)
        if isinstance(ref, UDFRef) and (udf := self._udf):
            if backend_id != FileSystemType.UDF:
                raise ValueError(F'File System Inconsistency; expected UDF, got {backend_id.name}.')
            return udf.extract(ref)
        return b''

Sub-modules

refinery.lib.iso.eltorito

El Torito boot catalog parser for ISO 9660 images.

refinery.lib.iso.iso9660

ISO 9660 filesystem parser ported from 7zip's Archive/Iso/ implementation.

refinery.lib.iso.udf

UDF (ECMA-167) filesystem parser ported from 7zip's Archive/Udf/ implementation.

Classes

class FileSystemType (*args, **kwds)

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access:

Color.RED

  • value lookup:

Color(1)

  • name lookup:

Color['RED']

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Expand source code Browse git
class FileSystemType(enum.Enum):
    AUTO = 'auto'
    ISO = 'iso'
    JOLIET = 'joliet'
    RR = 'rr'
    UDF = 'udf'

Ancestors

  • enum.Enum

Class variables

var AUTO

The type of the None singleton.

var ISO

The type of the None singleton.

var JOLIET

The type of the None singleton.

var RR

The type of the None singleton.

var UDF

The type of the None singleton.

class ISOFile (path, size, date, is_dir, extents, key, inline=None)

Represents a single file entry within an ISO image.

Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key

Instance variables

var date
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
var extents
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
var is_dir
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
var key
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
var path
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
var size
Expand source code Browse git
class ISOFile:
    """
    Represents a single file entry within an ISO image.
    """
    __slots__ = ('path', 'size', 'date', 'is_dir', 'extents', '_inline', 'key')

    def __init__(
        self,
        path: str,
        size: int,
        date: datetime.datetime | None,
        is_dir: bool,
        extents: list[tuple[int, int]],
        key: tuple[FileSystemType, ISORef | UDFRef],
        inline: bytes | None = None,
    ):
        self.path = path
        self.size = size
        self.date = date
        self.is_dir = is_dir
        self.extents = extents
        self._inline = inline
        self.key = key
class ISOArchive (data)

Unified ISO/UDF archive reader. Tries UDF first, falls back to ISO 9660.

Expand source code Browse git
class ISOArchive:
    """
    Unified ISO/UDF archive reader. Tries UDF first, falls back to ISO 9660.
    """

    def __init__(self, data):
        from refinery.lib.iso.udf import ANCHOR_SECTOR, UDFArchive, _verify_tag

        self._data = data
        self._iso = None
        self._udf = None
        self._type = FileSystemType.ISO

        udf_found = False

        dv = memoryview(self._data)
        for ss in (2048, 512, 4096):
            anchor_pos = ANCHOR_SECTOR * ss
            if anchor_pos + 16 <= len(dv):
                tag_id = _verify_tag(dv, anchor_pos)
                if tag_id == 2:
                    udf_found = True
                    break

        if udf_found:
            udf = UDFArchive()
            try:
                udf.open(self._data)
            except Exception:
                udf = None
            if udf is not None and udf.refs:
                self._udf = udf
                self._type = FileSystemType.UDF
                return

        from refinery.lib.iso.iso9660 import ISO9660Archive
        iso = ISO9660Archive()
        iso.open(self._data)
        self._iso = iso
        self._type = iso.filesystem_type

    @property
    def filesystem_type(self) -> str:
        return self._type.value

    def select_filesystem(self, fs: FileSystemType) -> None:
        if fs is FileSystemType.UDF:
            if self._udf is not None:
                self._type = FileSystemType.UDF
                return
            from refinery.lib.iso.udf import UDFArchive
            udf = UDFArchive()
            try:
                udf.open(self._data)
            except Exception:
                return
            if udf.refs:
                self._udf = udf
                self._type = FileSystemType.UDF
        elif fs in (FileSystemType.JOLIET, FileSystemType.RR, FileSystemType.ISO):
            if self._iso is None:
                from refinery.lib.iso.iso9660 import ISO9660Archive
                self._iso = ISO9660Archive()
                self._iso.open(self._data)
            self._iso.select_filesystem(fs)
            self._type = self._iso.filesystem_type

    def entries(self):
        if self._type is FileSystemType.UDF and self._udf is not None:
            for ref in self._udf.entries():
                yield ISOFile(
                    path=ref.path,
                    size=ref.total_size,
                    date=ref.date,
                    is_dir=ref.is_dir,
                    extents=ref.extents,
                    inline=ref.inline_data,
                    key=(FileSystemType.UDF, ref),
                )
        elif self._iso is not None:
            for ref in self._iso.entries():
                yield ISOFile(
                    path=ref.path,
                    size=ref.total_size,
                    date=ref.date,
                    is_dir=ref.is_dir,
                    extents=ref.extents,
                    key=(FileSystemType.ISO, ref),
                )

    def extract(self, entry: ISOFile) -> bytes | bytearray:
        from refinery.lib.iso.iso9660 import ISORef
        from refinery.lib.iso.udf import UDFRef
        backend_id, ref = entry.key
        if isinstance(ref, ISORef) and (iso := self._iso):
            if backend_id != FileSystemType.ISO:
                raise ValueError(F'File System Inconsistency; expected ISO, got {backend_id.name}.')
            return iso.extract(ref)
        if isinstance(ref, UDFRef) and (udf := self._udf):
            if backend_id != FileSystemType.UDF:
                raise ValueError(F'File System Inconsistency; expected UDF, got {backend_id.name}.')
            return udf.extract(ref)
        return b''

Instance variables

var filesystem_type
Expand source code Browse git
@property
def filesystem_type(self) -> str:
    return self._type.value

Methods

def select_filesystem(self, fs)
Expand source code Browse git
def select_filesystem(self, fs: FileSystemType) -> None:
    if fs is FileSystemType.UDF:
        if self._udf is not None:
            self._type = FileSystemType.UDF
            return
        from refinery.lib.iso.udf import UDFArchive
        udf = UDFArchive()
        try:
            udf.open(self._data)
        except Exception:
            return
        if udf.refs:
            self._udf = udf
            self._type = FileSystemType.UDF
    elif fs in (FileSystemType.JOLIET, FileSystemType.RR, FileSystemType.ISO):
        if self._iso is None:
            from refinery.lib.iso.iso9660 import ISO9660Archive
            self._iso = ISO9660Archive()
            self._iso.open(self._data)
        self._iso.select_filesystem(fs)
        self._type = self._iso.filesystem_type
def entries(self)
Expand source code Browse git
def entries(self):
    if self._type is FileSystemType.UDF and self._udf is not None:
        for ref in self._udf.entries():
            yield ISOFile(
                path=ref.path,
                size=ref.total_size,
                date=ref.date,
                is_dir=ref.is_dir,
                extents=ref.extents,
                inline=ref.inline_data,
                key=(FileSystemType.UDF, ref),
            )
    elif self._iso is not None:
        for ref in self._iso.entries():
            yield ISOFile(
                path=ref.path,
                size=ref.total_size,
                date=ref.date,
                is_dir=ref.is_dir,
                extents=ref.extents,
                key=(FileSystemType.ISO, ref),
            )
def extract(self, entry)
Expand source code Browse git
def extract(self, entry: ISOFile) -> bytes | bytearray:
    from refinery.lib.iso.iso9660 import ISORef
    from refinery.lib.iso.udf import UDFRef
    backend_id, ref = entry.key
    if isinstance(ref, ISORef) and (iso := self._iso):
        if backend_id != FileSystemType.ISO:
            raise ValueError(F'File System Inconsistency; expected ISO, got {backend_id.name}.')
        return iso.extract(ref)
    if isinstance(ref, UDFRef) and (udf := self._udf):
        if backend_id != FileSystemType.UDF:
            raise ValueError(F'File System Inconsistency; expected UDF, got {backend_id.name}.')
        return udf.extract(ref)
    return b''