diff -r 000000000000 -r ca70ae20a155 src/tools/py2sis/ensymble/cmd_simplesis.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tools/py2sis/ensymble/cmd_simplesis.py Tue Feb 16 10:07:05 2010 +0530 @@ -0,0 +1,518 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################## +# cmd_simplesis.py - Ensymble command line tool, simplesis command +# Copyright 2006, 2007, 2008, 2009 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 sys +import os +import re +import getopt +import getpass +import locale +import struct +import zlib + +import sisfile +import sisfield +import symbianutil +import rscfile +import miffile + + +############################################################################## +# Help texts +############################################################################## + +shorthelp = 'Create a SIS package from a directory structure' +longhelp = '''simplesis + [--uid=0x01234567] [--version=1.0.0] [--lang=EN,...] + [--caption="Package Name",...] [--drive=C] [--textfile=mytext_%C.txt] + [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] + [--vendor="Vendor Name",...] [--encoding=terminal,filesystem] + [--verbose] + [sisfile] + +Create a SIS package from a directory structure. Only supports very +simple SIS files. There is no support for conditionally included +files, dependencies etc. + +Options: + srcdir - Source directory + sisfile - Path of the created SIS file + uid - Symbian OS UID for the SIS package + version - SIS package version: X.Y.Z or X,Y,Z (major, minor, build) + lang - Comma separated list of two-character language codes + caption - Comma separated list of package names in all languages + drive - Drive where the package will be installed (any by default) + textfile - Text file (or pattern, see below) to display during install + cert - Certificate to use for signing (PEM format) + privkey - Private key of the certificate (PEM format) + passphrase - Pass phrase of the private key (insecure, use stdin instead) + vendor - Vendor name or a comma separated list of names in all lang. + encoding - Local character encodings for terminal and filesystem + verbose - Print extra statistics + +If no certificate and its private key are given, a default self-signed +certificate is used to sign the SIS file. Software authors are encouraged +to create their own unique certificates for SIS packages that are to be +distributed. + +Text to display uses UTF-8 encoding. The file name may contain formatting +characters that are substituted for each selected language. If no formatting +characters are present, the same text will be used for all languages. + + %% - literal % + %n - language number (01 - 99) + %c - two-character language code in lowercase letters + %C - two-character language code in capital letters + %l - language name in English, using only lowercase letters + %l - language name in English, using mixed case letters +''' + + +############################################################################## +# Parameters +############################################################################## + +MAXPASSPHRASELENGTH = 256 +MAXCERTIFICATELENGTH = 65536 +MAXPRIVATEKEYLENGTH = 65536 +MAXFILESIZE = 1024 * 1024 * 8 # Eight megabytes +MAXTEXTFILELENGTH = 1024 + + +############################################################################## +# Global variables +############################################################################## + +debug = False + + +############################################################################## +# Public module-level functions +############################################################################## + +def run(pgmname, argv): + global debug + + # Determine system character encodings. + try: + # getdefaultlocale() may sometimes return None. + # Fall back to ASCII encoding in that case. + terminalenc = locale.getdefaultlocale()[1] + "" + except TypeError: + # Invalid locale, fall back to ASCII terminal encoding. + terminalenc = "ascii" + + try: + # sys.getfilesystemencoding() was introduced in Python v2.3 and + # it can sometimes return None. Fall back to ASCII if something + # goes wrong. + filesystemenc = sys.getfilesystemencoding() + "" + except (AttributeError, TypeError): + filesystemenc = "ascii" + + try: + gopt = getopt.gnu_getopt + except: + # Python MAXCERTIFICATELENGTH: + raise ValueError("certificate file too large") + + # Read private key file. + f = file(privkey, "rb") + privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) + f.close() + + if len(privkeydata) > MAXPRIVATEKEYLENGTH: + raise ValueError("private key file too large") + elif cert == None and privkey == None: + # No certificate given, use the Ensymble default certificate. + # defaultcert.py is not imported when not needed. This speeds + # up program start-up a little. + import defaultcert + certdata = defaultcert.cert + privkeydata = defaultcert.privkey + + print ("%s: warning: no certificate given, using " + "insecure built-in one" % pgmname) + + # Warn if the UID is in the protected range. + # Resulting SIS file will probably not install. + if puid < 0x80000000L: + print ("%s: warning: UID is in the protected range " + "(0x00000000 - 0x7ffffff)" % pgmname) + else: + raise ValueError("missing certificate or private key") + + # Get pass phrase. Pass phrase remains in terminal encoding. + passphrase = opts.get("--passphrase", opts.get("-p", None)) + if passphrase == None and privkey != None: + # Private key given without "--passphrase" option, ask it. + if sys.stdin.isatty(): + # Standard input is a TTY, ask password interactively. + passphrase = getpass.getpass("Enter private key pass phrase:") + else: + # Not connected to a TTY, read stdin non-interactively instead. + passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) + + if len(passphrase) > MAXPASSPHRASELENGTH: + raise ValueError("pass phrase too long") + + passphrase = passphrase.strip() + + # Determine verbosity. + verbose = False + if "--verbose" in opts.keys() or "-v" in opts.keys(): + verbose = True + + # Determine if debug output is requested. + if "--debug" in opts.keys(): + debug = True + + # Enable debug output for OpenSSL-related functions. + import cryptutil + cryptutil.setdebug(True) + + # Ingredients for successful SIS generation: + # + # terminalenc Terminal character encoding (autodetected) + # filesystemenc File system name encoding (autodetected) + # basename Base for generated file names on host, filesystemenc encoded + # srcdir Directory of source files, filesystemenc encoded + # srcfiles List of filesystemenc encoded source file names in srcdir + # outfile Output SIS file name, filesystemenc encoded + # puid Package UID, long integer + # version A triple-item tuple (major, minor, build) + # lang List of two-character language codes, ASCII strings + # caption List of Unicode package captions, one per language + # drive Installation drive letter or "!" + # textfile File name pattern of text file(s) to display during install + # texts Actual texts to display during install, one per language + # cert Certificate in PEM format + # privkey Certificate private key in PEM format + # passphrase Pass phrase of private key, terminalenc encoded string + # vendor List of Unicode vendor names, one per language + # verbose Boolean indicating verbose terminal output + + if verbose: + print + print "Input files %s" % " ".join( + [s.decode(filesystemenc).encode(terminalenc) for s in srcfiles]) + print "Output SIS file %s" % ( + outfile.decode(filesystemenc).encode(terminalenc)) + print "UID 0x%08x" % puid + print "Version %d.%d.%d" % ( + version[0], version[1], version[2]) + print "Language(s) %s" % ", ".join(lang) + print "Package caption(s) %s" % ", ".join( + [s.encode(terminalenc) for s in caption]) + print "Install drive %s" % ((drive == "!") and + "" or drive) + print "Text file(s) %s" % ((textfile and + textfile.decode(filesystemenc).encode(terminalenc)) or "") + print "Certificate %s" % ((cert and + cert.decode(filesystemenc).encode(terminalenc)) or "") + print "Private key %s" % ((privkey and + privkey.decode(filesystemenc).encode(terminalenc)) or "") + print "Vendor name(s) %s" % ", ".join( + [s.encode(terminalenc) for s in vendor]) + print + + # Generate SimpleSISWriter object. + sw = sisfile.SimpleSISWriter(lang, caption, puid, version, + vendor[0], vendor) + + # Add text file or files to the SIS object. Text dialog is + # supposed to be displayed before anything else is installed. + if len(texts) == 1: + sw.addfile(texts[0], operation = sisfield.EOpText) + elif len(texts) > 1: + sw.addlangdepfile(texts, operation = sisfield.EOpText) + + # Add files to SIS object. + sysbinprefix = os.path.join("sys", "bin", "") + for srcfile in srcfiles: + # Read file. + f = file(os.path.join(srcdir, srcfile), "rb") + string = f.read(MAXFILESIZE + 1) + f.close() + + if len(string) > MAXFILESIZE: + raise ValueError("input file too large") + + # Check if the file is an E32Image (EXE or DLL). + caps = symbianutil.e32imagecaps(string) + + if caps != None and not srcfile.startswith(sysbinprefix): + print ("%s: warning: %s is an E32Image (EXE or DLL) outside %s%s" % + (pgmname, srcfile, os.sep, sysbinprefix)) + + # Add file to the SIS object. + target = srcfile.decode(filesystemenc).replace(os.sep, "\\") + sw.addfile(string, "%s:\\%s" % (drive, target), capabilities = caps) + del string + + # Add target device dependency. + sw.addtargetdevice(0x101f7961L, (0, 0, 0), None, + ["Series60ProductID"] * numlang) + + # Add certificate. + sw.addcertificate(privkeydata, certdata, passphrase) + + # Generate SIS file out of the SimpleSISWriter object. + sw.tofile(outfile) + + +############################################################################## +# Module-level functions which are normally only used by this module +############################################################################## + +def parseversion(version): + '''Parse a version string: "v1.2.3" or similar. + + Initial "v" can optionally be a capital "V" or omitted altogether. Minor + and build numbers can also be omitted. Separator can be a comma or a + period.''' + + version = version.strip().lower() + + # Strip initial "v" or "V". + if version[0] == "v": + version = version[1:] + + if "." in version: + parts = [int(n) for n in version.split(".")] + else: + parts = [int(n) for n in version.split(",")] + + # Allow missing minor and build numbers. + parts.extend([0, 0]) + + return parts[0:3] + +def readtextfiles(pattern, languages): + '''Read language dependent text files. + + Files are assumed to be in UTF-8 encoding and re-encoded + in UCS-2 (UTF-16LE) for Symbian OS to display during installation.''' + + if "%" not in pattern: + # Only one file, read it. + filenames = [pattern] + else: + filenames = [] + for langid in languages: + langnum = symbianutil.langidtonum[langid] + langname = symbianutil.langnumtoname[langnum] + + # Replace formatting characters in file name pattern. + filename = pattern + filename = filename.replace("%n", "%02d" % langnum) + filename = filename.replace("%c", langid.lower()) + filename = filename.replace("%C", langid.upper()) + filename = filename.replace("%l", langname.lower()) + filename = filename.replace("%L", langname) + filename = filename.replace("%%", "%") + + filenames.append(filename) + + texts = [] + + for filename in filenames: + f = file(filename, "r") # Read as text. + text = f.read(MAXTEXTFILELENGTH + 1) + f.close() + + if len(text) > MAXTEXTFILELENGTH: + raise ValueError("%s: text file too large" % filename) + + texts.append(text.decode("UTF-8").encode("UTF-16LE")) + + return texts