src/tools/py2sis/ensymble/sisfield.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 ##############################################################################
       
     5 # sisfield.py - Symbian OS v9.x SIS file utilities, SISField support classes
       
     6 # Copyright 2006, 2007 Jussi Ylänen
       
     7 #
       
     8 # This file is part of Ensymble developer utilities for Symbian OS(TM).
       
     9 #
       
    10 # Ensymble is free software; you can redistribute it and/or modify
       
    11 # it under the terms of the GNU General Public License as published by
       
    12 # the Free Software Foundation; either version 2 of the License, or
       
    13 # (at your option) any later version.
       
    14 #
       
    15 # Ensymble is distributed in the hope that it will be useful,
       
    16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    18 # GNU General Public License for more details.
       
    19 #
       
    20 # You should have received a copy of the GNU General Public License
       
    21 # along with Ensymble; if not, write to the Free Software
       
    22 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    23 ##############################################################################
       
    24 
       
    25 import struct
       
    26 import zlib
       
    27 
       
    28 import symbianutil
       
    29 
       
    30 
       
    31 # TODO: 1. Make all tostring() methods cache the result.
       
    32 # TODO: 2. Allow modifying objects after creation, keeping string cache in sync.
       
    33 # TODO: 3. Implement a list-of-strings type, which the tostring() (or
       
    34 #          some other method with a better name) can use, to eliminate
       
    35 #          superfluous string concatenations.
       
    36 
       
    37 
       
    38 ##############################################################################
       
    39 # Parameters
       
    40 ##############################################################################
       
    41 
       
    42 DEBUG               = 0     # (0: None, 1: Basic, 2: Verbose)
       
    43 MAXNUMSIGNATURES    = 8     # Maximum number of signatures in a SISController
       
    44 
       
    45 
       
    46 ##############################################################################
       
    47 # Public SISField constants (in original Symbian OS naming style)
       
    48 ##############################################################################
       
    49 
       
    50 ECompressAuto                   = -1    # Not a real compression type
       
    51 ECompressNone                   = 0
       
    52 ECompressDeflate                = 1
       
    53 
       
    54 EInstInstallation               = 0
       
    55 EInstAugmentation               = 1
       
    56 EInstPartialUpgrade             = 2
       
    57 EInstPreInstalledApp            = 3
       
    58 EInstPreInstalledPatch          = 4
       
    59 
       
    60 EInstFlagShutdownApps           = 1
       
    61 
       
    62 EOpInstall                      = 1
       
    63 EOpRun                          = 2
       
    64 EOpText                         = 4
       
    65 EOpNull                         = 8
       
    66 
       
    67 EInstVerifyOnRestore            = 1 << 15
       
    68 
       
    69 EInstFileRunOptionInstall       = 1 << 1
       
    70 EInstFileRunOptionUninstall     = 1 << 2
       
    71 EInstFileRunOptionByMimeType    = 1 << 3
       
    72 EInstFileRunOptionWaitEnd       = 1 << 4
       
    73 EInstFileRunOptionSendEnd       = 1 << 5
       
    74 
       
    75 EInstFileTextOptionContinue     = 1 << 9    # Not used by makesis v. 4, 0, 0, 2
       
    76 EInstFileTextOptionSkipIfNo     = 1 << 10
       
    77 EInstFileTextOptionAbortIfNo    = 1 << 11
       
    78 EInstFileTextOptionExitIfNo     = 1 << 12
       
    79 
       
    80 ESISHashAlgSHA1                 = 1
       
    81 
       
    82 ESISSignatureAlgSHA1RSA         = "1.2.840.113549.1.1.5"
       
    83 ESISSignatureAlgSHA1DSA         = "1.2.840.10040.4.3"
       
    84 
       
    85 EBinOpEqual                     = 1
       
    86 EBinOpNotEqual                  = 2
       
    87 EBinOpGreaterThan               = 3
       
    88 EBinOpLessThan                  = 4
       
    89 EBinOpGreaterOrEqual            = 5
       
    90 EBinOpLessOrEqual               = 6
       
    91 ELogOpAnd                       = 7
       
    92 ELogOpOr                        = 8
       
    93 EUnaryOpNot                     = 9
       
    94 EFuncExists                     = 10
       
    95 EFuncAppProperties              = 11
       
    96 EFuncDevProperties              = 12
       
    97 EPrimTypeString                 = 13
       
    98 EPrimTypeOption                 = 14
       
    99 EPrimTypeVariable               = 15
       
   100 EPrimTypeNumber                 = 16
       
   101 
       
   102 EVarLanguage                    = 0x1000
       
   103 EVarRemoteInstall               = 0x1001
       
   104 
       
   105 
       
   106 ##############################################################################
       
   107 # Public exception class for SIS parsing / generation
       
   108 ##############################################################################
       
   109 
       
   110 class SISException(StandardError):
       
   111     '''SIS parsing / generation error'''
       
   112     pass
       
   113 
       
   114 
       
   115 ##############################################################################
       
   116 # Public module-level functions
       
   117 ##############################################################################
       
   118 
       
   119 def SISField(fromstring, exactlength = True):
       
   120     '''Generator function for creating SISField subclass instances from a string
       
   121 
       
   122     If exactlength is False, return a tuple of (SISField, bytes consumed).
       
   123     Otherwise return SISField directly.'''
       
   124 
       
   125     # Determine SISField subclass type.
       
   126     ftype, hdrlen, flen, padlen = parsesisfieldheader(fromstring, None,
       
   127                                                       exactlength)
       
   128 
       
   129     try:
       
   130         fclass = fieldnumtoclass[ftype]
       
   131     except KeyError:
       
   132         raise SISException("invalid SISField type '%d'" % ftype)
       
   133 
       
   134     # Limit string to actual data length (in case exactlength was False).
       
   135     fromstring = fromstring[:(hdrlen + flen + padlen)]
       
   136 
       
   137     # Create a subclass instance.
       
   138     field = fclass(fromstring = fromstring)
       
   139 
       
   140     if exactlength:
       
   141         return field
       
   142     else:
       
   143         # Normal SISField subclasses use SISField() to parse adjacent SISFields.
       
   144         # Number of consumed bytes need to be passed back in that case. This
       
   145         # may violate the idea of a generator function somewhat, but eliminates
       
   146         # a bit of duplicated code.
       
   147         return (field, len(fromstring))
       
   148 
       
   149 def stripheaderandpadding(fromstring):
       
   150     '''Return the actual content of SISField string.
       
   151 
       
   152     stripheaderandpadding(...) -> contents
       
   153 
       
   154     fromstring  a SISField string
       
   155 
       
   156     contents    SISField contents without the header and padding'''
       
   157 
       
   158     # Parse field header.
       
   159     ftype, hdrlen, flen, padlen = parsesisfieldheader(fromstring)
       
   160 
       
   161     # Return field contents.
       
   162     return fromstring[hdrlen:(hdrlen + flen)]
       
   163 
       
   164 
       
   165 ##############################################################################
       
   166 # Module-level functions which are normally only used by this module
       
   167 ##############################################################################
       
   168 
       
   169 def parsesisfieldheader(string, requiredtype = None, exactlength = True):
       
   170     '''Parse the header of a SISField string and return the field type and
       
   171     lengths of the various parts (header, data, padding). Optionally, check
       
   172     that the type is correct and that the string length is not too long.'''
       
   173 
       
   174     hdrlen = 8
       
   175     if hdrlen > len(string):
       
   176         raise SISException("not enough data for a complete SISField header")
       
   177 
       
   178     # Get SISField type and first part of the length.
       
   179     ftype, flen = struct.unpack("<LL", string[:8])
       
   180 
       
   181     # Get rest of the SISField length, 31-bit or 63-bit.
       
   182     flen2 = None
       
   183     if flen & 0x8000000L:
       
   184         # 63-bit length, read rest of length.
       
   185         hdrlen = 12
       
   186         if hdrlen > len(string):
       
   187             raise SISException("not enough data for a complete SISField header")
       
   188         flen2 = struct.unpack("<L", string[8:12])[0]
       
   189         flen = (flen & 0x7ffffffL) | (flen2 << 31)
       
   190 
       
   191     # Calculate padding to 32-bit boundary.
       
   192     padlen = ((flen + 3) & ~0x3L) - flen
       
   193 
       
   194     if requiredtype != None and ftype != requiredtype:
       
   195         raise SISException("invalid SISField type '%d'" % ftype)
       
   196 
       
   197     if (hdrlen + flen + padlen) > len(string):
       
   198         raise SISException("SISField contents too short")
       
   199 
       
   200     # Allow oversized strings when parsing recursive SISFields.
       
   201     if exactlength and (hdrlen + flen + padlen) < len(string):
       
   202         raise SISException("SISField contents too long")
       
   203 
       
   204     return ftype, hdrlen, flen, padlen
       
   205 
       
   206 def makesisfieldheader(fieldtype, fieldlen):
       
   207     '''Create a SISField header string from type and length.'''
       
   208 
       
   209     if fieldlen < 0x80000000L:
       
   210         # 31-bit length
       
   211         return struct.pack("<LL", fieldtype, fieldlen)
       
   212     else:
       
   213         # 63-bit length
       
   214         fieldlen2 = fieldlen >> 31
       
   215         fieldlen  = (fieldlen & 0x7fffffffL) | 0x80000000L
       
   216         return struct.pack("<LLL", fieldtype, fieldlen, fieldlen2)
       
   217 
       
   218 def makesisfieldpadding(fieldlen):
       
   219     '''Create a string of zero bytes for padding to 32-bit boundary.
       
   220     Parameter may be either the whole field length (header + data)
       
   221     or just the data length.'''
       
   222 
       
   223     # Calculate padding to 32-bit boundary.
       
   224     padlen = ((fieldlen + 3) & ~0x3L) - fieldlen
       
   225 
       
   226     return "\x00" * padlen
       
   227 
       
   228 
       
   229 ##############################################################################
       
   230 # SISField base classes
       
   231 ##############################################################################
       
   232 
       
   233 class SISFieldBase(object):
       
   234     '''SISField base class'''
       
   235     def __init__(self, **kwds):
       
   236         if DEBUG > 0:
       
   237             # DEBUG: Print class name during initialization.
       
   238             print "%s.__init__()" % self.__class__.__name__
       
   239 
       
   240         # Get the names of all instance variables.
       
   241         validkwds = self.__dict__.keys()
       
   242 
       
   243         # Filter out private instance variables (all in lowercase).
       
   244         validkwds = filter(lambda s: s != s.lower(), validkwds)
       
   245 
       
   246         # Set type code.
       
   247         self.fieldtype = fieldnametonum[self.__class__.__name__]
       
   248 
       
   249         if "fromstring" in kwds:
       
   250             if DEBUG > 1:
       
   251                 # DEBUG: Dump of string parameter.
       
   252                 print repr(kwds["fromstring"])
       
   253 
       
   254             # Load instance variables from string.
       
   255             if len(kwds) != 1:
       
   256                 raise TypeError(
       
   257                     "keyword 'fromstring' may not be given with other keywords")
       
   258             self.fromstring(kwds["fromstring"])
       
   259         else:
       
   260             # Load instance variables from keywords.
       
   261             # Only accept existing variable names.
       
   262             for kwd in kwds.keys():
       
   263                 if kwd not in validkwds:
       
   264                     raise AttributeError("'%s' object has no attribute '%s'" %
       
   265                                          (self.__class__.__name__, kwd))
       
   266                 self.__dict__[kwd] = kwds[kwd]
       
   267 
       
   268     def __str__(self):
       
   269         # Default __str__() for SISFields, only return the field name.
       
   270         return "<%s>" % self.__class__.__name__
       
   271 
       
   272 class SISFieldNormal(SISFieldBase):
       
   273     '''SISField base class for normal fields (fields containing only other
       
   274     fields and integers)'''
       
   275 
       
   276     # Subfield types
       
   277     FTYPE_INTEGRAL  = 0     # Integer
       
   278     FTYPE_MANDATORY = 1     # Mandatory SISField
       
   279     FTYPE_OPTIONAL  = 2     # Optional SISField
       
   280     FTYPE_ARRAY     = 3     # SISArray with zero or more items
       
   281 
       
   282     def __init__(self, **kwds):
       
   283         # Initialize instance variables to None.
       
   284         for fattr, fkind, ffmt in self.subfields:
       
   285             self.__dict__[fattr] = None
       
   286 
       
   287         # Set instance variables.
       
   288         SISFieldBase.__init__(self, **kwds)
       
   289 
       
   290         for fattr, fkind, ffmt in self.subfields:
       
   291             # Check that all required instance variables are set.
       
   292             if fkind != self.FTYPE_OPTIONAL and self.__dict__[fattr] == None:
       
   293                 raise AttributeError("missing '%s' attribute for '%s'" %
       
   294                                      (fattr, self.__class__.__name__))
       
   295 
       
   296             if fkind in (self.FTYPE_MANDATORY, self.FTYPE_OPTIONAL):
       
   297                 # Verify SISField types.
       
   298                 if (self.__dict__[fattr] != None and
       
   299                     fieldnumtoname[self.__dict__[fattr].fieldtype] != ffmt):
       
   300                     raise TypeError(
       
   301                         "attribute '%s' for '%s' is of invalid SISField type" %
       
   302                         (fattr, self.__class__.__name__))
       
   303             elif fkind == self.FTYPE_ARRAY:
       
   304                 # Verify SISArray contents.
       
   305                 if (fieldnumtoname[self.__dict__[fattr].fieldtype] != "SISArray"
       
   306                     or
       
   307                     fieldnumtoname[self.__dict__[fattr].SISFieldType] != ffmt):
       
   308                     raise TypeError(
       
   309                         "SISArray attribute '%s' for '%s' is of invalid type" %
       
   310                         (fattr, self.__class__.__name__))
       
   311 
       
   312     def fromstring(self, string):
       
   313         # Parse field header.
       
   314         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   315                                                           self.fieldtype)
       
   316 
       
   317         # Recursively parse subfields.
       
   318         pos = hdrlen
       
   319         reuse = None    # SISField to re-use or None
       
   320         try:
       
   321             for fattr, fkind, ffmt in self.subfields:
       
   322                 field = None    # No value by default
       
   323 
       
   324                 if fkind == self.FTYPE_INTEGRAL:
       
   325                     # Integer, unpack it.
       
   326                     if reuse:
       
   327                         # It is an error if there is a field to
       
   328                         # re-use present at this time.
       
   329                         raise ValueError("integral field preceded optional")
       
   330 
       
   331                     n = struct.calcsize(ffmt)
       
   332                     field = struct.unpack(ffmt, string[pos:(pos + n)])[0]
       
   333                     pos += n
       
   334                 else:
       
   335                     # SISField, read data from string or
       
   336                     # re-use field from previous round.
       
   337                     if not reuse:
       
   338                         # No old field to handle, convert string to SISField.
       
   339                         if pos < (hdrlen + flen):
       
   340                             field, n = SISField(string[pos:(hdrlen + flen)],
       
   341                                                 False)
       
   342                             pos += n
       
   343                         elif fkind != self.FTYPE_OPTIONAL:
       
   344                             # No more data in string, raise an exception.
       
   345                             raise ValueError("unexpected end-of-data")
       
   346                     else:
       
   347                         # Field from previous round present, re-use it.
       
   348                         field = reuse
       
   349                         reuse = None
       
   350 
       
   351                     # Verify SISField type.
       
   352                     if field != None:
       
   353                         fname = fieldnumtoname[field.fieldtype]
       
   354                         if fkind == self.FTYPE_ARRAY:
       
   355                             if (fname != "SISArray" or
       
   356                                 fieldnumtoname[field.SISFieldType] != ffmt):
       
   357                                     # Wrong type of fields inside SISArray,
       
   358                                     # raise an exception.
       
   359                                     raise ValueError("invalid SISArray type")
       
   360                         elif fkind == self.FTYPE_MANDATORY:
       
   361                             if fname != ffmt:
       
   362                                 # Mandatory field missing, raise an exception.
       
   363                                 raise ValueError("mandatory field missing")
       
   364                         elif fkind == self.FTYPE_OPTIONAL:
       
   365                             if fname != ffmt:
       
   366                                 # Wrong type for optional field. Skip optional
       
   367                                 # field and re-use already parsed field on next
       
   368                                 # round.
       
   369                                 reuse = field
       
   370                                 field = None
       
   371 
       
   372                 # Introduce field as an instance variable.
       
   373                 self.__dict__[fattr] = field
       
   374         except (ValueError, KeyError, struct.error):
       
   375             if DEBUG > 0:
       
   376                 # DEBUG: Raise a detailed exception.
       
   377                 raise
       
   378             else:
       
   379                 raise SISException("invalid '%s' structure" %
       
   380                                    self.__class__.__name__)
       
   381 
       
   382     def tostring(self):
       
   383         # Recursively create strings from subfields.
       
   384         fstrings = [None]
       
   385         totlen = 0
       
   386         for fattr, fkind, ffmt in self.subfields:
       
   387             field = self.__dict__[fattr]
       
   388             if fkind == self.FTYPE_INTEGRAL:
       
   389                 # Integer, pack it.
       
   390                 try:
       
   391                     string = struct.pack(ffmt, field)
       
   392                 except:
       
   393                     print "%s %s %s" % (self.__class__, ffmt, repr(field))
       
   394                     raise
       
   395                 fstrings.append(string)
       
   396                 totlen += len(string)
       
   397             else:
       
   398                 if field == None:
       
   399                     if fkind == self.FTYPE_OPTIONAL:
       
   400                         # Optional field missing, skip it.
       
   401                         pass
       
   402                     else:
       
   403                         # Mandatory field missing, raise an exception.
       
   404                         raise SISException("field '%s' missing for '%s'" %
       
   405                                            (fattr, self.__dict__.__name__))
       
   406                 else:
       
   407                     # Convert SISField to string.
       
   408                     string = field.tostring()
       
   409                     fstrings.append(string)
       
   410                     totlen += len(string)
       
   411 
       
   412         try:
       
   413             del string  # Try to free some memory early.
       
   414         except:
       
   415             pass
       
   416 
       
   417         fstrings[0] = makesisfieldheader(self.fieldtype, totlen)
       
   418         fstrings.append(makesisfieldpadding(totlen))
       
   419 
       
   420         # TODO: Heavy on memory, optimize (new string type with concat.)
       
   421         return "".join(fstrings)
       
   422 
       
   423 class SISFieldSpecial(SISFieldBase):
       
   424     '''SISField base class for special fields (fields that do something
       
   425     special for the data they contain or the data is of variable length)'''
       
   426     def __init__(self, **kwds):
       
   427         # Set instance variables.
       
   428         SISFieldBase.__init__(self, **kwds)
       
   429 
       
   430 
       
   431 ##############################################################################
       
   432 # Special SISField subclasses
       
   433 ##############################################################################
       
   434 
       
   435 class SISString(SISFieldSpecial):
       
   436     '''UCS-2 (UTF-16LE) string'''
       
   437     def __init__(self, **kwds):
       
   438         # Set default values.
       
   439         self.String = None
       
   440 
       
   441         # Parse keyword parameters.
       
   442         SISFieldSpecial.__init__(self, **kwds)
       
   443 
       
   444         # Check that all required instance variables are set.
       
   445         if self.String == None:
       
   446             raise AttributeError("missing '%s' attribute for '%s'" %
       
   447                                  ("String", self.__class__.__name__))
       
   448 
       
   449     def fromstring(self, string):
       
   450         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   451                                                           self.fieldtype)
       
   452         self.String = string[hdrlen:(hdrlen + flen)].decode("UTF-16LE")
       
   453 
       
   454     def tostring(self):
       
   455         encstr = self.String.encode("UTF-16LE")
       
   456         return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(encstr)),
       
   457                            encstr, makesisfieldpadding(len(encstr)))
       
   458 
       
   459     def __str__(self):
       
   460         # Always return Unicode string. Let Python default encoding handle it.
       
   461         return u"<SISString '%s'>" % self.String
       
   462 
       
   463 class SISArray(SISFieldSpecial):
       
   464     '''An array of other SISFields, all of the same type'''
       
   465     def __init__(self, **kwds):
       
   466         # Set default values.
       
   467         self.SISFieldType = None    # Invalid type, checked later.
       
   468         self.SISFields = []
       
   469 
       
   470         # Parse keyword parameters.
       
   471         SISFieldSpecial.__init__(self, **kwds)
       
   472 
       
   473         # Make a copy of the supplied list
       
   474         # (caller may try to modify the original).
       
   475         self.SISFields = self.SISFields[:]
       
   476 
       
   477         # Allow type to be a string or number.
       
   478         self.SISFieldType = fieldnametonum.get(self.SISFieldType,
       
   479                                                self.SISFieldType)
       
   480 
       
   481         # Get type of first field if not given explicitly.
       
   482         if self.SISFieldType == None:
       
   483             if len(self.SISFields) > 0:
       
   484                 self.SISFieldType = self.SISFields[0].fieldtype
       
   485             else:
       
   486                 raise AttributeError("no SISFieldType given")
       
   487 
       
   488         # Check that all fields are of the same type.
       
   489         for f in self.SISFields:
       
   490             if f.fieldtype != self.SISFieldType:
       
   491                 raise TypeError("SISFieldType mismatch for SISArray")
       
   492 
       
   493     def fromstring(self, string):
       
   494         # Parse field header.
       
   495         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   496                                                           self.fieldtype)
       
   497 
       
   498         if flen < 4:
       
   499             raise SISException("not enough data for a complete SISArray header")
       
   500 
       
   501         # Get array type (type of SISFields in the array).
       
   502         atype = struct.unpack("<L", string[hdrlen:(hdrlen + 4)])[0]
       
   503 
       
   504         pos = hdrlen + 4    # Skip SISFieldType.
       
   505         totlen = hdrlen + flen
       
   506         fields = []
       
   507         while pos < totlen:
       
   508             # Get first part of the SISField length.
       
   509             alen = struct.unpack("<L", string[pos:(pos+4)])[0]
       
   510             pos += 4
       
   511 
       
   512             # Get rest of the SISField length, 31-bit or 63-bit.
       
   513             alen2 = None
       
   514             if alen & 0x8000000L:
       
   515                 # 63-bit length, read rest of length.
       
   516                 alen2 = struct.unpack("<L", string[pos:(pos+4)])[0]
       
   517                 alen = (alen & 0x7ffffffL) | (alen2 << 31)
       
   518                 pos += 4
       
   519 
       
   520             # Calculate padding to 32-bit boundary.
       
   521             apadlen = ((alen + 3) & ~0x3L) - alen
       
   522 
       
   523             # Construct a valid SISField header and proper padding.
       
   524             fhdr = makesisfieldheader(atype, alen)
       
   525             fpad = makesisfieldpadding(alen)
       
   526 
       
   527             # Create a SISField.
       
   528             # TODO: Heavy on memory, optimize (new string type with concat.)
       
   529             field = SISField(fhdr + string[pos:(pos + alen)] + fpad)
       
   530 
       
   531             fields.append(field)
       
   532 
       
   533             pos += alen + apadlen
       
   534 
       
   535         self.SISFieldType = atype
       
   536         self.SISFields = fields
       
   537 
       
   538     def tostring(self):
       
   539         totlen = 4  # For the SISFieldType of the array
       
   540         fstrings = ["", struct.pack("<L", self.SISFieldType)]
       
   541         for f in self.SISFields:
       
   542             s = f.tostring()[4:]    # Strip type code.
       
   543             fstrings.append(s)
       
   544             totlen += len(s)
       
   545         fstrings[0] = makesisfieldheader(self.fieldtype, totlen)
       
   546         fstrings.append(makesisfieldpadding(totlen))    # Not really necessary.
       
   547         return "".join(fstrings)
       
   548 
       
   549     def __str__(self):
       
   550         return "<SISArray of %d %s fields>" % (
       
   551             len(self.SISFields), fieldnumtoname[self.SISFieldType])
       
   552 
       
   553     # Standard list semantics ([n:m], len, append, insert, pop, del, iteration)
       
   554     def __getitem__(self, key):
       
   555         # Support older Python versions as well (v2.0 onwards).
       
   556         try:
       
   557             return self.SISFields[key]
       
   558         except TypeError:
       
   559             return self.SISFields[key.start:key.stop]
       
   560 
       
   561     def __setitem__(self, key, value):
       
   562         # Support older Python versions as well (v2.0 onwards).
       
   563         try:
       
   564             self.SISFields[key] = value
       
   565         except TypeError:
       
   566             self.SISFields[key.start:key.stop] = value
       
   567 
       
   568     def __delitem__(self, key):
       
   569         # Support older Python versions as well (v2.0 onwards).
       
   570         try:
       
   571             del self.SISFields[key]
       
   572         except TypeError:
       
   573             del self.SISFields[key.start:key.stop]
       
   574 
       
   575 # Not supported in Python v2.2, where __getitem__() is used instead.
       
   576 #    def __iter__(self):
       
   577 #        return self.SISFields.__iter__()
       
   578 
       
   579     def __len__(self):
       
   580         return self.SISFields.__len__()
       
   581 
       
   582     def append(self, obj):
       
   583         return self.SISFields.append(obj)
       
   584 
       
   585     def insert(self, idx, obj):
       
   586         return self.SISFields.insert(idx, obj)
       
   587 
       
   588     def extend(self, iterable):
       
   589         return self.SISFields.extend(iterable)
       
   590 
       
   591     def pop(self):
       
   592         return self.SISFields.pop()
       
   593 
       
   594 class SISCompressed(SISFieldSpecial):
       
   595     '''A compression wrapper for another SISField or raw data'''
       
   596     def __init__(self, **kwds):
       
   597         # Set default values.
       
   598         self.CompressionAlgorithm = None
       
   599         self.Data = None
       
   600 
       
   601         if "rawdatainside" in kwds:
       
   602             self.rawdatainside = kwds["rawdatainside"]
       
   603             del kwds["rawdatainside"]
       
   604         else:
       
   605             # Wrap a SISField by default.
       
   606             self.rawdatainside = False
       
   607 
       
   608         # Parse keyword parameters.
       
   609         SISFieldSpecial.__init__(self, **kwds)
       
   610 
       
   611         # Check that all required instance variables are set.
       
   612         if self.CompressionAlgorithm == None or self.Data == None:
       
   613             raise AttributeError("missing '%s' or '%s' attribute for '%s'" %
       
   614                                  ("CompressionAlgorithm", "Data",
       
   615                                   self.__class__.__name__))
       
   616 
       
   617         # Check that the compression algorithm is a known one.
       
   618         if self.CompressionAlgorithm not in (ECompressAuto, ECompressNone,
       
   619                                              ECompressDeflate):
       
   620             raise TypeError("invalid CompressionAlgorithm '%d'" %
       
   621                             self.CompressionAlgorithm)
       
   622 
       
   623     def fromstring(self, string):
       
   624         # Parse field header.
       
   625         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   626                                                           self.fieldtype)
       
   627 
       
   628         if flen < 12:
       
   629             raise SISException("SISCompressed contents too short")
       
   630 
       
   631         compalgo, uncomplen = struct.unpack("<LQ", string[hdrlen:(hdrlen + 12)])
       
   632 
       
   633         if compalgo == ECompressNone:
       
   634             # No compression, use as-is.
       
   635             dstring = string[(hdrlen + 12):(hdrlen + flen)]
       
   636         elif compalgo == ECompressDeflate:
       
   637             # RFC1950 (zlib header and checksum) compression, decompress.
       
   638             dstring = zlib.decompress(string[(hdrlen + 12):(hdrlen + flen)])
       
   639         else:
       
   640             raise SISException("invalid SISCompressed algorithm '%d'" %
       
   641                                compalgo)
       
   642 
       
   643         if uncomplen != len(dstring):
       
   644             raise SISException(
       
   645                 "SISCompressed uncompressed data length mismatch")
       
   646 
       
   647         if self.rawdatainside:
       
   648             # Raw data inside
       
   649             self.Data = dstring
       
   650         else:
       
   651             # SISField inside
       
   652             if dstring != "":
       
   653                 # Construct a SISField out of the decompressed data.
       
   654                 self.Data = SISField(dstring)
       
   655             else:
       
   656                 # Decompressed to nothing, duh!
       
   657                 self.Data = None
       
   658 
       
   659         self.CompressionAlgorithm = compalgo
       
   660 
       
   661     def tostring(self):
       
   662         if self.rawdatainside:
       
   663             # Raw data inside
       
   664             string = self.Data
       
   665         else:
       
   666             # SISField inside
       
   667             if self.Data != None:
       
   668                 # Compress the enclosed SISField.
       
   669                 string = self.Data.tostring()
       
   670             else:
       
   671                 # No data inside, compress an empty string.
       
   672                 string = ""
       
   673 
       
   674         # Compress or not, depending on selected algorithm.
       
   675         if self.CompressionAlgorithm in (ECompressAuto, ECompressDeflate):
       
   676             cstring = zlib.compress(string, 9)  # Maximum compression
       
   677             compalgo = ECompressDeflate
       
   678 
       
   679         if self.CompressionAlgorithm == ECompressAuto:
       
   680             if len(cstring) >= len(string):
       
   681                 # Compression is not beneficial, use data as-is.
       
   682                 cstring = string
       
   683                 compalgo = ECompressNone
       
   684         elif self.CompressionAlgorithm == ECompressNone:
       
   685             # No compression, simply use data as-is.
       
   686             cstring = string
       
   687             compalgo = ECompressNone
       
   688         elif self.CompressionAlgorithm == ECompressDeflate:
       
   689             # Already handled above.
       
   690             pass
       
   691         else:
       
   692             raise SISException("invalid SISCompressed algorithm '%d'" %
       
   693                                self.CompressionAlgorithm)
       
   694 
       
   695         # Construct the SISCompressed and SISField headers.
       
   696         chdr = struct.pack("<LQ", compalgo, len(string))
       
   697         fhdr = makesisfieldheader(self.fieldtype, len(chdr) + len(cstring))
       
   698         fpad = makesisfieldpadding(len(cstring))
       
   699 
       
   700         del string      # Try to free some memory early.
       
   701 
       
   702         # TODO: Heavy on memory, optimize (new string type with concat.)
       
   703         return "%s%s%s%s" % (fhdr, chdr, cstring, fpad)
       
   704 
       
   705     def __str__(self):
       
   706         dtype = (self.rawdatainside and "raw data") or "SISField"
       
   707         compalgo = ("not compressed",
       
   708                     "compressed with \"deflate\"")[self.CompressionAlgorithm]
       
   709         return "<SISCompressed %s, %s>" % (dtype, compalgo)
       
   710 
       
   711 class SISBlob(SISFieldSpecial):
       
   712     '''Arbitrary binary data holder'''
       
   713     def __init__(self, **kwds):
       
   714         # Set default values.
       
   715         self.Data = None
       
   716 
       
   717         # Parse keyword parameters.
       
   718         SISFieldSpecial.__init__(self, **kwds)
       
   719 
       
   720         # Check that all required instance variables are set.
       
   721         if self.Data == None:
       
   722             raise AttributeError("missing '%s' attribute for '%s'" %
       
   723                                  ("Data", self.__class__.__name__))
       
   724 
       
   725     def fromstring(self, string):
       
   726         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   727                                                           self.fieldtype)
       
   728 
       
   729         # Does not get any simpler than this.
       
   730         self.Data = string[hdrlen:(hdrlen + flen)]
       
   731 
       
   732     def tostring(self):
       
   733         # TODO: Heavy on memory, optimize (new string type with concat.)
       
   734         return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(self.Data)),
       
   735                            self.Data, makesisfieldpadding(len(self.Data)))
       
   736 
       
   737     def __str__(self):
       
   738         return u"<SISBlob, %d bytes>" % len(self.Data)
       
   739 
       
   740 class SISFileData(SISFieldSpecial):
       
   741     '''File binary data holder (wraps a special SISCompressed SISField)'''
       
   742     def __init__(self, **kwds):
       
   743         # Create a special SISCompressed object.
       
   744         self.FileData = SISCompressed(CompressionAlgorithm = ECompressNone,
       
   745                                       Data = "", rawdatainside = True)
       
   746 
       
   747         # Parse keyword parameters.
       
   748         SISFieldSpecial.__init__(self, **kwds)
       
   749 
       
   750     def fromstring(self, string):
       
   751         # Parse field header.
       
   752         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   753                                                           self.fieldtype)
       
   754 
       
   755         if flen < 20:
       
   756             raise SISException("SISFileData contents too short")
       
   757 
       
   758         self.FileData.fromstring(string[hdrlen:(hdrlen + flen)])
       
   759 
       
   760     def tostring(self):
       
   761         string = self.FileData.tostring()
       
   762 
       
   763         # TODO: Heavy on memory, optimize (new string type with concat.)
       
   764         return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(string)),
       
   765                            string, makesisfieldpadding(len(string)))
       
   766 
       
   767     def getcompressedlength(self):
       
   768         # TODO: This is stupid! Compressing the data just
       
   769         # to find the resulting length is not very efficient...
       
   770         string = self.FileData.tostring()
       
   771 
       
   772         ftype, hdrlen, flen, padlen = parsesisfieldheader(string)
       
   773 
       
   774         return (flen - 12)  # SISCompressed has an internal header of 12 bytes.
       
   775 
       
   776     def __str__(self):
       
   777         return "<SISFileData, %d bytes>" % len(self.FileData.Data)
       
   778 
       
   779 class SISCapabilities(SISFieldSpecial):
       
   780     '''Variable length capability bitmask'''
       
   781     def __init__(self, **kwds):
       
   782         # Set default values.
       
   783         self.Capabilities = None
       
   784 
       
   785         # Parse keyword parameters.
       
   786         SISFieldSpecial.__init__(self, **kwds)
       
   787 
       
   788         # Check that all required instance variables are set.
       
   789         if self.Capabilities == None:
       
   790             raise AttributeError("missing '%s' attribute for '%s'" %
       
   791                                  ("Capabilities", self.__class__.__name__))
       
   792 
       
   793         # Check that the bitmask is a multiple of 32 bits.
       
   794         if len(self.Capabilities) & 3 != 0:
       
   795             raise SISException("capabilities length not a multiple of 32 bits")
       
   796 
       
   797     def fromstring(self, string):
       
   798         ftype, hdrlen, flen, padlen = parsesisfieldheader(string,
       
   799                                                           self.fieldtype)
       
   800 
       
   801         caps = string[hdrlen:(hdrlen + flen)]
       
   802         if len(caps) & 3 != 0:
       
   803             raise SISException("capabilities length not a multiple of 32 bits")
       
   804 
       
   805         self.Capabilities = caps
       
   806 
       
   807     def tostring(self):
       
   808         if len(self.Capabilities) & 3 != 0:
       
   809             raise SISException("capabilities length not a multiple of 32 bits")
       
   810 
       
   811         return "%s%s" % (makesisfieldheader(self.fieldtype,
       
   812                          len(self.Capabilities)), self.Capabilities)
       
   813 
       
   814 
       
   815 ##############################################################################
       
   816 # Normal SISField subclasses (fields containing only other fields and integers)
       
   817 ##############################################################################
       
   818 
       
   819 class SISVersion(SISFieldNormal):
       
   820     '''Major, minor and build numbers'''
       
   821     def __init__(self, **kwds):
       
   822         self.subfields = [
       
   823             ("Major", self.FTYPE_INTEGRAL, "<l"),
       
   824             ("Minor", self.FTYPE_INTEGRAL, "<l"),
       
   825             ("Build", self.FTYPE_INTEGRAL, "<l")]
       
   826 
       
   827         # Parse keyword parameters.
       
   828         SISFieldNormal.__init__(self, **kwds)
       
   829 
       
   830     def __str__(self):
       
   831         return "<SISVersion %d, %d, %d>" % (self.Major, self.Minor, self.Build)
       
   832 
       
   833 class SISVersionRange(SISFieldNormal):
       
   834     '''A range of two SISVersions, or optionally only one'''
       
   835     def __init__(self, **kwds):
       
   836         self.subfields = [
       
   837             ("FromVersion", self.FTYPE_MANDATORY, "SISVersion"),
       
   838             ("ToVersion",   self.FTYPE_OPTIONAL,  "SISVersion")]
       
   839 
       
   840         # Parse keyword parameters.
       
   841         SISFieldNormal.__init__(self, **kwds)
       
   842 
       
   843     def __str__(self):
       
   844         ver1 = "from %d, %d, %d" % (self.FromVersion.Major,
       
   845                                self.FromVersion.Minor,
       
   846                                self.FromVersion.Build)
       
   847         ver2 = "onwards"
       
   848         if self.ToVersion:
       
   849             ver2 = "to %d, %d, %d" % (self.ToVersion.Major,
       
   850                                    self.ToVersion.Minor,
       
   851                                    self.ToVersion.Build)
       
   852         return "<SISVersionRange %s %s>" % (ver1, ver2)
       
   853 
       
   854 class SISDate(SISFieldNormal):
       
   855     '''Year, month (0-11) and day (1-31)'''
       
   856     def __init__(self, **kwds):
       
   857         self.subfields = [
       
   858             ("Year",  self.FTYPE_INTEGRAL, "<H"),
       
   859             ("Month", self.FTYPE_INTEGRAL, "<B"),
       
   860             ("Day",   self.FTYPE_INTEGRAL, "<B")]
       
   861 
       
   862         # Parse keyword parameters.
       
   863         SISFieldNormal.__init__(self, **kwds)
       
   864 
       
   865     def __str__(self):
       
   866         return "<SISDate %04d-%02d-%02d>" % (self.Year, self.Month + 1,
       
   867                                              self.Day)
       
   868 
       
   869 class SISTime(SISFieldNormal):
       
   870     '''Hours (0-23), minutes (0-59) and seconds (0-59)'''
       
   871     def __init__(self, **kwds):
       
   872         self.subfields = [
       
   873             ("Hours",   self.FTYPE_INTEGRAL, "<B"),
       
   874             ("Minutes", self.FTYPE_INTEGRAL, "<B"),
       
   875             ("Seconds", self.FTYPE_INTEGRAL, "<B")]
       
   876 
       
   877         # Parse keyword parameters.
       
   878         SISFieldNormal.__init__(self, **kwds)
       
   879 
       
   880     def __str__(self):
       
   881         return "<SISTime %02d:%02d:%02d>" % (
       
   882             self.Hours, self.Minutes, self.Seconds)
       
   883 
       
   884 class SISDateTime(SISFieldNormal):
       
   885     '''A bundled SISDate and a SISTime'''
       
   886     def __init__(self, **kwds):
       
   887         self.subfields = [
       
   888             ("Date", self.FTYPE_MANDATORY, "SISDate"),
       
   889             ("Time", self.FTYPE_MANDATORY, "SISTime")]
       
   890 
       
   891         # Parse keyword parameters.
       
   892         SISFieldNormal.__init__(self, **kwds)
       
   893 
       
   894     def __str__(self):
       
   895         return "<SISDateTime %04d-%02d-%02d %02d:%02d:%02d>" % (
       
   896             self.Date.Year, self.Date.Month, self.Date.Day,
       
   897             self.Time.Hours, self.Time.Minutes, self.Time.Seconds)
       
   898 
       
   899 class SISUid(SISFieldNormal):
       
   900     '''A 32-bit Symbian OS UID'''
       
   901     def __init__(self, **kwds):
       
   902         self.subfields = [("UID1", self.FTYPE_INTEGRAL, "<L")]
       
   903 
       
   904         # Parse keyword parameters.
       
   905         SISFieldNormal.__init__(self, **kwds)
       
   906 
       
   907     def __str__(self):
       
   908         return "<SISUid 0x%08x>" % self.UID1
       
   909 
       
   910 class SISLanguage(SISFieldNormal):
       
   911     '''A Symbian OS language number'''
       
   912     def __init__(self, **kwds):
       
   913         self.subfields = [("Language", self.FTYPE_INTEGRAL, "<L")]
       
   914 
       
   915         # Parse keyword parameters.
       
   916         SISFieldNormal.__init__(self, **kwds)
       
   917 
       
   918     def __str__(self):
       
   919         try:
       
   920             lname = symbianutil.langnumtoname[self.Language]
       
   921         except KeyError:
       
   922             lname = "Unknown"
       
   923         return "<SISLanguage %d (%s)>" % (self.Language, lname)
       
   924 
       
   925 class SISContents(SISFieldNormal):
       
   926     '''The root type of a SIS file'''
       
   927     def __init__(self, **kwds):
       
   928         self.subfields = [
       
   929             ("ControllerChecksum", self.FTYPE_OPTIONAL,  "SISControllerChecksum"),
       
   930             ("DataChecksum",       self.FTYPE_OPTIONAL,  "SISDataChecksum"),
       
   931             ("Controller",         self.FTYPE_MANDATORY, "SISCompressed"),
       
   932             ("Data",               self.FTYPE_MANDATORY, "SISData")]
       
   933 
       
   934         # Parse keyword parameters.
       
   935         SISFieldNormal.__init__(self, **kwds)
       
   936 
       
   937     def __str__(self):
       
   938         cksum1 = "N/A"
       
   939         if self.ControllerChecksum:
       
   940             cksum1 = "0x%04x" % self.ControllerChecksum.Checksum
       
   941         cksum2 = "N/A"
       
   942         if self.DataChecksum:
       
   943             cksum2 = "0x%04x" % self.DataChecksum.Checksum
       
   944         return "<SISContents, checksums: %s, %s>" % (cksum1, cksum2)
       
   945 
       
   946 class SISController(SISFieldNormal):
       
   947     '''SIS file metadata'''
       
   948     def __init__(self, **kwds):
       
   949         # Convert a list of signatures to separate parameters
       
   950         # so that base class constructor can parse them.
       
   951         # Support upto MAXNUMSIGNATURES signature certificates.
       
   952         if "Signatures" in kwds:
       
   953             signatures = kwds["Signatures"]
       
   954             if len(signatures) > MAXNUMSIGNATURES:
       
   955                 raise ValueError("too many signatures for SISController")
       
   956             for n in xrange(len(signatures)):
       
   957                 kwds["Signature%d" % n] = signatures[n]
       
   958             del kwds["Signatures"]
       
   959 
       
   960         # DataIndex is really not optional. However, calculating
       
   961         # signatures require that SISController strings without
       
   962         # the DataIndex field can be generated.
       
   963         self.subfields = [
       
   964             ("Info",          self.FTYPE_MANDATORY, "SISInfo"),
       
   965             ("Options",       self.FTYPE_MANDATORY, "SISSupportedOptions"),
       
   966             ("Languages",     self.FTYPE_MANDATORY, "SISSupportedLanguages"),
       
   967             ("Prerequisites", self.FTYPE_MANDATORY, "SISPrerequisites"),
       
   968             ("Properties",    self.FTYPE_MANDATORY, "SISProperties"),
       
   969             ("Logo",          self.FTYPE_OPTIONAL,  "SISLogo"),
       
   970             ("InstallBlock",  self.FTYPE_MANDATORY, "SISInstallBlock"),
       
   971             ("Signature0",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   972             ("Signature1",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   973             ("Signature2",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   974             ("Signature3",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   975             ("Signature4",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   976             ("Signature5",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   977             ("Signature6",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   978             ("Signature7",    self.FTYPE_OPTIONAL,  "SISSignatureCertificateChain"),
       
   979             ("DataIndex",     self.FTYPE_OPTIONAL,  "SISDataIndex")]
       
   980 
       
   981         # Parse keyword parameters.
       
   982         SISFieldNormal.__init__(self, **kwds)
       
   983 
       
   984     # Helper routines to deal with the special signature fields.
       
   985     def getsignatures(self):
       
   986         # Return signatures as a list.
       
   987         signatures = []
       
   988         for n in xrange(MAXNUMSIGNATURES):
       
   989             sig = self.__dict__["Signature%d" % n]
       
   990             if sig != None:
       
   991                 signatures.append(sig)
       
   992         return signatures
       
   993 
       
   994     def setsignatures(self, signatures):
       
   995         # Replace signatures with the ones from list. If there are
       
   996         # less than MAXNUMSIGNATURES signatures in the list, the
       
   997         # rest are erased. To erase all signatures, call
       
   998         # controller.setsignatures([]).
       
   999         numsig = len(signatures)
       
  1000         if numsig > MAXNUMSIGNATURES:
       
  1001             raise ValueError("too many signatures for SISController")
       
  1002         for n in xrange(MAXNUMSIGNATURES):
       
  1003             if n < numsig:
       
  1004                 sig = signatures[n]
       
  1005             else:
       
  1006                 sig = None
       
  1007             self.__dict__["Signature%d" % n] = sig
       
  1008 
       
  1009 class SISInfo(SISFieldNormal):
       
  1010     '''Information about the SIS file'''
       
  1011     def __init__(self, **kwds):
       
  1012         self.subfields = [
       
  1013             ("UID",              self.FTYPE_MANDATORY, "SISUid"),
       
  1014             ("VendorUniqueName", self.FTYPE_MANDATORY, "SISString"),
       
  1015             ("Names",            self.FTYPE_ARRAY,     "SISString"),
       
  1016             ("VendorNames",      self.FTYPE_ARRAY,     "SISString"),
       
  1017             ("Version",          self.FTYPE_MANDATORY, "SISVersion"),
       
  1018             ("CreationTime",     self.FTYPE_MANDATORY, "SISDateTime"),
       
  1019             ("InstallType",      self.FTYPE_INTEGRAL,  "<B"),
       
  1020             ("InstallFlags",     self.FTYPE_INTEGRAL,  "<B")]
       
  1021 
       
  1022         # Parse keyword parameters.
       
  1023         SISFieldNormal.__init__(self, **kwds)
       
  1024 
       
  1025 class SISSupportedLanguages(SISFieldNormal):
       
  1026     '''An array of SISLanguage fields'''
       
  1027     def __init__(self, **kwds):
       
  1028         self.subfields = [("Languages", self.FTYPE_ARRAY, "SISLanguage")]
       
  1029 
       
  1030         # Parse keyword parameters.
       
  1031         SISFieldNormal.__init__(self, **kwds)
       
  1032 
       
  1033 class SISSupportedOptions(SISFieldNormal):
       
  1034     '''An array of SISSupportedOption fields, user selectable options'''
       
  1035     def __init__(self, **kwds):
       
  1036         self.subfields = [("Options", self.FTYPE_ARRAY, "SISSupportedOption")]
       
  1037 
       
  1038         # Parse keyword parameters.
       
  1039         SISFieldNormal.__init__(self, **kwds)
       
  1040 
       
  1041 class SISPrerequisites(SISFieldNormal):
       
  1042     '''An array of SISDependency fields'''
       
  1043     def __init__(self, **kwds):
       
  1044         self.subfields = [
       
  1045             ("TargetDevices", self.FTYPE_ARRAY, "SISDependency"),
       
  1046             ("Dependencies",  self.FTYPE_ARRAY, "SISDependency")]
       
  1047 
       
  1048         # Parse keyword parameters.
       
  1049         SISFieldNormal.__init__(self, **kwds)
       
  1050 
       
  1051 class SISDependency(SISFieldNormal):
       
  1052     '''Versioned SIS package dependency'''
       
  1053     def __init__(self, **kwds):
       
  1054         self.subfields = [
       
  1055             ("UID",             self.FTYPE_MANDATORY, "SISUid"),
       
  1056             ("VersionRange",    self.FTYPE_OPTIONAL,  "SISVersionRange"),
       
  1057             ("DependencyNames", self.FTYPE_ARRAY,     "SISString")]
       
  1058 
       
  1059         # Parse keyword parameters.
       
  1060         SISFieldNormal.__init__(self, **kwds)
       
  1061 
       
  1062 class SISProperties(SISFieldNormal):
       
  1063     '''An array of SISProperty fields'''
       
  1064     def __init__(self, **kwds):
       
  1065         self.subfields = [("Properties", self.FTYPE_ARRAY, "SISProperty")]
       
  1066 
       
  1067         # Parse keyword parameters.
       
  1068         SISFieldNormal.__init__(self, **kwds)
       
  1069 
       
  1070 class SISProperty(SISFieldNormal):
       
  1071     '''Key:value pair'''
       
  1072     def __init__(self, **kwds):
       
  1073         self.subfields = [
       
  1074             ("Key",   self.FTYPE_INTEGRAL, "<l"),
       
  1075             ("Value", self.FTYPE_INTEGRAL, "<l")]
       
  1076 
       
  1077         # Parse keyword parameters.
       
  1078         SISFieldNormal.__init__(self, **kwds)
       
  1079 
       
  1080 # SISSignatures: Legacy field type, not used
       
  1081 #
       
  1082 # class SISSignatures(SISFieldNormal):
       
  1083 #     pass
       
  1084 
       
  1085 class SISCertificateChain(SISFieldNormal):
       
  1086     '''ASN.1 encoded X509 certificate chain'''
       
  1087     def __init__(self, **kwds):
       
  1088         self.subfields = [("CertificateData", self.FTYPE_MANDATORY, "SISBlob")]
       
  1089 
       
  1090         # Parse keyword parameters.
       
  1091         SISFieldNormal.__init__(self, **kwds)
       
  1092 
       
  1093 class SISLogo(SISFieldNormal):
       
  1094     '''A logo file to display during installation'''
       
  1095     def __init__(self, **kwds):
       
  1096         self.subfields = [
       
  1097             ("LogoFile", self.FTYPE_MANDATORY, "SISFileDescription")]
       
  1098 
       
  1099         # Parse keyword parameters.
       
  1100         SISFieldNormal.__init__(self, **kwds)
       
  1101 
       
  1102 class SISFileDescription(SISFieldNormal):
       
  1103     '''Information about an enclosed file'''
       
  1104     def __init__(self, **kwds):
       
  1105         self.subfields = [
       
  1106             ("Target",             self.FTYPE_MANDATORY, "SISString"),
       
  1107             ("MIMEType",           self.FTYPE_MANDATORY, "SISString"),
       
  1108             ("Capabilities",       self.FTYPE_OPTIONAL,  "SISCapabilities"),
       
  1109             ("Hash",               self.FTYPE_MANDATORY, "SISHash"),
       
  1110             ("Operation",          self.FTYPE_INTEGRAL,  "<L"),
       
  1111             ("OperationOptions",   self.FTYPE_INTEGRAL,  "<L"),
       
  1112             ("Length",             self.FTYPE_INTEGRAL,  "<Q"),
       
  1113             ("UncompressedLength", self.FTYPE_INTEGRAL,  "<Q"),
       
  1114             ("FileIndex",          self.FTYPE_INTEGRAL,  "<L")]
       
  1115 
       
  1116         # Parse keyword parameters.
       
  1117         SISFieldNormal.__init__(self, **kwds)
       
  1118 
       
  1119 class SISHash(SISFieldNormal):
       
  1120     '''File hash'''
       
  1121     def __init__(self, **kwds):
       
  1122         self.subfields = [
       
  1123             ("HashAlgorithm", self.FTYPE_INTEGRAL,  "<L"),
       
  1124             ("HashData",      self.FTYPE_MANDATORY, "SISBlob")]
       
  1125 
       
  1126         # Parse keyword parameters.
       
  1127         SISFieldNormal.__init__(self, **kwds)
       
  1128 
       
  1129         # Check that the hash algorithm is a supported one (SHA-1 only for now).
       
  1130         if self.HashAlgorithm != ESISHashAlgSHA1:
       
  1131             raise SISException("invalid SISHash algorithm '%d'" %
       
  1132                                self.HashAlgorithm)
       
  1133 
       
  1134 class SISIf(SISFieldNormal):
       
  1135     '''An "if"-branch of a conditional expression'''
       
  1136     def __init__(self, **kwds):
       
  1137         self.subfields = [
       
  1138             ("Expression",   self.FTYPE_MANDATORY, "SISExpression"),
       
  1139             ("InstallBlock", self.FTYPE_MANDATORY, "SISInstallBlock"),
       
  1140             ("ElseIfs",      self.FTYPE_ARRAY,     "SISElseIf")]
       
  1141 
       
  1142         # Parse keyword parameters.
       
  1143         SISFieldNormal.__init__(self, **kwds)
       
  1144 
       
  1145 class SISElseIf(SISFieldNormal):
       
  1146     '''An "else if"-branch of a conditional expression'''
       
  1147     def __init__(self, **kwds):
       
  1148         self.subfields = [
       
  1149             ("Expression",   self.FTYPE_MANDATORY, "SISExpression"),
       
  1150             ("InstallBlock", self.FTYPE_MANDATORY, "SISInstallBlock")]
       
  1151 
       
  1152         # Parse keyword parameters.
       
  1153         SISFieldNormal.__init__(self, **kwds)
       
  1154 
       
  1155 class SISInstallBlock(SISFieldNormal):
       
  1156     '''A conditional file installation hierarchy'''
       
  1157     def __init__(self, **kwds):
       
  1158         self.subfields = [
       
  1159             ("Files",            self.FTYPE_ARRAY, "SISFileDescription"),
       
  1160             ("EmbeddedSISFiles", self.FTYPE_ARRAY, "SISController"),
       
  1161             ("IfBlocks",         self.FTYPE_ARRAY, "SISIf")]
       
  1162 
       
  1163         # Parse keyword parameters.
       
  1164         SISFieldNormal.__init__(self, **kwds)
       
  1165 
       
  1166 class SISExpression(SISFieldNormal):
       
  1167     '''A conditional expression'''
       
  1168     def __init__(self, **kwds):
       
  1169         self.subfields = [
       
  1170             ("Operator",        self.FTYPE_INTEGRAL, "<L"),
       
  1171             ("IntegerValue",    self.FTYPE_INTEGRAL, "<l"),
       
  1172             ("StringValue",     self.FTYPE_OPTIONAL, "SISString"),
       
  1173             ("LeftExpression",  self.FTYPE_OPTIONAL, "SISExpression"),
       
  1174             ("RightExpression", self.FTYPE_OPTIONAL, "SISExpression")]
       
  1175 
       
  1176         # Parse keyword parameters.
       
  1177         SISFieldNormal.__init__(self, **kwds)
       
  1178 
       
  1179 class SISData(SISFieldNormal):
       
  1180     '''An array of SISDataUnit fields'''
       
  1181     def __init__(self, **kwds):
       
  1182         self.subfields = [("DataUnits", self.FTYPE_ARRAY, "SISDataUnit")]
       
  1183 
       
  1184         # Parse keyword parameters.
       
  1185         SISFieldNormal.__init__(self, **kwds)
       
  1186 
       
  1187 class SISDataUnit(SISFieldNormal):
       
  1188     '''An array of SISFileData fields'''
       
  1189     def __init__(self, **kwds):
       
  1190         self.subfields = [("FileData", self.FTYPE_ARRAY, "SISFileData")]
       
  1191 
       
  1192         # Parse keyword parameters.
       
  1193         SISFieldNormal.__init__(self, **kwds)
       
  1194 
       
  1195 class SISSupportedOption(SISFieldNormal):
       
  1196     '''An array of supported option names in different languages'''
       
  1197     def __init__(self, **kwds):
       
  1198         self.subfields = [("Names", self.FTYPE_ARRAY, "SISString")]
       
  1199 
       
  1200         # Parse keyword parameters.
       
  1201         SISFieldNormal.__init__(self, **kwds)
       
  1202 
       
  1203 class SISControllerChecksum(SISFieldNormal):
       
  1204     '''CCITT CRC-16 of the SISController SISField'''
       
  1205     def __init__(self, **kwds):
       
  1206         self.subfields = [("Checksum", self.FTYPE_INTEGRAL, "<H")]
       
  1207 
       
  1208         # Parse keyword parameters.
       
  1209         SISFieldNormal.__init__(self, **kwds)
       
  1210 
       
  1211     def __str__(self):
       
  1212         return "<SISControllerChecksum 0x%04x>" % self.Checksum
       
  1213 
       
  1214 class SISDataChecksum(SISFieldNormal):
       
  1215     '''CCITT CRC-16 of the SISData SISField'''
       
  1216     def __init__(self, **kwds):
       
  1217         self.subfields = [("Checksum", self.FTYPE_INTEGRAL, "<H")]
       
  1218 
       
  1219         # Parse keyword parameters.
       
  1220         SISFieldNormal.__init__(self, **kwds)
       
  1221 
       
  1222     def __str__(self):
       
  1223         return "<SISDataChecksum 0x%04x>" % self.Checksum
       
  1224 
       
  1225 class SISSignature(SISFieldNormal):
       
  1226     '''Cryptographic signature of preceding SIS metadata'''
       
  1227     def __init__(self, **kwds):
       
  1228         self.subfields = [
       
  1229             ("SignatureAlgorithm", self.FTYPE_MANDATORY, "SISSignatureAlgorithm"),
       
  1230             ("SignatureData",      self.FTYPE_MANDATORY, "SISBlob")]
       
  1231 
       
  1232         # Parse keyword parameters.
       
  1233         SISFieldNormal.__init__(self, **kwds)
       
  1234 
       
  1235 class SISSignatureAlgorithm(SISFieldNormal):
       
  1236     '''Object identifier string of a signature algorithm'''
       
  1237     def __init__(self, **kwds):
       
  1238         self.subfields = [
       
  1239             ("AlgorithmIdentifier", self.FTYPE_MANDATORY, "SISString")]
       
  1240 
       
  1241         # Parse keyword parameters.
       
  1242         SISFieldNormal.__init__(self, **kwds)
       
  1243 
       
  1244     def __str__(self):
       
  1245         return "<SISSignatureAlgorithm '%s'>" % (
       
  1246             self.AlgorithmIdentifier.String)
       
  1247 
       
  1248 class SISSignatureCertificateChain(SISFieldNormal):
       
  1249     '''An array of SISSignatures and a SIScertificateChain
       
  1250     for signature validation'''
       
  1251     def __init__(self, **kwds):
       
  1252         self.subfields = [
       
  1253             ("Signatures",       self.FTYPE_ARRAY,     "SISSignature"),
       
  1254             ("CertificateChain", self.FTYPE_MANDATORY, "SISCertificateChain")]
       
  1255 
       
  1256         # Parse keyword parameters.
       
  1257         SISFieldNormal.__init__(self, **kwds)
       
  1258 
       
  1259 class SISDataIndex(SISFieldNormal):
       
  1260     '''Data index for files belonging to a SISController'''
       
  1261     def __init__(self, **kwds):
       
  1262         self.subfields = [("DataIndex", self.FTYPE_INTEGRAL, "<L")]
       
  1263 
       
  1264         # Parse keyword parameters.
       
  1265         SISFieldNormal.__init__(self, **kwds)
       
  1266 
       
  1267     def __str__(self):
       
  1268         return "<SISDataIndex %d>" % self.DataIndex
       
  1269 
       
  1270 
       
  1271 ##############################################################################
       
  1272 # Utility dictionaries
       
  1273 ##############################################################################
       
  1274 
       
  1275 fieldinfo = [
       
  1276     (1,     "SISString",                    SISString),
       
  1277     (2,     "SISArray",                     SISArray),
       
  1278     (3,     "SISCompressed",                SISCompressed),
       
  1279     (4,     "SISVersion",                   SISVersion),
       
  1280     (5,     "SISVersionRange",              SISVersionRange),
       
  1281     (6,     "SISDate",                      SISDate),
       
  1282     (7,     "SISTime",                      SISTime),
       
  1283     (8,     "SISDateTime",                  SISDateTime),
       
  1284     (9,     "SISUid",                       SISUid),
       
  1285     (11,    "SISLanguage",                  SISLanguage),
       
  1286     (12,    "SISContents",                  SISContents),
       
  1287     (13,    "SISController",                SISController),
       
  1288     (14,    "SISInfo",                      SISInfo),
       
  1289     (15,    "SISSupportedLanguages",        SISSupportedLanguages),
       
  1290     (16,    "SISSupportedOptions",          SISSupportedOptions),
       
  1291     (17,    "SISPrerequisites",             SISPrerequisites),
       
  1292     (18,    "SISDependency",                SISDependency),
       
  1293     (19,    "SISProperties",                SISProperties),
       
  1294     (20,    "SISProperty",                  SISProperty),
       
  1295 # SISSignatures: Legacy field type, not used
       
  1296 #    (21,    "SISSignatures",                SISSignatures),
       
  1297     (22,    "SISCertificateChain",          SISCertificateChain),
       
  1298     (23,    "SISLogo",                      SISLogo),
       
  1299     (24,    "SISFileDescription",           SISFileDescription),
       
  1300     (25,    "SISHash",                      SISHash),
       
  1301     (26,    "SISIf",                        SISIf),
       
  1302     (27,    "SISElseIf",                    SISElseIf),
       
  1303     (28,    "SISInstallBlock",              SISInstallBlock),
       
  1304     (29,    "SISExpression",                SISExpression),
       
  1305     (30,    "SISData",                      SISData),
       
  1306     (31,    "SISDataUnit",                  SISDataUnit),
       
  1307     (32,    "SISFileData",                  SISFileData),
       
  1308     (33,    "SISSupportedOption",           SISSupportedOption),
       
  1309     (34,    "SISControllerChecksum",        SISControllerChecksum),
       
  1310     (35,    "SISDataChecksum",              SISDataChecksum),
       
  1311     (36,    "SISSignature",                 SISSignature),
       
  1312     (37,    "SISBlob",                      SISBlob),
       
  1313     (38,    "SISSignatureAlgorithm",        SISSignatureAlgorithm),
       
  1314     (39,    "SISSignatureCertificateChain", SISSignatureCertificateChain),
       
  1315     (40,    "SISDataIndex",                 SISDataIndex),
       
  1316     (41,    "SISCapabilities",              SISCapabilities)
       
  1317 ]
       
  1318 
       
  1319 fieldnumtoclass = dict([(num,  klass) for num, name, klass in fieldinfo])
       
  1320 fieldnametonum  = dict([(name, num)   for num, name, klass in fieldinfo])
       
  1321 fieldnumtoname  = dict([(num,  name)  for num, name, klass in fieldinfo])