diff -r 000000000000 -r ca70ae20a155 src/tools/py2sis/ensymble/sisfield.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tools/py2sis/ensymble/sisfield.py Tue Feb 16 10:07:05 2010 +0530 @@ -0,0 +1,1321 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################## +# sisfield.py - Symbian OS v9.x SIS file utilities, SISField support classes +# Copyright 2006, 2007 Jussi Ylänen +# +# This file is part of Ensymble developer utilities for Symbian OS(TM). +# +# Ensymble is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Ensymble is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ensymble; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +############################################################################## + +import struct +import zlib + +import symbianutil + + +# TODO: 1. Make all tostring() methods cache the result. +# TODO: 2. Allow modifying objects after creation, keeping string cache in sync. +# TODO: 3. Implement a list-of-strings type, which the tostring() (or +# some other method with a better name) can use, to eliminate +# superfluous string concatenations. + + +############################################################################## +# Parameters +############################################################################## + +DEBUG = 0 # (0: None, 1: Basic, 2: Verbose) +MAXNUMSIGNATURES = 8 # Maximum number of signatures in a SISController + + +############################################################################## +# Public SISField constants (in original Symbian OS naming style) +############################################################################## + +ECompressAuto = -1 # Not a real compression type +ECompressNone = 0 +ECompressDeflate = 1 + +EInstInstallation = 0 +EInstAugmentation = 1 +EInstPartialUpgrade = 2 +EInstPreInstalledApp = 3 +EInstPreInstalledPatch = 4 + +EInstFlagShutdownApps = 1 + +EOpInstall = 1 +EOpRun = 2 +EOpText = 4 +EOpNull = 8 + +EInstVerifyOnRestore = 1 << 15 + +EInstFileRunOptionInstall = 1 << 1 +EInstFileRunOptionUninstall = 1 << 2 +EInstFileRunOptionByMimeType = 1 << 3 +EInstFileRunOptionWaitEnd = 1 << 4 +EInstFileRunOptionSendEnd = 1 << 5 + +EInstFileTextOptionContinue = 1 << 9 # Not used by makesis v. 4, 0, 0, 2 +EInstFileTextOptionSkipIfNo = 1 << 10 +EInstFileTextOptionAbortIfNo = 1 << 11 +EInstFileTextOptionExitIfNo = 1 << 12 + +ESISHashAlgSHA1 = 1 + +ESISSignatureAlgSHA1RSA = "1.2.840.113549.1.1.5" +ESISSignatureAlgSHA1DSA = "1.2.840.10040.4.3" + +EBinOpEqual = 1 +EBinOpNotEqual = 2 +EBinOpGreaterThan = 3 +EBinOpLessThan = 4 +EBinOpGreaterOrEqual = 5 +EBinOpLessOrEqual = 6 +ELogOpAnd = 7 +ELogOpOr = 8 +EUnaryOpNot = 9 +EFuncExists = 10 +EFuncAppProperties = 11 +EFuncDevProperties = 12 +EPrimTypeString = 13 +EPrimTypeOption = 14 +EPrimTypeVariable = 15 +EPrimTypeNumber = 16 + +EVarLanguage = 0x1000 +EVarRemoteInstall = 0x1001 + + +############################################################################## +# Public exception class for SIS parsing / generation +############################################################################## + +class SISException(StandardError): + '''SIS parsing / generation error''' + pass + + +############################################################################## +# Public module-level functions +############################################################################## + +def SISField(fromstring, exactlength = True): + '''Generator function for creating SISField subclass instances from a string + + If exactlength is False, return a tuple of (SISField, bytes consumed). + Otherwise return SISField directly.''' + + # Determine SISField subclass type. + ftype, hdrlen, flen, padlen = parsesisfieldheader(fromstring, None, + exactlength) + + try: + fclass = fieldnumtoclass[ftype] + except KeyError: + raise SISException("invalid SISField type '%d'" % ftype) + + # Limit string to actual data length (in case exactlength was False). + fromstring = fromstring[:(hdrlen + flen + padlen)] + + # Create a subclass instance. + field = fclass(fromstring = fromstring) + + if exactlength: + return field + else: + # Normal SISField subclasses use SISField() to parse adjacent SISFields. + # Number of consumed bytes need to be passed back in that case. This + # may violate the idea of a generator function somewhat, but eliminates + # a bit of duplicated code. + return (field, len(fromstring)) + +def stripheaderandpadding(fromstring): + '''Return the actual content of SISField string. + + stripheaderandpadding(...) -> contents + + fromstring a SISField string + + contents SISField contents without the header and padding''' + + # Parse field header. + ftype, hdrlen, flen, padlen = parsesisfieldheader(fromstring) + + # Return field contents. + return fromstring[hdrlen:(hdrlen + flen)] + + +############################################################################## +# Module-level functions which are normally only used by this module +############################################################################## + +def parsesisfieldheader(string, requiredtype = None, exactlength = True): + '''Parse the header of a SISField string and return the field type and + lengths of the various parts (header, data, padding). Optionally, check + that the type is correct and that the string length is not too long.''' + + hdrlen = 8 + if hdrlen > len(string): + raise SISException("not enough data for a complete SISField header") + + # Get SISField type and first part of the length. + ftype, flen = struct.unpack(" len(string): + raise SISException("not enough data for a complete SISField header") + flen2 = struct.unpack(" len(string): + raise SISException("SISField contents too short") + + # Allow oversized strings when parsing recursive SISFields. + if exactlength and (hdrlen + flen + padlen) < len(string): + raise SISException("SISField contents too long") + + return ftype, hdrlen, flen, padlen + +def makesisfieldheader(fieldtype, fieldlen): + '''Create a SISField header string from type and length.''' + + if fieldlen < 0x80000000L: + # 31-bit length + return struct.pack("> 31 + fieldlen = (fieldlen & 0x7fffffffL) | 0x80000000L + return struct.pack(" 0: + # DEBUG: Print class name during initialization. + print "%s.__init__()" % self.__class__.__name__ + + # Get the names of all instance variables. + validkwds = self.__dict__.keys() + + # Filter out private instance variables (all in lowercase). + validkwds = filter(lambda s: s != s.lower(), validkwds) + + # Set type code. + self.fieldtype = fieldnametonum[self.__class__.__name__] + + if "fromstring" in kwds: + if DEBUG > 1: + # DEBUG: Dump of string parameter. + print repr(kwds["fromstring"]) + + # Load instance variables from string. + if len(kwds) != 1: + raise TypeError( + "keyword 'fromstring' may not be given with other keywords") + self.fromstring(kwds["fromstring"]) + else: + # Load instance variables from keywords. + # Only accept existing variable names. + for kwd in kwds.keys(): + if kwd not in validkwds: + raise AttributeError("'%s' object has no attribute '%s'" % + (self.__class__.__name__, kwd)) + self.__dict__[kwd] = kwds[kwd] + + def __str__(self): + # Default __str__() for SISFields, only return the field name. + return "<%s>" % self.__class__.__name__ + +class SISFieldNormal(SISFieldBase): + '''SISField base class for normal fields (fields containing only other + fields and integers)''' + + # Subfield types + FTYPE_INTEGRAL = 0 # Integer + FTYPE_MANDATORY = 1 # Mandatory SISField + FTYPE_OPTIONAL = 2 # Optional SISField + FTYPE_ARRAY = 3 # SISArray with zero or more items + + def __init__(self, **kwds): + # Initialize instance variables to None. + for fattr, fkind, ffmt in self.subfields: + self.__dict__[fattr] = None + + # Set instance variables. + SISFieldBase.__init__(self, **kwds) + + for fattr, fkind, ffmt in self.subfields: + # Check that all required instance variables are set. + if fkind != self.FTYPE_OPTIONAL and self.__dict__[fattr] == None: + raise AttributeError("missing '%s' attribute for '%s'" % + (fattr, self.__class__.__name__)) + + if fkind in (self.FTYPE_MANDATORY, self.FTYPE_OPTIONAL): + # Verify SISField types. + if (self.__dict__[fattr] != None and + fieldnumtoname[self.__dict__[fattr].fieldtype] != ffmt): + raise TypeError( + "attribute '%s' for '%s' is of invalid SISField type" % + (fattr, self.__class__.__name__)) + elif fkind == self.FTYPE_ARRAY: + # Verify SISArray contents. + if (fieldnumtoname[self.__dict__[fattr].fieldtype] != "SISArray" + or + fieldnumtoname[self.__dict__[fattr].SISFieldType] != ffmt): + raise TypeError( + "SISArray attribute '%s' for '%s' is of invalid type" % + (fattr, self.__class__.__name__)) + + def fromstring(self, string): + # Parse field header. + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + # Recursively parse subfields. + pos = hdrlen + reuse = None # SISField to re-use or None + try: + for fattr, fkind, ffmt in self.subfields: + field = None # No value by default + + if fkind == self.FTYPE_INTEGRAL: + # Integer, unpack it. + if reuse: + # It is an error if there is a field to + # re-use present at this time. + raise ValueError("integral field preceded optional") + + n = struct.calcsize(ffmt) + field = struct.unpack(ffmt, string[pos:(pos + n)])[0] + pos += n + else: + # SISField, read data from string or + # re-use field from previous round. + if not reuse: + # No old field to handle, convert string to SISField. + if pos < (hdrlen + flen): + field, n = SISField(string[pos:(hdrlen + flen)], + False) + pos += n + elif fkind != self.FTYPE_OPTIONAL: + # No more data in string, raise an exception. + raise ValueError("unexpected end-of-data") + else: + # Field from previous round present, re-use it. + field = reuse + reuse = None + + # Verify SISField type. + if field != None: + fname = fieldnumtoname[field.fieldtype] + if fkind == self.FTYPE_ARRAY: + if (fname != "SISArray" or + fieldnumtoname[field.SISFieldType] != ffmt): + # Wrong type of fields inside SISArray, + # raise an exception. + raise ValueError("invalid SISArray type") + elif fkind == self.FTYPE_MANDATORY: + if fname != ffmt: + # Mandatory field missing, raise an exception. + raise ValueError("mandatory field missing") + elif fkind == self.FTYPE_OPTIONAL: + if fname != ffmt: + # Wrong type for optional field. Skip optional + # field and re-use already parsed field on next + # round. + reuse = field + field = None + + # Introduce field as an instance variable. + self.__dict__[fattr] = field + except (ValueError, KeyError, struct.error): + if DEBUG > 0: + # DEBUG: Raise a detailed exception. + raise + else: + raise SISException("invalid '%s' structure" % + self.__class__.__name__) + + def tostring(self): + # Recursively create strings from subfields. + fstrings = [None] + totlen = 0 + for fattr, fkind, ffmt in self.subfields: + field = self.__dict__[fattr] + if fkind == self.FTYPE_INTEGRAL: + # Integer, pack it. + try: + string = struct.pack(ffmt, field) + except: + print "%s %s %s" % (self.__class__, ffmt, repr(field)) + raise + fstrings.append(string) + totlen += len(string) + else: + if field == None: + if fkind == self.FTYPE_OPTIONAL: + # Optional field missing, skip it. + pass + else: + # Mandatory field missing, raise an exception. + raise SISException("field '%s' missing for '%s'" % + (fattr, self.__dict__.__name__)) + else: + # Convert SISField to string. + string = field.tostring() + fstrings.append(string) + totlen += len(string) + + try: + del string # Try to free some memory early. + except: + pass + + fstrings[0] = makesisfieldheader(self.fieldtype, totlen) + fstrings.append(makesisfieldpadding(totlen)) + + # TODO: Heavy on memory, optimize (new string type with concat.) + return "".join(fstrings) + +class SISFieldSpecial(SISFieldBase): + '''SISField base class for special fields (fields that do something + special for the data they contain or the data is of variable length)''' + def __init__(self, **kwds): + # Set instance variables. + SISFieldBase.__init__(self, **kwds) + + +############################################################################## +# Special SISField subclasses +############################################################################## + +class SISString(SISFieldSpecial): + '''UCS-2 (UTF-16LE) string''' + def __init__(self, **kwds): + # Set default values. + self.String = None + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + # Check that all required instance variables are set. + if self.String == None: + raise AttributeError("missing '%s' attribute for '%s'" % + ("String", self.__class__.__name__)) + + def fromstring(self, string): + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + self.String = string[hdrlen:(hdrlen + flen)].decode("UTF-16LE") + + def tostring(self): + encstr = self.String.encode("UTF-16LE") + return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(encstr)), + encstr, makesisfieldpadding(len(encstr))) + + def __str__(self): + # Always return Unicode string. Let Python default encoding handle it. + return u"" % self.String + +class SISArray(SISFieldSpecial): + '''An array of other SISFields, all of the same type''' + def __init__(self, **kwds): + # Set default values. + self.SISFieldType = None # Invalid type, checked later. + self.SISFields = [] + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + # Make a copy of the supplied list + # (caller may try to modify the original). + self.SISFields = self.SISFields[:] + + # Allow type to be a string or number. + self.SISFieldType = fieldnametonum.get(self.SISFieldType, + self.SISFieldType) + + # Get type of first field if not given explicitly. + if self.SISFieldType == None: + if len(self.SISFields) > 0: + self.SISFieldType = self.SISFields[0].fieldtype + else: + raise AttributeError("no SISFieldType given") + + # Check that all fields are of the same type. + for f in self.SISFields: + if f.fieldtype != self.SISFieldType: + raise TypeError("SISFieldType mismatch for SISArray") + + def fromstring(self, string): + # Parse field header. + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + if flen < 4: + raise SISException("not enough data for a complete SISArray header") + + # Get array type (type of SISFields in the array). + atype = struct.unpack("" % ( + len(self.SISFields), fieldnumtoname[self.SISFieldType]) + + # Standard list semantics ([n:m], len, append, insert, pop, del, iteration) + def __getitem__(self, key): + # Support older Python versions as well (v2.0 onwards). + try: + return self.SISFields[key] + except TypeError: + return self.SISFields[key.start:key.stop] + + def __setitem__(self, key, value): + # Support older Python versions as well (v2.0 onwards). + try: + self.SISFields[key] = value + except TypeError: + self.SISFields[key.start:key.stop] = value + + def __delitem__(self, key): + # Support older Python versions as well (v2.0 onwards). + try: + del self.SISFields[key] + except TypeError: + del self.SISFields[key.start:key.stop] + +# Not supported in Python v2.2, where __getitem__() is used instead. +# def __iter__(self): +# return self.SISFields.__iter__() + + def __len__(self): + return self.SISFields.__len__() + + def append(self, obj): + return self.SISFields.append(obj) + + def insert(self, idx, obj): + return self.SISFields.insert(idx, obj) + + def extend(self, iterable): + return self.SISFields.extend(iterable) + + def pop(self): + return self.SISFields.pop() + +class SISCompressed(SISFieldSpecial): + '''A compression wrapper for another SISField or raw data''' + def __init__(self, **kwds): + # Set default values. + self.CompressionAlgorithm = None + self.Data = None + + if "rawdatainside" in kwds: + self.rawdatainside = kwds["rawdatainside"] + del kwds["rawdatainside"] + else: + # Wrap a SISField by default. + self.rawdatainside = False + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + # Check that all required instance variables are set. + if self.CompressionAlgorithm == None or self.Data == None: + raise AttributeError("missing '%s' or '%s' attribute for '%s'" % + ("CompressionAlgorithm", "Data", + self.__class__.__name__)) + + # Check that the compression algorithm is a known one. + if self.CompressionAlgorithm not in (ECompressAuto, ECompressNone, + ECompressDeflate): + raise TypeError("invalid CompressionAlgorithm '%d'" % + self.CompressionAlgorithm) + + def fromstring(self, string): + # Parse field header. + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + if flen < 12: + raise SISException("SISCompressed contents too short") + + compalgo, uncomplen = struct.unpack("= len(string): + # Compression is not beneficial, use data as-is. + cstring = string + compalgo = ECompressNone + elif self.CompressionAlgorithm == ECompressNone: + # No compression, simply use data as-is. + cstring = string + compalgo = ECompressNone + elif self.CompressionAlgorithm == ECompressDeflate: + # Already handled above. + pass + else: + raise SISException("invalid SISCompressed algorithm '%d'" % + self.CompressionAlgorithm) + + # Construct the SISCompressed and SISField headers. + chdr = struct.pack("" % (dtype, compalgo) + +class SISBlob(SISFieldSpecial): + '''Arbitrary binary data holder''' + def __init__(self, **kwds): + # Set default values. + self.Data = None + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + # Check that all required instance variables are set. + if self.Data == None: + raise AttributeError("missing '%s' attribute for '%s'" % + ("Data", self.__class__.__name__)) + + def fromstring(self, string): + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + # Does not get any simpler than this. + self.Data = string[hdrlen:(hdrlen + flen)] + + def tostring(self): + # TODO: Heavy on memory, optimize (new string type with concat.) + return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(self.Data)), + self.Data, makesisfieldpadding(len(self.Data))) + + def __str__(self): + return u"" % len(self.Data) + +class SISFileData(SISFieldSpecial): + '''File binary data holder (wraps a special SISCompressed SISField)''' + def __init__(self, **kwds): + # Create a special SISCompressed object. + self.FileData = SISCompressed(CompressionAlgorithm = ECompressNone, + Data = "", rawdatainside = True) + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + def fromstring(self, string): + # Parse field header. + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + if flen < 20: + raise SISException("SISFileData contents too short") + + self.FileData.fromstring(string[hdrlen:(hdrlen + flen)]) + + def tostring(self): + string = self.FileData.tostring() + + # TODO: Heavy on memory, optimize (new string type with concat.) + return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(string)), + string, makesisfieldpadding(len(string))) + + def getcompressedlength(self): + # TODO: This is stupid! Compressing the data just + # to find the resulting length is not very efficient... + string = self.FileData.tostring() + + ftype, hdrlen, flen, padlen = parsesisfieldheader(string) + + return (flen - 12) # SISCompressed has an internal header of 12 bytes. + + def __str__(self): + return "" % len(self.FileData.Data) + +class SISCapabilities(SISFieldSpecial): + '''Variable length capability bitmask''' + def __init__(self, **kwds): + # Set default values. + self.Capabilities = None + + # Parse keyword parameters. + SISFieldSpecial.__init__(self, **kwds) + + # Check that all required instance variables are set. + if self.Capabilities == None: + raise AttributeError("missing '%s' attribute for '%s'" % + ("Capabilities", self.__class__.__name__)) + + # Check that the bitmask is a multiple of 32 bits. + if len(self.Capabilities) & 3 != 0: + raise SISException("capabilities length not a multiple of 32 bits") + + def fromstring(self, string): + ftype, hdrlen, flen, padlen = parsesisfieldheader(string, + self.fieldtype) + + caps = string[hdrlen:(hdrlen + flen)] + if len(caps) & 3 != 0: + raise SISException("capabilities length not a multiple of 32 bits") + + self.Capabilities = caps + + def tostring(self): + if len(self.Capabilities) & 3 != 0: + raise SISException("capabilities length not a multiple of 32 bits") + + return "%s%s" % (makesisfieldheader(self.fieldtype, + len(self.Capabilities)), self.Capabilities) + + +############################################################################## +# Normal SISField subclasses (fields containing only other fields and integers) +############################################################################## + +class SISVersion(SISFieldNormal): + '''Major, minor and build numbers''' + def __init__(self, **kwds): + self.subfields = [ + ("Major", self.FTYPE_INTEGRAL, "" % (self.Major, self.Minor, self.Build) + +class SISVersionRange(SISFieldNormal): + '''A range of two SISVersions, or optionally only one''' + def __init__(self, **kwds): + self.subfields = [ + ("FromVersion", self.FTYPE_MANDATORY, "SISVersion"), + ("ToVersion", self.FTYPE_OPTIONAL, "SISVersion")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + + def __str__(self): + ver1 = "from %d, %d, %d" % (self.FromVersion.Major, + self.FromVersion.Minor, + self.FromVersion.Build) + ver2 = "onwards" + if self.ToVersion: + ver2 = "to %d, %d, %d" % (self.ToVersion.Major, + self.ToVersion.Minor, + self.ToVersion.Build) + return "" % (ver1, ver2) + +class SISDate(SISFieldNormal): + '''Year, month (0-11) and day (1-31)''' + def __init__(self, **kwds): + self.subfields = [ + ("Year", self.FTYPE_INTEGRAL, "" % (self.Year, self.Month + 1, + self.Day) + +class SISTime(SISFieldNormal): + '''Hours (0-23), minutes (0-59) and seconds (0-59)''' + def __init__(self, **kwds): + self.subfields = [ + ("Hours", self.FTYPE_INTEGRAL, "" % ( + self.Hours, self.Minutes, self.Seconds) + +class SISDateTime(SISFieldNormal): + '''A bundled SISDate and a SISTime''' + def __init__(self, **kwds): + self.subfields = [ + ("Date", self.FTYPE_MANDATORY, "SISDate"), + ("Time", self.FTYPE_MANDATORY, "SISTime")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + + def __str__(self): + return "" % ( + self.Date.Year, self.Date.Month, self.Date.Day, + self.Time.Hours, self.Time.Minutes, self.Time.Seconds) + +class SISUid(SISFieldNormal): + '''A 32-bit Symbian OS UID''' + def __init__(self, **kwds): + self.subfields = [("UID1", self.FTYPE_INTEGRAL, "" % self.UID1 + +class SISLanguage(SISFieldNormal): + '''A Symbian OS language number''' + def __init__(self, **kwds): + self.subfields = [("Language", self.FTYPE_INTEGRAL, "" % (self.Language, lname) + +class SISContents(SISFieldNormal): + '''The root type of a SIS file''' + def __init__(self, **kwds): + self.subfields = [ + ("ControllerChecksum", self.FTYPE_OPTIONAL, "SISControllerChecksum"), + ("DataChecksum", self.FTYPE_OPTIONAL, "SISDataChecksum"), + ("Controller", self.FTYPE_MANDATORY, "SISCompressed"), + ("Data", self.FTYPE_MANDATORY, "SISData")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + + def __str__(self): + cksum1 = "N/A" + if self.ControllerChecksum: + cksum1 = "0x%04x" % self.ControllerChecksum.Checksum + cksum2 = "N/A" + if self.DataChecksum: + cksum2 = "0x%04x" % self.DataChecksum.Checksum + return "" % (cksum1, cksum2) + +class SISController(SISFieldNormal): + '''SIS file metadata''' + def __init__(self, **kwds): + # Convert a list of signatures to separate parameters + # so that base class constructor can parse them. + # Support upto MAXNUMSIGNATURES signature certificates. + if "Signatures" in kwds: + signatures = kwds["Signatures"] + if len(signatures) > MAXNUMSIGNATURES: + raise ValueError("too many signatures for SISController") + for n in xrange(len(signatures)): + kwds["Signature%d" % n] = signatures[n] + del kwds["Signatures"] + + # DataIndex is really not optional. However, calculating + # signatures require that SISController strings without + # the DataIndex field can be generated. + self.subfields = [ + ("Info", self.FTYPE_MANDATORY, "SISInfo"), + ("Options", self.FTYPE_MANDATORY, "SISSupportedOptions"), + ("Languages", self.FTYPE_MANDATORY, "SISSupportedLanguages"), + ("Prerequisites", self.FTYPE_MANDATORY, "SISPrerequisites"), + ("Properties", self.FTYPE_MANDATORY, "SISProperties"), + ("Logo", self.FTYPE_OPTIONAL, "SISLogo"), + ("InstallBlock", self.FTYPE_MANDATORY, "SISInstallBlock"), + ("Signature0", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature1", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature2", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature3", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature4", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature5", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature6", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("Signature7", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), + ("DataIndex", self.FTYPE_OPTIONAL, "SISDataIndex")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + + # Helper routines to deal with the special signature fields. + def getsignatures(self): + # Return signatures as a list. + signatures = [] + for n in xrange(MAXNUMSIGNATURES): + sig = self.__dict__["Signature%d" % n] + if sig != None: + signatures.append(sig) + return signatures + + def setsignatures(self, signatures): + # Replace signatures with the ones from list. If there are + # less than MAXNUMSIGNATURES signatures in the list, the + # rest are erased. To erase all signatures, call + # controller.setsignatures([]). + numsig = len(signatures) + if numsig > MAXNUMSIGNATURES: + raise ValueError("too many signatures for SISController") + for n in xrange(MAXNUMSIGNATURES): + if n < numsig: + sig = signatures[n] + else: + sig = None + self.__dict__["Signature%d" % n] = sig + +class SISInfo(SISFieldNormal): + '''Information about the SIS file''' + def __init__(self, **kwds): + self.subfields = [ + ("UID", self.FTYPE_MANDATORY, "SISUid"), + ("VendorUniqueName", self.FTYPE_MANDATORY, "SISString"), + ("Names", self.FTYPE_ARRAY, "SISString"), + ("VendorNames", self.FTYPE_ARRAY, "SISString"), + ("Version", self.FTYPE_MANDATORY, "SISVersion"), + ("CreationTime", self.FTYPE_MANDATORY, "SISDateTime"), + ("InstallType", self.FTYPE_INTEGRAL, "" % self.Checksum + +class SISDataChecksum(SISFieldNormal): + '''CCITT CRC-16 of the SISData SISField''' + def __init__(self, **kwds): + self.subfields = [("Checksum", self.FTYPE_INTEGRAL, "" % self.Checksum + +class SISSignature(SISFieldNormal): + '''Cryptographic signature of preceding SIS metadata''' + def __init__(self, **kwds): + self.subfields = [ + ("SignatureAlgorithm", self.FTYPE_MANDATORY, "SISSignatureAlgorithm"), + ("SignatureData", self.FTYPE_MANDATORY, "SISBlob")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + +class SISSignatureAlgorithm(SISFieldNormal): + '''Object identifier string of a signature algorithm''' + def __init__(self, **kwds): + self.subfields = [ + ("AlgorithmIdentifier", self.FTYPE_MANDATORY, "SISString")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + + def __str__(self): + return "" % ( + self.AlgorithmIdentifier.String) + +class SISSignatureCertificateChain(SISFieldNormal): + '''An array of SISSignatures and a SIScertificateChain + for signature validation''' + def __init__(self, **kwds): + self.subfields = [ + ("Signatures", self.FTYPE_ARRAY, "SISSignature"), + ("CertificateChain", self.FTYPE_MANDATORY, "SISCertificateChain")] + + # Parse keyword parameters. + SISFieldNormal.__init__(self, **kwds) + +class SISDataIndex(SISFieldNormal): + '''Data index for files belonging to a SISController''' + def __init__(self, **kwds): + self.subfields = [("DataIndex", self.FTYPE_INTEGRAL, "" % self.DataIndex + + +############################################################################## +# Utility dictionaries +############################################################################## + +fieldinfo = [ + (1, "SISString", SISString), + (2, "SISArray", SISArray), + (3, "SISCompressed", SISCompressed), + (4, "SISVersion", SISVersion), + (5, "SISVersionRange", SISVersionRange), + (6, "SISDate", SISDate), + (7, "SISTime", SISTime), + (8, "SISDateTime", SISDateTime), + (9, "SISUid", SISUid), + (11, "SISLanguage", SISLanguage), + (12, "SISContents", SISContents), + (13, "SISController", SISController), + (14, "SISInfo", SISInfo), + (15, "SISSupportedLanguages", SISSupportedLanguages), + (16, "SISSupportedOptions", SISSupportedOptions), + (17, "SISPrerequisites", SISPrerequisites), + (18, "SISDependency", SISDependency), + (19, "SISProperties", SISProperties), + (20, "SISProperty", SISProperty), +# SISSignatures: Legacy field type, not used +# (21, "SISSignatures", SISSignatures), + (22, "SISCertificateChain", SISCertificateChain), + (23, "SISLogo", SISLogo), + (24, "SISFileDescription", SISFileDescription), + (25, "SISHash", SISHash), + (26, "SISIf", SISIf), + (27, "SISElseIf", SISElseIf), + (28, "SISInstallBlock", SISInstallBlock), + (29, "SISExpression", SISExpression), + (30, "SISData", SISData), + (31, "SISDataUnit", SISDataUnit), + (32, "SISFileData", SISFileData), + (33, "SISSupportedOption", SISSupportedOption), + (34, "SISControllerChecksum", SISControllerChecksum), + (35, "SISDataChecksum", SISDataChecksum), + (36, "SISSignature", SISSignature), + (37, "SISBlob", SISBlob), + (38, "SISSignatureAlgorithm", SISSignatureAlgorithm), + (39, "SISSignatureCertificateChain", SISSignatureCertificateChain), + (40, "SISDataIndex", SISDataIndex), + (41, "SISCapabilities", SISCapabilities) +] + +fieldnumtoclass = dict([(num, klass) for num, name, klass in fieldinfo]) +fieldnametonum = dict([(name, num) for num, name, klass in fieldinfo]) +fieldnumtoname = dict([(num, name) for num, name, klass in fieldinfo])