Module refinery.units.formats.archive.xtrar
Expand source code Browse git
from __future__ import annotations
import codecs
from refinery.lib.id import buffer_offset
from refinery.lib.types import buf
from refinery.lib.unrar import (
RAR_HEADER_V14,
RAR_HEADER_V15,
RAR_HEADER_V50,
RarFile,
RarInvalidChecksum,
RarInvalidPassword,
RarMissingPassword,
detect_format,
)
from refinery.units import RefineryPartialResult
from refinery.units.formats.archive import ArchiveUnit
class xtrar(ArchiveUnit, docs='{0}{p}{PathExtractorUnit}'):
"""
Extract files from a RAR archive.
"""
@staticmethod
def _find_rar_file(data: buf):
view = memoryview(data)
if detect_format(view) is not None:
return view
from refinery.lib.id import get_pe_type
if get_pe_type(data):
from refinery.units.formats.pe import get_pe_size
offset = get_pe_size(data)
overlay = view[offset:]
if detect_format(overlay) is not None:
return overlay
for signature in (
RAR_HEADER_V50,
RAR_HEADER_V15,
RAR_HEADER_V14,
):
offset = buffer_offset(view, signature)
if offset >= 0:
return view[offset:]
def unpack(self, data):
if (view := self._find_rar_file(data)) is None:
raise ValueError('Input does not appear to be a RAR file.')
password = self.args.pwd
if not password:
password = None
elif not isinstance(password, str):
password = codecs.decode(password, self.codec)
rar = RarFile(view, password=password)
if rar.is_encrypted and not password and not rar.entries:
raise RarMissingPassword
if not self.args.list:
has_encrypted = any(e.is_encrypted for e in rar.entries)
if has_encrypted and not password:
first_encrypted = next(
(e for e in rar.entries if e.is_encrypted and not e.is_dir), None)
if first_encrypted is not None:
for pwd in self.CommonPasswords:
try:
rar.read(first_encrypted, pwd)
except (
RarMissingPassword,
RarInvalidPassword,
RarInvalidChecksum,
):
continue
except Exception:
break
else:
password = pwd
self.log_info(F'using password: {pwd}')
break
for entry in rar.entries:
def extract(r=rar, e=entry, p=password):
try:
return r.read(e, p)
except RarInvalidChecksum as check:
raise RefineryPartialResult(str(check), check.data) from check
except RarInvalidPassword as E:
raise ValueError(F'invalid password: {entry.name}') from E
except RarMissingPassword as E:
raise ValueError(F'missing password: {entry.name}') from E
if entry.is_dir:
continue
yield self._pack(entry.name, entry.date, extract)
@classmethod
def handles(cls, data) -> bool:
return cls._find_rar_file(data) is not None
Classes
class xtrar (*paths, list=False, join_path=False, drop_path=False, fuzzy=0, exact=False, regex=False, path=b'path', exclude=None, date=b'date', pwd=b'')-
Extract files from a RAR archive.
This unit extracts items with an associated virtual path from a container; each extracted item is emitted as a separate chunk with a corresponding meta variable named "path".
Positional arguments to xtrar are patterns to filter the extracted items. Use the
-xflag to add an exclusion pattern. To extract all files with a foo or bar extension, but none that has the word "temp" in its path:xtrar .foo .bar -x tempTo view only the paths of all chunks, use the listing switch:
emit data | ... | xtrar -lOtherwise, extracted items are written to the standard output port and usually require a frame to properly process. In order to dump all extracted data to disk, the following pipeline can be used:
emit data | ... | xtrar [| dump extracted/{path} ]The value
{path}is a placeholder which is substituted by the virtual path of the extracted item. When using xtrar to unpack a file on disk, the following pattern can be useful:ef pack.bin [| xtrar -j | d2p ]The unit
efis also a path extractor. By specifying-j(or--join), the paths of extracted items are combined. Here,d2pis a shortcut fordump {path}. It deconflicts the joined paths with the local file system: Ifpack.bincontains itemsone.txtandtwo.txt, the following local file tree would be the result:pack.bin pack/one.txt pack/two.txtFinally, the
-d(or--drop) switch can be used to not create (or alter) the path metadata at all, which is useful in cases where path metadata from a previous unit should be preserved.Expand source code Browse git
class xtrar(ArchiveUnit, docs='{0}{p}{PathExtractorUnit}'): """ Extract files from a RAR archive. """ @staticmethod def _find_rar_file(data: buf): view = memoryview(data) if detect_format(view) is not None: return view from refinery.lib.id import get_pe_type if get_pe_type(data): from refinery.units.formats.pe import get_pe_size offset = get_pe_size(data) overlay = view[offset:] if detect_format(overlay) is not None: return overlay for signature in ( RAR_HEADER_V50, RAR_HEADER_V15, RAR_HEADER_V14, ): offset = buffer_offset(view, signature) if offset >= 0: return view[offset:] def unpack(self, data): if (view := self._find_rar_file(data)) is None: raise ValueError('Input does not appear to be a RAR file.') password = self.args.pwd if not password: password = None elif not isinstance(password, str): password = codecs.decode(password, self.codec) rar = RarFile(view, password=password) if rar.is_encrypted and not password and not rar.entries: raise RarMissingPassword if not self.args.list: has_encrypted = any(e.is_encrypted for e in rar.entries) if has_encrypted and not password: first_encrypted = next( (e for e in rar.entries if e.is_encrypted and not e.is_dir), None) if first_encrypted is not None: for pwd in self.CommonPasswords: try: rar.read(first_encrypted, pwd) except ( RarMissingPassword, RarInvalidPassword, RarInvalidChecksum, ): continue except Exception: break else: password = pwd self.log_info(F'using password: {pwd}') break for entry in rar.entries: def extract(r=rar, e=entry, p=password): try: return r.read(e, p) except RarInvalidChecksum as check: raise RefineryPartialResult(str(check), check.data) from check except RarInvalidPassword as E: raise ValueError(F'invalid password: {entry.name}') from E except RarMissingPassword as E: raise ValueError(F'missing password: {entry.name}') from E if entry.is_dir: continue yield self._pack(entry.name, entry.date, extract) @classmethod def handles(cls, data) -> bool: return cls._find_rar_file(data) is not NoneAncestors
Subclasses
Class variables
var reverse-
The type of the None singleton.
Methods
def unpack(self, data)-
Expand source code Browse git
def unpack(self, data): if (view := self._find_rar_file(data)) is None: raise ValueError('Input does not appear to be a RAR file.') password = self.args.pwd if not password: password = None elif not isinstance(password, str): password = codecs.decode(password, self.codec) rar = RarFile(view, password=password) if rar.is_encrypted and not password and not rar.entries: raise RarMissingPassword if not self.args.list: has_encrypted = any(e.is_encrypted for e in rar.entries) if has_encrypted and not password: first_encrypted = next( (e for e in rar.entries if e.is_encrypted and not e.is_dir), None) if first_encrypted is not None: for pwd in self.CommonPasswords: try: rar.read(first_encrypted, pwd) except ( RarMissingPassword, RarInvalidPassword, RarInvalidChecksum, ): continue except Exception: break else: password = pwd self.log_info(F'using password: {pwd}') break for entry in rar.entries: def extract(r=rar, e=entry, p=password): try: return r.read(e, p) except RarInvalidChecksum as check: raise RefineryPartialResult(str(check), check.data) from check except RarInvalidPassword as E: raise ValueError(F'invalid password: {entry.name}') from E except RarMissingPassword as E: raise ValueError(F'missing password: {entry.name}') from E if entry.is_dir: continue yield self._pack(entry.name, entry.date, extract)
Inherited members
ArchiveUnit:CommonPasswordsCustomJoinBehaviourCustomPathSeparatorFilterEverythingRequiresactassemblecodecconsolefilterfinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesprocessreadread1required_dependenciesresetrunsourcesuperinit