Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
from hachoir_parser.misc.file_3do import File3do
|
||||
from hachoir_parser.misc.file_3ds import File3ds
|
||||
from hachoir_parser.misc.torrent import TorrentFile
|
||||
from hachoir_parser.misc.ttf import TrueTypeFontFile
|
||||
from hachoir_parser.misc.chm import ChmFile
|
||||
from hachoir_parser.misc.lnk import LnkFile
|
||||
from hachoir_parser.misc.pcf import PcfFile
|
||||
from hachoir_parser.misc.ole2 import OLE2_File
|
||||
from hachoir_parser.misc.pdf import PDFDocument
|
||||
from hachoir_parser.misc.pifv import PIFVFile
|
||||
from hachoir_parser.misc.hlp import HlpFile
|
||||
from hachoir_parser.misc.gnome_keyring import GnomeKeyring
|
||||
from hachoir_parser.misc.bplist import BPList
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
Apple/NeXT Binary Property List (BPLIST) parser.
|
||||
|
||||
Also includes a .createXML() function which produces an XML representation of the object.
|
||||
Note that it will discard unknown objects, nulls and fill values, but should work for most files.
|
||||
|
||||
Documents:
|
||||
- CFBinaryPList.c
|
||||
http://src.gnu-darwin.org/DarwinSourceArchive/expanded/CF/CF-299/Parsing.subproj/CFBinaryPList.c
|
||||
- ForFoundationOnly.h (for structure formats)
|
||||
http://src.gnu-darwin.org/DarwinSourceArchive/expanded/CF/CF-299/Base.subproj/ForFoundationOnly.h
|
||||
- XML <-> BPList converter
|
||||
http://scw.us/iPhone/plutil/plutil.pl
|
||||
Author: Robert Xiao
|
||||
Created: 2008-09-21
|
||||
"""
|
||||
|
||||
from hachoir_parser import HachoirParser
|
||||
from hachoir_core.field import (RootSeekableFieldSet, FieldSet, Enum,
|
||||
Bits, GenericInteger, Float32, Float64, UInt8, UInt64, Bytes, NullBytes, RawBytes, String)
|
||||
from hachoir_core.endian import BIG_ENDIAN
|
||||
from hachoir_core.text_handler import displayHandler
|
||||
from hachoir_core.tools import humanDatetime
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class BPListTrailer(FieldSet):
|
||||
def createFields(self):
|
||||
yield NullBytes(self, "unused", 6)
|
||||
yield UInt8(self, "offsetIntSize", "Size (in bytes) of offsets in the offset table")
|
||||
yield UInt8(self, "objectRefSize", "Size (in bytes) of object numbers in object references")
|
||||
yield UInt64(self, "numObjects", "Number of objects in this file")
|
||||
yield UInt64(self, "topObject", "Top-level object reference")
|
||||
yield UInt64(self, "offsetTableOffset", "File offset to the offset table")
|
||||
|
||||
def createDescription(self):
|
||||
return "Binary PList trailer"
|
||||
|
||||
class BPListOffsetTable(FieldSet):
|
||||
def createFields(self):
|
||||
size = self["../trailer/offsetIntSize"].value*8
|
||||
for i in range(self["../trailer/numObjects"].value):
|
||||
yield Bits(self, "offset[]", size)
|
||||
|
||||
class BPListSize(FieldSet):
|
||||
def createFields(self):
|
||||
yield Bits(self, "size", 4)
|
||||
if self['size'].value == 0xF:
|
||||
yield BPListObject(self, "fullsize")
|
||||
|
||||
def createValue(self):
|
||||
if 'fullsize' in self:
|
||||
return self['fullsize'].value
|
||||
else:
|
||||
return self['size'].value
|
||||
|
||||
class BPListObjectRef(GenericInteger):
|
||||
def __init__(self, parent, name, description=None):
|
||||
size = parent['/trailer/objectRefSize'].value*8
|
||||
GenericInteger.__init__(self, parent, name, False, size, description)
|
||||
|
||||
def getRef(self):
|
||||
return self.parent['/object[' + str(self.value) + ']']
|
||||
|
||||
def createDisplay(self):
|
||||
return self.getRef().display
|
||||
|
||||
def createXML(self, prefix=''):
|
||||
return self.getRef().createXML(prefix)
|
||||
|
||||
class BPListArray(FieldSet):
|
||||
def __init__(self, parent, name, size, description=None):
|
||||
FieldSet.__init__(self, parent, name, description=description)
|
||||
self.numels = size
|
||||
|
||||
def createFields(self):
|
||||
for i in range(self.numels):
|
||||
yield BPListObjectRef(self, "ref[]")
|
||||
|
||||
def createValue(self):
|
||||
return self.array('ref')
|
||||
|
||||
def createDisplay(self):
|
||||
return '[' + ', '.join([x.display for x in self.value]) + ']'
|
||||
|
||||
def createXML(self,prefix=''):
|
||||
return prefix + '<array>\n' + ''.join([x.createXML(prefix + '\t' ) + '\n' for x in self.value]) + prefix + '</array>'
|
||||
|
||||
class BPListDict(FieldSet):
|
||||
def __init__(self, parent, name, size, description=None):
|
||||
FieldSet.__init__(self, parent, name, description=description)
|
||||
self.numels = size
|
||||
|
||||
def createFields(self):
|
||||
for i in range(self.numels):
|
||||
yield BPListObjectRef(self, "keyref[]")
|
||||
for i in range(self.numels):
|
||||
yield BPListObjectRef(self, "valref[]")
|
||||
|
||||
def createValue(self):
|
||||
return zip(self.array('keyref'),self.array('valref'))
|
||||
|
||||
def createDisplay(self):
|
||||
return '{' + ', '.join(['%s: %s'%(k.display,v.display) for k,v in self.value]) + '}'
|
||||
|
||||
def createXML(self, prefix=''):
|
||||
return prefix + '<dict>\n' + ''.join(['%s\t<key>%s</key>\n%s\n'%(prefix,k.getRef().value.encode('utf-8'),v.createXML(prefix + '\t')) for k,v in self.value]) + prefix + '</dict>'
|
||||
|
||||
class BPListObject(FieldSet):
|
||||
def createFields(self):
|
||||
yield Enum(Bits(self, "marker_type", 4),
|
||||
{0: "Simple",
|
||||
1: "Int",
|
||||
2: "Real",
|
||||
3: "Date",
|
||||
4: "Data",
|
||||
5: "ASCII String",
|
||||
6: "UTF-16-BE String",
|
||||
8: "UID",
|
||||
10: "Array",
|
||||
13: "Dict",})
|
||||
markertype = self['marker_type'].value
|
||||
if markertype == 0:
|
||||
# Simple (Null)
|
||||
yield Enum(Bits(self, "value", 4),
|
||||
{0: "Null",
|
||||
8: "False",
|
||||
9: "True",
|
||||
15: "Fill Byte",})
|
||||
if self['value'].display == "False":
|
||||
self.xml=lambda prefix:prefix + "<false/>"
|
||||
elif self['value'].display == "True":
|
||||
self.xml=lambda prefix:prefix + "<true/>"
|
||||
else:
|
||||
self.xml=lambda prefix:prefix + ""
|
||||
|
||||
elif markertype == 1:
|
||||
# Int
|
||||
yield Bits(self, "size", 4, "log2 of number of bytes")
|
||||
size=self['size'].value
|
||||
# 8-bit (size=0), 16-bit (size=1) and 32-bit (size=2) numbers are unsigned
|
||||
# 64-bit (size=3) numbers are signed
|
||||
yield GenericInteger(self, "value", (size>=3), (2**size)*8)
|
||||
self.xml=lambda prefix:prefix + "<integer>%s</integer>"%self['value'].value
|
||||
|
||||
elif markertype == 2:
|
||||
# Real
|
||||
yield Bits(self, "size", 4, "log2 of number of bytes")
|
||||
if self['size'].value == 2: # 2**2 = 4 byte float
|
||||
yield Float32(self, "value")
|
||||
elif self['size'].value == 3: # 2**3 = 8 byte float
|
||||
yield Float64(self, "value")
|
||||
else:
|
||||
# FIXME: What is the format of the real?
|
||||
yield Bits(self, "value", (2**self['size'].value)*8)
|
||||
self.xml=lambda prefix:prefix + "<real>%s</real>"%self['value'].value
|
||||
|
||||
elif markertype == 3:
|
||||
# Date
|
||||
yield Bits(self, "extra", 4, "Extra value, should be 3")
|
||||
# Use a heuristic to determine which epoch to use
|
||||
def cvt_time(v):
|
||||
v=timedelta(seconds=v)
|
||||
epoch2001 = datetime(2001,1,1)
|
||||
epoch1970 = datetime(1970,1,1)
|
||||
if (epoch2001 + v - datetime.today()).days > 5*365:
|
||||
return epoch1970 + v
|
||||
return epoch2001 + v
|
||||
yield displayHandler(Float64(self, "value"),lambda x:humanDatetime(cvt_time(x)))
|
||||
self.xml=lambda prefix:prefix + "<date>%sZ</date>"%(cvt_time(self['value'].value).isoformat())
|
||||
|
||||
elif markertype == 4:
|
||||
# Data
|
||||
yield BPListSize(self, "size")
|
||||
if self['size'].value:
|
||||
yield Bytes(self, "value", self['size'].value)
|
||||
self.xml=lambda prefix:prefix + "<data>\n%s\n%s</data>"%(self['value'].value.encode('base64').strip(),prefix)
|
||||
else:
|
||||
self.xml=lambda prefix:prefix + '<data></data>'
|
||||
|
||||
elif markertype == 5:
|
||||
# ASCII String
|
||||
yield BPListSize(self, "size")
|
||||
if self['size'].value:
|
||||
yield String(self, "value", self['size'].value, charset="ASCII")
|
||||
self.xml=lambda prefix:prefix + "<string>%s</string>"%(self['value'].value.replace('&','&').encode('iso-8859-1'))
|
||||
else:
|
||||
self.xml=lambda prefix:prefix + '<string></string>'
|
||||
|
||||
elif markertype == 6:
|
||||
# UTF-16-BE String
|
||||
yield BPListSize(self, "size")
|
||||
if self['size'].value:
|
||||
yield String(self, "value", self['size'].value*2, charset="UTF-16-BE")
|
||||
self.xml=lambda prefix:prefix + "<string>%s</string>"%(self['value'].value.replace('&','&').encode('utf-8'))
|
||||
else:
|
||||
self.xml=lambda prefix:prefix + '<string></string>'
|
||||
|
||||
elif markertype == 8:
|
||||
# UID
|
||||
yield Bits(self, "size", 4, "Number of bytes minus 1")
|
||||
yield GenericInteger(self, "value", False, (self['size'].value + 1)*8)
|
||||
self.xml=lambda prefix:prefix + "" # no equivalent?
|
||||
|
||||
elif markertype == 10:
|
||||
# Array
|
||||
yield BPListSize(self, "size")
|
||||
size = self['size'].value
|
||||
if size:
|
||||
yield BPListArray(self, "value", size)
|
||||
self.xml=lambda prefix:self['value'].createXML(prefix)
|
||||
|
||||
elif markertype == 13:
|
||||
# Dict
|
||||
yield BPListSize(self, "size")
|
||||
yield BPListDict(self, "value", self['size'].value)
|
||||
self.xml=lambda prefix:self['value'].createXML(prefix)
|
||||
|
||||
else:
|
||||
yield Bits(self, "value", 4)
|
||||
self.xml=lambda prefix:''
|
||||
|
||||
def createValue(self):
|
||||
if 'value' in self:
|
||||
return self['value'].value
|
||||
elif self['marker_type'].value in [4,5,6]:
|
||||
return u''
|
||||
else:
|
||||
return None
|
||||
|
||||
def createDisplay(self):
|
||||
if 'value' in self:
|
||||
return unicode(self['value'].display)
|
||||
elif self['marker_type'].value in [4,5,6]:
|
||||
return u''
|
||||
else:
|
||||
return None
|
||||
|
||||
def createXML(self, prefix=''):
|
||||
if 'value' in self:
|
||||
try:
|
||||
return self.xml(prefix)
|
||||
except AttributeError:
|
||||
return ''
|
||||
return ''
|
||||
|
||||
def getFieldType(self):
|
||||
return '%s<%s>'%(FieldSet.getFieldType(self), self['marker_type'].display)
|
||||
|
||||
class BPList(HachoirParser, RootSeekableFieldSet):
|
||||
endian = BIG_ENDIAN
|
||||
MAGIC = "bplist00"
|
||||
PARSER_TAGS = {
|
||||
"id": "bplist",
|
||||
"category": "misc",
|
||||
"file_ext": ("plist",),
|
||||
"magic": ((MAGIC, 0),),
|
||||
"min_size": 8 + 32, # bplist00 + 32-byte trailer
|
||||
"description": "Apple/NeXT Binary Property List",
|
||||
}
|
||||
|
||||
def __init__(self, stream, **args):
|
||||
RootSeekableFieldSet.__init__(self, None, "root", stream, None, stream.askSize(self))
|
||||
HachoirParser.__init__(self, stream, **args)
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
|
||||
return "Invalid magic"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield Bytes(self, "magic", 8, "File magic (bplist00)")
|
||||
if self.size:
|
||||
self.seekByte(self.size//8-32, True)
|
||||
else:
|
||||
# FIXME: UNTESTED
|
||||
while True:
|
||||
try:
|
||||
self.seekByte(1024)
|
||||
except:
|
||||
break
|
||||
self.seekByte(self.size//8-32)
|
||||
yield BPListTrailer(self, "trailer")
|
||||
self.seekByte(self['trailer/offsetTableOffset'].value)
|
||||
yield BPListOffsetTable(self, "offset_table")
|
||||
for i in self.array("offset_table/offset"):
|
||||
if self.current_size > i.value*8:
|
||||
self.seekByte(i.value)
|
||||
elif self.current_size < i.value*8:
|
||||
# try to detect files with gaps or unparsed content
|
||||
yield RawBytes(self, "padding[]", i.value-self.current_size//8)
|
||||
yield BPListObject(self, "object[]")
|
||||
|
||||
def createXML(self, prefix=''):
|
||||
return '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
''' + self['/object[' + str(self['/trailer/topObject'].value) + ']'].createXML(prefix) + '''
|
||||
</plist>'''
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
InfoTech Storage Format (ITSF) parser, used by Microsoft's HTML Help (.chm)
|
||||
|
||||
Document:
|
||||
- Microsoft's HTML Help (.chm) format
|
||||
http://www.wotsit.org (search "chm")
|
||||
- chmlib library
|
||||
http://www.jedrea.com/chmlib/
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 2007-03-04
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (Field, FieldSet, ParserError,
|
||||
Int32, UInt32, UInt64,
|
||||
RawBytes, PaddingBytes,
|
||||
Enum, String)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_parser.common.win32 import GUID
|
||||
from hachoir_parser.common.win32_lang_id import LANGUAGE_ID
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal, filesizeHandler
|
||||
|
||||
class CWord(Field):
|
||||
"""
|
||||
Compressed double-word
|
||||
"""
|
||||
def __init__(self, parent, name, description=None):
|
||||
Field.__init__(self, parent, name, 8, description)
|
||||
|
||||
endian = self._parent.endian
|
||||
stream = self._parent.stream
|
||||
addr = self.absolute_address
|
||||
|
||||
value = 0
|
||||
byte = stream.readBits(addr, 8, endian)
|
||||
while byte & 0x80:
|
||||
value <<= 7
|
||||
value += (byte & 0x7f)
|
||||
self._size += 8
|
||||
if 64 < self._size:
|
||||
raise ParserError("CHM: CWord is limited to 64 bits")
|
||||
addr += 8
|
||||
byte = stream.readBits(addr, 8, endian)
|
||||
value += byte
|
||||
self.createValue = lambda: value
|
||||
|
||||
class Filesize_Header(FieldSet):
|
||||
def createFields(self):
|
||||
yield textHandler(UInt32(self, "unknown[]", "0x01FE"), hexadecimal)
|
||||
yield textHandler(UInt32(self, "unknown[]", "0x0"), hexadecimal)
|
||||
yield filesizeHandler(UInt64(self, "file_size"))
|
||||
yield textHandler(UInt32(self, "unknown[]", "0x0"), hexadecimal)
|
||||
yield textHandler(UInt32(self, "unknown[]", "0x0"), hexadecimal)
|
||||
|
||||
class ITSP(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "magic", 4, "ITSP", charset="ASCII")
|
||||
yield UInt32(self, "version", "Version (=1)")
|
||||
yield filesizeHandler(UInt32(self, "size", "Length (in bytes) of the directory header (84)"))
|
||||
yield UInt32(self, "unknown[]", "(=10)")
|
||||
yield filesizeHandler(UInt32(self, "block_size", "Directory block size"))
|
||||
yield UInt32(self, "density", "Density of quickref section, usually 2")
|
||||
yield UInt32(self, "index_depth", "Depth of the index tree")
|
||||
yield Int32(self, "nb_dir", "Chunk number of root index chunk")
|
||||
yield UInt32(self, "first_pmgl", "Chunk number of first PMGL (listing) chunk")
|
||||
yield UInt32(self, "last_pmgl", "Chunk number of last PMGL (listing) chunk")
|
||||
yield Int32(self, "unknown[]", "-1")
|
||||
yield UInt32(self, "nb_dir_chunk", "Number of directory chunks (total)")
|
||||
yield Enum(UInt32(self, "lang_id", "Windows language ID"), LANGUAGE_ID)
|
||||
yield GUID(self, "system_uuid", "{5D02926A-212E-11D0-9DF9-00A0C922E6EC}")
|
||||
yield filesizeHandler(UInt32(self, "size2", "Same value than size"))
|
||||
yield Int32(self, "unknown[]", "-1")
|
||||
yield Int32(self, "unknown[]", "-1")
|
||||
yield Int32(self, "unknown[]", "-1")
|
||||
|
||||
class ITSF(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "magic", 4, "ITSF", charset="ASCII")
|
||||
yield UInt32(self, "version")
|
||||
yield UInt32(self, "header_size", "Total header length (in bytes)")
|
||||
yield UInt32(self, "one")
|
||||
yield UInt32(self, "last_modified")
|
||||
yield Enum(UInt32(self, "lang_id", "Windows Language ID"), LANGUAGE_ID)
|
||||
yield GUID(self, "dir_uuid", "{7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}")
|
||||
yield GUID(self, "stream_uuid", "{7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}")
|
||||
yield UInt64(self, "filesize_offset")
|
||||
yield filesizeHandler(UInt64(self, "filesize_len"))
|
||||
yield UInt64(self, "dir_offset")
|
||||
yield filesizeHandler(UInt64(self, "dir_len"))
|
||||
if 3 <= self["version"].value:
|
||||
yield UInt64(self, "data_offset")
|
||||
|
||||
class PMGL_Entry(FieldSet):
|
||||
def createFields(self):
|
||||
yield CWord(self, "name_len")
|
||||
yield String(self, "name", self["name_len"].value, charset="UTF-8")
|
||||
yield CWord(self, "space")
|
||||
yield CWord(self, "start")
|
||||
yield filesizeHandler(CWord(self, "length"))
|
||||
|
||||
def createDescription(self):
|
||||
return "%s (%s)" % (self["name"].value, self["length"].display)
|
||||
|
||||
class PMGL(FieldSet):
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield String(self, "magic", 4, "PMGL", charset="ASCII")
|
||||
yield filesizeHandler(Int32(self, "free_space",
|
||||
"Length of free space and/or quickref area at end of directory chunk"))
|
||||
yield Int32(self, "unknown")
|
||||
yield Int32(self, "previous", "Chunk number of previous listing chunk")
|
||||
yield Int32(self, "next", "Chunk number of previous listing chunk")
|
||||
|
||||
# Entries
|
||||
stop = self.size - self["free_space"].value * 8
|
||||
while self.current_size < stop:
|
||||
yield PMGL_Entry(self, "entry[]")
|
||||
|
||||
# Padding
|
||||
padding = (self.size - self.current_size) // 8
|
||||
if padding:
|
||||
yield PaddingBytes(self, "padding", padding)
|
||||
|
||||
class PMGI_Entry(FieldSet):
|
||||
def createFields(self):
|
||||
yield CWord(self, "name_len")
|
||||
yield String(self, "name", self["name_len"].value, charset="UTF-8")
|
||||
yield CWord(self, "page")
|
||||
|
||||
def createDescription(self):
|
||||
return "%s (page #%u)" % (self["name"].value, self["page"].value)
|
||||
|
||||
class PMGI(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "magic", 4, "PMGI", charset="ASCII")
|
||||
yield filesizeHandler(UInt32(self, "free_space",
|
||||
"Length of free space and/or quickref area at end of directory chunk"))
|
||||
|
||||
stop = self.size - self["free_space"].value * 8
|
||||
while self.current_size < stop:
|
||||
yield PMGI_Entry(self, "entry[]")
|
||||
|
||||
padding = (self.size - self.current_size) // 8
|
||||
if padding:
|
||||
yield PaddingBytes(self, "padding", padding)
|
||||
|
||||
class Directory(FieldSet):
|
||||
def createFields(self):
|
||||
yield ITSP(self, "itsp")
|
||||
block_size = self["itsp/block_size"].value * 8
|
||||
|
||||
nb_dir = self["itsp/nb_dir"].value
|
||||
|
||||
if nb_dir < 0:
|
||||
nb_dir = 1
|
||||
for index in xrange(nb_dir):
|
||||
yield PMGL(self, "pmgl[]", size=block_size)
|
||||
|
||||
if self.current_size < self.size:
|
||||
yield PMGI(self, "pmgi", size=block_size)
|
||||
|
||||
class ChmFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "chm",
|
||||
"category": "misc",
|
||||
"file_ext": ("chm",),
|
||||
"min_size": 4*8,
|
||||
"magic": (("ITSF\3\0\0\0", 0),),
|
||||
"description": "Microsoft's HTML Help (.chm)",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 4) != "ITSF":
|
||||
return "Invalid magic"
|
||||
if self["itsf/version"].value != 3:
|
||||
return "Invalid version"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield ITSF(self, "itsf")
|
||||
yield Filesize_Header(self, "file_size", size=self["itsf/filesize_len"].value*8)
|
||||
|
||||
padding = self.seekByte(self["itsf/dir_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield Directory(self, "dir", size=self["itsf/dir_len"].value*8)
|
||||
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield RawBytes(self, "raw_end", size)
|
||||
|
||||
def createContentSize(self):
|
||||
return self["file_size/file_size"].value * 8
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from hachoir_core.field import StaticFieldSet, Float32
|
||||
|
||||
class Vertex(StaticFieldSet):
|
||||
format = ((Float32, "x"), (Float32, "y"), (Float32, "z"))
|
||||
|
||||
def createValue(self):
|
||||
return (self["x"].value, self["y"].value, self["z"].value)
|
||||
|
||||
class MapUV(StaticFieldSet):
|
||||
format = ((Float32, "u"), (Float32, "v"))
|
||||
|
||||
def createValue(self):
|
||||
return (self["u"].value, self["v"].value)
|
||||
@@ -0,0 +1,214 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
3do model parser.
|
||||
|
||||
Author: Cyril Zorin
|
||||
Creation date: 28 september 2006
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
UInt32, Int32, String, Float32,
|
||||
RawBytes, PaddingBytes)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
|
||||
from hachoir_parser.misc.common import Vertex, MapUV
|
||||
|
||||
class Vector(FieldSet):
|
||||
def __init__(self, parent, name,
|
||||
count, type, ename, edesc=None, description=None):
|
||||
FieldSet.__init__(self, parent, name, description)
|
||||
self.count = count
|
||||
self.type = type
|
||||
self.ename = ename+"[]"
|
||||
self.edesc = edesc
|
||||
try:
|
||||
item_size = self.type.static_size(self.ename, self.edesc)
|
||||
except TypeError:
|
||||
item_size = self.type.static_size
|
||||
if item_size:
|
||||
self._size = item_size * self.count
|
||||
|
||||
def createFields(self):
|
||||
for index in xrange(self.count):
|
||||
yield self.type(self, self.ename, self.edesc)
|
||||
|
||||
class Face(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "id")
|
||||
yield UInt32(self, "type")
|
||||
yield UInt32(self, "geometry_mode")
|
||||
yield UInt32(self, "lighting_mode")
|
||||
yield UInt32(self, "texture_mode")
|
||||
yield UInt32(self, "nvertices")
|
||||
yield Float32(self, "unknown[]", "unknown")
|
||||
yield UInt32(self, "has_texture", "Has texture?")
|
||||
yield UInt32(self, "has_material", "Has material?")
|
||||
yield Vertex(self, "unknown[]")
|
||||
yield Float32(self, "extra_light")
|
||||
yield Vertex(self, "unknown[]")
|
||||
yield Vertex(self, "normal")
|
||||
if self["nvertices"].value:
|
||||
yield Vector(self, "vertex_indices",
|
||||
self["nvertices"].value, UInt32, "vertex")
|
||||
if self["has_texture"].value:
|
||||
yield Vector(self, "texture_vertex_indices",
|
||||
self["nvertices"].value, UInt32, "texture_vertex")
|
||||
if self["has_material"].value:
|
||||
yield UInt32(self, "material_index", "material index")
|
||||
|
||||
def createDescription(self):
|
||||
return "Face: id=%s" % self["id"].value
|
||||
|
||||
class Mesh(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "name", 32, strip="\0")
|
||||
yield UInt32(self, "id")
|
||||
yield UInt32(self, "geometry_mode")
|
||||
yield UInt32(self, "lighting_mode")
|
||||
yield UInt32(self, "texture_mode")
|
||||
yield UInt32(self, "nmesh_vertices")
|
||||
yield UInt32(self, "ntexture_vertices")
|
||||
yield UInt32(self, "nfaces")
|
||||
|
||||
nb_vert = self["nmesh_vertices"].value
|
||||
if nb_vert:
|
||||
yield Vector(self, "vertices",
|
||||
nb_vert, Vertex, "vertex")
|
||||
if self["ntexture_vertices"].value:
|
||||
yield Vector(self, "texture vertices",
|
||||
self["ntexture_vertices"].value, MapUV, "texture_vertex")
|
||||
if nb_vert:
|
||||
yield Vector(self, "light vertices",
|
||||
nb_vert, Float32, "extra_light")
|
||||
yield Vector(self, "unknown[]",
|
||||
nb_vert, Float32, "unknown")
|
||||
if self["nfaces"].value:
|
||||
yield Vector(self, "faces", self["nfaces"].value, Face, "face")
|
||||
if nb_vert:
|
||||
yield Vector(self, "vertex normals",
|
||||
nb_vert, Vertex, "normal")
|
||||
|
||||
yield UInt32(self, "has_shadow")
|
||||
yield Float32(self, "unknown[]")
|
||||
yield Float32(self, "radius")
|
||||
yield Vertex(self, "unknown[]")
|
||||
yield Vertex(self, "unknown[]")
|
||||
|
||||
def createDescription(self):
|
||||
return 'Mesh "%s" (id %s)' % (self["name"].value, self["id"].value)
|
||||
|
||||
class Geoset(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "count")
|
||||
for index in xrange(self["count"].value):
|
||||
yield Mesh(self, "mesh[]")
|
||||
|
||||
def createDescription(self):
|
||||
return "Set of %s meshes" % self["count"].value
|
||||
|
||||
class Node(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
size = (188-4)*8
|
||||
if self["parent_offset"].value != 0:
|
||||
size += 32
|
||||
if self["first_child_offset"].value != 0:
|
||||
size += 32
|
||||
if self["next_sibling_offset"].value != 0:
|
||||
size += 32
|
||||
self._size = size
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "name", 32, strip="\0")
|
||||
yield PaddingBytes(self, "unknown[]", 32, pattern="\xCC")
|
||||
yield UInt32(self, "flags")
|
||||
yield UInt32(self, "id")
|
||||
yield UInt32(self, "type")
|
||||
yield Int32(self, "mesh_id")
|
||||
yield UInt32(self, "depth")
|
||||
yield Int32(self, "parent_offset")
|
||||
yield UInt32(self, "nchildren")
|
||||
yield UInt32(self, "first_child_offset")
|
||||
yield UInt32(self, "next_sibling_offset")
|
||||
yield Vertex(self, "pivot")
|
||||
yield Vertex(self, "position")
|
||||
yield Float32(self, "pitch")
|
||||
yield Float32(self, "yaw")
|
||||
yield Float32(self, "roll")
|
||||
for index in xrange(4):
|
||||
yield Vertex(self, "unknown_vertex[]")
|
||||
if self["parent_offset"].value != 0:
|
||||
yield UInt32(self, "parent_id")
|
||||
if self["first_child_offset"].value != 0:
|
||||
yield UInt32(self, "first_child_id")
|
||||
if self["next_sibling_offset"].value != 0:
|
||||
yield UInt32(self, "next_sibling_id")
|
||||
|
||||
def createDescription(self):
|
||||
return 'Node "%s"' % self["name"].value
|
||||
|
||||
class Nodes(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "count")
|
||||
for index in xrange(self["count"].value):
|
||||
yield Node(self, "node[]")
|
||||
|
||||
def createDescription(self):
|
||||
return 'Nodes (%s)' % self["count"].value
|
||||
|
||||
class Materials(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
count = self["count"]
|
||||
self._size = count.size + count.value * (32*8)
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "count")
|
||||
for index in xrange(self["count"].value):
|
||||
yield String(self, "filename[]", 32, "Material file name", strip="\0")
|
||||
|
||||
def createDescription(self):
|
||||
return 'Material file names (%s)' % self["count"].value
|
||||
|
||||
class File3do(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "3do",
|
||||
"category": "misc",
|
||||
"file_ext": ("3do",),
|
||||
"mime": (u"image/x-3do",),
|
||||
"min_size": 8*4,
|
||||
"description": "renderdroid 3d model."
|
||||
}
|
||||
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
signature = self.stream.readBytes(0, 4)
|
||||
return signature in ('LDOM', 'MODL') # lazy endian-safe hack =D
|
||||
|
||||
def createFields(self):
|
||||
# Read file signature, and fix endian if needed
|
||||
yield String(self, "file_sig", 4, "File signature", charset="ASCII")
|
||||
if self["file_sig"].value == "MODL":
|
||||
self.endian = BIG_ENDIAN
|
||||
|
||||
# Read file content
|
||||
yield Materials(self, "materials")
|
||||
yield String(self, "model_name", 32, "model file name", strip="\0")
|
||||
yield RawBytes(self, "unknown[]", 4)
|
||||
yield UInt32(self, "ngeosets")
|
||||
for index in xrange(self["ngeosets"].value):
|
||||
yield Geoset(self, "geoset[]")
|
||||
yield RawBytes(self, "unknown[]", 4)
|
||||
yield Nodes(self, "nodes")
|
||||
yield Float32(self, "model_radius")
|
||||
yield Vertex(self, "insertion_offset")
|
||||
|
||||
# Read the end of the file
|
||||
if self.current_size < self._size:
|
||||
yield self.seekBit(self._size, "end")
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
3D Studio Max file (.3ds) parser.
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (StaticFieldSet, FieldSet,
|
||||
UInt16, UInt32, RawBytes, Enum, CString)
|
||||
from hachoir_parser.image.common import RGB
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_parser.misc.common import Vertex, MapUV
|
||||
|
||||
def readObject(parent):
|
||||
yield CString(parent, "name", "Object name")
|
||||
size = parent["size"].value * 8
|
||||
while parent.current_size < size:
|
||||
yield Chunk(parent, "chunk[]")
|
||||
|
||||
def readTextureFilename(parent):
|
||||
yield CString(parent, "filename", "Texture filename")
|
||||
|
||||
def readVersion(parent):
|
||||
yield UInt32(parent, "version", "3DS file format version")
|
||||
|
||||
def readMaterialName(parent):
|
||||
yield CString(parent, "name", "Material name")
|
||||
|
||||
class Polygon(StaticFieldSet):
|
||||
format = (
|
||||
(UInt16, "a", "Vertex A"),
|
||||
(UInt16, "b", "Vertex B"),
|
||||
(UInt16, "c", "Vertex C"),
|
||||
(UInt16, "flags", "Flags"))
|
||||
|
||||
def readMapList(parent):
|
||||
yield UInt16(parent, "count", "Map count")
|
||||
for index in xrange(parent["count"].value):
|
||||
yield MapUV(parent, "map_uv[]", "Mapping UV")
|
||||
|
||||
def readColor(parent):
|
||||
yield RGB(parent, "color")
|
||||
|
||||
def readVertexList(parent):
|
||||
yield UInt16(parent, "count", "Vertex count")
|
||||
for index in range(0, parent["count"].value):
|
||||
yield Vertex(parent, "vertex[]", "Vertex")
|
||||
|
||||
def readPolygonList(parent):
|
||||
count = UInt16(parent, "count", "Vertex count")
|
||||
yield count
|
||||
for i in range(0, count.value):
|
||||
yield Polygon(parent, "polygon[]")
|
||||
size = parent["size"].value * 8
|
||||
while parent.current_size < size:
|
||||
yield Chunk(parent, "chunk[]")
|
||||
|
||||
class Chunk(FieldSet):
|
||||
# List of chunk type name
|
||||
type_name = {
|
||||
0x0011: "Color",
|
||||
0x4D4D: "Main chunk",
|
||||
0x0002: "File version",
|
||||
0x3D3D: "Materials and objects",
|
||||
0x4000: "Object",
|
||||
0x4100: "Mesh (triangular)",
|
||||
0x4110: "Vertices list",
|
||||
0x4120: "Polygon (faces) list",
|
||||
0x4140: "Map UV list",
|
||||
0x4130: "Object material",
|
||||
0xAFFF: "New material",
|
||||
0xA000: "Material name",
|
||||
0xA010: "Material ambient",
|
||||
0xA020: "Material diffuse",
|
||||
0xA030: "Texture specular",
|
||||
0xA200: "Texture",
|
||||
0xA300: "Texture filename",
|
||||
|
||||
# Key frames
|
||||
0xB000: "Keyframes",
|
||||
0xB002: "Object node tag",
|
||||
0xB006: "Light target node tag",
|
||||
0xB007: "Spot light node tag",
|
||||
0xB00A: "Keyframes header",
|
||||
0xB009: "Keyframe current time",
|
||||
0xB030: "Node identifier",
|
||||
0xB010: "Node header",
|
||||
0x7001: "Viewport layout"
|
||||
}
|
||||
|
||||
chunk_id_by_type = {
|
||||
0x4d4d: "main",
|
||||
0x0002: "version",
|
||||
0x3d3d: "obj_mat",
|
||||
0xb000: "keyframes",
|
||||
0xafff: "material[]",
|
||||
0x4000: "object[]",
|
||||
0x4110: "vertices_list",
|
||||
0x4120: "polygon_list",
|
||||
0x4140: "mapuv_list",
|
||||
0x4100: "mesh"
|
||||
}
|
||||
|
||||
# List of chunks which contains other chunks
|
||||
sub_chunks = \
|
||||
(0x4D4D, 0x4100, 0x3D3D, 0xAFFF, 0xA200,
|
||||
0xB002, 0xB006, 0xB007,
|
||||
0xA010, 0xA030, 0xA020, 0xB000)
|
||||
|
||||
# List of chunk type handlers
|
||||
handlers = {
|
||||
0xA000: readMaterialName,
|
||||
0x4000: readObject,
|
||||
0xA300: readTextureFilename,
|
||||
0x0011: readColor,
|
||||
0x0002: readVersion,
|
||||
0x4110: readVertexList,
|
||||
0x4120: readPolygonList,
|
||||
0x4140: readMapList
|
||||
}
|
||||
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
|
||||
# Set description
|
||||
self._description = "Chunk: %s" % self["type"].display
|
||||
|
||||
# Set name based on type field
|
||||
type = self["type"].value
|
||||
if type in Chunk.chunk_id_by_type:
|
||||
self._name = Chunk.chunk_id_by_type[type]
|
||||
else:
|
||||
self._name = "chunk_%04x" % type
|
||||
|
||||
# Guess chunk size
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(textHandler(UInt16(self, "type", "Chunk type"), hexadecimal), Chunk.type_name)
|
||||
yield UInt32(self, "size", "Chunk size (in bytes)")
|
||||
content_size = self["size"].value - 6
|
||||
if content_size == 0:
|
||||
return
|
||||
type = self["type"].value
|
||||
if type in Chunk.sub_chunks:
|
||||
while self.current_size < self.size:
|
||||
yield Chunk(self, "chunk[]")
|
||||
else:
|
||||
if type in Chunk.handlers:
|
||||
fields = Chunk.handlers[type] (self)
|
||||
for field in fields:
|
||||
yield field
|
||||
else:
|
||||
yield RawBytes(self, "data", content_size)
|
||||
|
||||
class File3ds(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "3ds",
|
||||
"category": "misc",
|
||||
"file_ext": ("3ds",),
|
||||
"mime": (u"image/x-3ds",),
|
||||
"min_size": 16*8,
|
||||
"description": "3D Studio Max model"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 2) != "MM":
|
||||
return "Wrong signature"
|
||||
if self["main/version/version"].value not in (2, 3):
|
||||
return "Unknown format version"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
while not self.eof:
|
||||
yield Chunk(self, "chunk[]")
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
Gnome keyring parser.
|
||||
|
||||
Sources:
|
||||
- Gnome Keyring source code,
|
||||
function generate_file() in keyrings/gkr-keyring.c,
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 2008-04-09
|
||||
"""
|
||||
|
||||
from hachoir_core.tools import paddingSize
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
Bit, NullBits, NullBytes,
|
||||
UInt8, UInt32, String, RawBytes, Enum,
|
||||
TimestampUnix64, CompressedField,
|
||||
SubFile)
|
||||
from hachoir_core.endian import BIG_ENDIAN
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
def sha256(data):
|
||||
hash = hashlib.new('sha256')
|
||||
hash.update(data)
|
||||
return hash.digest()
|
||||
except ImportError:
|
||||
def sha256(data):
|
||||
raise ImportError("hashlib module is missing")
|
||||
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
class DeflateStream:
|
||||
def __init__(self, stream):
|
||||
hash_iterations = 1234
|
||||
password = "x" * 8
|
||||
salt = "\0" * 8
|
||||
key, iv = generate_key(password, salt, hash_iterations)
|
||||
self.cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
|
||||
def __call__(self, size, data=None):
|
||||
if data is None:
|
||||
return ''
|
||||
return self.cipher.decrypt(data)
|
||||
|
||||
def Deflate(field):
|
||||
CompressedField(field, DeflateStream)
|
||||
return field
|
||||
except ImportError:
|
||||
def Deflate(field):
|
||||
return field
|
||||
|
||||
class KeyringString(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "length")
|
||||
length = self["length"].value
|
||||
if length == 0xffffffff:
|
||||
return
|
||||
yield String(self, "text", length, charset="UTF-8")
|
||||
|
||||
def createValue(self):
|
||||
if "text" in self:
|
||||
return self["text"].value
|
||||
else:
|
||||
return u''
|
||||
|
||||
def createDescription(self):
|
||||
if "text" in self:
|
||||
return self["text"].value
|
||||
else:
|
||||
return u"(empty string)"
|
||||
|
||||
class Attribute(FieldSet):
|
||||
def createFields(self):
|
||||
yield KeyringString(self, "name")
|
||||
yield UInt32(self, "type")
|
||||
type = self["type"].value
|
||||
if type == 0:
|
||||
yield KeyringString(self, "value")
|
||||
elif type == 1:
|
||||
yield UInt32(self, "value")
|
||||
else:
|
||||
raise TypeError("Unknown attribute type (%s)" % type)
|
||||
|
||||
def createDescription(self):
|
||||
return 'Attribute "%s"' % self["name"].value
|
||||
|
||||
class ACL(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "types_allowed")
|
||||
yield KeyringString(self, "display_name")
|
||||
yield KeyringString(self, "pathname")
|
||||
yield KeyringString(self, "reserved[]")
|
||||
yield UInt32(self, "reserved[]")
|
||||
|
||||
class Item(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "id")
|
||||
yield UInt32(self, "type")
|
||||
yield UInt32(self, "attr_count")
|
||||
for index in xrange(self["attr_count"].value):
|
||||
yield Attribute(self, "attr[]")
|
||||
|
||||
def createDescription(self):
|
||||
return "Item #%s: %s attributes" % (self["id"].value, self["attr_count"].value)
|
||||
|
||||
class Items(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "count")
|
||||
for index in xrange(self["count"].value):
|
||||
yield Item(self, "item[]")
|
||||
|
||||
class EncryptedItem(FieldSet):
|
||||
def createFields(self):
|
||||
yield KeyringString(self, "display_name")
|
||||
yield KeyringString(self, "secret")
|
||||
yield TimestampUnix64(self, "mtime")
|
||||
yield TimestampUnix64(self, "ctime")
|
||||
yield KeyringString(self, "reserved[]")
|
||||
for index in xrange(4):
|
||||
yield UInt32(self, "reserved[]")
|
||||
yield UInt32(self, "attr_count")
|
||||
for index in xrange(self["attr_count"].value):
|
||||
yield Attribute(self, "attr[]")
|
||||
yield UInt32(self, "acl_count")
|
||||
for index in xrange(self["acl_count"].value):
|
||||
yield ACL(self, "acl[]")
|
||||
# size = 8 # paddingSize((self.stream.size - self.current_size) // 8, 16)
|
||||
# if size:
|
||||
# yield NullBytes(self, "hash_padding", size, "16 bytes alignment")
|
||||
|
||||
class EncryptedData(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "gnomeencryptedkeyring",
|
||||
"min_size": 16*8,
|
||||
"description": u"Gnome encrypted keyring",
|
||||
}
|
||||
endian = BIG_ENDIAN
|
||||
def validate(self):
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield RawBytes(self, "md5", 16)
|
||||
while True:
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size < 77:
|
||||
break
|
||||
yield EncryptedItem(self, "item[]")
|
||||
size = paddingSize(self.current_size // 8, 16)
|
||||
if size:
|
||||
yield NullBytes(self, "padding_align", size)
|
||||
|
||||
class GnomeKeyring(Parser):
|
||||
MAGIC = "GnomeKeyring\n\r\0\n"
|
||||
PARSER_TAGS = {
|
||||
"id": "gnomekeyring",
|
||||
"category": "misc",
|
||||
"magic": ((MAGIC, 0),),
|
||||
"min_size": 47*8,
|
||||
"description": u"Gnome keyring",
|
||||
}
|
||||
CRYPTO_NAMES = {
|
||||
0: u"AEL",
|
||||
}
|
||||
HASH_NAMES = {
|
||||
0: u"MD5",
|
||||
}
|
||||
|
||||
endian = BIG_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
|
||||
return u"Invalid magic string"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "magic", len(self.MAGIC), 'Magic string (%r)' % self.MAGIC, charset="ASCII")
|
||||
yield UInt8(self, "major_version")
|
||||
yield UInt8(self, "minor_version")
|
||||
yield Enum(UInt8(self, "crypto"), self.CRYPTO_NAMES)
|
||||
yield Enum(UInt8(self, "hash"), self.HASH_NAMES)
|
||||
yield KeyringString(self, "keyring_name")
|
||||
yield TimestampUnix64(self, "mtime")
|
||||
yield TimestampUnix64(self, "ctime")
|
||||
yield Bit(self, "lock_on_idle")
|
||||
yield NullBits(self, "reserved[]", 31, "Reserved for future flags")
|
||||
yield UInt32(self, "lock_timeout")
|
||||
yield UInt32(self, "hash_iterations")
|
||||
yield RawBytes(self, "salt", 8)
|
||||
yield NullBytes(self, "reserved[]", 16)
|
||||
yield Items(self, "items")
|
||||
yield UInt32(self, "encrypted_size")
|
||||
yield Deflate(SubFile(self, "encrypted", self["encrypted_size"].value, "AES128 CBC", parser_class=EncryptedData))
|
||||
|
||||
def generate_key(password, salt, hash_iterations):
|
||||
sha = sha256(password+salt)
|
||||
for index in xrange(hash_iterations-1):
|
||||
sha = sha256(sha)
|
||||
return sha[:16], sha[16:]
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Microsoft Windows Help (HLP) parser for Hachoir project.
|
||||
|
||||
Documents:
|
||||
- Windows Help File Format / Annotation File Format / SHG and MRB File Format
|
||||
written by M. Winterhoff (100326.2776@compuserve.com)
|
||||
found on http://www.wotsit.org/
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 2007-09-03
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
Bits, Int32, UInt16, UInt32,
|
||||
NullBytes, RawBytes, PaddingBytes, String)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import (textHandler, hexadecimal,
|
||||
displayHandler, humanFilesize)
|
||||
|
||||
class FileEntry(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
self._size = self["res_space"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
yield displayHandler(UInt32(self, "res_space", "Reserved space"), humanFilesize)
|
||||
yield displayHandler(UInt32(self, "used_space", "Used space"), humanFilesize)
|
||||
yield Bits(self, "file_flags", 8, "(=4)")
|
||||
|
||||
yield textHandler(UInt16(self, "magic"), hexadecimal)
|
||||
yield Bits(self, "flags", 16)
|
||||
yield displayHandler(UInt16(self, "page_size", "Page size in bytes"), humanFilesize)
|
||||
yield String(self, "structure", 16, strip="\0", charset="ASCII")
|
||||
yield NullBytes(self, "zero", 2)
|
||||
yield UInt16(self, "nb_page_splits", "Number of page splits B+ tree has suffered")
|
||||
yield UInt16(self, "root_page", "Page number of B+ tree root page")
|
||||
yield PaddingBytes(self, "one", 2, pattern="\xFF")
|
||||
yield UInt16(self, "nb_page", "Number of B+ tree pages")
|
||||
yield UInt16(self, "nb_level", "Number of levels of B+ tree")
|
||||
yield UInt16(self, "nb_entry", "Number of entries in B+ tree")
|
||||
|
||||
size = (self.size - self.current_size)//8
|
||||
if size:
|
||||
yield PaddingBytes(self, "reserved_space", size)
|
||||
|
||||
class HlpFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "hlp",
|
||||
"category": "misc",
|
||||
"file_ext": ("hlp",),
|
||||
"min_size": 32,
|
||||
"description": "Microsoft Windows Help (HLP)",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self["magic"].value != 0x00035F3F:
|
||||
return "Invalid magic"
|
||||
if self["filesize"].value != self.stream.size//8:
|
||||
return "Invalid magic"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield textHandler(UInt32(self, "magic"), hexadecimal)
|
||||
yield UInt32(self, "dir_start", "Directory start")
|
||||
yield Int32(self, "first_free_block", "First free block")
|
||||
yield UInt32(self, "filesize", "File size in bytes")
|
||||
|
||||
yield self.seekByte(self["dir_start"].value)
|
||||
yield FileEntry(self, "file[]")
|
||||
|
||||
size = (self.size - self.current_size)//8
|
||||
if size:
|
||||
yield RawBytes(self, "end", size)
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
"""
|
||||
Windows Shortcut (.lnk) parser.
|
||||
|
||||
Documents:
|
||||
- The Windows Shortcut File Format (document version 1.0)
|
||||
Reverse-engineered by Jesse Hager
|
||||
http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf
|
||||
- Wine source code:
|
||||
http://source.winehq.org/source/include/shlobj.h (SHELL_LINK_DATA_FLAGS enum)
|
||||
http://source.winehq.org/source/dlls/shell32/pidl.h
|
||||
- Microsoft:
|
||||
http://msdn2.microsoft.com/en-us/library/ms538128.aspx
|
||||
|
||||
Author: Robert Xiao, Victor Stinner
|
||||
|
||||
Changes:
|
||||
2007-06-27 - Robert Xiao
|
||||
* Fixes to FileLocationInfo to correctly handle Unicode paths
|
||||
2007-06-13 - Robert Xiao
|
||||
* ItemID, FileLocationInfo and ExtraInfo structs, correct Unicode string handling
|
||||
2007-03-15 - Victor Stinner
|
||||
* Creation of the parser
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
CString, String,
|
||||
UInt32, UInt16, UInt8,
|
||||
Bit, Bits, PaddingBits,
|
||||
TimestampWin64, DateTimeMSDOS32,
|
||||
NullBytes, PaddingBytes, RawBytes, Enum)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_parser.common.win32 import GUID
|
||||
from hachoir_parser.common.msdos import MSDOSFileAttr16, MSDOSFileAttr32
|
||||
from hachoir_core.text_handler import filesizeHandler
|
||||
|
||||
from hachoir_core.tools import paddingSize
|
||||
|
||||
class ItemIdList(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
self._size = (self["size"].value+2) * 8
|
||||
|
||||
def createFields(self):
|
||||
yield UInt16(self, "size", "Size of item ID list")
|
||||
while True:
|
||||
item = ItemId(self, "itemid[]")
|
||||
yield item
|
||||
if not item["length"].value:
|
||||
break
|
||||
|
||||
class ItemId(FieldSet):
|
||||
ITEM_TYPE = {
|
||||
0x1F: "GUID",
|
||||
0x23: "Drive",
|
||||
0x25: "Drive",
|
||||
0x29: "Drive",
|
||||
0x2E: "Shell Extension",
|
||||
0x2F: "Drive",
|
||||
0x30: "Dir/File",
|
||||
0x31: "Directory",
|
||||
0x32: "File",
|
||||
0x34: "File [Unicode Name]",
|
||||
0x41: "Workgroup",
|
||||
0x42: "Computer",
|
||||
0x46: "Net Provider",
|
||||
0x47: "Whole Network",
|
||||
0x4C: "Web Folder",
|
||||
0x61: "MSITStore",
|
||||
0x70: "Printer/RAS Connection",
|
||||
0xB1: "History/Favorite",
|
||||
0xC3: "Network Share",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
if self["length"].value:
|
||||
self._size = self["length"].value * 8
|
||||
else:
|
||||
self._size = 16
|
||||
|
||||
def createFields(self):
|
||||
yield UInt16(self, "length", "Length of Item ID Entry")
|
||||
if not self["length"].value:
|
||||
return
|
||||
|
||||
yield Enum(UInt8(self, "type"),self.ITEM_TYPE)
|
||||
entrytype=self["type"].value
|
||||
if entrytype in (0x1F, 0x70):
|
||||
# GUID
|
||||
yield RawBytes(self, "dummy", 1, "should be 0x50")
|
||||
yield GUID(self, "guid")
|
||||
|
||||
elif entrytype == 0x2E:
|
||||
# Shell extension
|
||||
yield RawBytes(self, "dummy", 1, "should be 0x50")
|
||||
if self["dummy"].value == '\0':
|
||||
yield UInt16(self, "length_data", "Length of shell extension-specific data")
|
||||
if self["length_data"].value:
|
||||
yield RawBytes(self, "data", self["length_data"].value, "Shell extension-specific data")
|
||||
yield GUID(self, "handler_guid")
|
||||
yield GUID(self, "guid")
|
||||
|
||||
elif entrytype in (0x23, 0x25, 0x29, 0x2F):
|
||||
# Drive
|
||||
yield String(self, "drive", self["length"].value-3, strip="\0")
|
||||
|
||||
elif entrytype in (0x30, 0x31, 0x32, 0x61, 0xb1):
|
||||
yield RawBytes(self, "dummy", 1, "should be 0x00")
|
||||
yield UInt32(self, "size", "size of file; 0 for folders")
|
||||
yield DateTimeMSDOS32(self, "date_time", "File/folder date and time")
|
||||
yield MSDOSFileAttr16(self, "attribs", "File/folder attributes")
|
||||
yield CString(self, "name", "File/folder name")
|
||||
if self.root.hasUnicodeNames():
|
||||
# Align to 2-bytes
|
||||
n = paddingSize(self.current_size//8, 2)
|
||||
if n:
|
||||
yield PaddingBytes(self, "pad", n)
|
||||
|
||||
yield UInt16(self, "length_w", "Length of wide struct member")
|
||||
yield RawBytes(self, "unknown[]", 6)
|
||||
yield DateTimeMSDOS32(self, "creation_date_time", "File/folder creation date and time")
|
||||
yield DateTimeMSDOS32(self, "access_date_time", "File/folder last access date and time")
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
yield UInt16(self, "length_next", "Length of next two strings (if zero, ignore this field)")
|
||||
yield CString(self, "unicode_name", "File/folder name", charset="UTF-16-LE")
|
||||
if self["length_next"].value:
|
||||
yield CString(self, "localized_name", "Localized name")
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
else:
|
||||
yield CString(self, "name_short", "File/folder short name")
|
||||
|
||||
elif entrytype in (0x41, 0x42, 0x46):
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
yield CString(self, "name")
|
||||
yield CString(self, "protocol")
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
|
||||
elif entrytype == 0x47:
|
||||
# Whole Network
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
yield CString(self, "name")
|
||||
|
||||
elif entrytype == 0xC3:
|
||||
# Network Share
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
yield CString(self, "name")
|
||||
yield CString(self, "protocol")
|
||||
yield CString(self, "description")
|
||||
yield RawBytes(self, "unknown[]", 2)
|
||||
|
||||
elif entrytype == 0x4C:
|
||||
# Web Folder
|
||||
yield RawBytes(self, "unknown[]", 5)
|
||||
yield TimestampWin64(self, "modification_time")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield LnkString(self, "name")
|
||||
yield RawBytes(self, "padding[]", 2)
|
||||
yield LnkString(self, "address")
|
||||
if self["address/length"].value:
|
||||
yield RawBytes(self, "padding[]", 2)
|
||||
|
||||
else:
|
||||
yield RawBytes(self, "raw", self["length"].value-3)
|
||||
|
||||
def createDescription(self):
|
||||
if self["length"].value:
|
||||
return "Item ID Entry: "+self.ITEM_TYPE.get(self["type"].value,"Unknown")
|
||||
else:
|
||||
return "End of Item ID List"
|
||||
|
||||
def formatVolumeSerial(field):
|
||||
val = field.value
|
||||
return '%04X-%04X'%(val>>16, val&0xFFFF)
|
||||
|
||||
class LocalVolumeTable(FieldSet):
|
||||
VOLUME_TYPE={
|
||||
1: "No root directory",
|
||||
2: "Removable (Floppy, Zip, etc.)",
|
||||
3: "Fixed (Hard disk)",
|
||||
4: "Remote (Network drive)",
|
||||
5: "CD-ROM",
|
||||
6: "Ram drive",
|
||||
}
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "length", "Length of this structure")
|
||||
yield Enum(UInt32(self, "volume_type", "Volume Type"),self.VOLUME_TYPE)
|
||||
yield textHandler(UInt32(self, "volume_serial", "Volume Serial Number"), formatVolumeSerial)
|
||||
|
||||
yield UInt32(self, "label_offset", "Offset to volume label")
|
||||
padding = self.seekByte(self["label_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "drive")
|
||||
|
||||
def hasValue(self):
|
||||
return bool(self["drive"].value)
|
||||
|
||||
def createValue(self):
|
||||
return self["drive"].value
|
||||
|
||||
class NetworkVolumeTable(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "length", "Length of this structure")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield UInt32(self, "share_name_offset", "Offset to share name")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield UInt32(self, "unknown[]")
|
||||
padding = self.seekByte(self["share_name_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "share_name")
|
||||
|
||||
def createValue(self):
|
||||
return self["share_name"].value
|
||||
|
||||
class FileLocationInfo(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "length", "Length of this structure")
|
||||
if not self["length"].value:
|
||||
return
|
||||
|
||||
yield UInt32(self, "first_offset_pos", "Position of first offset")
|
||||
has_unicode_paths = (self["first_offset_pos"].value == 0x24)
|
||||
yield Bit(self, "on_local_volume")
|
||||
yield Bit(self, "on_network_volume")
|
||||
yield PaddingBits(self, "reserved[]", 30)
|
||||
yield UInt32(self, "local_info_offset", "Offset to local volume table; only meaningful if on_local_volume = 1")
|
||||
yield UInt32(self, "local_pathname_offset", "Offset to local base pathname; only meaningful if on_local_volume = 1")
|
||||
yield UInt32(self, "remote_info_offset", "Offset to network volume table; only meaningful if on_network_volume = 1")
|
||||
yield UInt32(self, "pathname_offset", "Offset of remaining pathname")
|
||||
if has_unicode_paths:
|
||||
yield UInt32(self, "local_pathname_unicode_offset", "Offset to Unicode version of local base pathname; only meaningful if on_local_volume = 1")
|
||||
yield UInt32(self, "pathname_unicode_offset", "Offset to Unicode version of remaining pathname")
|
||||
if self["on_local_volume"].value:
|
||||
padding = self.seekByte(self["local_info_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield LocalVolumeTable(self, "local_volume_table", "Local Volume Table")
|
||||
|
||||
padding = self.seekByte(self["local_pathname_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "local_base_pathname", "Local Base Pathname")
|
||||
if has_unicode_paths:
|
||||
padding = self.seekByte(self["local_pathname_unicode_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "local_base_pathname_unicode", "Local Base Pathname in Unicode", charset="UTF-16-LE")
|
||||
|
||||
if self["on_network_volume"].value:
|
||||
padding = self.seekByte(self["remote_info_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield NetworkVolumeTable(self, "network_volume_table")
|
||||
|
||||
padding = self.seekByte(self["pathname_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "final_pathname", "Final component of the pathname")
|
||||
|
||||
if has_unicode_paths:
|
||||
padding = self.seekByte(self["pathname_unicode_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "final_pathname_unicode", "Final component of the pathname in Unicode", charset="UTF-16-LE")
|
||||
|
||||
padding=self.seekByte(self["length"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
|
||||
class LnkString(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "length", "Length of this string")
|
||||
if self["length"].value:
|
||||
if self.root.hasUnicodeNames():
|
||||
yield String(self, "data", self["length"].value*2, charset="UTF-16-LE")
|
||||
else:
|
||||
yield String(self, "data", self["length"].value, charset="ASCII")
|
||||
|
||||
def createValue(self):
|
||||
if self["length"].value:
|
||||
return self["data"].value
|
||||
else:
|
||||
return ""
|
||||
|
||||
class ColorRef(FieldSet):
|
||||
''' COLORREF struct, 0x00bbggrr '''
|
||||
static_size=32
|
||||
def createFields(self):
|
||||
yield UInt8(self, "red", "Red")
|
||||
yield UInt8(self, "green", "Green")
|
||||
yield UInt8(self, "blue", "Blue")
|
||||
yield PaddingBytes(self, "pad", 1, "Padding (must be 0)")
|
||||
def createDescription(self):
|
||||
rgb = self["red"].value, self["green"].value, self["blue"].value
|
||||
return "RGB Color: #%02X%02X%02X" % rgb
|
||||
|
||||
class ColorTableIndex(Bits):
|
||||
def __init__(self, parent, name, size, description=None):
|
||||
Bits.__init__(self, parent, name, size, None)
|
||||
self.desc=description
|
||||
def createDescription(self):
|
||||
assert hasattr(self, 'parent') and hasattr(self, 'value')
|
||||
return "%s: %s"%(self.desc,
|
||||
self.parent["color[%i]"%self.value].description)
|
||||
|
||||
class ExtraInfo(FieldSet):
|
||||
INFO_TYPE={
|
||||
0xA0000001: "Link Target Information", # EXP_SZ_LINK_SIG
|
||||
0xA0000002: "Console Window Properties", # NT_CONSOLE_PROPS_SIG
|
||||
0xA0000003: "Hostname and Other Stuff",
|
||||
0xA0000004: "Console Codepage Information", # NT_FE_CONSOLE_PROPS_SIG
|
||||
0xA0000005: "Special Folder Info", # EXP_SPECIAL_FOLDER_SIG
|
||||
0xA0000006: "DarwinID (Windows Installer ID) Information", # EXP_DARWIN_ID_SIG
|
||||
0xA0000007: "Custom Icon Details", # EXP_LOGO3_ID_SIG or EXP_SZ_ICON_SIG
|
||||
}
|
||||
SPECIAL_FOLDER = {
|
||||
0: "DESKTOP",
|
||||
1: "INTERNET",
|
||||
2: "PROGRAMS",
|
||||
3: "CONTROLS",
|
||||
4: "PRINTERS",
|
||||
5: "PERSONAL",
|
||||
6: "FAVORITES",
|
||||
7: "STARTUP",
|
||||
8: "RECENT",
|
||||
9: "SENDTO",
|
||||
10: "BITBUCKET",
|
||||
11: "STARTMENU",
|
||||
16: "DESKTOPDIRECTORY",
|
||||
17: "DRIVES",
|
||||
18: "NETWORK",
|
||||
19: "NETHOOD",
|
||||
20: "FONTS",
|
||||
21: "TEMPLATES",
|
||||
22: "COMMON_STARTMENU",
|
||||
23: "COMMON_PROGRAMS",
|
||||
24: "COMMON_STARTUP",
|
||||
25: "COMMON_DESKTOPDIRECTORY",
|
||||
26: "APPDATA",
|
||||
27: "PRINTHOOD",
|
||||
28: "LOCAL_APPDATA",
|
||||
29: "ALTSTARTUP",
|
||||
30: "COMMON_ALTSTARTUP",
|
||||
31: "COMMON_FAVORITES",
|
||||
32: "INTERNET_CACHE",
|
||||
33: "COOKIES",
|
||||
34: "HISTORY",
|
||||
35: "COMMON_APPDATA",
|
||||
36: "WINDOWS",
|
||||
37: "SYSTEM",
|
||||
38: "PROGRAM_FILES",
|
||||
39: "MYPICTURES",
|
||||
40: "PROFILE",
|
||||
41: "SYSTEMX86",
|
||||
42: "PROGRAM_FILESX86",
|
||||
43: "PROGRAM_FILES_COMMON",
|
||||
44: "PROGRAM_FILES_COMMONX86",
|
||||
45: "COMMON_TEMPLATES",
|
||||
46: "COMMON_DOCUMENTS",
|
||||
47: "COMMON_ADMINTOOLS",
|
||||
48: "ADMINTOOLS",
|
||||
49: "CONNECTIONS",
|
||||
53: "COMMON_MUSIC",
|
||||
54: "COMMON_PICTURES",
|
||||
55: "COMMON_VIDEO",
|
||||
56: "RESOURCES",
|
||||
57: "RESOURCES_LOCALIZED",
|
||||
58: "COMMON_OEM_LINKS",
|
||||
59: "CDBURN_AREA",
|
||||
61: "COMPUTERSNEARME",
|
||||
}
|
||||
BOOL_ENUM = {
|
||||
0: "False",
|
||||
1: "True",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
if self["length"].value:
|
||||
self._size = self["length"].value * 8
|
||||
else:
|
||||
self._size = 32
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "length", "Length of this structure")
|
||||
if not self["length"].value:
|
||||
return
|
||||
|
||||
yield Enum(textHandler(UInt32(self, "signature", "Signature determining the function of this structure"),hexadecimal),self.INFO_TYPE)
|
||||
|
||||
if self["signature"].value == 0xA0000003:
|
||||
# Hostname and Other Stuff
|
||||
yield UInt32(self, "remaining_length")
|
||||
yield UInt32(self, "unknown[]")
|
||||
yield String(self, "hostname", 16, "Computer hostname on which shortcut was last modified", strip="\0")
|
||||
yield RawBytes(self, "unknown[]", 32)
|
||||
yield RawBytes(self, "unknown[]", 32)
|
||||
|
||||
elif self["signature"].value == 0xA0000005:
|
||||
# Special Folder Info
|
||||
yield Enum(UInt32(self, "special_folder_id", "ID of the special folder"),self.SPECIAL_FOLDER)
|
||||
yield UInt32(self, "offset", "Offset to Item ID entry")
|
||||
|
||||
elif self["signature"].value in (0xA0000001, 0xA0000006, 0xA0000007):
|
||||
if self["signature"].value == 0xA0000001: # Link Target Information
|
||||
object_name="target"
|
||||
elif self["signature"].value == 0xA0000006: # DarwinID (Windows Installer ID) Information
|
||||
object_name="darwinID"
|
||||
else: # Custom Icon Details
|
||||
object_name="icon_path"
|
||||
yield CString(self, object_name, "Data (ASCII format)", charset="ASCII")
|
||||
remaining = self["length"].value - self.current_size/8 - 260*2 # 260*2 = size of next part
|
||||
if remaining:
|
||||
yield RawBytes(self, "slack_space[]", remaining, "Data beyond end of string")
|
||||
yield CString(self, object_name+'_unicode', "Data (Unicode format)", charset="UTF-16-LE", truncate="\0")
|
||||
remaining = self["length"].value - self.current_size/8
|
||||
if remaining:
|
||||
yield RawBytes(self, "slack_space[]", remaining, "Data beyond end of string")
|
||||
|
||||
elif self["signature"].value == 0xA0000002:
|
||||
# Console Window Properties
|
||||
yield ColorTableIndex(self, "color_text", 4, "Screen text color index")
|
||||
yield ColorTableIndex(self, "color_bg", 4, "Screen background color index")
|
||||
yield NullBytes(self, "reserved[]", 1)
|
||||
yield ColorTableIndex(self, "color_popup_text", 4, "Pop-up text color index")
|
||||
yield ColorTableIndex(self, "color_popup_bg", 4, "Pop-up background color index")
|
||||
yield NullBytes(self, "reserved[]", 1)
|
||||
yield UInt16(self, "buffer_width", "Screen buffer width (character cells)")
|
||||
yield UInt16(self, "buffer_height", "Screen buffer height (character cells)")
|
||||
yield UInt16(self, "window_width", "Window width (character cells)")
|
||||
yield UInt16(self, "window_height", "Window height (character cells)")
|
||||
yield UInt16(self, "position_left", "Window distance from left edge (screen coords)")
|
||||
yield UInt16(self, "position_top", "Window distance from top edge (screen coords)")
|
||||
yield UInt32(self, "font_number")
|
||||
yield UInt32(self, "input_buffer_size")
|
||||
yield UInt16(self, "font_width", "Font width in pixels; 0 for a non-raster font")
|
||||
yield UInt16(self, "font_height", "Font height in pixels; equal to the font size for non-raster fonts")
|
||||
yield UInt32(self, "font_family")
|
||||
yield UInt32(self, "font_weight")
|
||||
yield String(self, "font_name_unicode", 64, "Font Name (Unicode format)", charset="UTF-16-LE", truncate="\0")
|
||||
yield UInt32(self, "cursor_size", "Relative size of cursor (% of character size)")
|
||||
yield Enum(UInt32(self, "full_screen", "Run console in full screen?"), self.BOOL_ENUM)
|
||||
yield Enum(UInt32(self, "quick_edit", "Console uses quick-edit feature (using mouse to cut & paste)?"), self.BOOL_ENUM)
|
||||
yield Enum(UInt32(self, "insert_mode", "Console uses insertion mode?"), self.BOOL_ENUM)
|
||||
yield Enum(UInt32(self, "auto_position", "System automatically positions window?"), self.BOOL_ENUM)
|
||||
yield UInt32(self, "history_size", "Size of the history buffer (in lines)")
|
||||
yield UInt32(self, "history_count", "Number of history buffers (each process gets one up to this limit)")
|
||||
yield Enum(UInt32(self, "history_no_dup", "Automatically eliminate duplicate lines in the history buffer?"), self.BOOL_ENUM)
|
||||
for index in xrange(16):
|
||||
yield ColorRef(self, "color[]")
|
||||
|
||||
elif self["signature"].value == 0xA0000004:
|
||||
# Console Codepage Information
|
||||
yield UInt32(self, "codepage", "Console's code page")
|
||||
|
||||
else:
|
||||
yield RawBytes(self, "raw", self["length"].value-self.current_size/8)
|
||||
|
||||
def createDescription(self):
|
||||
if self["length"].value:
|
||||
return "Extra Info Entry: "+self["signature"].display
|
||||
else:
|
||||
return "End of Extra Info"
|
||||
|
||||
HOT_KEYS = {
|
||||
0x00: u'None',
|
||||
0x13: u'Pause',
|
||||
0x14: u'Caps Lock',
|
||||
0x21: u'Page Up',
|
||||
0x22: u'Page Down',
|
||||
0x23: u'End',
|
||||
0x24: u'Home',
|
||||
0x25: u'Left',
|
||||
0x26: u'Up',
|
||||
0x27: u'Right',
|
||||
0x28: u'Down',
|
||||
0x2d: u'Insert',
|
||||
0x2e: u'Delete',
|
||||
0x6a: u'Num *',
|
||||
0x6b: u'Num +',
|
||||
0x6d: u'Num -',
|
||||
0x6e: u'Num .',
|
||||
0x6f: u'Num /',
|
||||
0x90: u'Num Lock',
|
||||
0x91: u'Scroll Lock',
|
||||
0xba: u';',
|
||||
0xbb: u'=',
|
||||
0xbc: u',',
|
||||
0xbd: u'-',
|
||||
0xbe: u'.',
|
||||
0xbf: u'/',
|
||||
0xc0: u'`',
|
||||
0xdb: u'[',
|
||||
0xdc: u'\\',
|
||||
0xdd: u']',
|
||||
0xde: u"'",
|
||||
}
|
||||
|
||||
def text_hot_key(field):
|
||||
assert hasattr(field, "value")
|
||||
val=field.value
|
||||
if 0x30 <= val <= 0x39:
|
||||
return unichr(val)
|
||||
elif 0x41 <= val <= 0x5A:
|
||||
return unichr(val)
|
||||
elif 0x60 <= val <= 0x69:
|
||||
return u'Numpad %c' % unichr(val-0x30)
|
||||
elif 0x70 <= val <= 0x87:
|
||||
return 'F%i'%(val-0x6F)
|
||||
elif val in HOT_KEYS:
|
||||
return HOT_KEYS[val]
|
||||
return str(val)
|
||||
|
||||
class LnkFile(Parser):
|
||||
MAGIC = "\x4C\0\0\0\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
|
||||
PARSER_TAGS = {
|
||||
"id": "lnk",
|
||||
"category": "misc",
|
||||
"file_ext": ("lnk",),
|
||||
"mime": (u"application/x-ms-shortcut",),
|
||||
"magic": ((MAGIC, 0),),
|
||||
"min_size": len(MAGIC)*8, # signature + guid = 20 bytes
|
||||
"description": "Windows Shortcut (.lnk)",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
SHOW_WINDOW_STATE = {
|
||||
0: u"Hide",
|
||||
1: u"Show Normal",
|
||||
2: u"Show Minimized",
|
||||
3: u"Show Maximized",
|
||||
4: u"Show Normal, not activated",
|
||||
5: u"Show",
|
||||
6: u"Minimize",
|
||||
7: u"Show Minimized, not activated",
|
||||
8: u"Show, not activated",
|
||||
9: u"Restore",
|
||||
10: u"Show Default",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self["signature"].value != 0x0000004C:
|
||||
return "Invalid signature"
|
||||
if self["guid"].value != "00021401-0000-0000-C000-000000000046":
|
||||
return "Invalid GUID"
|
||||
return True
|
||||
|
||||
def hasUnicodeNames(self):
|
||||
return self["has_unicode_names"].value
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "signature", "Shortcut signature (0x0000004C)")
|
||||
yield GUID(self, "guid", "Shortcut GUID (00021401-0000-0000-C000-000000000046)")
|
||||
|
||||
yield Bit(self, "has_shell_id", "Is the Item ID List present?")
|
||||
yield Bit(self, "target_is_file", "Is a file or a directory?")
|
||||
yield Bit(self, "has_description", "Is the Description field present?")
|
||||
yield Bit(self, "has_rel_path", "Is the relative path to the target available?")
|
||||
yield Bit(self, "has_working_dir", "Is there a working directory?")
|
||||
yield Bit(self, "has_cmd_line_args", "Are there any command line arguments?")
|
||||
yield Bit(self, "has_custom_icon", "Is there a custom icon?")
|
||||
yield Bit(self, "has_unicode_names", "Are Unicode names used?")
|
||||
yield Bit(self, "force_no_linkinfo")
|
||||
yield Bit(self, "has_exp_sz")
|
||||
yield Bit(self, "run_in_separate")
|
||||
yield Bit(self, "has_logo3id", "Is LOGO3 ID info present?")
|
||||
yield Bit(self, "has_darwinid", "Is the DarwinID info present?")
|
||||
yield Bit(self, "runas_user", "Is the target run as another user?")
|
||||
yield Bit(self, "has_exp_icon_sz", "Is custom icon information available?")
|
||||
yield Bit(self, "no_pidl_alias")
|
||||
yield Bit(self, "force_unc_name")
|
||||
yield Bit(self, "run_with_shim_layer")
|
||||
yield PaddingBits(self, "reserved[]", 14, "Flag bits reserved for future use")
|
||||
|
||||
yield MSDOSFileAttr32(self, "target_attr")
|
||||
|
||||
yield TimestampWin64(self, "creation_time")
|
||||
yield TimestampWin64(self, "modification_time")
|
||||
yield TimestampWin64(self, "last_access_time")
|
||||
yield filesizeHandler(UInt32(self, "target_filesize"))
|
||||
yield UInt32(self, "icon_number")
|
||||
yield Enum(UInt32(self, "show_window"), self.SHOW_WINDOW_STATE)
|
||||
yield textHandler(UInt8(self, "hot_key", "Hot key used for quick access"),text_hot_key)
|
||||
yield Bit(self, "hot_key_shift", "Hot key: is Shift used?")
|
||||
yield Bit(self, "hot_key_ctrl", "Hot key: is Ctrl used?")
|
||||
yield Bit(self, "hot_key_alt", "Hot key: is Alt used?")
|
||||
yield PaddingBits(self, "hot_key_reserved", 21, "Hot key: (reserved)")
|
||||
yield NullBytes(self, "reserved[]", 8)
|
||||
|
||||
if self["has_shell_id"].value:
|
||||
yield ItemIdList(self, "item_idlist", "Item ID List")
|
||||
if self["target_is_file"].value:
|
||||
yield FileLocationInfo(self, "file_location_info", "File Location Info")
|
||||
if self["has_description"].value:
|
||||
yield LnkString(self, "description")
|
||||
if self["has_rel_path"].value:
|
||||
yield LnkString(self, "relative_path", "Relative path to target")
|
||||
if self["has_working_dir"].value:
|
||||
yield LnkString(self, "working_dir", "Working directory (dir to start target in)")
|
||||
if self["has_cmd_line_args"].value:
|
||||
yield LnkString(self, "cmd_line_args", "Command Line Arguments")
|
||||
if self["has_custom_icon"].value:
|
||||
yield LnkString(self, "custom_icon", "Custom Icon Path")
|
||||
|
||||
while not self.eof:
|
||||
yield ExtraInfo(self, "extra_info[]")
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Parsers for the different streams and fragments found in an OLE2 file.
|
||||
|
||||
Documents:
|
||||
- goffice source code
|
||||
|
||||
Author: Robert Xiao, Victor Stinner
|
||||
Creation: 2006-04-23
|
||||
"""
|
||||
|
||||
from hachoir_parser import HachoirParser
|
||||
from hachoir_core.field import FieldSet, RootSeekableFieldSet, RawBytes
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.stream import StringInputStream
|
||||
from hachoir_parser.misc.msoffice_summary import SummaryFieldSet, CompObj
|
||||
from hachoir_parser.misc.word_doc import WordDocumentFieldSet
|
||||
|
||||
PROPERTY_NAME = {
|
||||
u"\5DocumentSummaryInformation": "doc_summary",
|
||||
u"\5SummaryInformation": "summary",
|
||||
u"WordDocument": "word_doc",
|
||||
}
|
||||
|
||||
class OfficeRootEntry(HachoirParser, RootSeekableFieldSet):
|
||||
PARSER_TAGS = {
|
||||
"description": "Microsoft Office document subfragments",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def __init__(self, stream, **args):
|
||||
RootSeekableFieldSet.__init__(self, None, "root", stream, None, stream.askSize(self))
|
||||
HachoirParser.__init__(self, stream, **args)
|
||||
|
||||
def validate(self):
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
for index, property in enumerate(self.ole2.properties):
|
||||
if index == 0:
|
||||
continue
|
||||
try:
|
||||
name = PROPERTY_NAME[property["name"].value]
|
||||
except LookupError:
|
||||
name = property.name+"content"
|
||||
for field in self.parseProperty(index, property, name):
|
||||
yield field
|
||||
|
||||
def parseProperty(self, property_index, property, name_prefix):
|
||||
ole2 = self.ole2
|
||||
if not property["size"].value:
|
||||
return
|
||||
if property["size"].value >= ole2["header/threshold"].value:
|
||||
return
|
||||
name = "%s[]" % name_prefix
|
||||
first = None
|
||||
previous = None
|
||||
size = 0
|
||||
start = property["start"].value
|
||||
chain = ole2.getChain(start, True)
|
||||
blocksize = ole2.ss_size
|
||||
desc_format = "Small blocks %s..%s (%s)"
|
||||
while True:
|
||||
try:
|
||||
block = chain.next()
|
||||
contiguous = False
|
||||
if not first:
|
||||
first = block
|
||||
contiguous = True
|
||||
if previous and block == (previous+1):
|
||||
contiguous = True
|
||||
if contiguous:
|
||||
previous = block
|
||||
size += blocksize
|
||||
continue
|
||||
except StopIteration:
|
||||
block = None
|
||||
self.seekSBlock(first)
|
||||
desc = desc_format % (first, previous, previous-first+1)
|
||||
size = min(size, property["size"].value*8)
|
||||
if name_prefix in ("summary", "doc_summary"):
|
||||
yield SummaryFieldSet(self, name, desc, size=size)
|
||||
elif name_prefix == "word_doc":
|
||||
yield WordDocumentFieldSet(self, name, desc, size=size)
|
||||
elif property_index == 1:
|
||||
yield CompObj(self, "comp_obj", desc, size=size)
|
||||
else:
|
||||
yield RawBytes(self, name, size//8, desc)
|
||||
if block is None:
|
||||
break
|
||||
first = block
|
||||
previous = block
|
||||
size = ole2.sector_size
|
||||
|
||||
def seekSBlock(self, block):
|
||||
self.seekBit(block * self.ole2.ss_size)
|
||||
|
||||
class FragmentGroup:
|
||||
def __init__(self, parser):
|
||||
self.items = []
|
||||
self.parser = parser
|
||||
|
||||
def add(self, item):
|
||||
self.items.append(item)
|
||||
|
||||
def createInputStream(self):
|
||||
# FIXME: Use lazy stream creation
|
||||
data = []
|
||||
for item in self.items:
|
||||
data.append( item["rawdata"].value )
|
||||
data = "".join(data)
|
||||
|
||||
# FIXME: Use smarter code to send arguments
|
||||
args = {"ole2": self.items[0].root}
|
||||
tags = {"class": self.parser, "args": args}
|
||||
tags = tags.iteritems()
|
||||
return StringInputStream(data, "<fragment group>", tags=tags)
|
||||
|
||||
class CustomFragment(FieldSet):
|
||||
def __init__(self, parent, name, size, parser, description=None, group=None):
|
||||
FieldSet.__init__(self, parent, name, description, size=size)
|
||||
if not group:
|
||||
group = FragmentGroup(parser)
|
||||
self.group = group
|
||||
self.group.add(self)
|
||||
|
||||
def createFields(self):
|
||||
yield RawBytes(self, "rawdata", self.size//8)
|
||||
|
||||
def _createInputStream(self, **args):
|
||||
return self.group.createInputStream()
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
"""
|
||||
Microsoft Document summaries structures.
|
||||
|
||||
Documents
|
||||
---------
|
||||
|
||||
- Apache POI (HPSF Internals):
|
||||
http://poi.apache.org/hpsf/internals.html
|
||||
"""
|
||||
from hachoir_parser import HachoirParser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
RootSeekableFieldSet, SeekableFieldSet,
|
||||
Bit, Bits, NullBits,
|
||||
UInt8, UInt16, UInt32, TimestampWin64, TimedeltaWin64, Enum,
|
||||
Bytes, RawBytes, NullBytes, String,
|
||||
Int8, Int32, Float32, Float64, PascalString32)
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal, filesizeHandler
|
||||
from hachoir_core.tools import createDict
|
||||
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
|
||||
from hachoir_parser.common.win32 import GUID, PascalStringWin32, CODEPAGE_CHARSET
|
||||
from hachoir_parser.image.bmp import BmpHeader, parseImageData
|
||||
|
||||
MAX_SECTION_COUNT = 100
|
||||
|
||||
OS_MAC = 1
|
||||
OS_NAME = {
|
||||
0: "Windows 16-bit",
|
||||
1: "Macintosh",
|
||||
2: "Windows 32-bit",
|
||||
}
|
||||
|
||||
class OSConfig:
|
||||
def __init__(self, big_endian):
|
||||
if big_endian:
|
||||
self.charset = "MacRoman"
|
||||
self.utf16 = "UTF-16-BE"
|
||||
else:
|
||||
# FIXME: Don't guess the charset, use ISO-8859-1 or UTF-8
|
||||
#self.charset = "ISO-8859-1"
|
||||
self.charset = None
|
||||
self.utf16 = "UTF-16-LE"
|
||||
|
||||
class PropertyIndex(FieldSet):
|
||||
TAG_CODEPAGE = 1
|
||||
|
||||
COMMON_PROPERTY = {
|
||||
0: "Dictionary",
|
||||
1: "CodePage",
|
||||
0x80000000: "LOCALE_SYSTEM_DEFAULT",
|
||||
0x80000003: "CASE_SENSITIVE",
|
||||
}
|
||||
|
||||
DOCUMENT_PROPERTY = {
|
||||
2: "Category",
|
||||
3: "PresentationFormat",
|
||||
4: "NumBytes",
|
||||
5: "NumLines",
|
||||
6: "NumParagraphs",
|
||||
7: "NumSlides",
|
||||
8: "NumNotes",
|
||||
9: "NumHiddenSlides",
|
||||
10: "NumMMClips",
|
||||
11: "Scale",
|
||||
12: "HeadingPairs",
|
||||
13: "DocumentParts",
|
||||
14: "Manager",
|
||||
15: "Company",
|
||||
16: "LinksDirty",
|
||||
17: "DocSumInfo_17",
|
||||
18: "DocSumInfo_18",
|
||||
19: "DocSumInfo_19",
|
||||
20: "DocSumInfo_20",
|
||||
21: "DocSumInfo_21",
|
||||
22: "DocSumInfo_22",
|
||||
23: "DocSumInfo_23",
|
||||
}
|
||||
DOCUMENT_PROPERTY.update(COMMON_PROPERTY)
|
||||
|
||||
COMPONENT_PROPERTY = {
|
||||
2: "Title",
|
||||
3: "Subject",
|
||||
4: "Author",
|
||||
5: "Keywords",
|
||||
6: "Comments",
|
||||
7: "Template",
|
||||
8: "LastSavedBy",
|
||||
9: "RevisionNumber",
|
||||
10: "TotalEditingTime",
|
||||
11: "LastPrinted",
|
||||
12: "CreateTime",
|
||||
13: "LastSavedTime",
|
||||
14: "NumPages",
|
||||
15: "NumWords",
|
||||
16: "NumCharacters",
|
||||
17: "Thumbnail",
|
||||
18: "AppName",
|
||||
19: "Security",
|
||||
}
|
||||
COMPONENT_PROPERTY.update(COMMON_PROPERTY)
|
||||
|
||||
def createFields(self):
|
||||
if self["../.."].name.startswith("doc_summary"):
|
||||
enum = self.DOCUMENT_PROPERTY
|
||||
else:
|
||||
enum = self.COMPONENT_PROPERTY
|
||||
yield Enum(UInt32(self, "id"), enum)
|
||||
yield UInt32(self, "offset")
|
||||
|
||||
def createDescription(self):
|
||||
return "Property: %s" % self["id"].display
|
||||
|
||||
class Bool(Int8):
|
||||
def createValue(self):
|
||||
value = Int8.createValue(self)
|
||||
return (value == -1)
|
||||
|
||||
class Thumbnail(FieldSet):
|
||||
"""
|
||||
Thumbnail.
|
||||
|
||||
Documents:
|
||||
- See Jakarta POI
|
||||
http://jakarta.apache.org/poi/hpsf/thumbnails.html
|
||||
http://www.penguin-soft.com/penguin/developer/poi/
|
||||
org/apache/poi/hpsf/Thumbnail.html#CF_BITMAP
|
||||
- How To Extract Thumbnail Images
|
||||
http://sparks.discreet.com/knowledgebase/public/
|
||||
solutions/ExtractThumbnailImg.htm
|
||||
"""
|
||||
FORMAT_CLIPBOARD = -1
|
||||
FORMAT_NAME = {
|
||||
-1: "Windows clipboard",
|
||||
-2: "Macintosh clipboard",
|
||||
-3: "GUID that contains format identifier",
|
||||
0: "No data",
|
||||
2: "Bitmap",
|
||||
3: "Windows metafile format",
|
||||
8: "Device Independent Bitmap (DIB)",
|
||||
14: "Enhanced Windows metafile",
|
||||
}
|
||||
|
||||
DIB_BMP = 8
|
||||
DIB_FORMAT = {
|
||||
2: "Bitmap Obsolete (old BMP)",
|
||||
3: "Windows metafile format (WMF)",
|
||||
8: "Device Independent Bitmap (BMP)",
|
||||
14: "Enhanced Windows metafile (EMF)",
|
||||
}
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
yield filesizeHandler(UInt32(self, "size"))
|
||||
yield Enum(Int32(self, "format"), self.FORMAT_NAME)
|
||||
if self["format"].value == self.FORMAT_CLIPBOARD:
|
||||
yield Enum(UInt32(self, "dib_format"), self.DIB_FORMAT)
|
||||
if self["dib_format"].value == self.DIB_BMP:
|
||||
yield BmpHeader(self, "bmp_header")
|
||||
size = (self.size - self.current_size) // 8
|
||||
yield parseImageData(self, "pixels", size, self["bmp_header"])
|
||||
return
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield RawBytes(self, "data", size)
|
||||
|
||||
class PropertyContent(FieldSet):
|
||||
TYPE_LPSTR = 30
|
||||
TYPE_INFO = {
|
||||
0: ("EMPTY", None),
|
||||
1: ("NULL", None),
|
||||
2: ("UInt16", UInt16),
|
||||
3: ("UInt32", UInt32),
|
||||
4: ("Float32", Float32),
|
||||
5: ("Float64", Float64),
|
||||
6: ("CY", None),
|
||||
7: ("DATE", None),
|
||||
8: ("BSTR", None),
|
||||
9: ("DISPATCH", None),
|
||||
10: ("ERROR", None),
|
||||
11: ("BOOL", Bool),
|
||||
12: ("VARIANT", None),
|
||||
13: ("UNKNOWN", None),
|
||||
14: ("DECIMAL", None),
|
||||
16: ("I1", None),
|
||||
17: ("UI1", None),
|
||||
18: ("UI2", None),
|
||||
19: ("UI4", None),
|
||||
20: ("I8", None),
|
||||
21: ("UI8", None),
|
||||
22: ("INT", None),
|
||||
23: ("UINT", None),
|
||||
24: ("VOID", None),
|
||||
25: ("HRESULT", None),
|
||||
26: ("PTR", None),
|
||||
27: ("SAFEARRAY", None),
|
||||
28: ("CARRAY", None),
|
||||
29: ("USERDEFINED", None),
|
||||
30: ("LPSTR", PascalString32),
|
||||
31: ("LPWSTR", PascalString32),
|
||||
64: ("FILETIME", TimestampWin64),
|
||||
65: ("BLOB", None),
|
||||
66: ("STREAM", None),
|
||||
67: ("STORAGE", None),
|
||||
68: ("STREAMED_OBJECT", None),
|
||||
69: ("STORED_OBJECT", None),
|
||||
70: ("BLOB_OBJECT", None),
|
||||
71: ("THUMBNAIL", Thumbnail),
|
||||
72: ("CLSID", None),
|
||||
0x1000: ("Vector", None),
|
||||
}
|
||||
TYPE_NAME = createDict(TYPE_INFO, 0)
|
||||
|
||||
def createFields(self):
|
||||
self.osconfig = self.parent.osconfig
|
||||
if True:
|
||||
yield Enum(Bits(self, "type", 12), self.TYPE_NAME)
|
||||
yield Bit(self, "is_vector")
|
||||
yield NullBits(self, "padding", 32-12-1)
|
||||
else:
|
||||
yield Enum(Bits(self, "type", 32), self.TYPE_NAME)
|
||||
tag = self["type"].value
|
||||
kw = {}
|
||||
try:
|
||||
handler = self.TYPE_INFO[tag][1]
|
||||
if handler == PascalString32:
|
||||
osconfig = self.osconfig
|
||||
if tag == self.TYPE_LPSTR:
|
||||
kw["charset"] = osconfig.charset
|
||||
else:
|
||||
kw["charset"] = osconfig.utf16
|
||||
elif handler == TimestampWin64:
|
||||
if self.description == "TotalEditingTime":
|
||||
handler = TimedeltaWin64
|
||||
except LookupError:
|
||||
handler = None
|
||||
if not handler:
|
||||
raise ParserError("OLE2: Unable to parse property of type %s" \
|
||||
% self["type"].display)
|
||||
if self["is_vector"].value:
|
||||
yield UInt32(self, "count")
|
||||
for index in xrange(self["count"].value):
|
||||
yield handler(self, "item[]", **kw)
|
||||
else:
|
||||
yield handler(self, "value", **kw)
|
||||
self.createValue = lambda: self["value"].value
|
||||
PropertyContent.TYPE_INFO[12] = ("VARIANT", PropertyContent)
|
||||
|
||||
class SummarySection(SeekableFieldSet):
|
||||
def __init__(self, *args):
|
||||
SeekableFieldSet.__init__(self, *args)
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
self.osconfig = self.parent.osconfig
|
||||
yield UInt32(self, "size")
|
||||
yield UInt32(self, "property_count")
|
||||
for index in xrange(self["property_count"].value):
|
||||
yield PropertyIndex(self, "property_index[]")
|
||||
for index in xrange(self["property_count"].value):
|
||||
findex = self["property_index[%u]" % index]
|
||||
self.seekByte(findex["offset"].value)
|
||||
field = PropertyContent(self, "property[]", findex["id"].display)
|
||||
yield field
|
||||
if not self.osconfig.charset \
|
||||
and findex['id'].value == PropertyIndex.TAG_CODEPAGE:
|
||||
codepage = field['value'].value
|
||||
if codepage in CODEPAGE_CHARSET:
|
||||
self.osconfig.charset = CODEPAGE_CHARSET[codepage]
|
||||
else:
|
||||
self.warning("Unknown codepage: %r" % codepage)
|
||||
|
||||
class SummaryIndex(FieldSet):
|
||||
static_size = 20*8
|
||||
def createFields(self):
|
||||
yield String(self, "name", 16)
|
||||
yield UInt32(self, "offset")
|
||||
|
||||
class BaseSummary:
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def __init__(self):
|
||||
if self["endian"].value == "\xFF\xFE":
|
||||
self.endian = BIG_ENDIAN
|
||||
elif self["endian"].value == "\xFE\xFF":
|
||||
self.endian = LITTLE_ENDIAN
|
||||
else:
|
||||
raise ParserError("OLE2: Invalid endian value")
|
||||
self.osconfig = OSConfig(self["os_type"].value == OS_MAC)
|
||||
|
||||
def createFields(self):
|
||||
yield Bytes(self, "endian", 2, "Endian (0xFF 0xFE for Intel)")
|
||||
yield UInt16(self, "format", "Format (0)")
|
||||
yield UInt8(self, "os_version")
|
||||
yield UInt8(self, "os_revision")
|
||||
yield Enum(UInt16(self, "os_type"), OS_NAME)
|
||||
yield GUID(self, "format_id")
|
||||
yield UInt32(self, "section_count")
|
||||
if MAX_SECTION_COUNT < self["section_count"].value:
|
||||
raise ParserError("OLE2: Too much sections (%s)" % self["section_count"].value)
|
||||
|
||||
section_indexes = []
|
||||
for index in xrange(self["section_count"].value):
|
||||
section_index = SummaryIndex(self, "section_index[]")
|
||||
yield section_index
|
||||
section_indexes.append(section_index)
|
||||
|
||||
for section_index in section_indexes:
|
||||
self.seekByte(section_index["offset"].value)
|
||||
yield SummarySection(self, "section[]")
|
||||
|
||||
size = (self.size - self.current_size) // 8
|
||||
if 0 < size:
|
||||
yield NullBytes(self, "end_padding", size)
|
||||
|
||||
class SummaryParser(BaseSummary, HachoirParser, RootSeekableFieldSet):
|
||||
PARSER_TAGS = {
|
||||
"description": "Microsoft Office summary",
|
||||
}
|
||||
|
||||
def __init__(self, stream, **kw):
|
||||
RootSeekableFieldSet.__init__(self, None, "root", stream, None, stream.askSize(self))
|
||||
HachoirParser.__init__(self, stream, **kw)
|
||||
BaseSummary.__init__(self)
|
||||
|
||||
def validate(self):
|
||||
return True
|
||||
|
||||
class SummaryFieldSet(BaseSummary, FieldSet):
|
||||
def __init__(self, parent, name, description=None, size=None):
|
||||
FieldSet.__init__(self, parent, name, description=description, size=size)
|
||||
BaseSummary.__init__(self)
|
||||
|
||||
class CompObj(FieldSet):
|
||||
OS_VERSION = {
|
||||
0x0a03: "Windows 3.1",
|
||||
}
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield UInt16(self, "version", "Version (=1)")
|
||||
yield textHandler(UInt16(self, "endian", "Endian (0xFF 0xFE for Intel)"), hexadecimal)
|
||||
yield UInt8(self, "os_version")
|
||||
yield UInt8(self, "os_revision")
|
||||
yield Enum(UInt16(self, "os_type"), OS_NAME)
|
||||
yield Int32(self, "unused", "(=-1)")
|
||||
yield GUID(self, "clsid")
|
||||
|
||||
# User type
|
||||
yield PascalString32(self, "user_type", strip="\0")
|
||||
|
||||
# Clipboard format
|
||||
if self["os_type"].value == OS_MAC:
|
||||
yield Int32(self, "unused[]", "(=-2)")
|
||||
yield String(self, "clipboard_format", 4)
|
||||
else:
|
||||
yield PascalString32(self, "clipboard_format", strip="\0")
|
||||
if self.current_size == self.size:
|
||||
return
|
||||
|
||||
#-- OLE 2.01 ---
|
||||
|
||||
# Program ID
|
||||
yield PascalString32(self, "prog_id", strip="\0")
|
||||
|
||||
if self["os_type"].value != OS_MAC:
|
||||
# Magic number
|
||||
yield textHandler(UInt32(self, "magic", "Magic number (0x71B239F4)"), hexadecimal)
|
||||
|
||||
# Unicode version
|
||||
yield PascalStringWin32(self, "user_type_unicode", strip="\0")
|
||||
yield PascalStringWin32(self, "clipboard_format_unicode", strip="\0")
|
||||
yield PascalStringWin32(self, "prog_id_unicode", strip="\0")
|
||||
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield NullBytes(self, "end_padding", size)
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
"""
|
||||
Microsoft Office documents parser.
|
||||
|
||||
Informations:
|
||||
* wordole.c of AntiWord program (v0.35)
|
||||
Copyright (C) 1998-2003 A.J. van Os
|
||||
Released under GNU GPL
|
||||
http://www.winfield.demon.nl/
|
||||
* File gsf-infile-msole.c of libgsf library (v1.14.0)
|
||||
Copyright (C) 2002-2004 Jody Goldberg (jody@gnome.org)
|
||||
Released under GNU LGPL 2.1
|
||||
http://freshmeat.net/projects/libgsf/
|
||||
* PDF from AAF Association
|
||||
Copyright (C) 2004 AAF Association
|
||||
Copyright (C) 1991-2003 Microsoft Corporation
|
||||
http://www.aafassociation.org/html/specs/aafcontainerspec-v1.0.1.pdf
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation: 2006-04-23
|
||||
"""
|
||||
|
||||
from hachoir_parser import HachoirParser
|
||||
from hachoir_core.field import (
|
||||
FieldSet, ParserError, SeekableFieldSet, RootSeekableFieldSet,
|
||||
UInt8, UInt16, UInt32, UInt64, TimestampWin64, Enum,
|
||||
Bytes, RawBytes, NullBytes, String)
|
||||
from hachoir_core.text_handler import filesizeHandler
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_parser.common.win32 import GUID
|
||||
from hachoir_parser.misc.msoffice import CustomFragment, OfficeRootEntry, PROPERTY_NAME
|
||||
from hachoir_parser.misc.word_doc import WordDocumentParser
|
||||
from hachoir_parser.misc.msoffice_summary import SummaryParser
|
||||
|
||||
MIN_BIG_BLOCK_LOG2 = 6 # 512 bytes
|
||||
MAX_BIG_BLOCK_LOG2 = 14 # 64 kB
|
||||
|
||||
# Number of items in DIFAT
|
||||
NB_DIFAT = 109
|
||||
|
||||
class SECT(UInt32):
|
||||
UNUSED = 0xFFFFFFFF # -1
|
||||
END_OF_CHAIN = 0xFFFFFFFE # -2
|
||||
BFAT_SECTOR = 0xFFFFFFFD # -3
|
||||
DIFAT_SECTOR = 0xFFFFFFFC # -4
|
||||
SPECIALS = set((END_OF_CHAIN, UNUSED, BFAT_SECTOR, DIFAT_SECTOR))
|
||||
|
||||
special_value_name = {
|
||||
UNUSED: "unused",
|
||||
END_OF_CHAIN: "end of a chain",
|
||||
BFAT_SECTOR: "BFAT sector (in a FAT)",
|
||||
DIFAT_SECTOR: "DIFAT sector (in a FAT)",
|
||||
}
|
||||
|
||||
def __init__(self, parent, name, description=None):
|
||||
UInt32.__init__(self, parent, name, description)
|
||||
|
||||
def createDisplay(self):
|
||||
val = self.value
|
||||
return SECT.special_value_name.get(val, str(val))
|
||||
|
||||
class Property(FieldSet):
|
||||
TYPE_ROOT = 5
|
||||
TYPE_NAME = {
|
||||
1: "storage",
|
||||
2: "stream",
|
||||
3: "ILockBytes",
|
||||
4: "IPropertyStorage",
|
||||
5: "root"
|
||||
}
|
||||
DECORATOR_NAME = {
|
||||
0: "red",
|
||||
1: "black",
|
||||
}
|
||||
static_size = 128 * 8
|
||||
|
||||
def createFields(self):
|
||||
bytes = self.stream.readBytes(self.absolute_address, 4)
|
||||
if bytes == "\0R\0\0":
|
||||
charset = "UTF-16-BE"
|
||||
else:
|
||||
charset = "UTF-16-LE"
|
||||
yield String(self, "name", 64, charset=charset, truncate="\0")
|
||||
yield UInt16(self, "namelen", "Length of the name")
|
||||
yield Enum(UInt8(self, "type", "Property type"), self.TYPE_NAME)
|
||||
yield Enum(UInt8(self, "decorator", "Decorator"), self.DECORATOR_NAME)
|
||||
yield SECT(self, "left")
|
||||
yield SECT(self, "right")
|
||||
yield SECT(self, "child", "Child node (valid for storage and root types)")
|
||||
yield GUID(self, "clsid", "CLSID of this storage (valid for storage and root types)")
|
||||
yield NullBytes(self, "flags", 4, "User flags")
|
||||
yield TimestampWin64(self, "creation", "Creation timestamp(valid for storage and root types)")
|
||||
yield TimestampWin64(self, "lastmod", "Modify timestamp (valid for storage and root types)")
|
||||
yield SECT(self, "start", "Starting SECT of the stream (valid for stream and root types)")
|
||||
if self["/header/bb_shift"].value == 9:
|
||||
yield filesizeHandler(UInt32(self, "size", "Size in bytes (valid for stream and root types)"))
|
||||
yield NullBytes(self, "padding", 4)
|
||||
else:
|
||||
yield filesizeHandler(UInt64(self, "size", "Size in bytes (valid for stream and root types)"))
|
||||
|
||||
def createDescription(self):
|
||||
name = self["name"].display
|
||||
size = self["size"].display
|
||||
return "Property: %s (%s)" % (name, size)
|
||||
|
||||
class DIFat(SeekableFieldSet):
|
||||
def __init__(self, parent, name, db_start, db_count, description=None):
|
||||
SeekableFieldSet.__init__(self, parent, name, description)
|
||||
self.start=db_start
|
||||
self.count=db_count
|
||||
|
||||
def createFields(self):
|
||||
for index in xrange(NB_DIFAT):
|
||||
yield SECT(self, "index[%u]" % index)
|
||||
|
||||
for index in xrange(self.count):
|
||||
# this is relative to real DIFAT start
|
||||
self.seekBit(NB_DIFAT * SECT.static_size+self.parent.sector_size*(self.start+index))
|
||||
for sect_index in xrange(NB_DIFAT*(index+1),NB_DIFAT*(index+2)):
|
||||
yield SECT(self, "index[%u]" % sect_index)
|
||||
|
||||
class Header(FieldSet):
|
||||
static_size = 68 * 8
|
||||
def createFields(self):
|
||||
yield GUID(self, "clsid", "16 bytes GUID used by some apps")
|
||||
yield UInt16(self, "ver_min", "Minor version")
|
||||
yield UInt16(self, "ver_maj", "Minor version")
|
||||
yield Bytes(self, "endian", 2, "Endian (0xFFFE for Intel)")
|
||||
yield UInt16(self, "bb_shift", "Log, base 2, of the big block size")
|
||||
yield UInt16(self, "sb_shift", "Log, base 2, of the small block size")
|
||||
yield NullBytes(self, "reserved[]", 6, "(reserved)")
|
||||
yield UInt32(self, "csectdir", "Number of SECTs in directory chain for 4 KB sectors (version 4)")
|
||||
yield UInt32(self, "bb_count", "Number of Big Block Depot blocks")
|
||||
yield SECT(self, "bb_start", "Root start block")
|
||||
yield NullBytes(self, "transaction", 4, "Signature used for transactions (must be zero)")
|
||||
yield UInt32(self, "threshold", "Maximum size for a mini stream (typically 4096 bytes)")
|
||||
yield SECT(self, "sb_start", "Small Block Depot start block")
|
||||
yield UInt32(self, "sb_count")
|
||||
yield SECT(self, "db_start", "First block of DIFAT")
|
||||
yield UInt32(self, "db_count", "Number of SECTs in DIFAT")
|
||||
|
||||
# Header (ole_id, header, difat) size in bytes
|
||||
HEADER_SIZE = 64 + Header.static_size + NB_DIFAT * SECT.static_size
|
||||
|
||||
class SectFat(FieldSet):
|
||||
def __init__(self, parent, name, start, count, description=None):
|
||||
FieldSet.__init__(self, parent, name, description, size=count*32)
|
||||
self.count = count
|
||||
self.start = start
|
||||
|
||||
def createFields(self):
|
||||
for i in xrange(self.start, self.start + self.count):
|
||||
yield SECT(self, "index[%u]" % i)
|
||||
|
||||
class OLE2_File(HachoirParser, RootSeekableFieldSet):
|
||||
PARSER_TAGS = {
|
||||
"id": "ole2",
|
||||
"category": "misc",
|
||||
"file_ext": (
|
||||
"doc", "dot", # Microsoft Word
|
||||
"ppt", "ppz", "pps", "pot", # Microsoft Powerpoint
|
||||
"xls", "xla", # Microsoft Excel
|
||||
"msi", # Windows installer
|
||||
),
|
||||
"mime": (
|
||||
u"application/msword",
|
||||
u"application/msexcel",
|
||||
u"application/mspowerpoint",
|
||||
),
|
||||
"min_size": 512*8,
|
||||
"description": "Microsoft Office document",
|
||||
"magic": (("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 0),),
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def __init__(self, stream, **args):
|
||||
RootSeekableFieldSet.__init__(self, None, "root", stream, None, stream.askSize(self))
|
||||
HachoirParser.__init__(self, stream, **args)
|
||||
|
||||
def validate(self):
|
||||
if self["ole_id"].value != "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1":
|
||||
return "Invalid magic"
|
||||
if self["header/ver_maj"].value not in (3, 4):
|
||||
return "Unknown major version (%s)" % self["header/ver_maj"].value
|
||||
if self["header/endian"].value not in ("\xFF\xFE", "\xFE\xFF"):
|
||||
return "Unknown endian (%s)" % self["header/endian"].raw_display
|
||||
if not(MIN_BIG_BLOCK_LOG2 <= self["header/bb_shift"].value <= MAX_BIG_BLOCK_LOG2):
|
||||
return "Invalid (log 2 of) big block size (%s)" % self["header/bb_shift"].value
|
||||
if self["header/bb_shift"].value < self["header/sb_shift"].value:
|
||||
return "Small block size (log2=%s) is bigger than big block size (log2=%s)!" \
|
||||
% (self["header/sb_shift"].value, self["header/bb_shift"].value)
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
# Signature
|
||||
yield Bytes(self, "ole_id", 8, "OLE object signature")
|
||||
|
||||
header = Header(self, "header")
|
||||
yield header
|
||||
|
||||
# Configure values
|
||||
self.sector_size = (8 << header["bb_shift"].value)
|
||||
self.fat_count = header["bb_count"].value
|
||||
self.items_per_bbfat = self.sector_size / SECT.static_size
|
||||
self.ss_size = (8 << header["sb_shift"].value)
|
||||
self.items_per_ssfat = self.items_per_bbfat
|
||||
|
||||
# Read DIFAT (one level of indirection)
|
||||
yield DIFat(self, "difat", header["db_start"].value, header["db_count"].value, "Double Indirection FAT")
|
||||
|
||||
# Read FAT (one level of indirection)
|
||||
for field in self.readBFAT():
|
||||
yield field
|
||||
|
||||
# Read SFAT
|
||||
for field in self.readSFAT():
|
||||
yield field
|
||||
|
||||
# Read properties
|
||||
chain = self.getChain(self["header/bb_start"].value)
|
||||
prop_per_sector = self.sector_size // Property.static_size
|
||||
self.properties = []
|
||||
for block in chain:
|
||||
self.seekBlock(block)
|
||||
for index in xrange(prop_per_sector):
|
||||
property = Property(self, "property[]")
|
||||
yield property
|
||||
self.properties.append(property)
|
||||
|
||||
# Parse first property
|
||||
for index, property in enumerate(self.properties):
|
||||
if index == 0:
|
||||
name = "root"
|
||||
else:
|
||||
try:
|
||||
name = PROPERTY_NAME[property["name"].value]
|
||||
except LookupError:
|
||||
name = property.name+"content"
|
||||
for field in self.parseProperty(property, name):
|
||||
yield field
|
||||
|
||||
def parseProperty(self, property, name_prefix):
|
||||
if not property["size"].value:
|
||||
return
|
||||
if property.name != "property[0]" \
|
||||
and (property["size"].value < self["header/threshold"].value):
|
||||
# Field is stored in the ministream, skip it
|
||||
return
|
||||
name = "%s[]" % name_prefix
|
||||
first = None
|
||||
previous = None
|
||||
size = 0
|
||||
fragment_group = None
|
||||
chain = self.getChain(property["start"].value)
|
||||
while True:
|
||||
try:
|
||||
block = chain.next()
|
||||
contiguous = False
|
||||
if not first:
|
||||
first = block
|
||||
contiguous = True
|
||||
if previous and block == (previous+1):
|
||||
contiguous = True
|
||||
if contiguous:
|
||||
previous = block
|
||||
size += self.sector_size
|
||||
continue
|
||||
except StopIteration:
|
||||
block = None
|
||||
if first is None:
|
||||
break
|
||||
self.seekBlock(first)
|
||||
desc = "Big blocks %s..%s (%s)" % (first, previous, previous-first+1)
|
||||
desc += " of %s bytes" % (self.sector_size // 8)
|
||||
if name_prefix in set(("root", "summary", "doc_summary", "word_doc")):
|
||||
if name_prefix == "root":
|
||||
parser = OfficeRootEntry
|
||||
elif name_prefix == "word_doc":
|
||||
parser = WordDocumentParser
|
||||
else:
|
||||
parser = SummaryParser
|
||||
field = CustomFragment(self, name, size, parser, desc, fragment_group)
|
||||
yield field
|
||||
if not fragment_group:
|
||||
fragment_group = field.group
|
||||
else:
|
||||
yield RawBytes(self, name, size//8, desc)
|
||||
if block is None:
|
||||
break
|
||||
first = block
|
||||
previous = block
|
||||
size = self.sector_size
|
||||
|
||||
def getChain(self, start, use_sfat=False):
|
||||
if use_sfat:
|
||||
fat = self.ss_fat
|
||||
items_per_fat = self.items_per_ssfat
|
||||
err_prefix = "SFAT chain"
|
||||
else:
|
||||
fat = self.bb_fat
|
||||
items_per_fat = self.items_per_bbfat
|
||||
err_prefix = "BFAT chain"
|
||||
block = start
|
||||
block_set = set()
|
||||
previous = block
|
||||
while block != SECT.END_OF_CHAIN:
|
||||
if block in SECT.SPECIALS:
|
||||
raise ParserError("%s: Invalid block index (0x%08x), previous=%s" % (err_prefix, block, previous))
|
||||
if block in block_set:
|
||||
raise ParserError("%s: Found a loop (%s=>%s)" % (err_prefix, previous, block))
|
||||
block_set.add(block)
|
||||
yield block
|
||||
previous = block
|
||||
index = block // items_per_fat
|
||||
try:
|
||||
block = fat[index]["index[%u]" % block].value
|
||||
except LookupError:
|
||||
break
|
||||
|
||||
def readBFAT(self):
|
||||
self.bb_fat = []
|
||||
start = 0
|
||||
count = self.items_per_bbfat
|
||||
for index, block in enumerate(self.array("difat/index")):
|
||||
block = block.value
|
||||
if block == SECT.UNUSED:
|
||||
break
|
||||
|
||||
desc = "FAT %u/%u at block %u" % \
|
||||
(1+index, self["header/bb_count"].value, block)
|
||||
|
||||
self.seekBlock(block)
|
||||
field = SectFat(self, "bbfat[]", start, count, desc)
|
||||
yield field
|
||||
self.bb_fat.append(field)
|
||||
|
||||
start += count
|
||||
|
||||
def readSFAT(self):
|
||||
chain = self.getChain(self["header/sb_start"].value)
|
||||
start = 0
|
||||
self.ss_fat = []
|
||||
count = self.items_per_ssfat
|
||||
for index, block in enumerate(chain):
|
||||
self.seekBlock(block)
|
||||
field = SectFat(self, "sfat[]", \
|
||||
start, count, \
|
||||
"SFAT %u/%u at block %u" % \
|
||||
(1+index, self["header/sb_count"].value, block))
|
||||
yield field
|
||||
self.ss_fat.append(field)
|
||||
start += count
|
||||
|
||||
def createContentSize(self):
|
||||
max_block = 0
|
||||
for fat in self.array("bbfat"):
|
||||
for entry in fat:
|
||||
block = entry.value
|
||||
if block not in SECT.SPECIALS:
|
||||
max_block = max(block, max_block)
|
||||
if max_block in SECT.SPECIALS:
|
||||
return None
|
||||
else:
|
||||
return HEADER_SIZE + (max_block+1) * self.sector_size
|
||||
|
||||
def seekBlock(self, block):
|
||||
self.seekBit(HEADER_SIZE + block * self.sector_size)
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
X11 Portable Compiled Font (pcf) parser.
|
||||
|
||||
Documents:
|
||||
- Format for X11 pcf bitmap font files
|
||||
http://fontforge.sourceforge.net/pcf-format.html
|
||||
(file is based on the X11 sources)
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 2007-03-20
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, Enum,
|
||||
UInt8, UInt32, Bytes, RawBytes, NullBytes,
|
||||
Bit, Bits, PaddingBits, CString)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal, filesizeHandler
|
||||
from hachoir_core.tools import paddingSize
|
||||
|
||||
class TOC(FieldSet):
|
||||
TYPE_NAME = {
|
||||
0x00000001: "Properties",
|
||||
0x00000002: "Accelerators",
|
||||
0x00000004: "Metrics",
|
||||
0x00000008: "Bitmaps",
|
||||
0x00000010: "Ink metrics",
|
||||
0x00000020: "BDF encodings",
|
||||
0x00000040: "SWidths",
|
||||
0x00000080: "Glyph names",
|
||||
0x00000100: "BDF accelerators",
|
||||
}
|
||||
|
||||
FORMAT_NAME = {
|
||||
0x00000000: "Default",
|
||||
0x00000200: "Ink bounds",
|
||||
0x00000100: "Accelerator W ink bounds",
|
||||
# 0x00000200: "Compressed metrics",
|
||||
}
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(UInt32(self, "type"), self.TYPE_NAME)
|
||||
yield UInt32(self, "format")
|
||||
yield filesizeHandler(UInt32(self, "size"))
|
||||
yield UInt32(self, "offset")
|
||||
|
||||
def createDescription(self):
|
||||
return "%s at %s (%s)" % (
|
||||
self["type"].display, self["offset"].value, self["size"].display)
|
||||
|
||||
class PropertiesFormat(FieldSet):
|
||||
static_size = 32
|
||||
endian = LITTLE_ENDIAN
|
||||
def createFields(self):
|
||||
yield Bits(self, "reserved[]", 2)
|
||||
yield Bit(self, "byte_big_endian")
|
||||
yield Bit(self, "bit_big_endian")
|
||||
yield Bits(self, "scan_unit", 2)
|
||||
yield textHandler(PaddingBits(self, "reserved[]", 26), hexadecimal)
|
||||
|
||||
class Property(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "name_offset")
|
||||
yield UInt8(self, "is_string")
|
||||
yield UInt32(self, "value_offset")
|
||||
|
||||
def createDescription(self):
|
||||
# FIXME: Use link or any better way to read name value
|
||||
name = self["../name[%s]" % (self.index-2)].value
|
||||
return "Property %s" % name
|
||||
|
||||
class GlyphNames(FieldSet):
|
||||
def __init__(self, parent, name, toc, description, size=None):
|
||||
FieldSet.__init__(self, parent, name, description, size=size)
|
||||
self.toc = toc
|
||||
if self["format/byte_big_endian"].value:
|
||||
self.endian = BIG_ENDIAN
|
||||
else:
|
||||
self.endian = LITTLE_ENDIAN
|
||||
|
||||
def createFields(self):
|
||||
yield PropertiesFormat(self, "format")
|
||||
yield UInt32(self, "count")
|
||||
offsets = []
|
||||
for index in xrange(self["count"].value):
|
||||
offset = UInt32(self, "offset[]")
|
||||
yield offset
|
||||
offsets.append(offset.value)
|
||||
yield UInt32(self, "total_str_length")
|
||||
offsets.sort()
|
||||
offset0 = self.current_size // 8
|
||||
for offset in offsets:
|
||||
padding = self.seekByte(offset0+offset)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "name[]")
|
||||
padding = (self.size - self.current_size) // 8
|
||||
if padding:
|
||||
yield NullBytes(self, "end_padding", padding)
|
||||
|
||||
class Properties(GlyphNames):
|
||||
def createFields(self):
|
||||
yield PropertiesFormat(self, "format")
|
||||
yield UInt32(self, "nb_prop")
|
||||
properties = []
|
||||
for index in xrange(self["nb_prop"].value):
|
||||
property = Property(self, "property[]")
|
||||
yield property
|
||||
properties.append(property)
|
||||
padding = paddingSize(self.current_size//8, 4)
|
||||
if padding:
|
||||
yield NullBytes(self, "padding", padding)
|
||||
yield UInt32(self, "total_str_length")
|
||||
properties.sort(key=lambda entry: entry["name_offset"].value)
|
||||
offset0 = self.current_size // 8
|
||||
for property in properties:
|
||||
padding = self.seekByte(offset0+property["name_offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
yield CString(self, "name[]", "Name of %s" % property.name)
|
||||
if property["is_string"].value:
|
||||
yield CString(self, "value[]", "Value of %s" % property.name)
|
||||
padding = (self.size - self.current_size) // 8
|
||||
if padding:
|
||||
yield NullBytes(self, "end_padding", padding)
|
||||
|
||||
class PcfFile(Parser):
|
||||
MAGIC = "\1fcp"
|
||||
PARSER_TAGS = {
|
||||
"id": "pcf",
|
||||
"category": "misc",
|
||||
"file_ext": ("pcf",),
|
||||
"magic": ((MAGIC, 0),),
|
||||
"min_size": 32, # FIXME
|
||||
"description": "X11 Portable Compiled Font (pcf)",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self["signature"].value != self.MAGIC:
|
||||
return "Invalid signature"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield Bytes(self, "signature", 4, r'File signature ("\1pcf")')
|
||||
yield UInt32(self, "nb_toc")
|
||||
entries = []
|
||||
for index in xrange(self["nb_toc"].value):
|
||||
entry = TOC(self, "toc[]")
|
||||
yield entry
|
||||
entries.append(entry)
|
||||
entries.sort(key=lambda entry: entry["offset"].value)
|
||||
for entry in entries:
|
||||
size = entry["size"].value
|
||||
padding = self.seekByte(entry["offset"].value)
|
||||
if padding:
|
||||
yield padding
|
||||
maxsize = (self.size-self.current_size)//8
|
||||
if maxsize < size:
|
||||
self.warning("Truncate content of %s to %s bytes (was %s)" % (entry.path, maxsize, size))
|
||||
size = maxsize
|
||||
if not size:
|
||||
continue
|
||||
if entry["type"].value == 1:
|
||||
yield Properties(self, "properties", entry, "Properties", size=size*8)
|
||||
elif entry["type"].value == 128:
|
||||
yield GlyphNames(self, "glyph_names", entry, "Glyph names", size=size*8)
|
||||
else:
|
||||
yield RawBytes(self, "data[]", size, "Content of %s" % entry.path)
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
"""
|
||||
Adobe Portable Document Format (PDF) parser.
|
||||
|
||||
Author: Christophe Gisquet <christophe.gisquet@free.fr>
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (
|
||||
Field, FieldSet,
|
||||
ParserError,
|
||||
GenericVector,
|
||||
UInt8, UInt16, UInt32,
|
||||
String,
|
||||
RawBytes)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
|
||||
MAGIC = "%PDF-"
|
||||
ENDMAGIC = "%%EOF"
|
||||
|
||||
def getLineEnd(s, pos=None):
|
||||
if pos == None:
|
||||
pos = (s.absolute_address+s.current_size)//8
|
||||
end = s.stream.searchBytesLength("\x0D", False, 8*pos)
|
||||
other_end = s.stream.searchBytesLength("\x0A", False, 8*pos)
|
||||
if end == None or (other_end != None and other_end < end):
|
||||
return other_end
|
||||
return end
|
||||
|
||||
# TODO: rewrite to account for all possible terminations: ' ', '/', '\0XD'
|
||||
# But this probably requires changing *ALL* of the places they are used,
|
||||
# as ' ' is swallowed but not the others
|
||||
def getElementEnd(s, limit=' ', offset=0):
|
||||
addr = s.absolute_address+s.current_size
|
||||
addr += 8*offset
|
||||
pos = s.stream.searchBytesLength(limit, True, addr)
|
||||
if pos == None:
|
||||
#s.info("Can't find '%s' starting at %u" % (limit, addr))
|
||||
return None
|
||||
return pos
|
||||
|
||||
class PDFNumber(Field):
|
||||
LIMITS = ['[', '/', '\x0D', ']']
|
||||
"""
|
||||
sprintf("%i") or sprinf("%.?f")
|
||||
"""
|
||||
def __init__(self, parent, name, desc=None):
|
||||
Field.__init__(self, parent, name, description=desc)
|
||||
# Get size
|
||||
size = getElementEnd(parent)
|
||||
for limit in self.LIMITS:
|
||||
other_size = getElementEnd(parent, limit)
|
||||
if other_size != None:
|
||||
other_size -= 1
|
||||
if size == None or other_size < size:
|
||||
size = other_size
|
||||
|
||||
self._size = 8*size
|
||||
|
||||
# Get value
|
||||
val = parent.stream.readBytes(self.absolute_address, size)
|
||||
self.info("Number: size=%u value='%s'" % (size, val))
|
||||
if val.find('.') != -1:
|
||||
self.createValue = lambda: float(val)
|
||||
else:
|
||||
self.createValue = lambda: int(val)
|
||||
|
||||
class PDFString(Field):
|
||||
"""
|
||||
A string of the shape:
|
||||
( This string \
|
||||
uses 3 lines \
|
||||
with the CR(LF) inhibited )
|
||||
"""
|
||||
def __init__(self, parent, name, desc=None):
|
||||
Field.__init__(self, parent, name, description=desc)
|
||||
val = ""
|
||||
count = 1
|
||||
off = 1
|
||||
while not parent.eof:
|
||||
char = parent.stream.readBytes(self.absolute_address+8*off, 1)
|
||||
# Non-ASCII
|
||||
if not char.isalpha() or char == '\\':
|
||||
off += 1
|
||||
continue
|
||||
if char == '(':
|
||||
count += 1
|
||||
if char == ')':
|
||||
count -= 1
|
||||
# Parenthesis block = 0 => end of string
|
||||
if count == 0:
|
||||
off += 1
|
||||
break
|
||||
|
||||
# Add it to the string
|
||||
val += char
|
||||
|
||||
self._size = 8*off
|
||||
self.createValue = lambda: val
|
||||
|
||||
class PDFName(Field):
|
||||
LIMITS = ['[', '/', '<', ']']
|
||||
"""
|
||||
String starting with '/', where characters may be written using their
|
||||
ASCII code (exemple: '#20' would be ' '
|
||||
' ', ']' and '\0' are supposed not to be part of the name
|
||||
"""
|
||||
def __init__(self, parent, name, desc=None):
|
||||
Field.__init__(self, parent, name, description=desc)
|
||||
if parent.stream.readBytes(self.absolute_address, 1) != '/':
|
||||
raise ParserError("Unknown PDFName '%s'" %
|
||||
parent.stream.readBytes(self.absolute_address, 10))
|
||||
size = getElementEnd(parent, offset=1)
|
||||
#other_size = getElementEnd(parent, '[')-1
|
||||
#if size == None or (other_size != None and other_size < size):
|
||||
# size = other_size
|
||||
for limit in self.LIMITS:
|
||||
other_size = getElementEnd(parent, limit, 1)
|
||||
if other_size != None:
|
||||
other_size -= 1
|
||||
if size == None or other_size < size:
|
||||
#self.info("New size: %u" % other_size)
|
||||
size = other_size
|
||||
|
||||
self._size = 8*(size+1)
|
||||
# Value should be without the initial '/' and final ' '
|
||||
self.createValue = lambda: parent.stream.readBytes(self.absolute_address+8, size).strip(' ')
|
||||
|
||||
class PDFID(Field):
|
||||
"""
|
||||
Not described as an object, but let's do as it was.
|
||||
This ID has the shape <hexadecimal ASCII string>
|
||||
"""
|
||||
def __init__(self, parent, name, desc=None):
|
||||
Field.__init__(self, parent, name, description=desc)
|
||||
self._size = 8*getElementEnd(parent, '>')
|
||||
self.createValue = lambda: parent.stream.readBytes(self.absolute_address+8, (self._size//8)-1)
|
||||
|
||||
class NotABool(Exception): pass
|
||||
class PDFBool(Field):
|
||||
"""
|
||||
"true" or "false" string standing for the boolean value
|
||||
"""
|
||||
def __init__(self, parent, name, desc=None):
|
||||
Field.__init__(self, parent, name, description=desc)
|
||||
if parent.stream.readBytes(self.absolute_address, 4) == "true":
|
||||
self._size = 4
|
||||
self.createValue = lambda: True
|
||||
elif parent.stream.readBytes(self.absolute_address, 5) == "false":
|
||||
self._size = 5
|
||||
self.createValue = lambda: False
|
||||
raise NotABool
|
||||
|
||||
class LineEnd(FieldSet):
|
||||
"""
|
||||
Made of 0x0A, 0x0D (we may include several line ends)
|
||||
"""
|
||||
def createFields(self):
|
||||
while not self.eof:
|
||||
addr = self.absolute_address+self.current_size
|
||||
char = self.stream.readBytes(addr, 1)
|
||||
if char == '\x0A':
|
||||
yield UInt8(self, "lf", "Line feed")
|
||||
elif char == '\x0D':
|
||||
yield UInt8(self, "cr", "Line feed")
|
||||
else:
|
||||
self.info("Line ends at %u/%u, len %u" %
|
||||
(addr, self.stream._size, self.current_size))
|
||||
break
|
||||
|
||||
class PDFDictionaryPair(FieldSet):
|
||||
def createFields(self):
|
||||
yield PDFName(self, "name", getElementEnd(self))
|
||||
for field in parsePDFType(self):
|
||||
yield field
|
||||
|
||||
class PDFDictionary(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "dict_start", 2)
|
||||
while not self.eof:
|
||||
addr = self.absolute_address+self.current_size
|
||||
if self.stream.readBytes(addr, 2) != '>>':
|
||||
for field in parsePDFType(self):
|
||||
yield field
|
||||
else:
|
||||
break
|
||||
yield String(self, "dict_end", 2)
|
||||
|
||||
class PDFArray(FieldSet):
|
||||
"""
|
||||
Array of possibly non-homogeneous elements, starting with '[' and ending
|
||||
with ']'
|
||||
"""
|
||||
def createFields(self):
|
||||
yield String(self, "array_start", 1)
|
||||
while self.stream.readBytes(self.absolute_address+self.current_size, 1) != ']':
|
||||
for field in parsePDFType(self):
|
||||
yield field
|
||||
yield String(self, "array_end", 1)
|
||||
|
||||
def parsePDFType(s):
|
||||
addr = s.absolute_address+s.current_size
|
||||
char = s.stream.readBytes(addr, 1)
|
||||
if char == '/':
|
||||
yield PDFName(s, "type[]", getElementEnd(s))
|
||||
elif char == '<':
|
||||
if s.stream.readBytes(addr+8, 1) == '<':
|
||||
yield PDFDictionary(s, "dict[]")
|
||||
else:
|
||||
yield PDFID(s, "id[]")
|
||||
elif char == '(':
|
||||
yield PDFString(s, "string[]")
|
||||
elif char == '[':
|
||||
yield PDFArray(s, "array[]")
|
||||
else:
|
||||
# First parse size
|
||||
size = getElementEnd(s)
|
||||
for limit in ['/', '>', '<']:
|
||||
other_size = getElementEnd(s, limit)
|
||||
if other_size != None:
|
||||
other_size -= 1
|
||||
if size == None or (other_size>0 and other_size < size):
|
||||
size = other_size
|
||||
|
||||
# Get element
|
||||
name = s.stream.readBytes(addr, size)
|
||||
char = s.stream.readBytes(addr+8*size+8, 1)
|
||||
if name.count(' ') > 1 and char == '<':
|
||||
# Probably a catalog
|
||||
yield Catalog(s, "catalog[]")
|
||||
elif name[0] in ('.','-','+', '0', '1', '2', '3', \
|
||||
'4', '5', '6', '7', '8', '9'):
|
||||
s.info("Not a catalog: %u spaces and end='%s'" % (name.count(' '), char))
|
||||
yield PDFNumber(s, "integer[]")
|
||||
else:
|
||||
s.info("Trying to parse '%s': %u bytes" % \
|
||||
(s.stream.readBytes(s.absolute_address+s.current_size, 4), size))
|
||||
yield String(s, "unknown[]", size)
|
||||
|
||||
class Header(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "marker", 5, MAGIC)
|
||||
length = getLineEnd(self, 4)
|
||||
if length != None:
|
||||
#self.info("Found at position %08X" % len)
|
||||
yield String(self, "version", length-1)
|
||||
yield LineEnd(self, "line_end")
|
||||
else:
|
||||
self.warning("Can't determine version!")
|
||||
def createDescription(self):
|
||||
return "PDF version %s" % self["version"].display
|
||||
|
||||
class Body(FieldSet):
|
||||
def __init__(self, parent, name, desc=None):
|
||||
FieldSet.__init__(self, parent, name, desc)
|
||||
pos = self.stream.searchBytesLength(CrossReferenceTable.MAGIC, False)
|
||||
if pos == None:
|
||||
raise ParserError("Can't find xref starting at %u" %
|
||||
(self.absolute_address//8))
|
||||
self._size = 8*pos-self.absolute_address
|
||||
|
||||
def createFields(self):
|
||||
while self.stream.readBytes(self.absolute_address+self.current_size, 1) == '%':
|
||||
size = getLineEnd(self, 4)
|
||||
if size == 2:
|
||||
yield textHandler(UInt16(self, "crc32"), hexadecimal)
|
||||
elif size == 4:
|
||||
yield textHandler(UInt32(self, "crc32"), hexadecimal)
|
||||
elif self.stream.readBytes(self.absolute_address+self.current_size, size).isalpha():
|
||||
yield String(self, "comment[]", size)
|
||||
else:
|
||||
RawBytes(self, "unknown_data[]", size)
|
||||
yield LineEnd(self, "line_end[]")
|
||||
|
||||
#abs_offset = self.current_size//8
|
||||
# TODO: yield objects that read offsets and deduce size from
|
||||
# "/cross_ref_table/sub_section[]/entries/item[]"
|
||||
offsets = []
|
||||
for subsection in self.array("/cross_ref_table/sub_section"):
|
||||
for obj in subsection.array("entries/item"):
|
||||
if "byte_offset" in obj:
|
||||
# Could be inserted already sorted
|
||||
offsets.append(obj["byte_offset"].value)
|
||||
|
||||
offsets.append(self["/cross_ref_table"].absolute_address//8)
|
||||
offsets.sort()
|
||||
for index in xrange(len(offsets)-1):
|
||||
yield Catalog(self, "object[]", size=offsets[index+1]-offsets[index])
|
||||
|
||||
class Entry(FieldSet):
|
||||
static_size = 20*8
|
||||
def createFields(self):
|
||||
typ = self.stream.readBytes(self.absolute_address+17*8, 1)
|
||||
if typ == 'n':
|
||||
yield PDFNumber(self, "byte_offset")
|
||||
elif typ == 'f':
|
||||
yield PDFNumber(self, "next_free_object_number")
|
||||
else:
|
||||
yield PDFNumber(self, "unknown_string")
|
||||
yield PDFNumber(self, "generation_number")
|
||||
yield UInt8(self, "type")
|
||||
yield LineEnd(self, "line_end")
|
||||
def createDescription(self):
|
||||
if self["type"].value == 'n':
|
||||
return "In-use entry at offset %u" % int(self["byte_offset"].value)
|
||||
elif self["type"].value == 'f':
|
||||
return "Free entry before in-use object %u" % \
|
||||
int(self["next_free_object_number"].value)
|
||||
else:
|
||||
return "unknown %s" % self["unknown_string"].value
|
||||
|
||||
class SubSection(FieldSet):
|
||||
def __init__(self, parent, name, desc=None):
|
||||
FieldSet.__init__(self, parent, name, desc)
|
||||
self.info("Got entry count: '%s'" % self["entry_count"].value)
|
||||
self._size = self.current_size + 8*20*int(self["entry_count"].value) \
|
||||
+ self["line_end"].size
|
||||
|
||||
def createFields(self):
|
||||
yield PDFNumber(self, "start_number",
|
||||
"Object number of first entry in subsection")
|
||||
self.info("start_number = %i" % self["start_number"].value)
|
||||
|
||||
yield PDFNumber(self, "entry_count", "Number of entries in subsection")
|
||||
self.info("entry_count = %i" % self["entry_count"].value)
|
||||
yield LineEnd(self, "line_end")
|
||||
yield GenericVector(self, "entries", int(self["entry_count"].value),
|
||||
Entry)
|
||||
#yield LineEnd(self, "line_end[]")
|
||||
def createDescription(self):
|
||||
return "Subsection with %s elements, starting at %s" % \
|
||||
(self["entry_count"].value, self["start_number"])
|
||||
|
||||
class CrossReferenceTable(FieldSet):
|
||||
MAGIC = "xref"
|
||||
|
||||
def __init__(self, parent, name, desc=None):
|
||||
FieldSet.__init__(self, parent, name, description=desc)
|
||||
pos = self.stream.searchBytesLength(Trailer.MAGIC, False)
|
||||
if pos == None:
|
||||
raise ParserError("Can't find '%s' starting at %u" \
|
||||
(Trailer.MAGIC, self.absolute_address//8))
|
||||
self._size = 8*pos-self.absolute_address
|
||||
|
||||
def createFields(self):
|
||||
yield RawBytes(self, "marker", len(self.MAGIC))
|
||||
yield LineEnd(self, "line_end[]")
|
||||
while not self.eof:
|
||||
yield SubSection(self, "sub_section[]")
|
||||
|
||||
class Catalog(FieldSet):
|
||||
END_NAME = ['<', '/', '[']
|
||||
def __init__(self, parent, name, size=None, desc=None):
|
||||
FieldSet.__init__(self, parent, name, description=desc)
|
||||
if size != None:
|
||||
self._size = 8*size
|
||||
# object catalogs are ended with "obj"
|
||||
elif self["object"].value == "obj":
|
||||
size = self.stream.searchBytesLength("endobj", False)
|
||||
if size != None:
|
||||
self._size = 8*(size+2)
|
||||
def createFields(self):
|
||||
yield PDFNumber(self, "index")
|
||||
yield PDFNumber(self, "unknown[]")
|
||||
length = getElementEnd(self)
|
||||
for limit in self.END_NAME:
|
||||
new_length = getElementEnd(self, limit)-len(limit)
|
||||
if length == None or (new_length != None and new_length < length):
|
||||
length = new_length
|
||||
yield String(self, "object", length, strip=' ')
|
||||
if self.stream.readBytes(self.absolute_address+self.current_size, 2) == '<<':
|
||||
yield PDFDictionary(self, "key_list")
|
||||
# End of catalog: this one has "endobj"
|
||||
if self["object"].value == "obj":
|
||||
yield LineEnd(self, "line_end[]")
|
||||
yield String(self, "end_object", len("endobj"))
|
||||
yield LineEnd(self, "line_end[]")
|
||||
|
||||
class Trailer(FieldSet):
|
||||
MAGIC = "trailer"
|
||||
def createFields(self):
|
||||
yield RawBytes(self, "marker", len(self.MAGIC))
|
||||
yield LineEnd(self, "line_end[]")
|
||||
yield String(self, "start_attribute_marker", 2)
|
||||
addr = self.absolute_address + self.current_size
|
||||
while self.stream.readBytes(addr, 2) != '>>':
|
||||
t = PDFName(self, "type[]")
|
||||
yield t
|
||||
name = t.value
|
||||
self.info("Parsing PDFName '%s'" % name)
|
||||
if name == "Size":
|
||||
yield PDFNumber(self, "size", "Entries in the file cross-reference section")
|
||||
elif name == "Prev":
|
||||
yield PDFNumber(self, "offset")
|
||||
elif name == "Root":
|
||||
yield Catalog(self, "object_catalog")
|
||||
elif name == "Info":
|
||||
yield Catalog(self, "info")
|
||||
elif name == "ID":
|
||||
yield PDFArray(self, "id")
|
||||
elif name == "Encrypt":
|
||||
yield PDFDictionary(self, "decrypt")
|
||||
else:
|
||||
raise ParserError("Don't know trailer type '%s'" % name)
|
||||
addr = self.absolute_address + self.current_size
|
||||
yield String(self, "end_attribute_marker", 2)
|
||||
yield LineEnd(self, "line_end[]")
|
||||
yield String(self, "start_xref", 9)
|
||||
yield LineEnd(self, "line_end[]")
|
||||
yield PDFNumber(self, "cross_ref_table_start_address")
|
||||
yield LineEnd(self, "line_end[]")
|
||||
yield String(self, "end_marker", len(ENDMAGIC))
|
||||
yield LineEnd(self, "line_end[]")
|
||||
|
||||
class PDFDocument(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "pdf",
|
||||
"category": "misc",
|
||||
"file_ext": ("pdf",),
|
||||
"mime": (u"application/pdf",),
|
||||
"min_size": (5+4)*8,
|
||||
"magic": ((MAGIC, 5),),
|
||||
"description": "Portable Document Format (PDF) document"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, len(MAGIC)) != MAGIC:
|
||||
return "Invalid magic string"
|
||||
return True
|
||||
|
||||
# Size is not always determined by position of "%%EOF":
|
||||
# - updated documents have several of those
|
||||
# - PDF files should be parsed from *end*
|
||||
# => TODO: find when a document has been updated
|
||||
|
||||
def createFields(self):
|
||||
yield Header(self, "header")
|
||||
yield Body(self, "body")
|
||||
yield CrossReferenceTable(self, "cross_ref_table")
|
||||
yield Trailer(self, "trailer")
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
EFI Platform Initialization Firmware Volume parser.
|
||||
|
||||
Author: Alexandre Boeglin
|
||||
Creation date: 08 jul 2007
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
UInt8, UInt16, UInt24, UInt32, UInt64, Enum,
|
||||
CString, String, PaddingBytes, RawBytes, NullBytes)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.tools import paddingSize, humanFilesize
|
||||
from hachoir_parser.common.win32 import GUID
|
||||
|
||||
EFI_SECTION_COMPRESSION = 0x1
|
||||
EFI_SECTION_GUID_DEFINED = 0x2
|
||||
EFI_SECTION_PE32 = 0x10
|
||||
EFI_SECTION_PIC = 0x11
|
||||
EFI_SECTION_TE = 0x12
|
||||
EFI_SECTION_DXE_DEPEX = 0x13
|
||||
EFI_SECTION_VERSION = 0x14
|
||||
EFI_SECTION_USER_INTERFACE = 0x15
|
||||
EFI_SECTION_COMPATIBILITY16 = 0x16
|
||||
EFI_SECTION_FIRMWARE_VOLUME_IMAGE = 0x17
|
||||
EFI_SECTION_FREEFORM_SUBTYPE_GUID = 0x18
|
||||
EFI_SECTION_RAW = 0x19
|
||||
EFI_SECTION_PEI_DEPEX = 0x1b
|
||||
|
||||
EFI_SECTION_TYPE = {
|
||||
EFI_SECTION_COMPRESSION: "Encapsulation section where other sections" \
|
||||
+ " are compressed",
|
||||
EFI_SECTION_GUID_DEFINED: "Encapsulation section where other sections" \
|
||||
+ " have format defined by a GUID",
|
||||
EFI_SECTION_PE32: "PE32+ Executable image",
|
||||
EFI_SECTION_PIC: "Position-Independent Code",
|
||||
EFI_SECTION_TE: "Terse Executable image",
|
||||
EFI_SECTION_DXE_DEPEX: "DXE Dependency Expression",
|
||||
EFI_SECTION_VERSION: "Version, Text and Numeric",
|
||||
EFI_SECTION_USER_INTERFACE: "User-Friendly name of the driver",
|
||||
EFI_SECTION_COMPATIBILITY16: "DOS-style 16-bit EXE",
|
||||
EFI_SECTION_FIRMWARE_VOLUME_IMAGE: "PI Firmware Volume image",
|
||||
EFI_SECTION_FREEFORM_SUBTYPE_GUID: "Raw data with GUID in header to" \
|
||||
+ " define format",
|
||||
EFI_SECTION_RAW: "Raw data",
|
||||
EFI_SECTION_PEI_DEPEX: "PEI Dependency Expression",
|
||||
}
|
||||
|
||||
EFI_FV_FILETYPE_RAW = 0x1
|
||||
EFI_FV_FILETYPE_FREEFORM = 0x2
|
||||
EFI_FV_FILETYPE_SECURITY_CORE = 0x3
|
||||
EFI_FV_FILETYPE_PEI_CORE = 0x4
|
||||
EFI_FV_FILETYPE_DXE_CORE = 0x5
|
||||
EFI_FV_FILETYPE_PEIM = 0x6
|
||||
EFI_FV_FILETYPE_DRIVER = 0x7
|
||||
EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER = 0x8
|
||||
EFI_FV_FILETYPE_APPLICATION = 0x9
|
||||
EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE = 0xb
|
||||
EFI_FV_FILETYPE_FFS_PAD = 0xf0
|
||||
|
||||
EFI_FV_FILETYPE = {
|
||||
EFI_FV_FILETYPE_RAW: "Binary data",
|
||||
EFI_FV_FILETYPE_FREEFORM: "Sectioned data",
|
||||
EFI_FV_FILETYPE_SECURITY_CORE: "Platform core code used during the SEC" \
|
||||
+ " phase",
|
||||
EFI_FV_FILETYPE_PEI_CORE: "PEI Foundation",
|
||||
EFI_FV_FILETYPE_DXE_CORE: "DXE Foundation",
|
||||
EFI_FV_FILETYPE_PEIM: "PEI module (PEIM)",
|
||||
EFI_FV_FILETYPE_DRIVER: "DXE driver",
|
||||
EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER: "Combined PEIM/DXE driver",
|
||||
EFI_FV_FILETYPE_APPLICATION: "Application",
|
||||
EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE: "Firmware volume image",
|
||||
EFI_FV_FILETYPE_FFS_PAD: "Pad File For FFS",
|
||||
}
|
||||
for x in xrange(0xc0, 0xe0):
|
||||
EFI_FV_FILETYPE[x] = "OEM File"
|
||||
for x in xrange(0xe0, 0xf0):
|
||||
EFI_FV_FILETYPE[x] = "Debug/Test File"
|
||||
for x in xrange(0xf1, 0x100):
|
||||
EFI_FV_FILETYPE[x] = "Firmware File System Specific File"
|
||||
|
||||
|
||||
class BlockMap(FieldSet):
|
||||
static_size = 8*8
|
||||
def createFields(self):
|
||||
yield UInt32(self, "num_blocks")
|
||||
yield UInt32(self, "len")
|
||||
|
||||
def createDescription(self):
|
||||
return "%d blocks of %s" % (
|
||||
self["num_blocks"].value, humanFilesize(self["len"].value))
|
||||
|
||||
|
||||
class FileSection(FieldSet):
|
||||
COMPRESSION_TYPE = {
|
||||
0: 'Not Compressed',
|
||||
1: 'Standard Compression',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
self._size = self["size"].value * 8
|
||||
section_type = self["type"].value
|
||||
if section_type in (EFI_SECTION_DXE_DEPEX, EFI_SECTION_PEI_DEPEX):
|
||||
# These sections can sometimes be longer than what their size
|
||||
# claims! It's so nice to have so detailled specs and not follow
|
||||
# them ...
|
||||
if self.stream.readBytes(self.absolute_address +
|
||||
self._size, 1) == '\0':
|
||||
self._size = self._size + 16
|
||||
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield UInt24(self, "size")
|
||||
yield Enum(UInt8(self, "type"), EFI_SECTION_TYPE)
|
||||
section_type = self["type"].value
|
||||
|
||||
if section_type == EFI_SECTION_COMPRESSION:
|
||||
yield UInt32(self, "uncomp_len")
|
||||
yield Enum(UInt8(self, "comp_type"), self.COMPRESSION_TYPE)
|
||||
elif section_type == EFI_SECTION_FREEFORM_SUBTYPE_GUID:
|
||||
yield GUID(self, "sub_type_guid")
|
||||
elif section_type == EFI_SECTION_GUID_DEFINED:
|
||||
yield GUID(self, "section_definition_guid")
|
||||
yield UInt16(self, "data_offset")
|
||||
yield UInt16(self, "attributes")
|
||||
elif section_type == EFI_SECTION_USER_INTERFACE:
|
||||
yield CString(self, "file_name", charset="UTF-16-LE")
|
||||
elif section_type == EFI_SECTION_VERSION:
|
||||
yield UInt16(self, "build_number")
|
||||
yield CString(self, "version", charset="UTF-16-LE")
|
||||
|
||||
# Content
|
||||
content_size = (self.size - self.current_size) // 8
|
||||
if content_size == 0:
|
||||
return
|
||||
|
||||
if section_type == EFI_SECTION_COMPRESSION:
|
||||
compression_type = self["comp_type"].value
|
||||
if compression_type == 1:
|
||||
while not self.eof:
|
||||
yield RawBytes(self, "compressed_content", content_size)
|
||||
else:
|
||||
while not self.eof:
|
||||
yield FileSection(self, "section[]")
|
||||
elif section_type == EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
|
||||
yield FirmwareVolume(self, "firmware_volume")
|
||||
else:
|
||||
yield RawBytes(self, "content", content_size,
|
||||
EFI_SECTION_TYPE.get(self["type"].value,
|
||||
"Unknown Section Type"))
|
||||
|
||||
def createDescription(self):
|
||||
return EFI_SECTION_TYPE.get(self["type"].value,
|
||||
"Unknown Section Type")
|
||||
|
||||
|
||||
class File(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield GUID(self, "name")
|
||||
yield UInt16(self, "integrity_check")
|
||||
yield Enum(UInt8(self, "type"), EFI_FV_FILETYPE)
|
||||
yield UInt8(self, "attributes")
|
||||
yield UInt24(self, "size")
|
||||
yield UInt8(self, "state")
|
||||
|
||||
# Content
|
||||
while not self.eof:
|
||||
yield FileSection(self, "section[]")
|
||||
|
||||
def createDescription(self):
|
||||
return "%s: %s containing %d section(s)" % (
|
||||
self["name"].value,
|
||||
self["type"].display,
|
||||
len(self.array("section")))
|
||||
|
||||
|
||||
class FirmwareVolume(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
if not self._size:
|
||||
self._size = self["volume_len"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield NullBytes(self, "zero_vector", 16)
|
||||
yield GUID(self, "fs_guid")
|
||||
yield UInt64(self, "volume_len")
|
||||
yield String(self, "signature", 4)
|
||||
yield UInt32(self, "attributes")
|
||||
yield UInt16(self, "header_len")
|
||||
yield UInt16(self, "checksum")
|
||||
yield UInt16(self, "ext_header_offset")
|
||||
yield UInt8(self, "reserved")
|
||||
yield UInt8(self, "revision")
|
||||
while True:
|
||||
bm = BlockMap(self, "block_map[]")
|
||||
yield bm
|
||||
if bm['num_blocks'].value == 0 and bm['len'].value == 0:
|
||||
break
|
||||
# TODO must handle extended header
|
||||
|
||||
# Content
|
||||
while not self.eof:
|
||||
padding = paddingSize(self.current_size // 8, 8)
|
||||
if padding:
|
||||
yield PaddingBytes(self, "padding[]", padding)
|
||||
yield File(self, "file[]")
|
||||
|
||||
def createDescription(self):
|
||||
return "Firmware Volume containing %d file(s)" % len(self.array("file"))
|
||||
|
||||
|
||||
class PIFVFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
MAGIC = '_FVH'
|
||||
PARSER_TAGS = {
|
||||
"id": "pifv",
|
||||
"category": "program",
|
||||
"file_ext": ("bin", ""),
|
||||
"min_size": 64*8, # smallest possible header
|
||||
"magic_regex": (("\0{16}.{24}%s" % MAGIC, 0), ),
|
||||
"description": "EFI Platform Initialization Firmware Volume",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(40*8, 4) != self.MAGIC:
|
||||
return "Invalid magic number"
|
||||
if self.stream.readBytes(0, 16) != "\0"*16:
|
||||
return "Invalid zero vector"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
while not self.eof:
|
||||
yield FirmwareVolume(self, "firmware_volume[]")
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
.torrent metainfo file parser
|
||||
|
||||
http://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure
|
||||
|
||||
Status: To statufy
|
||||
Author: Christophe Gisquet <christophe.gisquet@free.fr>
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
String, RawBytes)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.tools import makePrintable, timestampUNIX, humanFilesize
|
||||
|
||||
# Maximum number of bytes for string length
|
||||
MAX_STRING_LENGTH = 6 # length in 0..999999
|
||||
|
||||
# Maximum number of bytes for integer value
|
||||
MAX_INTEGER_SIZE = 21 # 21 decimal digits (or "-" sign and 20 digits)
|
||||
|
||||
class Integer(FieldSet):
|
||||
# i<integer encoded in base ten ASCII>e
|
||||
def createFields(self):
|
||||
yield String(self, "start", 1, "Integer start delimiter (i)", charset="ASCII")
|
||||
|
||||
# Find integer end
|
||||
addr = self.absolute_address+self.current_size
|
||||
len = self.stream.searchBytesLength('e', False, addr, addr+(MAX_INTEGER_SIZE+1)*8)
|
||||
if len is None:
|
||||
raise ParserError("Torrent: Unable to find integer end delimiter (e)!")
|
||||
if not len:
|
||||
raise ParserError("Torrent: error, empty integer!")
|
||||
|
||||
yield String(self, "value", len, "Integer value", charset="ASCII")
|
||||
yield String(self, "end", 1, "Integer end delimiter")
|
||||
|
||||
def createValue(self):
|
||||
"""Read integer value (may raise ValueError)"""
|
||||
return int(self["value"].value)
|
||||
|
||||
class TorrentString(FieldSet):
|
||||
# <string length encoded in base ten ASCII>:<string data>
|
||||
|
||||
def createFields(self):
|
||||
addr = self.absolute_address
|
||||
len = self.stream.searchBytesLength(':', False, addr, addr+(MAX_STRING_LENGTH+1)*8)
|
||||
if len is None:
|
||||
raise ParserError("Torrent: unable to find string separator (':')")
|
||||
if not len:
|
||||
raise ParserError("Torrent: error: no string length!")
|
||||
val = String(self, "length", len, "String length")
|
||||
yield val
|
||||
try:
|
||||
len = int(val.value)
|
||||
except ValueError:
|
||||
len = -1
|
||||
if len < 0:
|
||||
raise ParserError("Invalid string length (%s)" % makePrintable(val.value, "ASCII", to_unicode=True))
|
||||
yield String(self, "separator", 1, "String length/value separator")
|
||||
if not len:
|
||||
self.info("Empty string: len=%i" % len)
|
||||
return
|
||||
if len<512:
|
||||
yield String(self, "value", len, "String value", charset="ISO-8859-1")
|
||||
else:
|
||||
# Probably raw data
|
||||
yield RawBytes(self, "value", len, "Raw data")
|
||||
|
||||
def createValue(self):
|
||||
if "value" in self:
|
||||
field = self["value"]
|
||||
if field.__class__ != RawBytes:
|
||||
return field.value
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
class Dictionary(FieldSet):
|
||||
# d<bencoded string><bencoded element>e
|
||||
def createFields(self):
|
||||
yield String(self, "start", 1, "Dictionary start delimiter (d)", charset="ASCII")
|
||||
while self.stream.readBytes(self.absolute_address+self.current_size, 1) != "e":
|
||||
yield DictionaryItem(self, "item[]")
|
||||
yield String(self, "end", 1, "Dictionary end delimiter")
|
||||
|
||||
class List(FieldSet):
|
||||
# l<bencoded values>e
|
||||
def createFields(self):
|
||||
yield String(self, "start", 1, "List start delimiter")
|
||||
while self.stream.readBytes(self.absolute_address+self.current_size, 1) != "e":
|
||||
yield Entry(self, "item[]")
|
||||
yield String(self, "end", 1, "List end delimiter")
|
||||
|
||||
class DictionaryItem(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
|
||||
# TODO: Remove this because it's not lazy?
|
||||
key = self["key"]
|
||||
if not key.hasValue():
|
||||
return
|
||||
key = key.value
|
||||
self._name = str(key).replace(" ", "_")
|
||||
|
||||
def createDisplay(self):
|
||||
if not self["value"].hasValue():
|
||||
return None
|
||||
if self._name in ("length", "piece_length"):
|
||||
return humanFilesize(self.value)
|
||||
return FieldSet.createDisplay(self)
|
||||
|
||||
def createValue(self):
|
||||
if not self["value"].hasValue():
|
||||
return None
|
||||
if self._name == "creation_date":
|
||||
return self.createTimestampValue()
|
||||
else:
|
||||
return self["value"].value
|
||||
|
||||
def createFields(self):
|
||||
yield Entry(self, "key")
|
||||
yield Entry(self, "value")
|
||||
|
||||
def createTimestampValue(self):
|
||||
return timestampUNIX(self["value"].value)
|
||||
|
||||
# Map first chunk byte => type
|
||||
TAGS = {'d': Dictionary, 'i': Integer, 'l': List}
|
||||
for index in xrange(1, 9+1):
|
||||
TAGS[str(index)] = TorrentString
|
||||
|
||||
# Create an entry
|
||||
def Entry(parent, name):
|
||||
addr = parent.absolute_address + parent.current_size
|
||||
tag = parent.stream.readBytes(addr, 1)
|
||||
if tag not in TAGS:
|
||||
raise ParserError("Torrent: Entry of type %r not handled" % type)
|
||||
cls = TAGS[tag]
|
||||
return cls(parent, name)
|
||||
|
||||
class TorrentFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
MAGIC = "d8:announce"
|
||||
PARSER_TAGS = {
|
||||
"id": "torrent",
|
||||
"category": "misc",
|
||||
"file_ext": ("torrent",),
|
||||
"min_size": 50*8,
|
||||
"mime": (u"application/x-bittorrent",),
|
||||
"magic": ((MAGIC, 0),),
|
||||
"description": "Torrent metainfo file"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
|
||||
return "Invalid magic"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield Dictionary(self, "root", size=self.size)
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
TrueType Font parser.
|
||||
|
||||
Documents:
|
||||
- "An Introduction to TrueType Fonts: A look inside the TTF format"
|
||||
written by "NRSI: Computers & Writing Systems"
|
||||
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter08
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 2007-02-08
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt16, UInt32, Bit, Bits,
|
||||
PaddingBits, NullBytes,
|
||||
String, RawBytes, Bytes, Enum,
|
||||
TimestampMac32)
|
||||
from hachoir_core.endian import BIG_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal, filesizeHandler
|
||||
|
||||
MAX_NAME_COUNT = 300
|
||||
MIN_NB_TABLE = 3
|
||||
MAX_NB_TABLE = 30
|
||||
|
||||
DIRECTION_NAME = {
|
||||
0: u"Mixed directional",
|
||||
1: u"Left to right",
|
||||
2: u"Left to right + neutrals",
|
||||
-1: u"Right to left",
|
||||
-2: u"Right to left + neutrals",
|
||||
}
|
||||
|
||||
NAMEID_NAME = {
|
||||
0: u"Copyright notice",
|
||||
1: u"Font family name",
|
||||
2: u"Font subfamily name",
|
||||
3: u"Unique font identifier",
|
||||
4: u"Full font name",
|
||||
5: u"Version string",
|
||||
6: u"Postscript name",
|
||||
7: u"Trademark",
|
||||
8: u"Manufacturer name",
|
||||
9: u"Designer",
|
||||
10: u"Description",
|
||||
11: u"URL Vendor",
|
||||
12: u"URL Designer",
|
||||
13: u"License Description",
|
||||
14: u"License info URL",
|
||||
16: u"Preferred Family",
|
||||
17: u"Preferred Subfamily",
|
||||
18: u"Compatible Full",
|
||||
19: u"Sample text",
|
||||
20: u"PostScript CID findfont name",
|
||||
}
|
||||
|
||||
PLATFORM_NAME = {
|
||||
0: "Unicode",
|
||||
1: "Macintosh",
|
||||
2: "ISO",
|
||||
3: "Microsoft",
|
||||
4: "Custom",
|
||||
}
|
||||
|
||||
CHARSET_MAP = {
|
||||
# (platform, encoding) => charset
|
||||
0: {3: "UTF-16-BE"},
|
||||
1: {0: "MacRoman"},
|
||||
3: {1: "UTF-16-BE"},
|
||||
}
|
||||
|
||||
class TableHeader(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "tag", 4)
|
||||
yield textHandler(UInt32(self, "checksum"), hexadecimal)
|
||||
yield UInt32(self, "offset")
|
||||
yield filesizeHandler(UInt32(self, "size"))
|
||||
|
||||
def createDescription(self):
|
||||
return "Table entry: %s (%s)" % (self["tag"].display, self["size"].display)
|
||||
|
||||
class NameHeader(FieldSet):
|
||||
def createFields(self):
|
||||
yield Enum(UInt16(self, "platformID"), PLATFORM_NAME)
|
||||
yield UInt16(self, "encodingID")
|
||||
yield UInt16(self, "languageID")
|
||||
yield Enum(UInt16(self, "nameID"), NAMEID_NAME)
|
||||
yield UInt16(self, "length")
|
||||
yield UInt16(self, "offset")
|
||||
|
||||
def getCharset(self):
|
||||
platform = self["platformID"].value
|
||||
encoding = self["encodingID"].value
|
||||
try:
|
||||
return CHARSET_MAP[platform][encoding]
|
||||
except KeyError:
|
||||
self.warning("TTF: Unknown charset (%s,%s)" % (platform, encoding))
|
||||
return "ISO-8859-1"
|
||||
|
||||
def createDescription(self):
|
||||
platform = self["platformID"].display
|
||||
name = self["nameID"].display
|
||||
return "Name record: %s (%s)" % (name, platform)
|
||||
|
||||
def parseFontHeader(self):
|
||||
yield UInt16(self, "maj_ver", "Major version")
|
||||
yield UInt16(self, "min_ver", "Minor version")
|
||||
yield UInt16(self, "font_maj_ver", "Font major version")
|
||||
yield UInt16(self, "font_min_ver", "Font minor version")
|
||||
yield textHandler(UInt32(self, "checksum"), hexadecimal)
|
||||
yield Bytes(self, "magic", 4, r"Magic string (\x5F\x0F\x3C\xF5)")
|
||||
if self["magic"].value != "\x5F\x0F\x3C\xF5":
|
||||
raise ParserError("TTF: invalid magic of font header")
|
||||
|
||||
# Flags
|
||||
yield Bit(self, "y0", "Baseline at y=0")
|
||||
yield Bit(self, "x0", "Left sidebearing point at x=0")
|
||||
yield Bit(self, "instr_point", "Instructions may depend on point size")
|
||||
yield Bit(self, "ppem", "Force PPEM to integer values for all")
|
||||
yield Bit(self, "instr_width", "Instructions may alter advance width")
|
||||
yield Bit(self, "vertical", "e laid out vertically?")
|
||||
yield PaddingBits(self, "reserved[]", 1)
|
||||
yield Bit(self, "linguistic", "Requires layout for correct linguistic rendering?")
|
||||
yield Bit(self, "gx", "Metamorphosis effects?")
|
||||
yield Bit(self, "strong", "Contains strong right-to-left glyphs?")
|
||||
yield Bit(self, "indic", "contains Indic-style rearrangement effects?")
|
||||
yield Bit(self, "lossless", "Data is lossless (Agfa MicroType compression)")
|
||||
yield Bit(self, "converted", "Font converted (produce compatible metrics)")
|
||||
yield Bit(self, "cleartype", "Optimised for ClearType")
|
||||
yield Bits(self, "adobe", 2, "(used by Adobe)")
|
||||
|
||||
yield UInt16(self, "unit_per_em", "Units per em")
|
||||
if not(16 <= self["unit_per_em"].value <= 16384):
|
||||
raise ParserError("TTF: Invalid unit/em value")
|
||||
yield UInt32(self, "created_high")
|
||||
yield TimestampMac32(self, "created")
|
||||
yield UInt32(self, "modified_high")
|
||||
yield TimestampMac32(self, "modified")
|
||||
yield UInt16(self, "xmin")
|
||||
yield UInt16(self, "ymin")
|
||||
yield UInt16(self, "xmax")
|
||||
yield UInt16(self, "ymax")
|
||||
|
||||
# Mac style
|
||||
yield Bit(self, "bold")
|
||||
yield Bit(self, "italic")
|
||||
yield Bit(self, "underline")
|
||||
yield Bit(self, "outline")
|
||||
yield Bit(self, "shadow")
|
||||
yield Bit(self, "condensed", "(narrow)")
|
||||
yield Bit(self, "expanded")
|
||||
yield PaddingBits(self, "reserved[]", 9)
|
||||
|
||||
yield UInt16(self, "lowest", "Smallest readable size in pixels")
|
||||
yield Enum(UInt16(self, "font_dir", "Font direction hint"), DIRECTION_NAME)
|
||||
yield Enum(UInt16(self, "ofst_format"), {0: "short offsets", 1: "long"})
|
||||
yield UInt16(self, "glyph_format", "(=0)")
|
||||
|
||||
def parseNames(self):
|
||||
# Read header
|
||||
yield UInt16(self, "format")
|
||||
if self["format"].value != 0:
|
||||
raise ParserError("TTF (names): Invalid format (%u)" % self["format"].value)
|
||||
yield UInt16(self, "count")
|
||||
yield UInt16(self, "offset")
|
||||
if MAX_NAME_COUNT < self["count"].value:
|
||||
raise ParserError("Invalid number of names (%s)"
|
||||
% self["count"].value)
|
||||
|
||||
# Read name index
|
||||
entries = []
|
||||
for index in xrange(self["count"].value):
|
||||
entry = NameHeader(self, "header[]")
|
||||
yield entry
|
||||
entries.append(entry)
|
||||
|
||||
# Sort names by their offset
|
||||
entries.sort(key=lambda field: field["offset"].value)
|
||||
|
||||
# Read name value
|
||||
last = None
|
||||
for entry in entries:
|
||||
# Skip duplicates values
|
||||
new = (entry["offset"].value, entry["length"].value)
|
||||
if last and last == new:
|
||||
self.warning("Skip duplicate %s %s" % (entry.name, new))
|
||||
continue
|
||||
last = (entry["offset"].value, entry["length"].value)
|
||||
|
||||
# Skip negative offset
|
||||
offset = entry["offset"].value + self["offset"].value
|
||||
if offset < self.current_size//8:
|
||||
self.warning("Skip value %s (negative offset)" % entry.name)
|
||||
continue
|
||||
|
||||
# Add padding if any
|
||||
padding = self.seekByte(offset, relative=True, null=True)
|
||||
if padding:
|
||||
yield padding
|
||||
|
||||
# Read value
|
||||
size = entry["length"].value
|
||||
if size:
|
||||
yield String(self, "value[]", size, entry.description, charset=entry.getCharset())
|
||||
|
||||
padding = (self.size - self.current_size) // 8
|
||||
if padding:
|
||||
yield NullBytes(self, "padding_end", padding)
|
||||
|
||||
class Table(FieldSet):
|
||||
TAG_INFO = {
|
||||
"head": ("header", "Font header", parseFontHeader),
|
||||
"name": ("names", "Names", parseNames),
|
||||
}
|
||||
|
||||
def __init__(self, parent, name, table, **kw):
|
||||
FieldSet.__init__(self, parent, name, **kw)
|
||||
self.table = table
|
||||
tag = table["tag"].value
|
||||
if tag in self.TAG_INFO:
|
||||
self._name, self._description, self.parser = self.TAG_INFO[tag]
|
||||
else:
|
||||
self.parser = None
|
||||
|
||||
def createFields(self):
|
||||
if self.parser:
|
||||
for field in self.parser(self):
|
||||
yield field
|
||||
else:
|
||||
yield RawBytes(self, "content", self.size//8)
|
||||
|
||||
def createDescription(self):
|
||||
return "Table %s (%s)" % (self.table["tag"].value, self.table.path)
|
||||
|
||||
class TrueTypeFontFile(Parser):
|
||||
endian = BIG_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "ttf",
|
||||
"category": "misc",
|
||||
"file_ext": ("ttf",),
|
||||
"min_size": 10*8, # FIXME
|
||||
"description": "TrueType font",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self["maj_ver"].value != 1:
|
||||
return "Invalid major version (%u)" % self["maj_ver"].value
|
||||
if self["min_ver"].value != 0:
|
||||
return "Invalid minor version (%u)" % self["min_ver"].value
|
||||
if not (MIN_NB_TABLE <= self["nb_table"].value <= MAX_NB_TABLE):
|
||||
return "Invalid number of table (%u)" % self["nb_table"].value
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield UInt16(self, "maj_ver", "Major version")
|
||||
yield UInt16(self, "min_ver", "Minor version")
|
||||
yield UInt16(self, "nb_table")
|
||||
yield UInt16(self, "search_range")
|
||||
yield UInt16(self, "entry_selector")
|
||||
yield UInt16(self, "range_shift")
|
||||
tables = []
|
||||
for index in xrange(self["nb_table"].value):
|
||||
table = TableHeader(self, "table_hdr[]")
|
||||
yield table
|
||||
tables.append(table)
|
||||
tables.sort(key=lambda field: field["offset"].value)
|
||||
for table in tables:
|
||||
padding = self.seekByte(table["offset"].value, null=True)
|
||||
if padding:
|
||||
yield padding
|
||||
size = table["size"].value
|
||||
if size:
|
||||
yield Table(self, "table[]", table, size=size*8)
|
||||
padding = self.seekBit(self.size, null=True)
|
||||
if padding:
|
||||
yield padding
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
Documents:
|
||||
|
||||
* libwx source code: see fib.c source code
|
||||
* "Microsoft Word 97 Binary File Format"
|
||||
http://bio.gsi.de/DOCS/AIX/wword8.html
|
||||
|
||||
Microsoft Word 97 (aka Version 8) for Windows and Macintosh. From the Office
|
||||
book, found in the Microsoft Office Development section in the MSDN Online
|
||||
Library. HTMLified June 1998. Revised Aug 1 1998, added missing Definitions
|
||||
section. Revised Dec 21 1998, added missing Document Properties (section).
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
Bit, Bits,
|
||||
UInt8, Int16, UInt16, UInt32, Int32,
|
||||
NullBytes, RawBytes, PascalString16,
|
||||
DateTimeMSDOS32)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
|
||||
TIMESTAMP = DateTimeMSDOS32
|
||||
|
||||
class BaseWordDocument:
|
||||
def createFields(self):
|
||||
yield UInt16(self, "wIdent", 2)
|
||||
yield UInt16(self, "nFib")
|
||||
yield UInt16(self, "nProduct")
|
||||
yield UInt16(self, "lid")
|
||||
yield Int16(self, "pnNext")
|
||||
|
||||
yield Bit(self, "fDot")
|
||||
yield Bit(self, "fGlsy")
|
||||
yield Bit(self, "fComplex")
|
||||
yield Bit(self, "fHasPic")
|
||||
yield Bits(self, "cQuickSaves", 4)
|
||||
yield Bit(self, "fEncrypted")
|
||||
yield Bit(self, "fWhichTblStm")
|
||||
yield Bit(self, "fReadOnlyRecommanded")
|
||||
yield Bit(self, "fWriteReservation")
|
||||
yield Bit(self, "fExtChar")
|
||||
yield Bit(self, "fLoadOverride")
|
||||
yield Bit(self, "fFarEeast")
|
||||
yield Bit(self, "fCrypto")
|
||||
|
||||
yield UInt16(self, "nFibBack")
|
||||
yield UInt32(self, "lKey")
|
||||
yield UInt8(self, "envr")
|
||||
|
||||
yield Bit(self, "fMac")
|
||||
yield Bit(self, "fEmptySpecial")
|
||||
yield Bit(self, "fLoadOverridePage")
|
||||
yield Bit(self, "fFutureSavedUndo")
|
||||
yield Bit(self, "fWord97Save")
|
||||
yield Bits(self, "fSpare0", 3)
|
||||
|
||||
yield UInt16(self, "chse")
|
||||
yield UInt16(self, "chsTables")
|
||||
yield UInt32(self, "fcMin")
|
||||
yield UInt32(self, "fcMac")
|
||||
|
||||
yield PascalString16(self, "file_creator", strip="\0")
|
||||
|
||||
yield NullBytes(self, "reserved[]", 12)
|
||||
|
||||
yield Int16(self, "lidFE")
|
||||
yield UInt16(self, "clw")
|
||||
yield Int32(self, "cbMac")
|
||||
yield UInt32(self, "lProductCreated")
|
||||
yield TIMESTAMP(self, "lProductRevised")
|
||||
|
||||
yield UInt32(self, "ccpText")
|
||||
yield Int32(self, "ccpFtn")
|
||||
yield Int32(self, "ccpHdr")
|
||||
yield Int32(self, "ccpMcr")
|
||||
yield Int32(self, "ccpAtn")
|
||||
yield Int32(self, "ccpEdn")
|
||||
yield Int32(self, "ccpTxbx")
|
||||
yield Int32(self, "ccpHdrTxbx")
|
||||
yield Int32(self, "pnFbpChpFirst")
|
||||
yield Int32(self, "pnChpFirst")
|
||||
yield Int32(self, "cpnBteChp")
|
||||
yield Int32(self, "pnFbpPapFirst")
|
||||
yield Int32(self, "pnPapFirst")
|
||||
yield Int32(self, "cpnBtePap")
|
||||
yield Int32(self, "pnFbpLvcFirst")
|
||||
yield Int32(self, "pnLvcFirst")
|
||||
yield Int32(self, "cpnBteLvc")
|
||||
yield Int32(self, "fcIslandFirst")
|
||||
yield Int32(self, "fcIslandLim")
|
||||
yield UInt16(self, "cfclcb")
|
||||
yield Int32(self, "fcStshfOrig")
|
||||
yield UInt32(self, "lcbStshfOrig")
|
||||
yield Int32(self, "fcStshf")
|
||||
yield UInt32(self, "lcbStshf")
|
||||
|
||||
yield Int32(self, "fcPlcffndRef")
|
||||
yield UInt32(self, "lcbPlcffndRef")
|
||||
yield Int32(self, "fcPlcffndTxt")
|
||||
yield UInt32(self, "lcbPlcffndTxt")
|
||||
yield Int32(self, "fcPlcfandRef")
|
||||
yield UInt32(self, "lcbPlcfandRef")
|
||||
yield Int32(self, "fcPlcfandTxt")
|
||||
yield UInt32(self, "lcbPlcfandTxt")
|
||||
yield Int32(self, "fcPlcfsed")
|
||||
yield UInt32(self, "lcbPlcfsed")
|
||||
yield Int32(self, "fcPlcpad")
|
||||
yield UInt32(self, "lcbPlcpad")
|
||||
yield Int32(self, "fcPlcfphe")
|
||||
yield UInt32(self, "lcbPlcfphe")
|
||||
yield Int32(self, "fcSttbfglsy")
|
||||
yield UInt32(self, "lcbSttbfglsy")
|
||||
yield Int32(self, "fcPlcfglsy")
|
||||
yield UInt32(self, "lcbPlcfglsy")
|
||||
yield Int32(self, "fcPlcfhdd")
|
||||
yield UInt32(self, "lcbPlcfhdd")
|
||||
yield Int32(self, "fcPlcfbteChpx")
|
||||
yield UInt32(self, "lcbPlcfbteChpx")
|
||||
yield Int32(self, "fcPlcfbtePapx")
|
||||
yield UInt32(self, "lcbPlcfbtePapx")
|
||||
yield Int32(self, "fcPlcfsea")
|
||||
yield UInt32(self, "lcbPlcfsea")
|
||||
yield Int32(self, "fcSttbfffn")
|
||||
yield UInt32(self, "lcbSttbfffn")
|
||||
yield Int32(self, "fcPlcffldMom")
|
||||
yield UInt32(self, "lcbPlcffldMom")
|
||||
yield Int32(self, "fcPlcffldHdr")
|
||||
yield UInt32(self, "lcbPlcffldHdr")
|
||||
yield Int32(self, "fcPlcffldFtn")
|
||||
yield UInt32(self, "lcbPlcffldFtn")
|
||||
yield Int32(self, "fcPlcffldAtn")
|
||||
yield UInt32(self, "lcbPlcffldAtn")
|
||||
yield Int32(self, "fcPlcffldMcr")
|
||||
yield UInt32(self, "lcbPlcffldMcr")
|
||||
yield Int32(self, "fcSttbfbkmk")
|
||||
yield UInt32(self, "lcbSttbfbkmk")
|
||||
yield Int32(self, "fcPlcfbkf")
|
||||
yield UInt32(self, "lcbPlcfbkf")
|
||||
yield Int32(self, "fcPlcfbkl")
|
||||
yield UInt32(self, "lcbPlcfbkl")
|
||||
yield Int32(self, "fcCmds")
|
||||
yield UInt32(self, "lcbCmds")
|
||||
yield Int32(self, "fcPlcmcr")
|
||||
yield UInt32(self, "lcbPlcmcr")
|
||||
yield Int32(self, "fcSttbfmcr")
|
||||
yield UInt32(self, "lcbSttbfmcr")
|
||||
yield Int32(self, "fcPrDrvr")
|
||||
yield UInt32(self, "lcbPrDrvr")
|
||||
yield Int32(self, "fcPrEnvPort")
|
||||
yield UInt32(self, "lcbPrEnvPort")
|
||||
yield Int32(self, "fcPrEnvLand")
|
||||
yield UInt32(self, "lcbPrEnvLand")
|
||||
yield Int32(self, "fcWss")
|
||||
yield UInt32(self, "lcbWss")
|
||||
yield Int32(self, "fcDop")
|
||||
yield UInt32(self, "lcbDop")
|
||||
yield Int32(self, "fcSttbfAssoc")
|
||||
yield UInt32(self, "lcbSttbfAssoc")
|
||||
yield Int32(self, "fcClx")
|
||||
yield UInt32(self, "lcbClx")
|
||||
yield Int32(self, "fcPlcfpgdFtn")
|
||||
yield UInt32(self, "lcbPlcfpgdFtn")
|
||||
yield Int32(self, "fcAutosaveSource")
|
||||
yield UInt32(self, "lcbAutosaveSource")
|
||||
yield Int32(self, "fcGrpXstAtnOwners")
|
||||
yield UInt32(self, "lcbGrpXstAtnOwners")
|
||||
yield Int32(self, "fcSttbfAtnbkmk")
|
||||
yield UInt32(self, "lcbSttbfAtnbkmk")
|
||||
yield Int32(self, "fcPlcdoaMom")
|
||||
yield UInt32(self, "lcbPlcdoaMom")
|
||||
yield Int32(self, "fcPlcdoaHdr")
|
||||
yield UInt32(self, "lcbPlcdoaHdr")
|
||||
yield Int32(self, "fcPlcspaMom")
|
||||
yield UInt32(self, "lcbPlcspaMom")
|
||||
yield Int32(self, "fcPlcspaHdr")
|
||||
yield UInt32(self, "lcbPlcspaHdr")
|
||||
yield Int32(self, "fcPlcfAtnbkf")
|
||||
yield UInt32(self, "lcbPlcfAtnbkf")
|
||||
yield Int32(self, "fcPlcfAtnbkl")
|
||||
yield UInt32(self, "lcbPlcfAtnbkl")
|
||||
yield Int32(self, "fcPms")
|
||||
yield UInt32(self, "lcbPms")
|
||||
yield Int32(self, "fcFormFldSttbs")
|
||||
yield UInt32(self, "lcbFormFldSttbs")
|
||||
yield Int32(self, "fcPlcfendRef")
|
||||
yield UInt32(self, "lcbPlcfendRef")
|
||||
yield Int32(self, "fcPlcfendTxt")
|
||||
yield UInt32(self, "lcbPlcfendTxt")
|
||||
yield Int32(self, "fcPlcffldEdn")
|
||||
yield UInt32(self, "lcbPlcffldEdn")
|
||||
yield Int32(self, "fcPlcfpgdEdn")
|
||||
yield UInt32(self, "lcbPlcfpgdEdn")
|
||||
yield Int32(self, "fcDggInfo")
|
||||
yield UInt32(self, "lcbDggInfo")
|
||||
yield Int32(self, "fcSttbfRMark")
|
||||
yield UInt32(self, "lcbSttbfRMark")
|
||||
yield Int32(self, "fcSttbCaption")
|
||||
yield UInt32(self, "lcbSttbCaption")
|
||||
yield Int32(self, "fcSttbAutoCaption")
|
||||
yield UInt32(self, "lcbSttbAutoCaption")
|
||||
yield Int32(self, "fcPlcfwkb")
|
||||
yield UInt32(self, "lcbPlcfwkb")
|
||||
yield Int32(self, "fcPlcfspl")
|
||||
yield UInt32(self, "lcbPlcfspl")
|
||||
yield Int32(self, "fcPlcftxbxTxt")
|
||||
yield UInt32(self, "lcbPlcftxbxTxt")
|
||||
yield Int32(self, "fcPlcffldTxbx")
|
||||
yield UInt32(self, "lcbPlcffldTxbx")
|
||||
yield Int32(self, "fcPlcfhdrtxbxTxt")
|
||||
yield UInt32(self, "lcbPlcfhdrtxbxTxt")
|
||||
yield Int32(self, "fcPlcffldHdrTxbx")
|
||||
yield UInt32(self, "lcbPlcffldHdrTxbx")
|
||||
yield Int32(self, "fcStwUser")
|
||||
yield UInt32(self, "lcbStwUser")
|
||||
yield Int32(self, "fcSttbttmbd")
|
||||
yield UInt32(self, "cbSttbttmbd")
|
||||
yield Int32(self, "fcUnused")
|
||||
yield UInt32(self, "lcbUnused")
|
||||
yield Int32(self, "fcPgdMother")
|
||||
yield UInt32(self, "lcbPgdMother")
|
||||
yield Int32(self, "fcBkdMother")
|
||||
yield UInt32(self, "lcbBkdMother")
|
||||
yield Int32(self, "fcPgdFtn")
|
||||
yield UInt32(self, "lcbPgdFtn")
|
||||
yield Int32(self, "fcBkdFtn")
|
||||
yield UInt32(self, "lcbBkdFtn")
|
||||
yield Int32(self, "fcPgdEdn")
|
||||
yield UInt32(self, "lcbPgdEdn")
|
||||
yield Int32(self, "fcBkdEdn")
|
||||
yield UInt32(self, "lcbBkdEdn")
|
||||
yield Int32(self, "fcSttbfIntlFld")
|
||||
yield UInt32(self, "lcbSttbfIntlFld")
|
||||
yield Int32(self, "fcRouteSlip")
|
||||
yield UInt32(self, "lcbRouteSlip")
|
||||
yield Int32(self, "fcSttbSavedBy")
|
||||
yield UInt32(self, "lcbSttbSavedBy")
|
||||
yield Int32(self, "fcSttbFnm")
|
||||
yield UInt32(self, "lcbSttbFnm")
|
||||
yield Int32(self, "fcPlcfLst")
|
||||
yield UInt32(self, "lcbPlcfLst")
|
||||
yield Int32(self, "fcPlfLfo")
|
||||
yield UInt32(self, "lcbPlfLfo")
|
||||
yield Int32(self, "fcPlcftxbxBkd")
|
||||
yield UInt32(self, "lcbPlcftxbxBkd")
|
||||
yield Int32(self, "fcPlcftxbxHdrBkd")
|
||||
yield UInt32(self, "lcbPlcftxbxHdrBkd")
|
||||
yield Int32(self, "fcDocUndo")
|
||||
yield UInt32(self, "lcbDocUndo")
|
||||
yield Int32(self, "fcRgbuse")
|
||||
yield UInt32(self, "lcbRgbuse")
|
||||
yield Int32(self, "fcUsp")
|
||||
yield UInt32(self, "lcbUsp")
|
||||
yield Int32(self, "fcUskf")
|
||||
yield UInt32(self, "lcbUskf")
|
||||
yield Int32(self, "fcPlcupcRgbuse")
|
||||
yield UInt32(self, "lcbPlcupcRgbuse")
|
||||
yield Int32(self, "fcPlcupcUsp")
|
||||
yield UInt32(self, "lcbPlcupcUsp")
|
||||
yield Int32(self, "fcSttbGlsyStyle")
|
||||
yield UInt32(self, "lcbSttbGlsyStyle")
|
||||
yield Int32(self, "fcPlgosl")
|
||||
yield UInt32(self, "lcbPlgosl")
|
||||
yield Int32(self, "fcPlcocx")
|
||||
yield UInt32(self, "lcbPlcocx")
|
||||
yield Int32(self, "fcPlcfbteLvc")
|
||||
yield UInt32(self, "lcbPlcfbteLvc")
|
||||
yield TIMESTAMP(self, "ftModified")
|
||||
yield Int32(self, "fcPlcflvc")
|
||||
yield UInt32(self, "lcbPlcflvc")
|
||||
yield Int32(self, "fcPlcasumy")
|
||||
yield UInt32(self, "lcbPlcasumy")
|
||||
yield Int32(self, "fcPlcfgram")
|
||||
yield UInt32(self, "lcbPlcfgram")
|
||||
yield Int32(self, "fcSttbListNames")
|
||||
yield UInt32(self, "lcbSttbListNames")
|
||||
yield Int32(self, "fcSttbfUssr")
|
||||
yield UInt32(self, "lcbSttbfUssr")
|
||||
|
||||
tail = (self.size - self.current_size) // 8
|
||||
if tail:
|
||||
yield RawBytes(self, "tail", tail)
|
||||
|
||||
class WordDocumentFieldSet(BaseWordDocument, FieldSet):
|
||||
pass
|
||||
|
||||
class WordDocumentParser(BaseWordDocument, Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "word_document",
|
||||
"min_size": 8,
|
||||
"description": "Microsoft Office Word document",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def __init__(self, stream, **kw):
|
||||
Parser.__init__(self, stream, **kw)
|
||||
|
||||
def validate(self):
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user