package com.jbidwatcher.util.browser; import java.lang.reflect.*; import java.util.*; import java.io.*; /* * WindowsBrowserLauncher provides Windows-specific registry-based * default browser launching if available, but uses reflection, so * it can run under non-Windows, non-1.4.x Java versions. * <p> * It's a companion to the BrowserLauncher class, modified slightly * to support a separate way to determine the default browser if * running under Windows. * <o> * This code is Copyright 2001-2004 by Morgan Schweers (cyberfox@jbidwatcher.com) and may be * redistributed or modified in any form without restrictions as long as the portion of this * comment from this paragraph through the end of the comment is not removed. The author * requests that he be notified of any application, applet, or other binary that makes use of * this code, but that's more out of curiosity than anything and is not required. This software * includes no warranty. The author is not repsonsible for any loss of data or functionality * or any adverse or unexpected effects of using this software. * <p> * Credits: * Spawned to improve the behavior of the code from * http://browserlauncher.sourceforge.net * under Windows, so I owe a great deal to Eric Albert * * @author Morgan Schweers (<a href="mailto:cyberfox@jbidwatcher.com">cyberfox@jbidwatcher.com</a>) * @version 1.0 (Released January 30, 2004) */ public class WindowsBrowserLauncher { /* Windows security masks */ private final static int KEY_QUERY_VALUE = 1; /* Constants used to interpret returns of native functions */ private final static int NATIVE_HANDLE = 0; private final static int ERROR_CODE = 1; /* Windows error codes. */ private final static int ERROR_SUCCESS = 0; private final static boolean _debug = false; public static String replace(String inSource, String search, String replacement) { String result = inSource; int srch_index = result.indexOf(search); while(srch_index != -1) { result = result.substring(0,srch_index) + replacement + result.substring(srch_index + search.length()); srch_index = result.indexOf(search, srch_index + replacement.length()); } return result; } /** * Split a command line into component parts (path to program, * parameters, etc.) supporting quoted strings (for parameters with * spaces in them), and one extra parameter (%1), which will be * quoted if it's not already. * * @param cmd - The command to split out and build an array from. * @param replace - A string to replace any "%1" or %1 parameter with. * * @return - An array of String values which are directly passed to Runtime.exec(...) */ public static String[] splitCommandLine(String cmd, String replace) { cmd = replace(cmd, "\\", "\\\\"); StreamTokenizer str = new StreamTokenizer(new StringReader(cmd)); List<String> result = new ArrayList<String>(); boolean found_param = false; str.resetSyntax(); str.wordChars(0,255); str.whitespaceChars(32,32); str.quoteChar('\"'); try { while(str.nextToken() != StreamTokenizer.TT_EOF) { result.add(str.sval); if(str.sval.equals("\"%1\"") || str.sval.equals("%1")) { found_param = true; } } } catch(IOException ioe) { // Can't catch an IO exception from a String! } String[] output; if(found_param || replace == null) { output = new String[result.size()]; } else { output = new String[result.size() + 1]; } for(int i=0; i<result.size(); i++) { output[i] = result.get(i); if(replace != null) { if(output[i].equals("\"%1\"") || output[i].equals("%1")) { output[i] = "\"" + replace + "\""; } } } if(!found_param && replace != null) { output[result.size()] = "\"" + replace + "\""; } return output; } public static String[] splitCommandLine(String cmd) { return splitCommandLine(cmd, null); } public static String getIndirect(String primary) { String clientName, command; clientName = getKeyDefault("HKEY_CLASSES_ROOT\\\\" + primary); if(clientName == null) return null; command = getKeyDefault("HKEY_CLASSES_ROOT\\\\" + clientName + "\\shell\\open\\command"); return command; } /** * Under the Windows registry, each key (path) has a default value. * Given a key, return the default string value. * * @param key - The key (HKEY_CLASSES_ROOT\\http\shell\open\command) * to get the default value of. * * @return - A string representation of the default value for the * given key. Will return 'null' if the JVM doesn't support * registry operations. */ public static String getKeyDefault(String key) { try { int hkey = getHKEY(key); byte[] rootpath = stripHKEY(key); return getValue(hkey, rootpath, ""); } catch(Exception e) { // For debugging, if necessary. if(_debug) { e.printStackTrace(); } return null; } } /** * Given a protocol, return the launch command needed to execute it. * * @param protocol - A protocol (http, https, mailto, rtsp, ftp, etc.) * * @return - A string given the pathspec and parameters to launch * the program that handles the given protocol. Will return 'null' if * it can't find an adequate program, or if the JVM doesn't support * registry operations. */ public static String getBrowser(String protocol) { String clientName; clientName = getKeyDefault("HKEY_CLASSES_ROOT\\\\" + protocol + "\\shell\\open\\command"); if(clientName == null) { clientName = getIndirect(protocol); if(clientName == null) return null; } return clientName; } /** * Returns the control value associated with each of the toplevel * registry elements in a given registry path. * * @param path - A registry key that starts with a known root. * * @return - An int representing the value to pass into registry * functions to retrieve the registry values along the given path. * * @throws Exception - An Exception with info on what failed. */ public static int getHKEY(String path) throws Exception { if (path.startsWith("HKEY_CURRENT_USER")) { return 0x80000001; } else if (path.startsWith("HKEY_LOCAL_MACHINE")) { return 0x80000002; } else if (path.startsWith("HKEY_CLASSES_ROOT")) { return 0x80000000; } else { throw new Exception("Path should start with HKEY_CURRENT_USER " + "or HKEY_LOCAL_MACHINE"); } } /** * Remove the registry root from a registry path, and turn the rest * of the path into a format compatible with the registry functions. * * @param path - The path to strip the root off of. * * @return - The registry path turned into a registry-compatible byte array. */ public static byte[] stripHKEY(String path) { int beginIndex = path.indexOf("\\\\"); return stringToByteArray(path.substring(beginIndex+2)); } /** * Get a string return from a registry key and value search. Uses * reflection to keep from bollixing up older JVMs. * * @param hkey - The integer identifier of the branch of the registry to search. * @param WINDOWS_ROOT_PATH - The path (in bytearray form) as returned from stripHKEY. * @param key - The value at that point in the registry to return. * * @return - The data associated with the given key at the W_R_P * point in the registry, under the root node hkey. * * @throws Exception - An Exception with info on what failed. */ public static String getValue(int hkey, byte[] WINDOWS_ROOT_PATH, String key) throws Exception { Class theClass = Class.forName("java.util.prefs.WindowsPreferences"); byte[] windowsName; int nativeHandle; Object value; int[] result; Method m; result = openKey1(hkey, windowsAbsolutePath(WINDOWS_ROOT_PATH), KEY_QUERY_VALUE); if (result[ERROR_CODE] != ERROR_SUCCESS) { throw new Exception("Path not found!"); } nativeHandle = result[NATIVE_HANDLE]; m = theClass.getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class); m.setAccessible(true); windowsName = toWindowsName(key); value = m.invoke(null, nativeHandle, windowsName); WindowsRegCloseKey(nativeHandle); if (value == null) { throw new Exception("Path found. Key not found."); } byte[] origBuffer = (byte[]) value; if(origBuffer.length == 0) return null; byte[] destBuffer = new byte[origBuffer.length - 1]; System.arraycopy(origBuffer, 0, destBuffer, 0, origBuffer.length - 1); return new String(destBuffer); } /** * Using reflection, call the windows registry close key function to * close the current key. * * @param nativeHandle - The system native value used to refer to the current key. * * @return - The return value from the WindowsRegCloseKey reflected call. * * @throws Exception - An Exception with info on what failed. */ public static int WindowsRegCloseKey(int nativeHandle) throws Exception { Class theClass = Class.forName("java.util.prefs.WindowsPreferences"); Method m = theClass.getDeclaredMethod("WindowsRegCloseKey", int.class); Object ret; m.setAccessible(true); ret = m.invoke(null, nativeHandle); return (Integer) ret; } /** * Using reflection, call the windows registry open key function to * open a new key to get data from. * * @param hkey - The int value of the registry root tree to look in. * @param windowsAbsolutePath - The path of the key to look up * @param securityMask - What is being done, so the security system can interrupt * * @return - An array where [0] is a handle to the key, and [1] is * an error code, if any errors were found, or ERROR_SUCCESS if no * errors. * * @throws Exception - An Exception with info on what failed. */ public static int[] openKey1(int hkey, byte[] windowsAbsolutePath, int securityMask) throws Exception { Class theClass = Class.forName("java.util.prefs.WindowsPreferences"); Method m = theClass.getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class); m.setAccessible(true); Object ret = m.invoke(null, hkey, windowsAbsolutePath, securityMask); return (int[]) ret; } /** * Convert a string into a byte[] for use with the actual registry functions. * * @param str - The string to convert. * * @return - An array (byte[]) containing the characters from the string. */ private static byte[] stringToByteArray(String str) { byte[] result = new byte[str.length() + 1]; for (int i = 0; i < str.length(); i++) { result[i] = (byte) str.charAt(i); } result[str.length()] = 0; return result; } /** * Returns a byte array with the absolute path stored into it, * potentially with a terminating '\' added. * * @param WINDOWS_ROOT_PATH - The root path to translate. * * @return - The absolute version of the translated path. */ private static byte[] windowsAbsolutePath(byte[] WINDOWS_ROOT_PATH) { ByteArrayOutputStream bstream = new ByteArrayOutputStream(); bstream.write(WINDOWS_ROOT_PATH, 0, WINDOWS_ROOT_PATH.length - 1); StringTokenizer tokenizer = new StringTokenizer("/", "/"); while (tokenizer.hasMoreTokens()) { bstream.write((byte) '\\'); String nextName = tokenizer.nextToken(); byte[] windowsNextName = toWindowsName(nextName); bstream.write(windowsNextName, 0, windowsNextName.length - 1); } bstream.write(0); return bstream.toByteArray(); } /** * Process a name into a Windows name from a Java name (path * conversion?) * * @param javaName - The name to translate * * @return - A byte array containing the Windows translated name. */ private static byte[] toWindowsName(String javaName) { StringBuffer windowsName = new StringBuffer(); for (int i = 0; i < javaName.length(); i++) { char ch = javaName.charAt(i); if ((ch < 0x0020) || (ch > 0x007f)) { throw new RuntimeException("Unable to convert to Windows name"); } if (ch == '\\') { windowsName.append("//"); } else if (ch == '/') { windowsName.append('\\'); } else if ((ch >= 'A') && (ch <= 'Z')) { windowsName.append("/").append(ch); } else { windowsName.append(ch); } } return stringToByteArray(windowsName.toString()); } public static Process Launch(String destURL) throws IOException { String extractor = destURL.substring(0,destURL.indexOf(':')); String launcher=getBrowser(extractor); String cmdLine[]; if(launcher == null) launcher = getBrowser("http"); if(launcher == null) return null; cmdLine = splitCommandLine(launcher, destURL); return Runtime.getRuntime().exec(cmdLine); } }