/* * I2P - An anonymous, secure, and fully-distributed communication network. * * UrlLauncher.java * 2004 The I2P Project * http://www.i2p.net * This code is public domain. */ package net.i2p.apps.systray; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.net.InetSocketAddress; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; import net.i2p.I2PAppContext; import net.i2p.app.*; import static net.i2p.app.ClientAppState.*; import net.i2p.util.I2PAppThread; import net.i2p.util.ShellCommand; import net.i2p.util.SystemVersion; /** * A quick and simple multi-platform URL launcher. It attempts to launch the * default browser for the host platform first, then popular third-party * browsers if that was not successful. * <p> * Handles Galeon, Internet Explorer, Konqueror, Links, Lynx, Mozilla, Mozilla * Firefox, Netscape, Opera, and Safari. * * @author hypercubus */ public class UrlLauncher implements ClientApp { private final ShellCommand _shellCommand; private volatile ClientAppState _state; private final I2PAppContext _context; private final ClientAppManager _mgr; private final String[] _args; private static final int WAIT_TIME = 5*1000; private static final int MAX_WAIT_TIME = 5*60*1000; private static final int MAX_TRIES = 99; private static final String REGISTERED_NAME = "UrlLauncher"; private static final String PROP_BROWSER = "routerconsole.browser"; /** * Browsers to try IN-ORDER */ private static final String[] BROWSERS = { // This debian script tries everything in $BROWSER, then gnome-www-browser and x-www-browser // if X is running and www-browser otherwise. Those point to the user's preferred // browser using the update-alternatives system. "sensible-browser", // another one that opens a preferred browser "xdg-open", // Try x-www-browser directly "x-www-browser", // general graphical browsers "defaultbrowser", // puppy linux "opera -newpage", "firefox", "mozilla", "netscape", "konqueror", "galeon", // Text Mode Browsers only below here "www-browser", "links", "lynx" }; /** * ClientApp constructor used from clients.config * * @since 0.9.18 */ public UrlLauncher(I2PAppContext context, ClientAppManager mgr, String[] args) { _state = UNINITIALIZED; _context = context; _mgr = mgr; if (args == null || args.length <= 0) args = new String[] {"http://127.0.0.1:7657/index.jsp"}; _args = args; _shellCommand = new ShellCommand(); _state = INITIALIZED; } /** * Constructor from SysTray * * @since 0.9.18 */ public UrlLauncher() { _state = UNINITIALIZED; _context = I2PAppContext.getGlobalContext(); _mgr = null; _args = null; _shellCommand = new ShellCommand(); _state = INITIALIZED; } /** * Prevent bad user experience by waiting for the server to be there * before launching the browser. * * @return success */ private static boolean waitForServer(String urlString) { URI url; try { url = new URI(urlString); } catch (URISyntaxException e) { return false; } String host = url.getHost(); int port = url.getPort(); if (port <= 0) { port = "https".equals(url.getScheme()) ? 443 : 80; } SocketAddress sa; try { sa = new InetSocketAddress(host, port); } catch (IllegalArgumentException iae) { return false; } long done = System.currentTimeMillis() + MAX_WAIT_TIME; for (int i = 0; i < MAX_TRIES; i++) { try { Socket test = new Socket(); // this will usually fail right away if it's going to fail since it's local test.connect(sa, WAIT_TIME); // it worked try { test.close(); } catch (IOException ioe) {} // Jetty 6 seems to start the Connector before the // webapp is completely ready try { Thread.sleep(2*1000); } catch (InterruptedException ie) {} return true; } catch (IOException e) {} if (System.currentTimeMillis() > done) break; try { Thread.sleep(WAIT_TIME); } catch (InterruptedException ie) {} } return false; } /** * Discovers the operating system the installer is running under and tries * to launch the given URL using the default browser for that platform; if * unsuccessful, an attempt is made to launch the URL using the most common * browsers. * * BLOCKING * * @param url The URL to open. * @return <code>true</code> if the operation was successful, otherwise * <code>false</code>. * * @throws IOException */ public boolean openUrl(String url) throws IOException { waitForServer(url); if (validateUrlFormat(url)) { String cbrowser = _context.getProperty(PROP_BROWSER); if (cbrowser != null) { return openUrl(url, cbrowser); } if (SystemVersion.isMac()) { String osName = System.getProperty("os.name"); if (osName.toLowerCase(Locale.US).startsWith("mac os x")) { if (_shellCommand.executeSilentAndWaitTimed("open " + url, 5)) return true; } else { return false; } if (_shellCommand.executeSilentAndWaitTimed("iexplore " + url, 5)) return true; } else if (SystemVersion.isWindows()) { String browserString = "\"C:\\Program Files\\Internet Explorer\\iexplore.exe\" -nohome"; BufferedReader bufferedReader = null; File foo = new File(_context.getTempDir(), "browser.reg"); _shellCommand.executeSilentAndWait("regedit /E \"" + foo.getAbsolutePath() + "\" \"HKEY_CLASSES_ROOT\\http\\shell\\open\\command\""); try { bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(foo), "UTF-16")); for (String line; (line = bufferedReader.readLine()) != null; ) { if (line.startsWith("@=")) { // we should really use the whole line and replace %1 with the url browserString = line.substring(3, line.toLowerCase(Locale.US).indexOf(".exe") + 4); if (browserString.startsWith("\\\"")) browserString = browserString.substring(2); browserString = "\"" + browserString + "\""; } } try { bufferedReader.close(); } catch (IOException e) { // No worries. } foo.delete(); } catch (IOException e) { // Defaults to IE. } finally { if (bufferedReader != null) try { bufferedReader.close(); } catch (IOException ioe) {} } if (_shellCommand.executeSilentAndWaitTimed(browserString + ' ' + url, 5)) return true; } else { // fall through } for (int i = 0; i < BROWSERS.length; i++) { if (_shellCommand.executeSilentAndWaitTimed(BROWSERS[i] + ' ' + url, 5)) return true; } } return false; } /** * Opens the given URL with the given browser. * * BLOCKING * * @param url The URL to open. * @param browser The browser to use. * @return <code>true</code> if the operation was successful, * otherwise <code>false</code>. * * @throws IOException */ public boolean openUrl(String url, String browser) throws IOException { waitForServer(url); if (validateUrlFormat(url)) { if (_shellCommand.executeSilentAndWaitTimed(browser + " " + url, 5)) return true; } return false; } private static boolean validateUrlFormat(String urlString) { try { // just to check validity new URI(urlString); } catch (URISyntaxException e) { return false; } return true; } /** * ClientApp interface * @since 0.9.18 */ public void startup() { String url = _args[0]; if (!validateUrlFormat(url)) { changeState(START_FAILED, new MalformedURLException("Bad url: " + url)); return; } changeState(STARTING); Thread t = new I2PAppThread(new Runner(), "UrlLauncher", true); t.start(); } private class Runner implements Runnable { public void run() { changeState(RUNNING); try { String url = _args[0]; openUrl(url); changeState(STOPPED); } catch (IOException e) { changeState(CRASHED, e); } } } /** * ClientApp interface * @since 0.9.18 */ public ClientAppState getState() { return _state; } /** * ClientApp interface * @since 0.9.18 */ public String getName() { return REGISTERED_NAME; } /** * ClientApp interface * @since 0.9.18 */ public String getDisplayName() { return REGISTERED_NAME + " \"" + _args[0] + '"'; } /** * @since 0.9.18 */ private void changeState(ClientAppState state) { changeState(state, null); } /** * @since 0.9.18 */ private synchronized void changeState(ClientAppState state, Exception e) { _state = state; if (_mgr != null) _mgr.notify(this, state, null, e); } /** * ClientApp interface * @since 0.9.18 */ public void shutdown(String[] args) { // doesn't really do anything changeState(STOPPED); } /** * Obsolete, now uses ClientApp interface */ public static void main(String args[]) { UrlLauncher launcher = new UrlLauncher(); try { if (args.length > 0) launcher.openUrl(args[0]); else launcher.openUrl("http://127.0.0.1:7657/index.jsp"); } catch (IOException e) {} } }