Extracted common Getdown applet config functionality so that it can be reused by applets that are doing more that just Getdown.

This commit is contained in:
Matt Jensen
2009-01-21 20:58:32 +00:00
parent 0453991622
commit 85d3456d97
3 changed files with 431 additions and 203 deletions
+13 -1
View File
@@ -24,8 +24,12 @@
*;
}
-keep public class com.threerings.getdown.data.Application {
*;
}
-keep public class com.threerings.getdown.launcher.Getdown {
public static void main (java.lang.String[]);
*;
}
-keep public class com.threerings.getdown.launcher.GetdownApp {
@@ -35,3 +39,11 @@
-keep public class com.threerings.getdown.launcher.GetdownApplet {
*;
}
-keep class * extends javax.swing.JApplet {
<methods>;
}
-keep public class com.threerings.getdown.launcher.GetdownAppletConfig {
*;
}
@@ -2,7 +2,7 @@
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2008 Three Rings Design, Inc.
// Copyright (C) 2004-2009 Three Rings Design, Inc.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
@@ -23,10 +23,8 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.threerings.getdown.launcher;
import java.awt.Color;
import java.awt.Container;
import java.awt.Image;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -36,19 +34,10 @@ import java.net.URL;
import javax.swing.JApplet;
import javax.swing.JPanel;
import java.util.Map;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.util.ConfigUtil;
import static com.threerings.getdown.Log.log;
/**
* An applet that can be used to launch a Getdown application (when signed and
* given privileges).
* An applet that can be used to launch a Getdown application (when signed and given privileges).
*/
public class GetdownApplet extends JApplet
implements ImageLoader
@@ -56,74 +45,28 @@ public class GetdownApplet extends JApplet
@Override // documentation inherited
public void init ()
{
// verify that we are not being hijacked to execute malicious code in the name of the
// signer
String appbase = getParameter("appbase");
String appname = getParameter("appname");
String imgpath = getParameter("bgimage");
String errorimgpath = getParameter("errorbgimage");
if (appbase == null) {
appbase = "";
}
if (appname == null) {
appname = "";
}
_config = new GetdownAppletConfig(this);
final RotatingBackgrounds bgimages;
if (imgpath == null) {
bgimages = new RotatingBackgrounds();
} else if (imgpath.indexOf(",") > -1) {
bgimages = new RotatingBackgrounds(imgpath.split(","), errorimgpath, this);
} else {
bgimages = new RotatingBackgrounds(loadImage(imgpath));
}
log.info("App Base: " + appbase);
log.info("App Name: " + appname);
File appdir = null;
try {
appdir = initGetdown(appbase, appname, imgpath);
// record a few things for posterity
log.info("------------------ VM Info ------------------");
log.info("-- OS Name: " + System.getProperty("os.name"));
log.info("-- OS Arch: " + System.getProperty("os.arch"));
log.info("-- OS Vers: " + System.getProperty("os.version"));
log.info("-- Java Vers: " + System.getProperty("java.version"));
log.info("-- Java Home: " + System.getProperty("java.home"));
log.info("-- User Name: " + System.getProperty("user.name"));
log.info("-- User Home: " + System.getProperty("user.home"));
log.info("-- Cur dir: " + System.getProperty("user.dir"));
log.info("---------------------------------------------");
} catch (Exception e) {
_errmsg = e.getMessage();
}
// Pull out system properties to pass through to the launched vm if they exist
String params = getParameter("app_properties");
String[] jvmargs;
if(params == null){
jvmargs = new String[0];
} else {
jvmargs = params.split(",");
for (int ii = 0; ii < jvmargs.length; ii++) {
jvmargs[ii] = "-D" + jvmargs[ii];
try {
// Check our permissions, download getdown.txt, etc.
_config.init();
} catch (Exception e) {
_errmsg = e.getMessage();
}
}
try {
// XXX getSigners() returns all certificates used to sign this applet which may allow
// a third party to insert a trusted certificate. This should be replaced with
// statically included trusted keys.
_getdown = new Getdown(appdir, null, GetdownApplet.class.getSigners(), jvmargs) {
_getdown = new Getdown(_config.appdir, null, GetdownApplet.class.getSigners(),
_config.jvmargs) {
protected Container createContainer () {
getContentPane().removeAll();
return getContentPane();
}
protected RotatingBackgrounds getBackground () {
return bgimages;
return _config.getBackgroundImages(GetdownApplet.this);
}
protected void showContainer () {
((JPanel)getContentPane()).revalidate();
@@ -132,55 +75,32 @@ public class GetdownApplet extends JApplet
// nothing to do as we're in an applet
}
protected boolean invokeDirect () {
return "true".equalsIgnoreCase(getParameter("direct"));
return _config.invokeDirect;
}
protected JApplet getApplet () {
return GetdownApplet.this;
}
protected void exit (int exitCode) {
_app.releaseLock();
// Redirect to the URL in 'redirect_on_finish' if we completed successfully.
// This allows us to use some javascript on that page to close Getdown's
// browser window. I'd prefer to use the javascript bridge from the applet
// rather than redirecting, but calling JSObject.getWindow(this).call("close")
// doesn't seem to do anything.
if (getParameter("redirect_on_finish") != null && exitCode == 0) {
URL dest;
try {
dest = new URL(getParameter("redirect_on_finish"));
} catch (MalformedURLException e) {
log.warning("URL in redirect_on_finish param is malformed: " + e);
return;
}
String target = getParameter("redirect_on_finish_target");
if (target == null) {
getAppletContext().showDocument(dest);
} else {
getAppletContext().showDocument(dest, target);
}
}
_config.redirect();
}
};
// configure the status/progress text in case something goes horribly wrong before we
// get a chance to read getdown.txt (like when the user rejects write permissions)
Rectangle statusBounds = Application.parseRect(getParameter("ui.status"));
if (statusBounds != null) {
_getdown._ifc.status = statusBounds;
}
Color statusColor = Application.parseColor(getParameter("ui.status_text"));
if (statusColor != null) {
_getdown._ifc.statusText = statusColor;
}
// set up our user interface immediately
// set up our user interface
_config.config(_getdown);
_getdown.preInit();
} catch (Exception e) {
// assume that if we already encountered an error, that is the root cause that we want
// to report back to the user
if (_errmsg == null) {
_errmsg = e.getMessage();
}
log.warning("init() failed.", e);
}
}
// implemented from ImageLoader
public Image loadImage (String path)
{
try {
@@ -215,107 +135,6 @@ public class GetdownApplet extends JApplet
_getdown._app.releaseLock();
}
/**
* Does all the fiddly initialization of Getdown and throws an exception if something goes
* horribly wrong. If an exception is thrown we will abort the whole process and display an
* error message to the user.
*/
protected File initGetdown (String appbase, String appname, String imgpath)
throws Exception
{
// getdown requires full read/write permissions to the system; if we don't have this, then
// we need to not do anything unsafe, and display a message to the user telling them they
// need to (groan) close out of the web browser entirely and re-launch the browser, go to
// our site, and accept the certificate
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkWrite("getdown");
sm.checkPropertiesAccess();
} catch (SecurityException se) {
log.warning("Signed applet rejected by user", "se", se);
throw new Exception("m.insufficient_permissions_error");
}
}
// pass through properties parameters
String properties = getParameter("properties");
if (properties != null) {
String[] proparray = properties.split(" ");
for (String property : proparray) {
String key = property.substring(property.indexOf("-D") + 2, property.indexOf("="));
String value = property.substring(property.indexOf("=") + 1);
System.setProperty(key, value);
}
}
// when run from an applet, we install to the user's home directory
String root;
if (RunAnywhere.isWindows()) {
root = "Application Data";
String verStr = System.getProperty("os.version");
try {
if (Float.parseFloat(verStr) >= 6.0f) {
// Vista makes us write it here.... Yay.
root = "AppData" + File.separator + "LocalLow";
}
} catch (Exception e) {
log.warning("Couldn't parse OS version", "vers", verStr, "error", e);
}
} else if (RunAnywhere.isMacOS()) {
root = "Library" + File.separator + "Application Support";
} else /* isLinux() or something wacky */ {
root = ".getdown";
}
File appdir = new File(System.getProperty("user.home") + File.separator + root +
File.separator + appname);
// if our application directory does not exist, auto-create it
if (!appdir.exists() || !appdir.isDirectory()) {
if (!appdir.mkdirs()) {
throw new Exception("m.create_appdir_failed");
}
}
// if an installer.txt file is desired, create that
String inststr = getParameter("installer");
if (!StringUtil.isBlank(inststr)) {
File infile = new File(appdir, "installer.txt");
if (!infile.exists()) {
writeToFile(infile, inststr);
}
}
// if our getdown.txt file does not exist, or it is corrupt, auto-/recreate it
File gdfile = new File(appdir, "getdown.txt");
boolean createGetdown = !gdfile.exists();
if (!createGetdown) {
try {
Map<String,Object> cdata = ConfigUtil.parseConfig(gdfile, false);
String oappbase = StringUtil.trim((String)cdata.get("appbase"));
createGetdown = (appbase != null && !appbase.trim().equals(oappbase));
if (createGetdown) {
log.warning("Recreating getdown.txt due to appbase mismatch",
"nappbase", appbase, "oappbase", oappbase);
}
} catch (Exception e) {
log.warning("Failure checking validity of getdown.txt, forcing recreate.",
"error", e);
createGetdown = true;
}
}
if (createGetdown) {
if (StringUtil.isBlank(appbase)) {
throw new Exception("m.missing_appbase");
}
if (!writeToFile(gdfile, "appbase = " + appbase)) {
throw new Exception("m.create_getdown_failed");
}
}
return appdir;
}
/**
* Creates the specified file and writes the supplied contents to it.
*/
@@ -332,6 +151,9 @@ public class GetdownApplet extends JApplet
}
}
/** The Getdown configuration as pulled from the applet params */
protected GetdownAppletConfig _config;
/** Handles all the actual getting down. */
protected Getdown _getdown;
@@ -0,0 +1,394 @@
//
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2009 Three Rings Design, Inc.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.threerings.getdown.launcher;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import javax.swing.JApplet;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.launcher.ImageLoader;
import com.threerings.getdown.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.ConfigUtil;
import static com.threerings.getdown.Log.log;
public class GetdownAppletConfig
{
public static final String APPBASE = "appbase";
public static final String APPNAME = "appname";
public static final String BGIMAGE = "bgimage";
public static final String ERRORBGIMAGE = "errorbgimage";
public static final String PROPERTIES = "properties";
public static final String PARAM_DELIMITER = ".";
public static final String APP_PROPERTIES = "app_properties";
public static final String DIRECT = "direct";
public static final String REDIRECT_ON_FINISH = "redirect_on_finish";
public static final String REDIRECT_ON_FINISH_TARGET = "redirect_on_finish_target";
public String appbase;
public String appname;
/** The list of background images and their display time */
public String imgpath;
/** The error background image */
public String errorimgpath;
public Properties properties = new Properties();
public File appdir;
public String[] jvmargs;
/** An optional URL to which the applet should redirect when done. */
public URL redirectUrl;
public String redirectTarget;
public String installerFileContents;
/**
* Indicates whether the downloaded app should be launched in the parent applet (true) or as a
* separate java process (false).
*/
public boolean invokeDirect;
/** Optional default bounds for the status panel. */
public Rectangle statusBounds;
public Color statusColor;
public GetdownAppletConfig (JApplet applet)
{
this(applet, null);
}
public GetdownAppletConfig (JApplet applet, String paramPrefix)
{
_applet = applet;
_prefix = paramPrefix;
appbase = getParameter(APPBASE, "");
appname = getParameter(APPNAME, "");
log.info("App Base: " + appbase);
log.info("App Name: " + appname);
imgpath = getParameter(BGIMAGE);
errorimgpath = getParameter(ERRORBGIMAGE);
String props = getParameter(PROPERTIES);
if (props != null) {
String[] proparray = props.split(" ");
for (String property : proparray) {
String key = property.substring(property.indexOf("-D") + 2, property.indexOf("="));
String value = property.substring(property.indexOf("=") + 1);
properties.setProperty(key, value);
}
}
String root;
if (RunAnywhere.isWindows()) {
root = "Application Data";
String verStr = System.getProperty("os.version");
try {
if (Float.parseFloat(verStr) >= 6.0f) {
// Vista makes us write it here.... Yay.
root = "AppData" + File.separator + "LocalLow";
}
} catch (Exception e) {
log.warning("Couldn't parse OS version", "vers", verStr, "error", e);
}
} else if (RunAnywhere.isMacOS()) {
root = "Library" + File.separator + "Application Support";
} else /* isLinux() or something wacky */{
root = ".getdown";
}
appdir = new File(System.getProperty("user.home") + File.separator + root + File.separator
+ appname);
installerFileContents = getParameter("installer");
// Pull out -D properties to pass through to the launched vm if they exist
String params = getParameter(APP_PROPERTIES);
if (params == null) {
jvmargs = new String[0];
} else {
jvmargs = params.split(",");
for (int ii = 0; ii < jvmargs.length; ii++) {
jvmargs[ii] = "-D" + jvmargs[ii];
}
}
String direct = getParameter(DIRECT, "false");
invokeDirect = Boolean.parseBoolean(direct);
String redirectURL = getParameter(REDIRECT_ON_FINISH);
if (redirectURL != null) {
try {
redirectUrl = new URL(redirectURL);
redirectTarget = getParameter(REDIRECT_ON_FINISH_TARGET);
} catch (MalformedURLException e) {
log.warning("URL in " + REDIRECT_ON_FINISH + " param is malformed: " + e);
}
}
// This allows us to configure the status panel from applet parameters in case something
// goes horribly wrong before we get a chance to read getdown.txt (like when the user
// rejects write permission for the applet)
statusBounds = Application.parseRect(getParameter("ui.status"));
statusColor = Application.parseColor(getParameter("ui.status_text"));
}
/**
* Does all the fiddly initialization of Getdown and throws an exception if something goes
* horribly wrong.
*/
public void init () throws Exception
{
securityCheck();
setSystemProperties();
ensureAppdirExists();
createFiles();
// record a few things for posterity
log.info("------------------ VM Info ------------------");
log.info("-- OS Name: " + System.getProperty("os.name"));
log.info("-- OS Arch: " + System.getProperty("os.arch"));
log.info("-- OS Vers: " + System.getProperty("os.version"));
log.info("-- Java Vers: " + System.getProperty("java.version"));
log.info("-- Java Home: " + System.getProperty("java.home"));
log.info("-- User Name: " + System.getProperty("user.name"));
log.info("-- User Home: " + System.getProperty("user.home"));
log.info("-- Cur dir: " + System.getProperty("user.dir"));
log.info("---------------------------------------------");
}
/**
* Sets getdown status panel size and text color from applet params.
*/
public void config (Getdown getdown)
{
if (statusBounds != null) {
getdown._ifc.status = statusBounds;
}
if (statusColor != null) {
getdown._ifc.statusText = statusColor;
}
}
/**
* Gets the value of the named parameter. If the param is not set, this will return null.
*/
public String getParameter (String param)
{
if (_prefix != null) {
return _applet.getParameter(_prefix + PARAM_DELIMITER + param);
}
return _applet.getParameter(param);
}
/**
* Gets the value of an optional Applet parameter. If the param is not set, the default value is
* returned.
*/
public String getParameter (String param, String defaultValue)
{
String value = getParameter(param);
return value == null ? defaultValue : value;
}
/**
* Uses the Applet context to redirect the browser to a URL (intended for use when the applet
* is done downloading). If the redirect_on_finish parameter was not set, this does nothing.
*/
public void redirect ()
{
if (redirectUrl != null) {
if (redirectTarget == null) {
_applet.getAppletContext().showDocument(redirectUrl);
} else {
_applet.getAppletContext().showDocument(redirectUrl, redirectTarget);
}
}
}
/**
*
*/
public RotatingBackgrounds getBackgroundImages (ImageLoader loader)
{
if (bgimages == null) {
// Load background images
bgimages = getBackgroundImages(imgpath, errorimgpath, loader);
}
return bgimages;
}
public static RotatingBackgrounds getBackgroundImages (String imageParam,
String errorImagePath, ImageLoader loader)
{
// Parse the image parameter and load the background images
RotatingBackgrounds images;
if (imageParam == null) {
images = new RotatingBackgrounds();
} else if (imageParam.indexOf(",") > -1) {
images = new RotatingBackgrounds(imageParam.split(","), errorImagePath, loader);
} else {
images = new RotatingBackgrounds(loader.loadImage(imageParam));
}
return images;
}
/**
* This checks whether the user has accepted our signed
*/
protected void securityCheck () throws Exception
{
// getdown requires full read/write permissions to the system; if we don't have this, then
// we need to not do anything unsafe, and display a message to the user telling them they
// need to (groan) close out of the web browser entirely and re-launch the browser, go to
// our site, and accept the certificate
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkPropertiesAccess();
sm.checkWrite("getdown");
} catch (SecurityException se) {
log.warning("Signed applet rejected by user", "se", se);
throw new Exception("m.insufficient_permissions_error");
}
}
}
/**
* Dumps the contents of the <code>properties</code> into the System properties.
*/
protected void setSystemProperties ()
{
System.getProperties().putAll(properties);
}
/**
* Makes sire that the appdir directory exists, creating it if necessary. Throws an exception if
* the directory does not exist and cannot be created.
*/
protected void ensureAppdirExists () throws Exception
{
// if our application directory does not exist, auto-create it
if (!appdir.exists() || !appdir.isDirectory()) {
if (!appdir.mkdirs()) {
throw new Exception("m.create_appdir_failed");
}
}
}
/**
* Writes the installer.txt and getdown.txt files to the local file system as needed.
*/
protected void createFiles () throws Exception
{
// if an installer.txt file is desired, create that
if (!StringUtil.isBlank(installerFileContents)) {
File infile = new File(appdir, "installer.txt");
if (!infile.exists()) {
writeToFile(infile, installerFileContents);
}
}
// if our getdown.txt file does not exist, or it is corrupt, auto-/recreate it
File gdfile = new File(appdir, "getdown.txt");
boolean createGetdown = !gdfile.exists();
if (!createGetdown) {
try {
Map<String,Object> cdata = ConfigUtil.parseConfig(gdfile, false);
String oappbase = StringUtil.trim((String)cdata.get(APPBASE));
createGetdown = (appbase != null && !appbase.trim().equals(oappbase));
if (createGetdown) {
log.warning("Recreating getdown.txt due to appbase mismatch",
"nappbase", appbase, "oappbase", oappbase);
}
} catch (Exception e) {
log.warning("Failure checking validity of getdown.txt, forcing recreate.",
"error", e);
createGetdown = true;
}
}
if (createGetdown) {
if (StringUtil.isBlank(appbase)) {
throw new Exception("m.missing_appbase");
}
if (!writeToFile(gdfile, "appbase = " + appbase)) {
throw new Exception("m.create_getdown_failed");
}
}
}
/**
* Creates the specified file and writes the supplied contents to it.
*/
protected boolean writeToFile (File tofile, String contents)
{
try {
PrintStream out = new PrintStream(new FileOutputStream(tofile));
out.println(contents);
out.close();
return true;
} catch (IOException ioe) {
log.warning("Failed to create '" + tofile + "'.", ioe);
return false;
}
}
/** A reference to the Applet in which Getdown is running */
protected JApplet _applet;
/** An optional prefix to prepend when looking for Getdown Applet params */
protected String _prefix;
/** The background images displayed on the status panel as Getdown is getting down. */
protected RotatingBackgrounds bgimages;
}