Remove obsolete Applet code.

This commit is contained in:
Michael Bayne
2018-09-04 12:17:30 -07:00
parent 4c383f99c7
commit 8195460ee2
9 changed files with 23 additions and 1053 deletions
-51
View File
@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.threerings</groupId>
<artifactId>getdown</artifactId>
<version>1.7.2-SNAPSHOT</version>
</parent>
<artifactId>getdown-app</artifactId>
<packaging>jar</packaging>
<name>Getdown Applet</name>
<description>The Getdown installer applet (obsolete)</description>
<dependencies>
<dependency>
<groupId>com.threerings</groupId>
<artifactId>getdown-launcher</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>java</groupId>
<artifactId>plugin</artifactId>
<version>1.5</version>
<scope>system</scope>
<systemPath>${plugin.jar.path}</systemPath>
</dependency>
</dependencies>
<profiles>
<!-- finagling to deal with plugin.jar not being bundled with OpenJDK -->
<profile>
<id>standard-jdk</id>
<activation>
<file><exists>${java.home}/lib/plugin.jar</exists></file>
</activation>
<properties>
<plugin.jar.path>${java.home}/lib/plugin.jar</plugin.jar.path>
</properties>
</profile>
<profile>
<id>icedtea-web</id>
<activation>
<file><exists>/usr/share/icedtea-web/plugin.jar</exists></file>
</activation>
<properties>
<plugin.jar.path>/usr/share/icedtea-web/plugin.jar</plugin.jar.path>
</properties>
</profile>
</profiles>
</project>
@@ -1,348 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.launcher;
import java.awt.Container;
import java.awt.Image;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import javax.swing.JApplet;
import javax.swing.JPanel;
import netscape.javascript.JSObject;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Properties;
import static com.threerings.getdown.Log.log;
/**
* An applet that can be used to launch a Getdown application (when signed and given privileges).
*/
public class GetdownApplet extends JApplet
implements ImageLoader
{
/**
* Sets the JavaScript callback to invoke when a message is received from the launched app.
* The callback should be a function that accepts a single string parameter (the received
* message).
*/
public synchronized void setMessageCallback (JSObject callback)
{
_messageCallback = callback;
}
/**
* Attempts to send a message to the launched app.
*
* @return true if we succeeded in sending the message, false if the launched app has not (yet)
* established a connection to Getdown, or the send failed.
*/
public synchronized boolean sendMessage (String message)
{
if (_connectOut != null) {
try {
_connectOut.writeUTF(message);
return true;
} catch (IOException e) {
log.warning("Error sending message to app.", "message", message, e);
}
}
return false;
}
@Override // documentation inherited
public void init ()
{
_config = new GetdownAppletConfig(this);
try {
try {
// Check our permissions, download getdown.txt, etc.
_config.init();
} catch (Exception e) {
_errmsg = e.getMessage();
}
List<Certificate> signers = new ArrayList<>();
Certificate cert = loadCertificate("resource.crt");
if (cert != null) {
signers.add(cert);
} else {
// getSigners() returns all certificates used to sign this applet which may allow a
// third party to insert a trusted certificate. This should be avoided.
log.warning("No resource certificate found, falling back to class signers");
Object[] candidates = GetdownApplet.class.getSigners();
if (candidates != null) {
for (Object signer : candidates) {
if (signer instanceof Certificate) {
signers.add((Certificate)signer);
}
}
}
}
_getdown = new Getdown(_config.appdir, null, signers,
_config.jvmargs, _config.appargs) {
@Override
protected Container createContainer () {
getContentPane().removeAll();
getContentPane().setBackground(_ifc.background);
return getContentPane();
}
@Override
protected RotatingBackgrounds getBackground () {
return _config.getBackgroundImages(GetdownApplet.this);
}
@Override
protected void showContainer () {
((JPanel)getContentPane()).revalidate();
}
@Override
protected void disposeContainer () {
// nothing to do as we're in an applet
}
@Override
protected boolean invokeDirect () {
return _config.invokeDirect;
}
@Override
protected JApplet getApplet () {
return GetdownApplet.this;
}
@Override
protected void showDocument (String url) {
try {
getAppletContext().showDocument(new URL(url), "_blank");
} catch (MalformedURLException e) {
log.warning("Invalid document url.", "url", url, e);
}
}
@Override
protected void launch () {
// if so configured, create a server socket to listen
// for a connection from the app
if (_config.allowConnect) {
try {
startConnectServer();
} catch (IOException e) {
log.warning("Failed to start connect server.", e);
}
}
super.launch();
}
@Override
protected void exit (int exitCode) {
_status.stopThrob();
_app.releaseLock();
_config.redirect();
}
};
// 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);
}
}
/**
* Attempts to start the server that will accept a connection from the launched app, allowing
* it to exchange messages with the JavaScript context.
*/
protected void startConnectServer ()
throws IOException
{
// bind and set a property with the local port that will be passed through to the app
_serverSocket = new ServerSocket(0, 0, InetAddress.getByName(null));
String port = String.valueOf(_serverSocket.getLocalPort());
log.info("Listening for connections from launched app.", "port", port);
System.setProperty(Application.PROP_PASSTHROUGH_PREFIX + Properties.CONNECT_PORT, port);
Thread thread = new Thread("ConnectServer") {
@Override
public void run () {
while (true) {
try {
acceptConnection();
} catch (IOException e) {
if (!_serverSocket.isClosed()) {
log.warning("Error accepting connection.", e);
}
break;
}
}
}
protected void acceptConnection () throws IOException {
Socket socket = _serverSocket.accept();
log.info("App connected.", "port", socket.getPort());
DataInputStream connectIn = new DataInputStream(socket.getInputStream());
synchronized (GetdownApplet.this) {
_connectOut = new DataOutputStream(socket.getOutputStream());
}
while (true) {
try {
String message = connectIn.readUTF();
synchronized (GetdownApplet.this) {
if (message.equals("CLOSE")) {
socket.close();
log.info("App closed connection.");
break;
} else if (_messageCallback != null) {
_messageCallback.call("call",
new Object[] { _messageCallback, message });
}
}
} catch (IOException e) {
if (!socket.isClosed()) {
log.warning("Error reading message.", e);
}
break;
}
}
synchronized (GetdownApplet.this) {
_connectOut = null;
}
}
};
thread.setDaemon(true);
thread.start();
}
// implemented from ImageLoader
@Override
public Image loadImage (String path)
{
try {
return getImage(new URL(getDocumentBase(), path));
} catch (MalformedURLException e) {
log.warning("Failed to load background image", "path", path, e);
return null;
}
}
@Override // documentation inherited
public void start ()
{
if (_errmsg != null) {
_getdown.fail(_errmsg);
} else {
try {
_getdown.start();
} catch (Exception e) {
log.warning("start() failed.", e);
}
}
}
@Override // documentation inherited
public void stop ()
{
// Interrupt the getdown thread to tell it to kill its current downloading or verifying
// before launching
_getdown.interrupt();
// release the lock if the applet window is closed or replaced
_getdown._app.releaseLock();
}
@Override // documentation inherited
public synchronized void destroy ()
{
if (_serverSocket != null) {
try {
_serverSocket.close();
} catch (IOException e) {
log.warning("Error closing server socket.", e);
}
}
if (_connectOut != null) {
try {
_connectOut.writeUTF("CLOSE");
_connectOut.close();
log.info("Disconnected from app.");
} catch (IOException e) {
log.warning("Error closing connect socket/output stream.", e);
}
}
}
/**
* Creates the specified file and writes the supplied contents to it.
*/
protected boolean writeToFile (File tofile, String contents)
{
try (FileOutputStream fos = new FileOutputStream(tofile);
PrintStream out = new PrintStream(fos)) {
out.println(contents);
return true;
} catch (IOException ioe) {
log.warning("Failed to create '" + tofile + "'.", ioe);
return false;
}
}
protected static Certificate loadCertificate (String path)
{
try {
ClassLoader classLoader = GetdownApplet.class.getClassLoader();
if (classLoader == null) {
return null;
}
URL keyUrl = classLoader.getResource(path);
if (keyUrl == null) {
return null;
}
try (InputStream is = keyUrl.openStream()) {
return CertificateFactory.getInstance("X.509").generateCertificate(is);
}
} catch (CertificateException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** The Getdown configuration as pulled from the applet params */
protected GetdownAppletConfig _config;
/** Handles all the actual getting down. */
protected Getdown _getdown;
/** An error encountered during initialization. */
protected String _errmsg;
/** The message callback registered by JavaScript on the containing page, if any. */
protected JSObject _messageCallback;
/** The server socket on which we listen for connections, if any. */
protected ServerSocket _serverSocket;
/** The output stream to the launched app, if a connection has been established. */
protected DataOutputStream _connectOut;
}
@@ -1,416 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
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.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.swing.JApplet;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.launcher.ImageLoader;
import com.threerings.getdown.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.Config;
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 PARAM_DELIMITER = ".";
public static final String DIRECT = "direct";
public static final String CONNECT = "connect";
public static final String REDIRECT_ON_FINISH = "redirect_on_finish";
public static final String REDIRECT_ON_FINISH_TARGET = "redirect_on_finish_target";
/** Used to specify the names of additional jvmargs properties. Each jvmarg property will
* be suffixed by a monotonically increasing integer starting at zero, e.g.
* <pre>{@code
* <param name="jvmarg0" value="-Xmx256M"/>
* <param name="jvmarg1" value="-Dfoo=bar"/>
* }</pre>
* When a number is reached for which no value exists, we stop looking. */
public static final String JVMARG_PREFIX = "jvmargs";
/** A property specifying the names of additional appargs properties. Each apparg property will
* be suffixed by a monotonically increasing integer starting at zero, e.g.
* <pre>{@code
* <param name="apparg0" value="my awesome value"/>
* <param name="apparg1" value="monkeys"/>
* }</pre>
* When a number is reached for which no value exists, we stop looking. */
public static final String APPARG_PREFIX = "appargs";
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 File appdir;
/** Additional jvmargs to supply to the launching app. */
public String[] jvmargs;
/** Additional appargs to supply to the launching app. */
public String[] appargs;
/** 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;
/** Indicates whether Getdown should allow the launched app to connect to it through a server
* socket bound to localhost on any available port in order to allow interaction with
* JavaScript code on the page containing the applet. */
public boolean allowConnect;
/** 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);
// DEPRECATED LEGACY CRAP: extract system properties to set in applet JVM (not app JVM)
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);
}
}
// END DLC
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");
// extract arguments to be added to getdown.txt's jvmargs
List<String> jvmalist = parseArgList(JVMARG_PREFIX);
// DEPRECATED LEGACY CRAP: extract system properties to set in app JVM
String appprops = getParameter("app_properties");
if (appprops != null) {
for (String jvmarg : appprops.split(",")) {
jvmalist.add("-D" + jvmarg);
}
}
// END DLC
jvmargs = jvmalist.toArray(new String[jvmalist.size()]);
// extract arguments to be added to getdown.txt's appargs
List<String> appalist = parseArgList(APPARG_PREFIX);
appargs = appalist.toArray(new String[appalist.size()]);
String direct = getParameter(DIRECT, "false");
invokeDirect = Boolean.valueOf(direct);
String connect = getParameter(CONNECT, "false");
allowConnect = Boolean.valueOf(connect);
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 = Config.parseRect("ui.status", getParameter("ui.status"));
statusColor = Config.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)
{
return (_prefix == null) ?
_applet.getParameter(param) :
_applet.getParameter(_prefix + PARAM_DELIMITER + 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);
}
}
}
/**
* Gets the rotation background images and error image.The given image loader will be used if
* the images have not been loaded yet. The locations of the images are pulled from the Applet
* parameters.
*/
public RotatingBackgrounds getBackgroundImages (ImageLoader loader)
{
if (bgimages == null) {
// Load background images
bgimages = getBackgroundImages(imgpath, errorimgpath, loader);
}
return bgimages;
}
/**
* Gets the rotation background images and error image.The given image loader will be used if
* the images have not been loaded yet.
*/
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;
}
/**
* Parses parameters named {@code prefix0}, {@code prefix1}, ... into a list.
*/
protected List<String> parseArgList (String prefix)
{
List<String> arglist = new ArrayList<>();
String value;
for (int ii = 0; (value = getParameter(prefix + ii)) != null; ii++) {
arglist.add(value);
}
return arglist;
}
/**
* This checks whether the user has accepted our signed
*/
protected final 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()) && !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 {
Config config = Config.parseConfig(gdfile, Config.createOpts(false));
String oappbase = StringUtil.trim(config.getString(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 (FileOutputStream fos = new FileOutputStream(tofile);
PrintStream out = new PrintStream(fos)) {
out.println(contents);
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;
/** System properties to set in the applet JVM. */
protected Properties _properties = new Properties();
}
@@ -25,8 +25,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import javax.swing.JApplet;
import com.samskivert.io.StreamUtil; import com.samskivert.io.StreamUtil;
import com.samskivert.text.MessageUtil; import com.samskivert.text.MessageUtil;
import com.samskivert.util.ArrayUtil; import com.samskivert.util.ArrayUtil;
@@ -141,12 +139,6 @@ public class Application
/** The patch notes URL. */ /** The patch notes URL. */
public String patchNotesUrl; public String patchNotesUrl;
/** The dimensions of the play again button. */
public Rectangle playAgain;
/** The path (relative to the appdir) to a single play again image. */
public String playAgainImage;
/** Whether window decorations are hidden for the UI. */ /** Whether window decorations are hidden for the UI. */
public boolean hideDecorations; public boolean hideDecorations;
@@ -172,7 +164,6 @@ public class Application
", pb=" + progressBar + ", srect=" + status + ", st=" + statusText + ", pb=" + progressBar + ", srect=" + status + ", st=" + statusText +
", shadow=" + textShadow + ", err=" + installError + ", nrect=" + patchNotes + ", shadow=" + textShadow + ", err=" + installError + ", nrect=" + patchNotes +
", notes=" + patchNotesUrl + ", stepPercentages=" + stepPercentages + ", notes=" + patchNotesUrl + ", stepPercentages=" + stepPercentages +
", parect=" + playAgain + ", paimage=" + playAgainImage +
", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]"; ", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]";
} }
@@ -753,10 +744,6 @@ public class Application
ui.patchNotes = config.getRect("ui.patch_notes", ui.patchNotes); ui.patchNotes = config.getRect("ui.patch_notes", ui.patchNotes);
ui.patchNotesUrl = config.getUrl("ui.patch_notes_url", null); ui.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
// the play again bits
ui.playAgain = config.getRect("ui.play_again", ui.playAgain);
ui.playAgainImage = config.getString("ui.play_again_image");
// step progress percentages // step progress percentages
for (UpdateInterface.Step step : UpdateInterface.Step.values()) { for (UpdateInterface.Step step : UpdateInterface.Step.values()) {
String spec = config.getString("ui.percents." + step.name()); String spec = config.getString("ui.percents." + step.name());
@@ -1058,7 +1045,7 @@ public class Application
/** /**
* Runs this application directly in the current VM. * Runs this application directly in the current VM.
*/ */
public void invokeDirect (JApplet applet) throws IOException public void invokeDirect () throws IOException
{ {
ClassPath classPath = ClassPaths.buildClassPath(this); ClassPath classPath = ClassPaths.buildClassPath(this);
URL[] jarUrls = classPath.asUrls(); URL[] jarUrls = classPath.asUrls();
@@ -1103,9 +1090,6 @@ public class Application
System.setProperty(entry.getKey(), entry.getValue()); System.setProperty(entry.getKey(), entry.getValue());
} }
// make a note that we're running in "applet" mode
System.setProperty("applet", "true");
// prepare our app arguments // prepare our app arguments
String[] args = new String[_appargs.size()]; String[] args = new String[_appargs.size()];
for (int ii = 0; ii < args.length; ii++) args[ii] = processArg(_appargs.get(ii)); for (int ii = 0; ii < args.length; ii++) args[ii] = processArg(_appargs.get(ii));
@@ -1113,17 +1097,9 @@ public class Application
try { try {
log.info("Loading " + _class); log.info("Loading " + _class);
Class<?> appclass = loader.loadClass(_class); Class<?> appclass = loader.loadClass(_class);
Method main; Method main = appclass.getMethod("main", SA_PROTO.getClass());
try { log.info("Invoking main({" + StringUtil.join(args, ", ") + "})");
// first see if the class has a special applet-aware main main.invoke(null, new Object[] { args });
main = appclass.getMethod("main", JApplet.class, SA_PROTO.getClass());
log.info("Invoking main(JApplet, {" + StringUtil.join(args, ", ") + "})");
main.invoke(null, new Object[] { applet, args });
} catch (NoSuchMethodException nsme) {
main = appclass.getMethod("main", SA_PROTO.getClass());
log.info("Invoking main({" + StringUtil.join(args, ", ") + "})");
main.invoke(null, new Object[] { args });
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
} }
@@ -1321,9 +1297,6 @@ public class Application
for (int ii = 0; ii < sizes.length; ii++) { for (int ii = 0; ii < sizes.length; ii++) {
final Resource rsrc = rsrcs.get(ii); final Resource rsrc = rsrcs.get(ii);
if (Thread.interrupted()) {
throw new InterruptedException("m.applet_stopped");
}
final int index = ii; final int index = ii;
exec.execute(new Runnable() { exec.execute(new Runnable() {
public void run () { public void run () {
@@ -1414,9 +1387,6 @@ public class Application
ProgressAggregator pagg = new ProgressAggregator(obs, sizes); ProgressAggregator pagg = new ProgressAggregator(obs, sizes);
for (int ii = 0; ii < sizes.length; ii++) { for (int ii = 0; ii < sizes.length; ii++) {
if (Thread.interrupted()) {
throw new InterruptedException("m.applet_stopped");
}
Resource rsrc = rsrcs.get(ii); Resource rsrc = rsrcs.get(ii);
ProgressObserver pobs = pagg.startElement(ii); ProgressObserver pobs = pagg.startElement(ii);
try { try {
@@ -1539,12 +1509,6 @@ public class Application
* Downloads a new copy of the specified control file, optionally validating its signature. * Downloads a new copy of the specified control file, optionally validating its signature.
* If the download is successful, moves it over the old file on the filesystem. * If the download is successful, moves it over the old file on the filesystem.
* *
* <p> We implement simple signing of the digest.txt file for use with the Getdown applet, but
* this should never be used as-is with a non-applet getdown installation, as the signing
* format has no provisions for declaring arbitrary signing key IDs, signature algorithm, et al
* -- it is entirely reliant on the ability to upgrade the Getdown applet, and its signature
* validation implementation, at-will (ie, via an Applet).
*
* <p> TODO: Switch to PKCS #7 or CMS. * <p> TODO: Switch to PKCS #7 or CMS.
* *
* @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this * @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this
@@ -18,7 +18,6 @@ import java.awt.image.BufferedImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
@@ -70,7 +69,7 @@ import static com.threerings.getdown.Log.log;
* Manages the main control for the Getdown application updater and deployment system. * Manages the main control for the Getdown application updater and deployment system.
*/ */
public abstract class Getdown extends Thread public abstract class Getdown extends Thread
implements Application.StatusDisplay, ImageLoader implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
{ {
public static void main (String[] args) public static void main (String[] args)
{ {
@@ -128,7 +127,7 @@ public abstract class Getdown extends Thread
/** /**
* Installs the currently pending new resources. * Installs the currently pending new resources.
*/ */
public void install () throws IOException, InterruptedException public void install () throws IOException
{ {
if (SysProps.noInstall()) { if (SysProps.noInstall()) {
log.info("Skipping install due to 'no_install' sysprop."); log.info("Skipping install due to 'no_install' sysprop.");
@@ -136,9 +135,6 @@ public abstract class Getdown extends Thread
log.info("Installing " + _toInstallResources.size() + " downloaded resources:"); log.info("Installing " + _toInstallResources.size() + " downloaded resources:");
for (Resource resource : _toInstallResources) { for (Resource resource : _toInstallResources) {
resource.install(); resource.install();
if (Thread.interrupted()) {
throw new InterruptedException("m.applet_stopped");
}
} }
_toInstallResources.clear(); _toInstallResources.clear();
_readyToInstall = false; _readyToInstall = false;
@@ -148,21 +144,6 @@ public abstract class Getdown extends Thread
} }
} }
/**
* This is used by the applet which always needs a user interface and wants to load it as soon
* as possible.
*/
public void preInit ()
{
try {
_ifc = _app.init(true);
createInterfaceAsync(true);
} catch (Exception e) {
log.warning("Failed to preinit: " + e);
createInterfaceAsync(true);
}
}
@Override @Override
public void run () public void run ()
{ {
@@ -530,10 +511,6 @@ public abstract class Getdown extends Thread
// Only launch if we aren't in silent mode. Some mystery program starting out // Only launch if we aren't in silent mode. Some mystery program starting out
// of the blue would be disconcerting. // of the blue would be disconcerting.
if (!_silent || _launchInSilent) { if (!_silent || _launchInSilent) {
if (Thread.interrupted()) {
// One last interrupted check so we don't launch as the applet aborts
throw new InterruptedException("m.applet_stopped");
}
// And another final check for the lock. It'll already be held unless // And another final check for the lock. It'll already be held unless
// we're in silent mode. // we're in silent mode.
_app.lockForUpdates(); _app.lockForUpdates();
@@ -611,7 +588,7 @@ public abstract class Getdown extends Thread
* running with the necessary Java version. * running with the necessary Java version.
*/ */
protected void updateJava () protected void updateJava ()
throws IOException, InterruptedException throws IOException
{ {
Resource vmjar = _app.getJavaVMResource(); Resource vmjar = _app.getJavaVMResource();
if (vmjar == null) { if (vmjar == null) {
@@ -652,7 +629,7 @@ public abstract class Getdown extends Thread
* Called if the application is determined to be of an old version. * Called if the application is determined to be of an old version.
*/ */
protected void update () protected void update ()
throws IOException, InterruptedException throws IOException
{ {
// first clear all validation markers // first clear all validation markers
_app.clearValidationMarkers(); _app.clearValidationMarkers();
@@ -724,7 +701,7 @@ public abstract class Getdown extends Thread
* Called if the application is determined to require resource downloads. * Called if the application is determined to require resource downloads.
*/ */
protected void download (Collection<Resource> resources) protected void download (Collection<Resource> resources)
throws IOException, InterruptedException throws IOException
{ {
// create our user interface // create our user interface
createInterfaceAsync(false); createInterfaceAsync(false);
@@ -746,12 +723,6 @@ public abstract class Getdown extends Thread
} }
_lastCheck = percent; _lastCheck = percent;
} }
if (Thread.currentThread().isInterrupted()) {
// The applet interrupts when it stops, so abort the download and quit. Use
// isInterrupted so the containing code can call interrupted outside of here
// to check if this was the reason for aborting.
return false;
}
setStatusAsync("m.downloading", stepToGlobalPercent(percent), remaining, true); setStatusAsync("m.downloading", stepToGlobalPercent(percent), remaining, true);
if (percent > 0) { if (percent > 0) {
reportTrackingEvent("progress", percent); reportTrackingEvent("progress", percent);
@@ -772,9 +743,6 @@ public abstract class Getdown extends Thread
// start the download and wait for it to complete // start the download and wait for it to complete
Downloader dl = new HTTPDownloader(resources, obs); Downloader dl = new HTTPDownloader(resources, obs);
if (!dl.download()) { if (!dl.download()) {
if (Thread.interrupted()) {
throw new InterruptedException("m.applet_stopped");
}
throw new MultipleGetdownRunning(); throw new MultipleGetdownRunning();
} }
} }
@@ -789,11 +757,10 @@ public abstract class Getdown extends Thread
try { try {
if (invokeDirect()) { if (invokeDirect()) {
// if we're in applet mode, this will NOOP; if we're in app mode and are invoking // we want to close the Getdown window, as the app is launching
// direct, we want to close the Getdown window, as the app is launching
disposeContainer(); disposeContainer();
_app.releaseLock(); _app.releaseLock();
_app.invokeDirect(getApplet()); _app.invokeDirect();
} else { } else {
Process proc; Process proc;
@@ -864,17 +831,6 @@ public abstract class Getdown extends Thread
setStatusAsync(null, 100, -1L, false); setStatusAsync(null, 100, -1L, false);
exit(0); exit(0);
if (_playAgain != null && _playAgain.isEnabled()) {
// wait a little time before showing the button
Timer timer = new Timer("playAgain", true);
timer.schedule(new TimerTask() {
@Override public void run () {
initPlayAgain();
_playAgain.setVisible(true);
}
}, PLAY_AGAIN_TIME);
}
} catch (Exception e) { } catch (Exception e) {
log.warning("launch() failed.", e); log.warning("launch() failed.", e);
} }
@@ -909,26 +865,6 @@ public abstract class Getdown extends Thread
}); });
_patchNotes.setFont(StatusPanel.FONT); _patchNotes.setFont(StatusPanel.FONT);
_layers.add(_patchNotes); _layers.add(_patchNotes);
if (getApplet() != null) {
_playAgain = new JButton();
_playAgain.setEnabled(false);
_playAgain.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
_playAgain.setFont(StatusPanel.FONT);
_playAgain.addActionListener(new ActionListener() {
@Override public void actionPerformed (ActionEvent event) {
_playAgain.setVisible(false);
_stepMinPercent = _lastGlobalPercent = 0;
EventQueue.invokeLater(new Runnable() {
public void run () {
getdown();
}
});
}
});
_layers.add(_playAgain);
}
_status = new StatusPanel(_msgs); _status = new StatusPanel(_msgs);
_layers.add(_status); _layers.add(_status);
initInterface(); initInterface();
@@ -957,37 +893,12 @@ public abstract class Getdown extends Thread
_patchNotes.setBounds(_ifc.patchNotes); _patchNotes.setBounds(_ifc.patchNotes);
_patchNotes.setVisible(false); _patchNotes.setVisible(false);
initPlayAgain();
// we were displaying progress while the UI wasn't up. Now that it is, whatever progress // we were displaying progress while the UI wasn't up. Now that it is, whatever progress
// is left is scaled into a 0-100 DISPLAYED progress. // is left is scaled into a 0-100 DISPLAYED progress.
_uiDisplayPercent = _lastGlobalPercent; _uiDisplayPercent = _lastGlobalPercent;
_stepMinPercent = _lastGlobalPercent = 0; _stepMinPercent = _lastGlobalPercent = 0;
} }
protected void initPlayAgain ()
{
if (_playAgain != null) {
Image image = loadImage(_ifc.playAgainImage);
boolean hasImage = image != null;
if (hasImage) {
_playAgain.setIcon(new ImageIcon(image));
_playAgain.setText("");
} else {
_playAgain.setText(_msgs.getString("m.play_again"));
_playAgain.setIcon(null);
}
_playAgain.setBorderPainted(!hasImage);
_playAgain.setOpaque(!hasImage);
_playAgain.setContentAreaFilled(!hasImage);
if (_ifc.playAgain != null) {
_playAgain.setBounds(_ifc.playAgain);
_playAgain.setEnabled(true);
}
_playAgain.setVisible(false);
}
}
protected RotatingBackgrounds getBackground () protected RotatingBackgrounds getBackground ()
{ {
if (_ifc.rotatingBackgrounds != null) { if (_ifc.rotatingBackgrounds != null) {
@@ -1140,27 +1051,16 @@ public abstract class Getdown extends Thread
*/ */
protected boolean invokeDirect () protected boolean invokeDirect ()
{ {
// by default check a sysprop (which itself defaults to false); in applet mode this is
// overridden to check the applet config
return SysProps.direct(); return SysProps.direct();
} }
/**
* Provides access to the applet that we'll pass on to our application when we're in "invoke
* direct" mode.
*/
protected JApplet getApplet ()
{
return null;
}
/** /**
* Requests to show the document at the specified URL in a new window. * Requests to show the document at the specified URL in a new window.
*/ */
protected abstract void showDocument (String url); protected abstract void showDocument (String url);
/** /**
* Requests that Getdown exit. In applet mode this does nothing. * Requests that Getdown exit.
*/ */
protected abstract void exit (int exitCode); protected abstract void exit (int exitCode);
@@ -1238,7 +1138,6 @@ public abstract class Getdown extends Thread
protected JLayeredPane _layers; protected JLayeredPane _layers;
protected StatusPanel _status; protected StatusPanel _status;
protected JButton _patchNotes; protected JButton _patchNotes;
protected JButton _playAgain;
protected AbortPanel _abort; protected AbortPanel _abort;
protected RotatingBackgrounds _background; protected RotatingBackgrounds _background;
@@ -1263,7 +1162,6 @@ public abstract class Getdown extends Thread
protected static final int MAX_LOOPS = 5; protected static final int MAX_LOOPS = 5;
protected static final long FALLBACK_CHECK_TIME = 1000L; protected static final long FALLBACK_CHECK_TIME = 1000L;
protected static final long PLAY_AGAIN_TIME = 3000L;
protected static final String PROXY_REGISTRY = protected static final String PROXY_REGISTRY =
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
} }
@@ -1,18 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.launcher;
import java.awt.Image;
/**
* Abstracts away the process of loading an image so that it can be done differently in the app and
* applet.
*/
public interface ImageLoader
{
/** Loads and returns the image with the supplied path. */
public Image loadImage (String path);
}
@@ -11,6 +11,10 @@ import static com.threerings.getdown.Log.log;
public class RotatingBackgrounds public class RotatingBackgrounds
{ {
public interface ImageLoader {
/** Loads and returns the image with the supplied path. */
public Image loadImage (String path);
}
/** /**
* Creates a placeholder if there are no images. Just returns null from getImage every time. * Creates a placeholder if there are no images. Just returns null from getImage every time.
-1
View File
@@ -45,7 +45,6 @@
<module>core</module> <module>core</module>
<module>tools</module> <module>tools</module>
<module>launcher</module> <module>launcher</module>
<!-- deprecated: <module>applet</module>-->
<module>ant</module> <module>ant</module>
</modules> </modules>
@@ -1,62 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.tools;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import com.threerings.getdown.data.Digest;
import com.threerings.getdown.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Produces a signed hash of the appbase, appname, and image path to ensure that signed copies of
* Getdown are not hijacked to run malicious code.
*/
public class AppletParamSigner
{
public static void main (String[] args)
{
if (args.length != 7) {
System.err.println("AppletParamSigner keystore storepass alias keypass " +
"appbase appname imgpath");
System.exit(255);
}
String keystore = args[0];
String storepass = args[1];
String alias = args[2];
String keypass = args[3];
String appbase = args[4];
String appname = args[5];
String imgpath = args[6];
String params = appbase + appname + imgpath;
try (FileInputStream fis = new FileInputStream(keystore);
BufferedInputStream bis = new BufferedInputStream(fis)) {
KeyStore store = KeyStore.getInstance("JKS");
store.load(bis, storepass.toCharArray());
PrivateKey key = (PrivateKey)store.getKey(alias, keypass.toCharArray());
Signature sig = Signature.getInstance(Digest.sigAlgorithm(Digest.VERSION));
sig.initSign(key);
sig.update(params.getBytes(UTF_8));
String signed = Base64.encodeToString(sig.sign(), Base64.DEFAULT);
System.out.println("<param name=\"appbase\" value=\"" + appbase + "\" />");
System.out.println("<param name=\"appname\" value=\"" + appname + "\" />");
System.out.println("<param name=\"bgimage\" value=\"" + imgpath + "\" />");
System.out.println("<param name=\"signature\" value=\"" + signed + "\" />");
} catch (Exception e) {
System.err.println("Failed to produce signature.");
e.printStackTrace();
}
}
}