Module refinery.units.formats.office.xtdoc
Expand source code Browse git
from __future__ import annotations
from refinery.lib.id import is_likely_doc
from refinery.lib.ole.file import DirectoryEntry, OleFile
from refinery.lib.types import Iterable, buf
from refinery.units.formats import PathExtractorUnit, UnpackResult
from refinery.units.formats.archive.xtzip import xtzip
_MSI_NAME_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._'
def convert_msi_name(name: str):
def _decode(alphabet=F'{_MSI_NAME_ALPHABET}!'):
for character in name:
code = ord(character)
if 0x3800 <= code < 0x4800:
yield alphabet[(code - 0x3800) & 0x3F] + alphabet[((code - 0x3800) >> 6) & 0x3F]
elif 0x4800 <= code <= 0x4840:
yield alphabet[code - 0x4800]
else:
yield character
return ''.join(_decode())
def msi_table_stream_name(table: str) -> str:
"""
The name of the stream in the compound file that holds the given table of an MSI database. The
Windows Installer opens a table only under this name, but `convert_msi_name` decodes other names
to the same text.
"""
encoded = [chr(0x4840)]
position = 0
while position < len(table):
first = _MSI_NAME_ALPHABET.find(table[position])
second = -1
if position + 1 < len(table):
second = _MSI_NAME_ALPHABET.find(table[position + 1])
if first < 0:
encoded.append(table[position])
position += 1
elif second < 0:
encoded.append(chr(0x4800 + first))
position += 1
else:
encoded.append(chr(0x3800 + first + (second << 6)))
position += 2
return ''.join(encoded)
class xtdoc(PathExtractorUnit):
"""
Extract embedded files from an OLE document such as a Microsoft Word DOCX file.
"""
def _streams(self, document: OleFile) -> Iterable[tuple[DirectoryEntry, UnpackResult]]:
"""
Generates every stream of the given compound file as its directory entry together with the
result that hands it out under its decoded path.
"""
for item, entry in document.walk():
if not item or not item[-1]:
continue
stream = document.openentry(entry)
c0 = ord(item[-1][:1])
if c0 < 20:
item[-1] = F'[{c0:d}]{item[-1][1:]}'
path = convert_msi_name('/'.join(item))
self.log_debug('exploring:', path)
yield entry, UnpackResult(path, stream.read())
def unpack(self, data: buf):
try:
document = OleFile(data)
except OSError as error:
self.log_info(F'error, {error}, treating input as zip file')
yield from xtzip().unpack(data)
return
for _, result in self._streams(document):
yield result
@classmethod
def handles(cls, data) -> bool | None:
if data[:8] == B'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1':
return True
return is_likely_doc(data)
Functions
def convert_msi_name(name)-
Expand source code Browse git
def convert_msi_name(name: str): def _decode(alphabet=F'{_MSI_NAME_ALPHABET}!'): for character in name: code = ord(character) if 0x3800 <= code < 0x4800: yield alphabet[(code - 0x3800) & 0x3F] + alphabet[((code - 0x3800) >> 6) & 0x3F] elif 0x4800 <= code <= 0x4840: yield alphabet[code - 0x4800] else: yield character return ''.join(_decode()) def msi_table_stream_name(table)-
The name of the stream in the compound file that holds the given table of an MSI database. The Windows Installer opens a table only under this name, but
convert_msi_name()decodes other names to the same text.Expand source code Browse git
def msi_table_stream_name(table: str) -> str: """ The name of the stream in the compound file that holds the given table of an MSI database. The Windows Installer opens a table only under this name, but `convert_msi_name` decodes other names to the same text. """ encoded = [chr(0x4840)] position = 0 while position < len(table): first = _MSI_NAME_ALPHABET.find(table[position]) second = -1 if position + 1 < len(table): second = _MSI_NAME_ALPHABET.find(table[position + 1]) if first < 0: encoded.append(table[position]) position += 1 elif second < 0: encoded.append(chr(0x4800 + first)) position += 1 else: encoded.append(chr(0x3800 + first + (second << 6))) position += 2 return ''.join(encoded)
Classes
class xtdoc (*paths, exclude=None, list=False, join_path=False, drop_path=False, fuzzy=0, exact=False, regex=False, path=b'path')-
Extract embedded files from an OLE document such as a Microsoft Word DOCX file.
Expand source code Browse git
class xtdoc(PathExtractorUnit): """ Extract embedded files from an OLE document such as a Microsoft Word DOCX file. """ def _streams(self, document: OleFile) -> Iterable[tuple[DirectoryEntry, UnpackResult]]: """ Generates every stream of the given compound file as its directory entry together with the result that hands it out under its decoded path. """ for item, entry in document.walk(): if not item or not item[-1]: continue stream = document.openentry(entry) c0 = ord(item[-1][:1]) if c0 < 20: item[-1] = F'[{c0:d}]{item[-1][1:]}' path = convert_msi_name('/'.join(item)) self.log_debug('exploring:', path) yield entry, UnpackResult(path, stream.read()) def unpack(self, data: buf): try: document = OleFile(data) except OSError as error: self.log_info(F'error, {error}, treating input as zip file') yield from xtzip().unpack(data) return for _, result in self._streams(document): yield result @classmethod def handles(cls, data) -> bool | None: if data[:8] == B'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1': return True return is_likely_doc(data)Ancestors
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: buf): try: document = OleFile(data) except OSError as error: self.log_info(F'error, {error}, treating input as zip file') yield from xtzip().unpack(data) return for _, result in self._streams(document): yield result
Inherited members
PathExtractorUnit:CustomJoinBehaviourCustomPathSeparatorFilterEverythingRequiresactassemblecarvedclosecodecconsolefilterfinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesreadread1required_dependenciesresetrunsourcesuperinit
UnitBase: