All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.getdown.launcher.GetdownApplet Maven / Gradle / Ivy

//
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2010 Three Rings Design, Inc.
// http://code.google.com/p/getdown/
//
// 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.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 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");
                for (Object signer : GetdownApplet.class.getSigners()) {
                    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();
                    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
    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 {
            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;
        }
    }

    protected static Certificate loadCertificate (String path)
    {
        try {
            URL keyUrl = GetdownApplet.class.getClassLoader().getResource(path);
            if (keyUrl == null) {
                return null;
            }
            InputStream is = keyUrl.openStream();
            try {
                return CertificateFactory.getInstance("X.509").generateCertificate(is);
            } finally {
                is.close();
            }
        } 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;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy