0001: /*
0002: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0004: *
0005: * This program is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU General Public License version
0007: * 2 only, as published by the Free Software Foundation.
0008: *
0009: * This program is distributed in the hope that it will be useful, but
0010: * WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * General Public License version 2 for more details (a copy is
0013: * included at /legal/license.txt).
0014: *
0015: * You should have received a copy of the GNU General Public License
0016: * version 2 along with this work; if not, write to the Free Software
0017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0018: * 02110-1301 USA
0019: *
0020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0021: * Clara, CA 95054 or visit www.sun.com if you need additional
0022: * information or have any questions.
0023: */
0024:
0025: package com.sun.midp.jump.installer;
0026:
0027: import com.sun.midp.installer.Verifier;
0028: import com.sun.midp.installer.InstallListener;
0029: import com.sun.midp.installer.InvalidJadException;
0030: import com.sun.midp.installer.JadProperties;
0031: import com.sun.midp.installer.ManifestProperties;
0032: import com.sun.midp.installer.OtaNotifier;
0033: import com.sun.midp.installer.InstallState;
0034: import com.sun.midp.installer.VerifierImpl;
0035: import com.sun.midp.installer.JarReader;
0036:
0037: import java.util.Vector;
0038:
0039: import java.io.IOException;
0040: import java.io.InputStream;
0041: import java.io.OutputStream;
0042: import java.io.ByteArrayInputStream;
0043:
0044: import javax.microedition.io.ConnectionNotFoundException;
0045:
0046: import com.sun.midp.security.SecurityHandler;
0047: import com.sun.midp.security.SecurityToken;
0048: import com.sun.midp.security.Permissions;
0049:
0050: import com.sun.midp.main.MIDletSuiteVerifier;
0051: import com.sun.midp.main.MIDletAppImageGenerator;
0052:
0053: import com.sun.midp.midlet.MIDletStateHandler;
0054: import com.sun.midp.midlet.MIDletSuite;
0055:
0056: import com.sun.midp.midletsuite.MIDletSuiteStorage;
0057: import com.sun.midp.midletsuite.MIDletSuiteImpl;
0058: import com.sun.midp.midletsuite.MIDletInfo;
0059: import com.sun.midp.midletsuite.MIDletSuiteInfo;
0060: import com.sun.midp.midletsuite.InstallInfo;
0061: import com.sun.midp.midletsuite.SuiteSettings;
0062: import com.sun.midp.midletsuite.MIDletSuiteLockedException;
0063: import com.sun.midp.midletsuite.MIDletSuiteCorruptedException;
0064:
0065: import com.sun.midp.io.HttpUrl;
0066: import com.sun.midp.io.Util;
0067:
0068: import com.sun.midp.io.j2me.push.PushRegistryInternal;
0069: import com.sun.midp.io.j2me.storage.RandomAccessStream;
0070: import com.sun.midp.io.j2me.storage.File;
0071:
0072: import com.sun.midp.rms.RecordStoreFactory;
0073:
0074: // IMPL_NOTE: sync with cldc installer for CHManager dependency
0075: // import com.sun.midp.content.CHManager;
0076:
0077: import com.sun.midp.log.Logging;
0078: import com.sun.midp.log.LogChannels;
0079:
0080: import com.sun.midp.configurator.Constants;
0081:
0082: /**
0083: * An JUMPInstaller manages MIDlet suites and libraries
0084: * present in a Java application environment. An MIDlet suite
0085: * distributed as a descriptor and JAR pair.
0086: * The descriptor identifies the configuration and contains security
0087: * information and the manifest of the JAR describes the contents.
0088: * The implementation of an JUMPInstaller is
0089: * specific to the platform and provides access to
0090: * procedures that make an MIDlet suite visible to users.
0091: * <P>
0092: * Each installed package is uniquely identified by a storage name
0093: * constructed from the combination
0094: * of the values of the <code>MIDlet-Name</code> and
0095: * <code>MIDlet-Vendor</code> attributes.
0096: * The syntax and content of the strings used to identify
0097: * installed packages are implementation dependent.
0098: * Only packages installed or upgraded using this API appear
0099: * in the list of known packages.
0100: *
0101: */
0102:
0103: public abstract class JUMPInstaller {
0104:
0105: /** Status code to signal connection to the JAD server was successful. */
0106: public static final int DOWNLOADING_JAD = 1;
0107:
0108: /** Status code to signal that another 1K of the JAD has been download. */
0109: public static final int DOWNLOADED_1K_OF_JAD = 2;
0110:
0111: /** Status code to signal connection to the JAR server was successful. */
0112: public static final int DOWNLOADING_JAR = 3;
0113:
0114: /** Status code to signal that another 1K of the JAR has been download. */
0115: public static final int DOWNLOADED_1K_OF_JAR = 4;
0116:
0117: /**
0118: * Status code to signal that download is done and the suite is being
0119: * verified.
0120: */
0121: public static final int VERIFYING_SUITE = 5;
0122:
0123: /**
0124: * Status code to signal that application image is being generating.
0125: */
0126: public static final int GENERATING_APP_IMAGE = 6;
0127:
0128: /**
0129: * Status code to signal that suite classes are being verified.
0130: */
0131: public static final int VERIFYING_SUITE_CLASSES = 7;
0132:
0133: /**
0134: * Status code for local writing of the verified MIDlet suite.
0135: * Stopping the install at this point has no effect, so there user
0136: * should not be given a chance to stop the install.
0137: */
0138: public static final int STORING_SUITE = 8;
0139:
0140: /** Status code for corrupted suite */
0141: public static final int CORRUPTED_SUITE = 9;
0142:
0143: /** System property containing the supported microedition profiles */
0144: protected static final String MICROEDITION_PROFILES = "microedition.profiles";
0145:
0146: /** System property containing the microedition configuration */
0147: protected static final String MICROEDITION_CONFIG = "microedition.configuration";
0148:
0149: /** System property containing the microedition locale */
0150: protected static final String MICROEDITION_LOCALE = "microedition.locale";
0151:
0152: /** Media-Type for valid application descriptor files. */
0153: public static final String JAD_MT = "text/vnd.sun.j2me.app-descriptor";
0154:
0155: /** Media-Type for valid Jar file. */
0156: public static final String JAR_MT_1 = "application/java";
0157:
0158: /** Media-Type for valid Jar file. */
0159: public static final String JAR_MT_2 = "application/java-archive";
0160:
0161: /**
0162: * Filename to save the JAR of the suite temporarily. This is used
0163: * to avoid overwriting an existing JAR prior to verification.
0164: */
0165: protected static final String TMP_FILENAME = "installer.tmp";
0166:
0167: /** Midlet suite signature verifier. */
0168: protected Verifier verifier;
0169:
0170: /** Holds the install state. */
0171: protected InstallStateImpl state;
0172:
0173: /** An alias for more state.installInfo to get more compact record. */
0174: protected InstallInfo info;
0175:
0176: /** An alias for more state.suiteSettings to get more compact record. */
0177: protected SuiteSettings settings;
0178:
0179: /** Holds the CLDC configuration string. */
0180: protected String cldcConfig;
0181:
0182: /** Holds the device's Runtime Execution Environment string. */
0183: protected final String cldcRuntimeEnv = "MIDP.CLDC";
0184:
0185: /** Holds the MIDP supported profiles. */
0186: private Vector supportedProfiles;
0187:
0188: /** Use this to be the security domain for unsigned suites. */
0189: protected String unsignedSecurityDomain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
0190:
0191: /* SecurityToken provided by the user of the JUMPInstaller */
0192: protected SecurityToken token = null;
0193:
0194: /**
0195: * Constructor of the JUMPInstaller.
0196: */
0197: public JUMPInstaller() {
0198: state = getInstallState();
0199: verifier = new VerifierImpl(state);
0200:
0201: /* Aliases for more compact record. */
0202: info = state.installInfo;
0203: settings = state.suiteSettings;
0204: }
0205:
0206: /**
0207: * Creates an instance of InstallState of the appropriate type
0208: * depending on the installer type. Should be overloaded in the
0209: * inherited classes.
0210: *
0211: * @return an instance of class containing the installation state
0212: */
0213: protected InstallStateImpl getInstallState() {
0214: if (state == null) {
0215: state = new InstallStateImpl();
0216: }
0217:
0218: return state;
0219: }
0220:
0221: /**
0222: * Installs a software package from the given URL. The URL is assumed
0223: * refer to an application descriptor.
0224: * <p>
0225: * If the component to be installed is the same as an existing
0226: * component (by comparing the <code>MIDlet-Name</code>,
0227: * <code>MIDlet-Vendor</code> attributes)
0228: * then this install is an upgrade if the version number is greater
0229: * than the current version. If so, the new version replaces in its
0230: * entirety the current version.
0231: * <p>
0232: * It is implementation dependent when the upgraded component is
0233: * made available for use.
0234: * <p>
0235: * The implementation of install must be robust in the presence
0236: * of failures such as running out of memory. If this method
0237: * throws an exception then the package must <em>not</em> be installed
0238: * and any previous version of the component must be left intact
0239: * and operational.
0240: * <p>
0241: * To receive status updates and installer warnings, provide an install
0242: * listener. If no listener is provided all warnings will be thrown
0243: * as exceptions.
0244: *
0245: * @param location the URL from which the application descriptor can be
0246: * updated
0247: * @param storageId ID of the storage where the suite should be saved
0248: * @param force if <code>true</code> the MIDlet suite components to be
0249: * installed will overwrite any existing components without
0250: * any version comparison
0251: * @param removeRMS if <code>true</code> and existing RMS data will be
0252: * removed when overwriting an existing suite
0253: * @param installListener object to receive status updates and install
0254: * warnings, can be null
0255: *
0256: * @return the unique ID of the installed package.
0257: *
0258: * @exception ConnectionNotFoundException if JAD URL is invalid
0259: * @exception IOException is thrown if any error prevents the installation
0260: * of the MIDlet suite, including being unable to access the application
0261: * descriptor or JAR
0262: * @exception InvalidJadException if the downloaded application descriptor
0263: * is invalid
0264: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0265: * locked
0266: * @exception SecurityException if the caller does not have permission
0267: * to install software
0268: * @exception IllegalArgumentException is thrown, if the location of the
0269: * descriptor file is not specified
0270: */
0271: public int installJad(String location, int storageId,
0272: boolean force, boolean removeRMS,
0273: InstallListener installListener) throws IOException,
0274: InvalidJadException, MIDletSuiteLockedException,
0275: SecurityException {
0276:
0277: info.jadUrl = location;
0278: state.force = force;
0279: state.removeRMS = removeRMS;
0280: state.nextStep = 1;
0281: state.listener = installListener;
0282: // IMPL_NOTE: sync with cldc installer for CHManager dependency
0283: //state.chmanager = CHManager.getManager(null);
0284: state.storageId = storageId;
0285:
0286: return performInstall();
0287: }
0288:
0289: public int installJad(String location, String jadLocal,
0290: String jarLocal, String encoding, int storageId,
0291: boolean force, boolean removeRMS,
0292: InstallListener installListener) throws IOException,
0293: InvalidJadException, MIDletSuiteLockedException,
0294: SecurityException {
0295:
0296: state.localJarUrl = jarLocal;
0297: state.localJadUrl = jadLocal;
0298: state.jadEncoding = encoding;
0299:
0300: return installJad(location, storageId, force, removeRMS,
0301: installListener);
0302: }
0303:
0304: /**
0305: * Installs a software package from the given URL. The URL is assumed
0306: * refer to a JAR.
0307: * <p>
0308: * If the component to be installed is the same as an existing
0309: * component (by comparing the <code>MIDlet-Name</code>,
0310: * <code>MIDlet-Vendor</code> attributes)
0311: * then this install is an upgrade if the version number is greater
0312: * than the current version. If so, the new version replaces in its
0313: * entirety the current version.
0314: * <p>
0315: * It is implementation dependent when the upgraded component is
0316: * made available for use.
0317: * <p>
0318: * The implementation of install must be robust in the presence
0319: * of failures such as running out of memory. If this method
0320: * throws an exception then the package must <em>not</em> be installed
0321: * and any previous version of the component must be left intact
0322: * and operational.
0323: * <p>
0324: * To receive status updates and installer warnings, provide an install
0325: * listener. If no listener is provided all warnings will be thrown
0326: * as exceptions.
0327: *
0328: * @param location the URL which the JAR originally came from
0329: * @param name the name of the suite to be updated
0330: * @param storageId ID of the storage where the suite should be saved
0331: * @param force if <code>true</code> the MIDlet suite components to be
0332: * installed will overwrite any existing components without
0333: * any version comparison
0334: * @param removeRMS if <code>true</code> and existing RMS data will be
0335: * removed when overwriting an existing suite
0336: * @param installListener object to receive status updates and install
0337: * warnings, can be null
0338: *
0339: * @return the unique ID of the installed package.
0340: *
0341: * @exception IOException is thrown if any error prevents the installation
0342: * of the MIDlet suite, including being unable to access the JAR
0343: * @exception InvalidJadException if the downloaded JAR is invalid
0344: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0345: * locked
0346: * @exception SecurityException if the caller does not have permission
0347: * to install software
0348: * @exception IllegalArgumentException is thrown, if the location of the
0349: * JAR specified
0350: */
0351: public int installJar(String location, String name, int storageId,
0352: boolean force, boolean removeRMS,
0353: InstallListener installListener) throws IOException,
0354: InvalidJadException, MIDletSuiteLockedException {
0355:
0356: if (location == null || location.length() == 0) {
0357: throw new IllegalArgumentException(
0358: "Must specify URL of .jar file");
0359: }
0360:
0361: info.jarUrl = location;
0362: info.suiteName = name;
0363: state.force = force;
0364: state.removeRMS = removeRMS;
0365: state.file = new File();
0366: state.nextStep = 5;
0367: state.listener = installListener;
0368: //state.chmanager = CHManager.getManager(null);
0369: state.storageId = storageId;
0370:
0371: return performInstall();
0372: }
0373:
0374: /**
0375: * @param local the location in local filesystem where the JAR can be updated
0376: **/
0377: public int installJar(String location, String local, String name,
0378: int storageId, boolean force, boolean removeRMS,
0379: InstallListener installListener) throws IOException,
0380: InvalidJadException, MIDletSuiteLockedException {
0381: state.localJarUrl = local;
0382: return installJar(location, name, storageId, force, removeRMS,
0383: installListener);
0384: }
0385:
0386: /**
0387: * Performs an install.
0388: *
0389: * @return the unique name of the installed package
0390: *
0391: * @exception IOException is thrown, if an I/O error occurs during
0392: * descriptor or jar file download
0393: * @exception InvalidJadException is thrown, if the descriptor file is not
0394: * properly formatted or does not contain the required
0395: * information
0396: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0397: * locked
0398: * @exception IllegalArgumentException is thrown, if the
0399: * descriptor file is not specified
0400: */
0401: protected int performInstall() throws IOException,
0402: InvalidJadException, MIDletSuiteLockedException {
0403:
0404: state.midletSuiteStorage = MIDletSuiteStorage
0405: .getMIDletSuiteStorage();
0406:
0407: /* Disable push interruptions during install. */
0408: PushRegistryInternal.enablePushLaunch(false);
0409:
0410: try {
0411: state.startTime = System.currentTimeMillis();
0412:
0413: while (state.nextStep < 9) {
0414:
0415: /*
0416: * clear the previous warning, so we can tell if another has
0417: * happened
0418: */
0419: state.exception = null;
0420:
0421: if (state.stopInstallation) {
0422: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0423: throw new IOException("stopped");
0424: }
0425:
0426: switch (state.nextStep) {
0427: case 1:
0428: installStep1();
0429: break;
0430:
0431: case 2:
0432: installStep2();
0433: break;
0434:
0435: case 3:
0436: installStep3();
0437: break;
0438:
0439: case 4:
0440: installStep4();
0441: break;
0442:
0443: case 5:
0444: installStep5();
0445: break;
0446:
0447: case 6:
0448: installStep6();
0449: break;
0450:
0451: case 7:
0452: installStep7();
0453: break;
0454:
0455: case 8:
0456: installStep8();
0457: break;
0458:
0459: default:
0460: // for safety/completeness.
0461: Logging.report(Logging.CRITICAL,
0462: LogChannels.LC_AMS,
0463: "JUMPInstaller: Unknown step: "
0464: + state.nextStep);
0465: break;
0466: }
0467:
0468: if (state.exception != null) {
0469: if (state.listener == null) {
0470: throw state.exception;
0471: }
0472:
0473: if (!state.listener.warnUser(state)) {
0474: state.stopInstallation = true;
0475: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0476: throw state.exception;
0477: }
0478: }
0479: }
0480: } finally {
0481: if (state.previousSuite != null) {
0482: state.previousSuite.close();
0483: }
0484: if (info.jarFilename != null) {
0485: if (state.file.exists(info.jarFilename)) {
0486: try {
0487: state.file.delete(info.jarFilename);
0488: } catch (Exception e) {
0489: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0490: Logging.report(Logging.WARNING,
0491: LogChannels.LC_AMS,
0492: "delete file threw an Exception");
0493: }
0494: }
0495: }
0496: }
0497:
0498: PushRegistryInternal.enablePushLaunch(true);
0499: }
0500:
0501: return info.id;
0502: }
0503:
0504: /**
0505: * Downloads the JAD, save it in the install state.
0506: * Parse the JAD, make sure it has
0507: * the required properties, and save them in the install state.
0508: *
0509: * @exception IOException is thrown, if an I/O error occurs during
0510: * descriptor or jar file download
0511: * @exception InvalidJadException is thrown, if the descriptor file is not
0512: * properly formatted or does not contain the required attributes
0513: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0514: * locked
0515: * @exception IllegalArgumentException is thrown, if the
0516: * descriptor file is not specified
0517: */
0518: private void installStep1() throws IOException,
0519: InvalidJadException, MIDletSuiteLockedException {
0520:
0521: if (state.localJarUrl == null
0522: || state.localJadUrl.length() == 0) {
0523: throw new IllegalArgumentException(
0524: "Must specify URL of .jad file");
0525: }
0526:
0527: try {
0528: state.jad = downloadJAD();
0529: } catch (OutOfMemoryError e) {
0530: try {
0531: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0532: } catch (Throwable t) {
0533: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0534: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
0535: "Throwable during posting install message");
0536: }
0537: }
0538:
0539: throw new InvalidJadException(
0540: InvalidJadException.TOO_MANY_PROPS);
0541: }
0542:
0543: if (state.exception != null) {
0544: return;
0545: }
0546:
0547: state.jadProps = new JadProperties();
0548:
0549: try {
0550: state.jadProps.load(new ByteArrayInputStream(state.jad),
0551: state.jadEncoding);
0552: } catch (OutOfMemoryError e) {
0553: state.jad = null;
0554: try {
0555: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0556: } catch (Throwable t) {
0557: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0558: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
0559: "Throwable during posting install message");
0560: }
0561: }
0562:
0563: throw new InvalidJadException(
0564: InvalidJadException.TOO_MANY_PROPS);
0565: } catch (InvalidJadException ije) {
0566: state.jad = null;
0567: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0568: throw ije;
0569: } catch (java.io.UnsupportedEncodingException uee) {
0570: state.jad = null;
0571: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0572: throw new InvalidJadException(
0573: InvalidJadException.UNSUPPORTED_CHAR_ENCODING,
0574: state.jadEncoding);
0575: }
0576:
0577: info.suiteName = state.jadProps
0578: .getProperty(MIDletSuite.SUITE_NAME_PROP);
0579: if (info.suiteName == null || info.suiteName.length() == 0) {
0580: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0581: throw new InvalidJadException(
0582: InvalidJadException.MISSING_SUITE_NAME);
0583: }
0584:
0585: info.suiteVendor = state.jadProps
0586: .getProperty(MIDletSuite.VENDOR_PROP);
0587: if (info.suiteVendor == null || info.suiteVendor.length() == 0) {
0588: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0589: throw new InvalidJadException(
0590: InvalidJadException.MISSING_VENDOR);
0591: }
0592:
0593: info.suiteVersion = state.jadProps
0594: .getProperty(MIDletSuite.VERSION_PROP);
0595: if (info.suiteVersion == null
0596: || info.suiteVersion.length() == 0) {
0597: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0598: throw new InvalidJadException(
0599: InvalidJadException.MISSING_VERSION);
0600: }
0601:
0602: try {
0603: checkVersionFormat(info.suiteVersion);
0604: } catch (NumberFormatException nfe) {
0605: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0606: throw new InvalidJadException(
0607: InvalidJadException.INVALID_VERSION);
0608: }
0609:
0610: info.id = state.midletSuiteStorage.createSuiteID();
0611:
0612: checkPreviousVersion();
0613: state.nextStep++;
0614: }
0615:
0616: /**
0617: * If the JAD belongs to an installed suite, check the URL against the
0618: * installed one.
0619: */
0620: private void installStep2() {
0621: state.nextStep++;
0622:
0623: if (state.isPreviousVersion) {
0624: checkForDifferentDomains(info.jadUrl);
0625: }
0626: }
0627:
0628: /**
0629: * Makes sure the suite can fit in storage.
0630: *
0631: * @exception IOException is thrown, if an I/O error occurs during
0632: * descriptor or jar file download
0633: * @exception InvalidJadException is thrown, if the descriptor file is not
0634: * properly formatted or does not contain the required
0635: */
0636: private void installStep3() throws IOException, InvalidJadException {
0637: String sizeString;
0638: int dataSize;
0639: int suiteSize;
0640:
0641: sizeString = state.jadProps
0642: .getProperty(MIDletSuite.JAR_SIZE_PROP);
0643: if (sizeString == null) {
0644: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0645: throw new InvalidJadException(
0646: InvalidJadException.MISSING_JAR_SIZE);
0647: }
0648:
0649: try {
0650: info.expectedJarSize = Integer.parseInt(sizeString);
0651: } catch (NumberFormatException e) {
0652: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0653: throw new InvalidJadException(
0654: InvalidJadException.INVALID_VALUE);
0655: }
0656:
0657: sizeString = state.jadProps
0658: .getProperty(MIDletSuite.DATA_SIZE_PROP);
0659: if (sizeString == null) {
0660: dataSize = 0;
0661: } else {
0662: try {
0663: dataSize = Integer.parseInt(sizeString);
0664: } catch (NumberFormatException e) {
0665: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0666: throw new InvalidJadException(
0667: InvalidJadException.INVALID_VALUE);
0668: }
0669: }
0670:
0671: /*
0672: * A suite is a jad + jar + manifest + url + data size.
0673: * lets say the manifest is the same size as the jad
0674: * since we do know at this point. the size is in bytes,
0675: * UTF-8 chars can be upto 3 bytes
0676: */
0677: suiteSize = info.expectedJarSize + (state.jad.length * 2)
0678: + (info.jadUrl.length() * 3) + dataSize;
0679: state.jad = null;
0680:
0681: state.file = new File();
0682:
0683: if (suiteSize > state.file
0684: .getBytesAvailableForFiles(state.storageId)) {
0685: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0686:
0687: // the size reported to the user should be in K and rounded up
0688: throw new InvalidJadException(
0689: InvalidJadException.INSUFFICIENT_STORAGE, Integer
0690: .toString((suiteSize + 1023) / 1024));
0691: }
0692:
0693: info.jarUrl = state.jadProps
0694: .getProperty(MIDletSuite.JAR_URL_PROP);
0695: if (info.jarUrl == null || info.jarUrl.length() == 0) {
0696: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0697: throw new InvalidJadException(
0698: InvalidJadException.MISSING_JAR_URL);
0699: }
0700:
0701: state.nextStep++;
0702: }
0703:
0704: /**
0705: * Confirm installation with the user.
0706: *
0707: * @exception IOException is thrown, if the user cancels installation
0708: */
0709: private void installStep4() throws IOException {
0710:
0711: synchronized (state) {
0712: /* One more check to see if user has already canceled */
0713: if (state.stopInstallation) {
0714: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0715: throw new IOException("stopped");
0716: }
0717: /*
0718: * Not canceled, so ignore cancel requests for now because below we
0719: * are going to ask anyway if user wants to install suite
0720: */
0721: state.ignoreCancel = true;
0722: }
0723:
0724: if (state.listener != null
0725: && !state.listener.confirmJarDownload(state)) {
0726: state.stopInstallation = true;
0727: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0728: throw new IOException("stopped");
0729: }
0730:
0731: synchronized (state) {
0732: /* Allow cancel requests again */
0733: state.ignoreCancel = false;
0734: }
0735: state.nextStep++;
0736: }
0737:
0738: /**
0739: * Downloads the JAR, make sure it is the correct size, make sure
0740: * the required attributes match the JAD's. Then store the
0741: * application.
0742: *
0743: * @exception IOException is thrown, if an I/O error occurs during
0744: * descriptor or jar file download
0745: * @exception InvalidJadException is thrown, if the descriptor file is not
0746: * properly formatted or does not contain the required
0747: */
0748: private void installStep5() throws IOException, InvalidJadException {
0749: int bytesDownloaded;
0750: MIDletInfo midletInfo;
0751: String midlet;
0752:
0753: // Send out delete notifications that have been queued, first
0754: OtaNotifier.postQueuedDeleteMsgsBackToProvider(
0755: state.proxyUsername, state.proxyPassword);
0756:
0757: // Save jar file to temp name; we need to do this to read the
0758: // manifest entry, but, don't want to overwrite an existing
0759: // application in case there are problems with the manifest
0760: state.storageRoot = File.getStorageRoot(state.storageId);
0761: info.jarFilename = state.storageRoot + TMP_FILENAME;
0762:
0763: bytesDownloaded = downloadJAR(info.jarFilename);
0764:
0765: if (state.exception != null) {
0766: return;
0767: }
0768:
0769: try {
0770: state.storage = new RandomAccessStream();
0771:
0772: state.installInfo.authPath = verifier.verifyJar(
0773: state.storage, info.jarFilename);
0774:
0775: if (state.listener != null) {
0776: state.listener.updateStatus(VERIFYING_SUITE, state);
0777: }
0778:
0779: // Create JAR Properties (From .jar file's MANIFEST)
0780:
0781: try {
0782: state.manifest = JarReader.readJarEntry(token,
0783: info.jarFilename, MIDletSuite.JAR_MANIFEST);
0784: if (state.manifest == null) {
0785: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0786: throw new InvalidJadException(
0787: InvalidJadException.CORRUPT_JAR,
0788: MIDletSuite.JAR_MANIFEST);
0789: }
0790: } catch (OutOfMemoryError e) {
0791: try {
0792: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0793: } catch (Throwable t) {
0794: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0795: Logging
0796: .report(Logging.WARNING,
0797: LogChannels.LC_AMS,
0798: "Throwable during posting the install message");
0799: }
0800: }
0801:
0802: throw new InvalidJadException(
0803: InvalidJadException.TOO_MANY_PROPS);
0804: } catch (IOException ioe) {
0805: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0806: throw new InvalidJadException(
0807: InvalidJadException.CORRUPT_JAR,
0808: MIDletSuite.JAR_MANIFEST);
0809: }
0810:
0811: state.jarProps = new ManifestProperties();
0812:
0813: try {
0814: // JarFile jarFile = new JarFile(new java.io.File(info.jarFilename));
0815: // Manifest manifest = jarFile.getManifest();
0816:
0817: // state.jarProps.readFromAttributes(manifest.getMainAttributes());
0818:
0819: state.jarProps.load(new ByteArrayInputStream(
0820: state.manifest));
0821: state.manifest = null;
0822: } catch (OutOfMemoryError e) {
0823: state.manifest = null;
0824: try {
0825: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0826: } catch (Throwable t) {
0827: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0828: Logging
0829: .report(Logging.WARNING,
0830: LogChannels.LC_AMS,
0831: "Throwable while posting install message ");
0832: }
0833: }
0834:
0835: throw new InvalidJadException(
0836: InvalidJadException.TOO_MANY_PROPS);
0837: } catch (InvalidJadException ije) {
0838: state.manifest = null;
0839:
0840: try {
0841: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0842: } catch (Throwable t) {
0843: // ignore
0844: }
0845:
0846: throw ije;
0847: }
0848:
0849: for (int i = 1;; i++) {
0850: midlet = state.getAppProperty("MIDlet-" + i);
0851: if (midlet == null) {
0852: break;
0853: }
0854:
0855: /*
0856: * Verify the MIDlet class is present in the JAR
0857: * An exception thrown if not.
0858: * Do the proper install notify on an exception
0859: */
0860: try {
0861: midletInfo = new MIDletInfo(midlet);
0862:
0863: verifyMIDlet(midletInfo.classname);
0864: } catch (InvalidJadException ije) {
0865: if (ije.getReason() == InvalidJadException.INVALID_VALUE) {
0866: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0867: } else {
0868: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0869: }
0870: throw ije;
0871: }
0872: }
0873:
0874: // move on to the next step after a warning
0875: state.nextStep++;
0876:
0877: // Check Manifest entries against .jad file
0878: if (info.jadUrl != null) {
0879: if (bytesDownloaded != info.expectedJarSize) {
0880: postInstallMsgBackToProvider(OtaNotifier.JAR_SIZE_MISMATCH_MSG);
0881: throw new InvalidJadException(
0882: InvalidJadException.JAR_SIZE_MISMATCH);
0883: }
0884:
0885: if (!info.suiteName.equals(state.jarProps
0886: .getProperty(MIDletSuite.SUITE_NAME_PROP))) {
0887: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0888: throw new InvalidJadException(
0889: InvalidJadException.SUITE_NAME_MISMATCH);
0890: }
0891:
0892: if (!info.suiteVersion.equals(state.jarProps
0893: .getProperty(MIDletSuite.VERSION_PROP))) {
0894: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0895: throw new InvalidJadException(
0896: InvalidJadException.VERSION_MISMATCH,
0897: new String(
0898: info.suiteVersion
0899: + ","
0900: + state.jarProps
0901: .getProperty(MIDletSuite.VERSION_PROP)));
0902: }
0903:
0904: if (!info.suiteVendor.equals(state.jarProps
0905: .getProperty(MIDletSuite.VENDOR_PROP))) {
0906: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0907: throw new InvalidJadException(
0908: InvalidJadException.VENDOR_MISMATCH);
0909: }
0910: } else {
0911: info.expectedJarSize = bytesDownloaded;
0912: info.suiteName = state.jarProps
0913: .getProperty(MIDletSuite.SUITE_NAME_PROP);
0914: if (info.suiteName == null
0915: || info.suiteName.length() == 0) {
0916: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0917: throw new InvalidJadException(
0918: InvalidJadException.MISSING_SUITE_NAME);
0919: }
0920:
0921: info.suiteVendor = state.jarProps
0922: .getProperty(MIDletSuite.VENDOR_PROP);
0923: if (info.suiteVendor == null
0924: || info.suiteVendor.length() == 0) {
0925: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0926: throw new InvalidJadException(
0927: InvalidJadException.MISSING_VENDOR);
0928: }
0929:
0930: info.suiteVersion = state.jarProps
0931: .getProperty(MIDletSuite.VERSION_PROP);
0932: if (info.suiteVersion == null
0933: || info.suiteVersion.length() == 0) {
0934: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0935: throw new InvalidJadException(
0936: InvalidJadException.MISSING_VERSION);
0937: }
0938:
0939: try {
0940: checkVersionFormat(info.suiteVersion);
0941: } catch (NumberFormatException nfe) {
0942: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0943: throw new InvalidJadException(
0944: InvalidJadException.INVALID_VERSION);
0945: }
0946:
0947: // if already installed, check the domain of the JAR URL
0948:
0949: info.id = state.midletSuiteStorage.createSuiteID();
0950:
0951: checkPreviousVersion();
0952: }
0953: } catch (Exception e) {
0954: state.file.delete(info.jarFilename);
0955:
0956: if (e instanceof IOException) {
0957: throw (IOException) e;
0958: }
0959:
0960: throw (RuntimeException) e;
0961: }
0962: }
0963:
0964: /**
0965: * If the JAR belongs to an installed suite if there was
0966: * no JAD, check the URL against the installed one.
0967: */
0968: private void installStep6() {
0969: state.nextStep++;
0970:
0971: if (info.jadUrl == null && state.isPreviousVersion) {
0972: checkForDifferentDomains(info.jarUrl);
0973: }
0974: }
0975:
0976: /**
0977: * Checks the permissions and store the suite.
0978: *
0979: * @exception IOException is thrown, if an I/O error occurs during
0980: * storing the suite
0981: * @exception InvalidJadException is thrown, if the there is
0982: * permission problem
0983: */
0984: private void installStep7() throws IOException, InvalidJadException {
0985:
0986: try {
0987: if (info.authPath != null) {
0988: // suite was signed
0989: info.domain = verifier
0990: .getSecurityDomainName(info.authPath[0]);
0991: if (state.listener != null
0992: && !state.listener.confirmAuthPath(state)) {
0993: state.stopInstallation = true;
0994: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0995: throw new IOException("stopped");
0996: }
0997: } else {
0998: info.domain = unsignedSecurityDomain;
0999: }
1000:
1001: info.trusted = Permissions.isTrusted(info.domain);
1002:
1003: // Do not overwrite trusted suites with untrusted ones
1004: if (!info.trusted && state.isPreviousVersion
1005: && state.previousSuite.isTrusted()) {
1006:
1007: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1008:
1009: throw new InvalidJadException(
1010: InvalidJadException.TRUSTED_OVERWRITE_FAILURE,
1011: state.previousInstallInfo.authPath[0]);
1012: }
1013:
1014: /*
1015: * The unidentified suites do not get checked for requested
1016: * permissions.
1017: */
1018: if (Permissions.UNIDENTIFIED_DOMAIN_BINDING
1019: .equals(info.domain)) {
1020:
1021: settings
1022: .setPermissions((Permissions
1023: .forDomain(info.domain))[Permissions.CUR_LEVELS]);
1024:
1025: /*
1026: * To keep public key management simple, there is only one
1027: * trusted keystore. So it is possible that the CA for
1028: * the suite is untrusted. This may be done on purpose for
1029: * testing. This is OK, but do not confuse the user by saying
1030: * the untrusted suite is authorized, so set the CA name to
1031: * null.
1032: */
1033: info.authPath = null;
1034: } else {
1035: /*
1036: * For identified suites, make sure an properties duplicated in
1037: * both the manifest and JAD are the same.
1038: */
1039: if (info.jadUrl != null) {
1040: checkForJadManifestMismatches();
1041: }
1042:
1043: settings
1044: .setPermissions(getInitialPermissions(info.domain));
1045: }
1046:
1047: if (state.isPreviousVersion) {
1048: applyCurrentUserLevelPermissions(
1049: state.previousSuite.getPermissions(),
1050: (Permissions.forDomain(info.domain))[Permissions.MAX_LEVELS],
1051: settings.getPermissions());
1052:
1053: if (state.removeRMS) {
1054: // override suite comparisons, just remove RMS
1055: RecordStoreFactory.removeRecordStoresForSuite(null,
1056: info.id);
1057: } else {
1058: processPreviousRMS();
1059: }
1060: }
1061:
1062: state.securityHandler = new SecurityHandler(settings
1063: .getPermissions(), info.domain);
1064:
1065: checkRuntimeEnv();
1066: checkConfiguration();
1067: matchProfile();
1068:
1069: // IMPL_NOTE: sync with cldc installer for CHManager dependency
1070: /**
1071: * try {
1072: * state.chmanager.preInstall(this,
1073: * (InstallState)state,
1074: * (MIDletSuite)state,
1075: * (info.authPath == null ?
1076: * null : info.authPath[0]));
1077: * } catch (InvalidJadException jex) {
1078: * // Post the correct install notify msg back to the server
1079: * String msg = OtaNotifier.INVALID_CONTENT_HANDLER;
1080: * if (jex.getReason() ==
1081: * InvalidJadException.CONTENT_HANDLER_CONFLICT) {
1082: * msg = OtaNotifier.CONTENT_HANDLER_CONFLICT;
1083: * }
1084:
1085: * postInstallMsgBackToProvider(msg);
1086: * throw jex;
1087: * } catch (SecurityException se) {
1088: * postInstallMsgBackToProvider(
1089: * OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1090:
1091: * // since our state object put the permission in message
1092: * throw new InvalidJadException(
1093: * InvalidJadException.AUTHORIZATION_FAILURE,
1094: * se.getMessage());
1095: * }
1096: **/
1097:
1098: // make sure at least 1 second has passed
1099: try {
1100: long waitTime = 1000 - (System.currentTimeMillis() - state.startTime);
1101:
1102: if (waitTime > 0) {
1103: Thread.sleep(waitTime);
1104: }
1105: } catch (InterruptedException ie) {
1106: // ignore
1107: }
1108:
1109: synchronized (state) {
1110: // this is the point of no return, one last check
1111: if (state.stopInstallation) {
1112: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1113: throw new IOException("stopped");
1114: }
1115:
1116: state.ignoreCancel = true;
1117: }
1118:
1119: if (state.listener != null) {
1120: state.listener.updateStatus(STORING_SUITE, state);
1121: }
1122:
1123: registerPushConnections();
1124:
1125: /** Do the Content Handler registration updates now */
1126: // IMPL_NOTE: sync with cldc installer for CHManager dependency
1127: // state.chmanager.install();
1128: /*
1129: * Store suite will remove the suite including push connections,
1130: * if there an error, but may not remove the temp jar file.
1131: */
1132: MIDletInfo midletInfo = state.getMidletInfo();
1133: String midletClassNameToRun = null, iconName = null;
1134: MIDletSuiteInfo msi;
1135:
1136: if (midletInfo != null) {
1137: midletClassNameToRun = midletInfo.classname;
1138: iconName = midletInfo.icon;
1139: }
1140:
1141: msi = new MIDletSuiteInfo(info.id);
1142: msi.displayName = state.getDisplayName();
1143: msi.midletToRun = midletClassNameToRun;
1144: msi.numberOfMidlets = state.getNumberOfMIDlets();
1145: /* default is to enable a newly installed suite */
1146: msi.enabled = true;
1147: msi.trusted = info.trusted;
1148: msi.preinstalled = false;
1149: msi.iconName = iconName;
1150: msi.storageId = state.storageId;
1151:
1152: state.midletSuiteStorage.storeSuite(info, settings, msi,
1153: state.jadProps, state.jarProps);
1154: } catch (Throwable e) {
1155: state.file.delete(info.jarFilename);
1156: if (e instanceof IOException) {
1157: throw (IOException) e;
1158: }
1159:
1160: if (e instanceof OutOfMemoryError) {
1161: try {
1162: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
1163: } catch (Throwable t) {
1164: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
1165: Logging
1166: .report(Logging.WARNING,
1167: LogChannels.LC_AMS,
1168: "Throwable during posting install message");
1169: }
1170: }
1171:
1172: throw new InvalidJadException(
1173: InvalidJadException.TOO_MANY_PROPS);
1174: }
1175:
1176: throw (RuntimeException) e;
1177: }
1178:
1179: state.nextStep++;
1180:
1181: try {
1182: postInstallMsgBackToProvider(OtaNotifier.SUCCESS_MSG);
1183: } catch (Throwable t) {
1184: /*
1185: * The suite is successfully installed, but the post of the
1186: * status message failed. Do not let this failure prevent
1187: * the suite from being used.
1188: */
1189: }
1190: }
1191:
1192: /**
1193: * Installation time optimizations are to be done in this step.
1194: * MONET optimization, VERIFY ONCE optimization, etc.
1195: * This step is done after obligatory installation part,
1196: * so the suite is downloaded, checked and stored by this moment.
1197: */
1198: private void installStep8() throws IOException {
1199:
1200: // In case of installation from JAR the suite id as well as other
1201: // properties are read from .jar file's MANIFEST rather then from
1202: // JAD file. Only after that application image can be generated.
1203: if (Constants.MONET_ENABLED) {
1204: if (state.listener != null) {
1205: state.listener
1206: .updateStatus(GENERATING_APP_IMAGE, state);
1207: }
1208: MIDletAppImageGenerator.createAppImage(info.id,
1209: state.midletSuiteStorage);
1210: }
1211: // Verification caching should be done if MONET disabled only
1212: else if (Constants.VERIFY_ONCE) {
1213: if (state.listener != null) {
1214: state.listener.updateStatus(VERIFYING_SUITE_CLASSES,
1215: state);
1216: }
1217: // Preverify all suite classes
1218: // in the case of success store hash value of the suite
1219: try {
1220: info.verifyHash = MIDletSuiteVerifier
1221: .verifySuiteClasses(info.id,
1222: state.midletSuiteStorage);
1223: if (info.verifyHash != null) {
1224: state.midletSuiteStorage.storeSuiteVerifyHash(
1225: info.id, info.verifyHash);
1226: }
1227: } catch (Throwable t) {
1228: // Notify installation listener of verifcation error
1229: state.exception = new InvalidJadException(
1230: InvalidJadException.JAR_CLASSES_VERIFICATION_FAILED);
1231: if (state.listener != null) {
1232: state.listener.updateStatus(
1233: VERIFYING_SUITE_CLASSES, state);
1234: }
1235:
1236: // Clean exception since this step is optional and its
1237: // problems shouldn't cause whole installation failure
1238: state.exception = null;
1239: }
1240: }
1241:
1242: state.nextStep++;
1243: }
1244:
1245: /**
1246: * Verify that a class is present in the JAR file.
1247: * If the classname is invalid or is not found an
1248: * InvalidJadException is thrown.
1249: * @param classname the name of the class to verify
1250: * @exception InvalidJadException is thrown if the name is null or empty
1251: * or if the file is not found
1252: */
1253: public void verifyMIDlet(String classname)
1254: throws InvalidJadException {
1255: if (classname == null || classname.length() == 0) {
1256: throw new InvalidJadException(
1257: InvalidJadException.INVALID_VALUE);
1258: }
1259:
1260: String file = classname.replace('.', '/').concat(".class");
1261:
1262: try {
1263: /* Attempt to read the MIDlet from the JAR file. */
1264: if (JarReader.readJarEntry(token, info.jarFilename, file) != null) {
1265: return; // File found, normal return
1266: }
1267: // Fall into throwing the exception
1268: } catch (IOException ioe) {
1269: // Fall into throwing the exception
1270: }
1271: // Throw the InvalidJadException
1272: throw new InvalidJadException(InvalidJadException.CORRUPT_JAR,
1273: file);
1274: }
1275:
1276: /**
1277: * Downloads an application descriptor file from the given URL.
1278: *
1279: * @return a byte array representation of the file or null if not found
1280: *
1281: * @exception IOException is thrown if any error prevents the download
1282: * of the JAD
1283: */
1284: protected abstract byte[] downloadJAD() throws IOException;
1285:
1286: /**
1287: * Downloads an application archive file from the given URL into the
1288: * given file. Automatically handle re-tries.
1289: *
1290: * @param filename name of the file to write. This file resides
1291: * in the storage area of the given application
1292: *
1293: * @return size of the JAR
1294: *
1295: * @exception IOException is thrown if any error prevents the download
1296: * of the JAR
1297: */
1298: protected abstract int downloadJAR(String filename)
1299: throws IOException;
1300:
1301: /**
1302: * If the JAD belongs to an installed suite, check the URL against the
1303: * installed one. Set the state.exception if the user needs to be warned.
1304: *
1305: * @param url JAD or JAR URL of the suite being installed
1306: */
1307: protected void checkForDifferentDomains(String url) {
1308: String previousUrl = state.previousInstallInfo.getDownloadUrl();
1309: // perform a domain check not a straight compare
1310: if (info.authPath == null && previousUrl != null) {
1311: HttpUrl old = new HttpUrl(previousUrl);
1312: HttpUrl current = new HttpUrl(url);
1313:
1314: if ((current.domain != null && old.domain == null)
1315: || (current.domain == null && old.domain != null)
1316: || (current.domain != null && old.domain != null && !current.domain
1317: .regionMatches(true, 0, old.domain, 0,
1318: old.domain.length()))) {
1319: /*
1320: * The jad is at new location, could be bad,
1321: * let the user decide
1322: */
1323: state.exception = new InvalidJadException(
1324: InvalidJadException.JAD_MOVED, previousUrl);
1325: return;
1326: }
1327: }
1328: }
1329:
1330: /**
1331: * See if there is an installed version of the suite being installed and
1332: * if so, make an necessary checks. Will set state fields, including
1333: * the exception field for warning the user.
1334: *
1335: * @exception InvalidJadException if the new version is formated
1336: * incorrectly
1337: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
1338: * locked
1339: */
1340: protected void checkPreviousVersion() throws InvalidJadException,
1341: MIDletSuiteLockedException {
1342:
1343: int id;
1344: MIDletSuiteImpl midletSuite;
1345: String installedVersion;
1346: int cmpResult;
1347:
1348: // Check if app already exists
1349: id = MIDletSuiteStorage.getSuiteID(info.suiteVendor,
1350: info.suiteName);
1351: if (id == MIDletSuite.UNUSED_SUITE_ID) {
1352: // there is no previous version
1353: return;
1354: }
1355:
1356: try {
1357: midletSuite = state.midletSuiteStorage.getMIDletSuite(id,
1358: true);
1359:
1360: if (midletSuite == null) {
1361: // there is no previous version
1362: return;
1363: }
1364: checkVersionFormat(info.suiteVersion);
1365:
1366: state.isPreviousVersion = true;
1367:
1368: // This is now an update, use the old ID
1369: info.id = id;
1370:
1371: state.previousSuite = midletSuite;
1372: state.previousInstallInfo = midletSuite.getInstallInfo();
1373:
1374: if (state.force) {
1375: // do not ask questions, force an overwrite
1376: return;
1377: }
1378:
1379: // If it does, check version information
1380: installedVersion = midletSuite
1381: .getProperty(MIDletSuite.VERSION_PROP);
1382: cmpResult = vercmp(info.suiteVersion, installedVersion);
1383: if (cmpResult < 0) {
1384: // older version, warn user
1385: state.exception = new InvalidJadException(
1386: InvalidJadException.OLD_VERSION,
1387: installedVersion);
1388: return;
1389: }
1390:
1391: if (cmpResult == 0) {
1392: // already installed, warn user
1393: state.exception = new InvalidJadException(
1394: InvalidJadException.ALREADY_INSTALLED,
1395: installedVersion);
1396: return;
1397: }
1398:
1399: // new version, warn user
1400: state.exception = new InvalidJadException(
1401: InvalidJadException.NEW_VERSION, installedVersion);
1402: return;
1403: } catch (MIDletSuiteCorruptedException mce) {
1404: if (state.listener != null) {
1405: state.listener.updateStatus(CORRUPTED_SUITE, state);
1406: }
1407: } catch (NumberFormatException nfe) {
1408: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1409: throw new InvalidJadException(
1410: InvalidJadException.INVALID_VERSION);
1411: }
1412: }
1413:
1414: /**
1415: * Posts a status message back to the provider's URL in JAD.
1416: *
1417: * @param message status message to post
1418: */
1419: protected void postInstallMsgBackToProvider(String message) {
1420: OtaNotifier.postInstallMsgBackToProvider(message, state,
1421: state.proxyUsername, state.proxyPassword);
1422: }
1423:
1424: /**
1425: * Function that actually does the work of transferring file data.
1426: * <p>
1427: * Updates the listener every 1 K bytes.
1428: * <p>
1429: * If the amount of data to be read is larger than <code>maxDLSize</code>
1430: * we will break the input into chunks no larger than
1431: * <code>chunkSize</code>. This prevents the VM from running out of
1432: * memory when processing large files.
1433: *
1434: * @param in the input stream to read from
1435: * @param out the output stream to write to
1436: * @param chunkSize size of piece to read from the input buffer
1437: *
1438: * @return number of bytes written to the output stream
1439: *
1440: * @exception IOException if any exceptions occur during transfer
1441: * of data
1442: */
1443: protected int transferData(InputStream in, OutputStream out,
1444: int chunkSize) throws IOException {
1445: byte[] buffer = new byte[chunkSize];
1446: int bytesRead;
1447: int totalBytesWritten = 0;
1448:
1449: if (state.listener != null) {
1450: state.listener.updateStatus(state.beginTransferDataStatus,
1451: state);
1452: }
1453:
1454: try {
1455: for (int nextUpdate = totalBytesWritten + 1024;;) {
1456: bytesRead = in.read(buffer);
1457:
1458: if (state.listener != null
1459: && (bytesRead == -1 || totalBytesWritten
1460: + bytesRead >= nextUpdate)) {
1461:
1462: synchronized (state) {
1463: if (state.stopInstallation) {
1464: throw new IOException("stopped");
1465: }
1466:
1467: state.listener.updateStatus(
1468: state.transferStatus, state);
1469: }
1470:
1471: nextUpdate = totalBytesWritten + 1024;
1472: }
1473:
1474: if (bytesRead == -1) {
1475: return totalBytesWritten;
1476: }
1477:
1478: out.write(buffer, 0, bytesRead);
1479: totalBytesWritten += bytesRead;
1480: }
1481: } catch (IOException ioe) {
1482: if (state.stopInstallation) {
1483: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1484: throw new IOException("stopped");
1485: } else {
1486: throw ioe;
1487: }
1488: }
1489: }
1490:
1491: /**
1492: * Retrieves a scheme component of the given URL.
1493: *
1494: * @param url url to parse
1495: * @param defaultScheme if the url has no scheme component, this one
1496: * will be returned; may be null
1497: *
1498: * @return scheme component of the given URL
1499: */
1500: public static String getUrlScheme(String url, String defaultScheme) {
1501: if (url == null) {
1502: return null;
1503: }
1504:
1505: /* this will parse any kind of URL, not only Http */
1506: HttpUrl parsedUrl = new HttpUrl(url);
1507:
1508: if (parsedUrl.scheme == null) {
1509: return defaultScheme;
1510: }
1511:
1512: return parsedUrl.scheme;
1513: }
1514:
1515: /**
1516: * Retrieves a path component of the given URL.
1517: *
1518: * @param url url to parse
1519: *
1520: * @return path component of the given URL
1521: */
1522: public static String getUrlPath(String url) {
1523: if (url == null) {
1524: return null;
1525: }
1526:
1527: /* this will parse any kind of URL, not only Http */
1528: HttpUrl parsedUrl = new HttpUrl(url);
1529: return parsedUrl.path;
1530: }
1531:
1532: /**
1533: * Compares two URLs for equality in sense that they have the same
1534: * scheme, host and path.
1535: *
1536: * @param url1 the first URL for comparision
1537: * @param url1 the second URL for comparision
1538: *
1539: * @return true if the scheme, host and path of the first given url
1540: * is identical to the scheme, host and path of the second
1541: * given url; false otherwise
1542: */
1543: protected abstract boolean isSameUrl(String url1, String url2);
1544:
1545: /**
1546: * If this is an update, make sure the RMS data is handle correctly
1547: * according to the OTA spec.
1548: * <p>
1549: * From the OTA spec:
1550: * <blockquote>
1551: * The RMS record stores of a MIDlet suite being updated MUST be
1552: * managed as follows:</p>
1553: * <ul>
1554: * <li>
1555: * If the cryptographic signer of the new MIDlet suite and the
1556: * original MIDlet suite are identical, then the RMS record
1557: * stores MUST be retained and made available to the new MIDlet
1558: * suite.</li>
1559: * <li>
1560: * If the scheme, host, and path of the URL that the new
1561: * Application Descriptor is downloaded from is identical to the
1562: * scheme, host, and path of the URL the original Application
1563: * Descriptor was downloaded from, then the RMS MUST be retained
1564: * and made available to the new MIDlet suite.</li>
1565: * <li>
1566: * If the scheme, host, and path of the URL that the new MIDlet
1567: * suite is downloaded from is identical to the scheme, host, and
1568: * path of the URL the original MIDlet suite was downloaded from,
1569: * then the RMS MUST be retained and made available to the new
1570: * MIDlet suite.</li>
1571: * <li>
1572: * If the above statements are false, then the device MUST ask
1573: * the user whether the data from the original MIDlet suite
1574: * should be retained and made available to the new MIDlet
1575: * suite.</li>
1576: * </ul>
1577: * </blockquote>
1578: *
1579: * @exception IOException if the install is stopped
1580: */
1581: protected void processPreviousRMS() throws IOException {
1582: if (!RecordStoreFactory.suiteHasRmsData(info.id)) {
1583: return;
1584: }
1585:
1586: if (state.previousInstallInfo.authPath != null
1587: && info.authPath != null
1588: && info.authPath[0]
1589: .equals(state.previousInstallInfo.authPath[0])) {
1590: // signers the same
1591: return;
1592: }
1593:
1594: if (isSameUrl(info.jadUrl, state.previousInstallInfo
1595: .getJadUrl())
1596: || isSameUrl(info.jarUrl, state.previousInstallInfo
1597: .getJarUrl())) {
1598: return;
1599: }
1600:
1601: // ask the user, if no listener assume no for user's answer
1602: if (state.listener != null) {
1603: if (state.listener.keepRMS(state)) {
1604: // user wants to keep the data
1605: return;
1606: }
1607: }
1608:
1609: // this is a good place to check for a stop installing call
1610: if (state.stopInstallation) {
1611: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1612: throw new IOException("stopped");
1613: }
1614:
1615: RecordStoreFactory.removeRecordStoresForSuite(null, info.id);
1616: }
1617:
1618: /**
1619: * Stops the installation. If installer is not installing then this
1620: * method has no effect. This will cause the install method to
1621: * throw an IOException if the install is not writing the suite
1622: * to storage which is the point of no return.
1623: *
1624: * @return true if the install will stop, false if it is too late
1625: */
1626: public boolean stopInstalling() {
1627: if (state == null) {
1628: return false;
1629: }
1630:
1631: synchronized (state) {
1632: if (state.ignoreCancel) {
1633: return false;
1634: }
1635:
1636: state.stopInstallation = true;
1637: }
1638:
1639: return true;
1640: }
1641:
1642: /**
1643: * Tells if the installation was stopped by another thread.
1644: * @return true if the installation was stopped by another thread
1645: */
1646: public boolean wasStopped() {
1647: if (state == null) {
1648: return false;
1649: }
1650:
1651: return state.stopInstallation;
1652: }
1653:
1654: /**
1655: * Builds the initial API permission for suite currently being installed.
1656: *
1657: * @param domain security domain name for the CA of the suite
1658: *
1659: * @return current level of permissions
1660: *
1661: * @exception InvalidJadException if a permission attribute is not
1662: * formatted properly or a required permission is denied
1663: */
1664: protected byte[] getInitialPermissions(String domain)
1665: throws InvalidJadException {
1666: byte[][] domainPermissions = Permissions.forDomain(domain);
1667: byte[] permissions = Permissions.getEmptySet();
1668:
1669: // only the current level of each permission has to be adjusted
1670: getRequestedPermissions(MIDletSuite.PERMISSIONS_PROP,
1671: domainPermissions[Permissions.CUR_LEVELS], permissions,
1672: true);
1673:
1674: getRequestedPermissions(MIDletSuite.PERMISSIONS_OPT_PROP,
1675: domainPermissions[Permissions.CUR_LEVELS], permissions,
1676: false);
1677:
1678: return permissions;
1679: }
1680:
1681: /**
1682: * Gets the permissions for a domain that are requested the manifest.
1683: *
1684: * @param propName name of the property in the manifest
1685: * @param domainPermissions array of the starting levels for permissions
1686: * of a domain
1687: * @param permissions array to put the permissions from the domain in
1688: * when found in the manifest property
1689: * @param required if set to true the manifest permissions are required
1690: *
1691: * @exception InvalidJadException if a permission attribute is not
1692: * formatted properly or a required permission is denied
1693: */
1694: private void getRequestedPermissions(String propName,
1695: byte[] domainPermissions, byte[] permissions,
1696: boolean required) throws InvalidJadException {
1697:
1698: String jadPermissionLine;
1699: String reqPermissionLine;
1700: Vector reqPermissions;
1701: String permission;
1702: boolean found;
1703: int i;
1704:
1705: reqPermissionLine = state.getAppProperty(propName);
1706: if (reqPermissionLine == null
1707: || reqPermissionLine.length() == 0) {
1708: // Zero properties are allowed.
1709: return;
1710: }
1711:
1712: reqPermissions = Util
1713: .getCommaSeparatedValues(reqPermissionLine);
1714: if (reqPermissions.size() == 0) {
1715: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1716: throw new InvalidJadException(
1717: InvalidJadException.INVALID_VALUE);
1718: }
1719:
1720: for (int j = 0; j < reqPermissions.size(); j++) {
1721: permission = (String) reqPermissions.elementAt(j);
1722:
1723: if (permission.length() == 0) {
1724: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1725: throw new InvalidJadException(
1726: InvalidJadException.INVALID_VALUE);
1727: }
1728:
1729: found = false;
1730: for (i = 0; i < Permissions.NUMBER_OF_PERMISSIONS; i++) {
1731: if (Permissions.getName(i).equals(permission)) {
1732: if (domainPermissions[i] != Permissions.NEVER) {
1733: found = true;
1734: }
1735:
1736: break;
1737: }
1738: }
1739:
1740: if (!found) {
1741: if (required) {
1742: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1743: throw new InvalidJadException(
1744: InvalidJadException.AUTHORIZATION_FAILURE,
1745: permission);
1746: }
1747:
1748: continue;
1749: }
1750:
1751: permissions[i] = domainPermissions[i];
1752: }
1753: }
1754:
1755: /**
1756: * Apply the previous user level permission of the currently installed
1757: * version of a suite to the next version of the suite in a secure way.
1758: *
1759: * @param current array permissions for the current version
1760: * @param domainPermissions array of the starting levels for permissions
1761: * of the new domain
1762: * @param next array permissions for the next version
1763: */
1764: private void applyCurrentUserLevelPermissions(byte[] current,
1765: byte[] domainPermissions, byte[] next) {
1766:
1767: for (int i = 0; i < current.length && i < next.length; i++) {
1768: switch (current[i]) {
1769: case Permissions.ALLOW:
1770: case Permissions.NEVER:
1771: // not a user level permission
1772: continue;
1773: }
1774:
1775: switch (domainPermissions[i]) {
1776: case Permissions.ALLOW:
1777: case Permissions.NEVER:
1778: // not a user level permission
1779: continue;
1780:
1781: case Permissions.ONESHOT:
1782: if (current[i] == Permissions.SESSION) {
1783: // do not apply
1784: continue;
1785: }
1786: // fall through; per-session permissions may be permitted.
1787:
1788: case Permissions.SESSION:
1789: if (current[i] == Permissions.BLANKET
1790: || current[i] == Permissions.BLANKET_GRANTED) {
1791: // do not apply
1792: continue;
1793: }
1794: // fall through to store the permission for the next version.
1795:
1796: default:
1797: next[i] = current[i];
1798: continue;
1799: }
1800: }
1801: }
1802:
1803: /**
1804: * Checks to see if the JAD has a signature, but does not verify the
1805: * signature. This is a place holder the the Secure JUMPInstaller and
1806: * just returns false.
1807: *
1808: * @return true if the JAD has a signature
1809: */
1810: public boolean isJadSigned() {
1811: return verifier.isJadSigned();
1812: }
1813:
1814: /**
1815: * Sets security domain for unsigned suites. The default is untrusted.
1816: * Can only be called by JAM for testing.
1817: *
1818: * @param domain name of a security domain
1819: */
1820: public void setUnsignedSecurityDomain(String domain) {
1821: MIDletSuite midletSuite = MIDletStateHandler
1822: .getMidletStateHandler().getMIDletSuite();
1823:
1824: // if a MIDlet suite is not started, assume the JAM is calling.
1825: if (midletSuite != null) {
1826: midletSuite.checkIfPermissionAllowed(Permissions.MIDP);
1827: }
1828:
1829: unsignedSecurityDomain = domain;
1830: }
1831:
1832: /**
1833: * Checks to see that if any properties that are both in the JAD and
1834: * JAR manifest are not equal and throw a exception and notify the
1835: * server when a mismatch is found. Only used for trusted suites.
1836: * @exception InvalidJadException if the properties do not match
1837: */
1838: protected void checkForJadManifestMismatches()
1839: throws InvalidJadException {
1840:
1841: for (int i = 0; i < state.jarProps.size(); i++) {
1842: String key = state.jarProps.getKeyAt(i);
1843: String value = state.jarProps.getValueAt(i);
1844: String dup = state.jadProps.getProperty(key);
1845:
1846: if (dup == null) {
1847: continue;
1848: }
1849:
1850: if (!dup.equals(value)) {
1851: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
1852: throw new InvalidJadException(
1853: InvalidJadException.ATTRIBUTE_MISMATCH, key);
1854: }
1855: }
1856: }
1857:
1858: /**
1859: * Compares two version strings. The return values are very similar to
1860: * that of strcmp() in 'C'. If the first version is less than the second
1861: * version, a negative number will be returned. If the first version is
1862: * greater than the second version, a positive number will be returned.
1863: * If the two versions are equal, zero is returned.
1864: * <p>
1865: * Versions must be in the form <em>xxx.yyy.zzz</em>, where:
1866: * <pre>
1867: * <em>xxx</em> is the major version
1868: * <em>yyy</em> is the minor version
1869: * <em>zzz</em> is the micro version
1870: * </pre>
1871: * It is acceptable to omit the micro and possibly the minor versions.
1872: * If these are not included in the version string, the period immediately
1873: * preceding the number must also be removed. So, the versions
1874: * <em>xxx.yyy</em> or <em>xxx</em> are also valid.
1875: * <p>
1876: * Version numbers do not have to be three digits wide. However, you may
1877: * pad versions with leading zeros if desired.
1878: * <p>
1879: * If a version number is omitted, its value is assumed to be zero. All
1880: * tests will be based on this assumption.
1881: * <p>
1882: * For example:
1883: * <pre>
1884: * 1.04 > 1.
1885: * 1.04 < 1.4.1
1886: * 1.04 = 1.4.0
1887: * </pre>
1888: * <p>
1889: *
1890: * @param ver1 the first version to compare
1891: * @param ver2 the second version to compare
1892: *
1893: * @return 1 if <code>ver1</code> is greater than <code>ver2</code>
1894: * 0 if <code>ver1</code> is equal to <code>ver2</code>
1895: * -1 if <code>ver1</code> is less than <code>ver2</code>
1896: *
1897: * @exception NumberFormatException if either <code>ver1</code> or
1898: * <code>ver2</code> contain characters that are not numbers or periods
1899: */
1900: private static int vercmp(String ver1, String ver2)
1901: throws NumberFormatException {
1902: String strVal1;
1903: String strVal2;
1904: int intVal1;
1905: int intVal2;
1906: int idx1 = 0;
1907: int idx2 = 0;
1908: int newidx;
1909:
1910: if ((ver1 == null) && (ver2 == null)) {
1911: return 0;
1912: }
1913:
1914: if (ver1 == null) {
1915: return -1;
1916: }
1917:
1918: if (ver2 == null) {
1919: return 1;
1920: }
1921:
1922: for (int i = 0; i < 3; i++) {
1923: strVal1 = "0"; // Default value
1924: strVal2 = "0"; // Default value
1925: if (idx1 >= 0) {
1926: newidx = ver1.indexOf('.', idx1);
1927: if (newidx < 0) {
1928: strVal1 = ver1.substring(idx1);
1929: } else {
1930: strVal1 = ver1.substring(idx1, newidx);
1931: newidx++; // Idx of '.'; need to go to next char
1932: }
1933:
1934: idx1 = newidx;
1935: }
1936:
1937: if (idx2 >= 0) {
1938: newidx = ver2.indexOf('.', idx2);
1939: if (newidx < 0) {
1940: strVal2 = ver2.substring(idx2);
1941: } else {
1942: strVal2 = ver2.substring(idx2, newidx);
1943: newidx++;
1944: }
1945:
1946: idx2 = newidx;
1947: }
1948:
1949: intVal1 = Integer.parseInt(strVal1); // May throw NFE
1950: intVal2 = Integer.parseInt(strVal2); // May throw NFE
1951:
1952: if (intVal1 > intVal2) {
1953: return 1;
1954: }
1955:
1956: if (intVal1 < intVal2) {
1957: return -1;
1958: }
1959: }
1960:
1961: return 0;
1962: }
1963:
1964: /**
1965: * Checks the format of a version string.
1966: * <p>
1967: * Versions must be in the form <em>xxx.yyy.zzz</em>, where:
1968: * <pre>
1969: * <em>xxx</em> is the major version
1970: * <em>yyy</em> is the minor version
1971: * <em>zzz</em> is the micro version
1972: * </pre>
1973: * It is acceptable to omit the micro and possibly the minor versions.
1974: * If these are not included in the version string, the period immediately
1975: * preceding the number must also be removed. So, the versions
1976: * <em>xxx.yyy</em> or <em>xxx</em> are also valid.
1977: * <p>
1978: * Version numbers do not have to be three digits wide. However, you may
1979: * pad versions with leading zeros if desired.
1980: *
1981: * @param ver the version to check
1982: *
1983: * @exception NumberFormatException if <code>ver</code>
1984: * contains any characters that are not numbers or periods
1985: */
1986: private static void checkVersionFormat(String ver)
1987: throws NumberFormatException {
1988: int length;
1989: int start = 0;
1990: int end;
1991:
1992: length = ver.length();
1993: for (int i = 0;; i++) {
1994: // check for more than 3 parts or a trailing '.'
1995: if (i == 3 || start == length) {
1996: throw new NumberFormatException();
1997: }
1998:
1999: end = ver.indexOf('.', start);
2000: if (end == -1) {
2001: end = length;
2002: }
2003:
2004: // throws NFE if the substring is not all digits
2005: Integer.parseInt(ver.substring(start, end));
2006:
2007: if (end == length) {
2008: // we are done
2009: return;
2010: }
2011:
2012: // next time around start after the index of '.'
2013: start = end + 1;
2014: }
2015: }
2016:
2017: /**
2018: * Checks to make sure the runtime environment required by
2019: * the application is supported.
2020: * Send a message back to the server if the check fails and
2021: * throw an exception.
2022: *
2023: * @exception InvalidJadException if the check fails
2024: */
2025: private void checkRuntimeEnv() throws InvalidJadException {
2026: String execEnv;
2027:
2028: execEnv = state
2029: .getAppProperty(MIDletSuite.RUNTIME_EXEC_ENV_PROP);
2030: if (execEnv == null || execEnv.length() == 0) {
2031: execEnv = MIDletSuite.RUNTIME_EXEC_ENV_DEFAULT;
2032: }
2033:
2034: // need to call trim to remove trailing spaces
2035: execEnv = execEnv.trim();
2036:
2037: if (execEnv.equals(cldcRuntimeEnv)) {
2038: // success, done
2039: return;
2040: }
2041:
2042: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2043: throw new InvalidJadException(
2044: InvalidJadException.DEVICE_INCOMPATIBLE);
2045: }
2046:
2047: /**
2048: * Match the name of the configuration or profile, and return
2049: * true if the first name has a greater or equal version than the
2050: * second. The names of the format "XXX-Y.Y" (e.g. CLDC-1.0, MIDP-2.0)
2051: * as used in the system properties (microedition.configuration &
2052: * microedition.profiles).
2053: *
2054: * This is used for checking both configuration and profiles.
2055: *
2056: * @param name1 name of configuration or profile
2057: * @param name2 name of configuration or profile
2058: * @return true is name1 matches name2 and is greater or equal in
2059: * version number. false otherwise
2060: */
2061: private static boolean matchVersion(String name1, String name2) {
2062: int dash1 = name1.indexOf('-');
2063:
2064: if (dash1 < 0) {
2065: return false;
2066: }
2067:
2068: int dash2 = name2.indexOf('-');
2069:
2070: if (dash2 < 0) {
2071: return false;
2072: }
2073:
2074: String base1 = name1.substring(0, dash1);
2075: String base2 = name2.substring(0, dash2);
2076:
2077: if (!base1.equals(base2)) {
2078: return false;
2079: }
2080:
2081: String ver1 = name1.substring(dash1 + 1, name1.length());
2082: String ver2 = name2.substring(dash2 + 1, name2.length());
2083:
2084: return (vercmp(ver1, ver2) >= 0);
2085: }
2086:
2087: /**
2088: * Checks to make sure the configration need by the application
2089: * is supported.
2090: * Send a message back to the server if the check fails and
2091: * throw an exception.
2092: *
2093: * @exception InvalidJadException if the check fails
2094: */
2095: private void checkConfiguration() throws InvalidJadException {
2096: String config;
2097:
2098: config = state.getAppProperty(MIDletSuite.CONFIGURATION_PROP);
2099: if (config == null || config.length() == 0) {
2100: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
2101: throw new InvalidJadException(
2102: InvalidJadException.MISSING_CONFIGURATION);
2103: }
2104:
2105: if (cldcConfig == null) {
2106: // need to call trim to remove trailing spaces
2107: cldcConfig = System.getProperty(MICROEDITION_CONFIG).trim();
2108: }
2109:
2110: if (matchVersion(cldcConfig, config)) {
2111: // success, done
2112: return;
2113: }
2114:
2115: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2116: throw new InvalidJadException(
2117: InvalidJadException.DEVICE_INCOMPATIBLE);
2118: }
2119:
2120: /**
2121: * Tries to match one of the supported profiles with a profile
2122: * listed in string of profiles separated by a space.
2123: * Send a message back to the server if a match is not found and
2124: * throw an exception.
2125: *
2126: * @exception InvalidJadException if there is no match
2127: */
2128: private void matchProfile() throws InvalidJadException {
2129: String profiles = state
2130: .getAppProperty(MIDletSuite.PROFILE_PROP);
2131:
2132: if (profiles == null || profiles.length() == 0) {
2133: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
2134: throw new InvalidJadException(
2135: InvalidJadException.MISSING_PROFILE);
2136: }
2137:
2138: // build the list of supported profiles if needed
2139: if (supportedProfiles == null) {
2140: int start;
2141: int nextSpace = -1;
2142: String meProfiles = System
2143: .getProperty(MICROEDITION_PROFILES);
2144: if (meProfiles == null || meProfiles.length() == 0) {
2145: throw new RuntimeException(
2146: "system property microedition.profiles not set");
2147: }
2148: supportedProfiles = new Vector();
2149: // need to call trim to remove trailing spaces
2150: meProfiles = meProfiles.trim();
2151:
2152: for (;;) {
2153: start = nextSpace + 1;
2154: nextSpace = meProfiles.indexOf(' ', start);
2155:
2156: // consecutive spaces, keep searching
2157: if (nextSpace == start) {
2158: continue;
2159: }
2160:
2161: if ((nextSpace < 0)) {
2162: supportedProfiles.addElement(meProfiles.substring(
2163: start, meProfiles.length()));
2164: break;
2165: }
2166:
2167: supportedProfiles.addElement(meProfiles.substring(
2168: start, nextSpace));
2169:
2170: }
2171: }
2172:
2173: /*
2174: * for each profiles listed in MicroEdition-Profile, we need to
2175: * find a matching profile in microedition.profiles.
2176: */
2177: int current = 0;
2178: int nextSeparatorIndex = 0;
2179: String requestedProfile;
2180: boolean supported = false;
2181:
2182: // convert tab to space so that the parsing later is simplified
2183: StringBuffer tmp = new StringBuffer(profiles);
2184: boolean modified = false;
2185: while ((nextSeparatorIndex = profiles.indexOf('\t', current)) != -1) {
2186: tmp.setCharAt(nextSeparatorIndex, ' ');
2187: current++;
2188: modified = true;
2189: }
2190:
2191: if (modified) {
2192: profiles = tmp.toString();
2193: }
2194:
2195: // reset the indices
2196: current = nextSeparatorIndex = 0;
2197: do {
2198: // get the next requested profiles
2199: nextSeparatorIndex = profiles.indexOf(' ', current);
2200:
2201: if (nextSeparatorIndex == current) {
2202: // consecutive spaces, keep searching
2203: current++;
2204: continue;
2205: }
2206:
2207: if (nextSeparatorIndex == -1) {
2208: // last (or the only one) value in the list
2209: requestedProfile = profiles.substring(current, profiles
2210: .length());
2211: } else {
2212: requestedProfile = profiles.substring(current,
2213: nextSeparatorIndex);
2214: current = nextSeparatorIndex + 1;
2215: }
2216:
2217: /*
2218: * try to match each requested profiles against the supported
2219: * ones.
2220: */
2221: supported = false;
2222: for (int i = 0; i < supportedProfiles.size(); i++) {
2223: String supportedProfile = (String) supportedProfiles
2224: .elementAt(i);
2225: if (matchVersion(supportedProfile, requestedProfile)) {
2226: supported = true;
2227: break;
2228: }
2229: }
2230:
2231: // short circuit the test if there is one mismatch
2232: if (!supported) {
2233: break;
2234: }
2235: } while (nextSeparatorIndex != -1);
2236:
2237: // matched all requested profiles against supported ones
2238: if (supported) {
2239: return;
2240: }
2241:
2242: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2243: throw new InvalidJadException(
2244: InvalidJadException.DEVICE_INCOMPATIBLE);
2245: }
2246:
2247: /**
2248: * Registers the push connections for the application.
2249: * Send a message back to the server if a connection cannot be
2250: * registered and throw an exception.
2251: *
2252: * @exception InvalidJadException if a connection cannot be registered
2253: */
2254: private void registerPushConnections() throws InvalidJadException {
2255: byte[] curLevels = settings.getPermissions();
2256:
2257: if (state.isPreviousVersion) {
2258: PushRegistryInternal.unregisterConnections(info.id);
2259: }
2260:
2261: for (int i = 1;; i++) {
2262: String pushProp;
2263:
2264: pushProp = state.getAppProperty("MIDlet-Push-" + i);
2265: if (pushProp == null) {
2266: break;
2267: }
2268:
2269: /*
2270: * Parse the comma separated values -
2271: * " connection, midlet, role, filter"
2272: */
2273: int comma1 = pushProp.indexOf(',', 0);
2274: int comma2 = pushProp.indexOf(',', comma1 + 1);
2275:
2276: String conn = pushProp.substring(0, comma1).trim();
2277: String midlet = pushProp.substring(comma1 + 1, comma2)
2278: .trim();
2279: String filter = pushProp.substring(comma2 + 1).trim();
2280:
2281: /* Register the new push connection string. */
2282: try {
2283: PushRegistryInternal.registerConnectionInternal(state,
2284: conn, midlet, filter, false);
2285: } catch (Exception e) {
2286: /* If already registered, abort the installation. */
2287: PushRegistryInternal.unregisterConnections(info.id);
2288:
2289: if (state.isPreviousVersion) {
2290: // put back the old ones, removed above
2291: redoPreviousPushConnections();
2292: }
2293:
2294: if (e instanceof SecurityException) {
2295: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
2296:
2297: // since our state object put the permission in message
2298: throw new InvalidJadException(
2299: InvalidJadException.AUTHORIZATION_FAILURE,
2300: e.getMessage());
2301: }
2302:
2303: postInstallMsgBackToProvider(OtaNotifier.PUSH_REG_FAILURE_MSG);
2304:
2305: if (e instanceof IllegalArgumentException) {
2306: throw new InvalidJadException(
2307: InvalidJadException.PUSH_FORMAT_FAILURE,
2308: pushProp);
2309: }
2310:
2311: if (e instanceof ConnectionNotFoundException) {
2312: throw new InvalidJadException(
2313: InvalidJadException.PUSH_PROTO_FAILURE,
2314: pushProp);
2315: }
2316:
2317: if (e instanceof IOException) {
2318: throw new InvalidJadException(
2319: InvalidJadException.PUSH_DUP_FAILURE,
2320: pushProp);
2321: }
2322:
2323: if (e instanceof ClassNotFoundException) {
2324: throw new InvalidJadException(
2325: InvalidJadException.PUSH_CLASS_FAILURE,
2326: pushProp);
2327: }
2328:
2329: // error in the implementation code
2330: throw (RuntimeException) e;
2331: }
2332: }
2333:
2334: if (state.isPreviousVersion) {
2335: // preserve the push options when updating
2336: settings.setPushOptions(state.previousSuite
2337: .getPushOptions());
2338:
2339: // use the old setting
2340: settings.setPushInterruptSetting((byte) state.previousSuite
2341: .getPushInterruptSetting());
2342:
2343: // The old suite may have not had push connections
2344: if (settings.getPushInterruptSetting() != Permissions.NEVER) {
2345: return;
2346: }
2347: }
2348:
2349: if (curLevels[Permissions.PUSH] == Permissions.NEVER) {
2350: settings.setPushInterruptSetting(Permissions.NEVER);
2351: } else if (curLevels[Permissions.PUSH] == Permissions.ALLOW) {
2352: // Start the default at session for usability when denying.
2353: settings.setPushInterruptSetting(Permissions.SESSION);
2354: } else {
2355: settings
2356: .setPushInterruptSetting(curLevels[Permissions.PUSH]);
2357: }
2358: }
2359:
2360: /**
2361: * Registers the push connections for previous version after
2362: * and aborted update.
2363: */
2364: private void redoPreviousPushConnections() {
2365: for (int i = 1;; i++) {
2366: String pushProp;
2367:
2368: pushProp = state.previousSuite.getProperty("MIDlet-Push-"
2369: + i);
2370: if (pushProp == null) {
2371: break;
2372: }
2373:
2374: /*
2375: * Parse the comma separated values -
2376: * " connection, midlet, role, filter"
2377: */
2378: int comma1 = pushProp.indexOf(',', 0);
2379: int comma2 = pushProp.indexOf(',', comma1 + 1);
2380:
2381: String conn = pushProp.substring(0, comma1).trim();
2382: String midlet = pushProp.substring(comma1 + 1, comma2)
2383: .trim();
2384: String filter = pushProp.substring(comma2 + 1).trim();
2385:
2386: /* Register the new push connection string. */
2387: try {
2388: PushRegistryInternal.registerConnectionInternal(state,
2389: conn, midlet, filter, true);
2390: } catch (IOException e) {
2391: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
2392: Logging
2393: .report(Logging.WARNING,
2394: LogChannels.LC_AMS,
2395: "registerConnectionInternal threw an IOException");
2396: }
2397: } catch (ClassNotFoundException e) {
2398: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
2399: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
2400: "registerConnectionInternal threw a "
2401: + "ClassNotFoundException");
2402: }
2403: }
2404: }
2405: }
2406: }
|