/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.desktop.ostermiller;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.Vector;
/**
* A stripped down version of the Browser class published by Stephen Ostermiller for use by the OSP project.
*
* Allows URLs to be opened in the system browser on Windows and Unix.
* More information about this class is available from <a target="_top" href=
* "http://ostermiller.org/utils/Browser.html">ostermiller.org</a>.
*
* @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
* @since ostermillerutils 1.00.00
*/
public class Browser {
/**
* A list of commands to try in order to display the url.
* The url is put into the command using MessageFormat, so
* the URL will be specified as {0} in the command.
* Some examples of commands to try might be:<br>
* <code>rundll32 url.dll,FileProtocolHandler {0}</code></br>
* <code>netscape {0}</code><br>
* These commands are passed in order to exec until something works
* when displayURL is used.
*
* @since ostermillerutils 1.00.00
*/
public static String[] exec = null;
/**
* Determine appropriate commands to start a browser on the current
* operating system. On windows: <br>
* <code>rundll32 url.dll,FileProtocolHandler {0}</code></br>
* On other operating systems, the "which" command is used to
* test if Mozilla, netscape, and lynx(xterm) are available (in that
* order).
*
* @since ostermillerutils 1.00.00
*/
public static void init() {
exec = defaultCommands();
}
/**
* Retrieve the default commands to open a browser for this system.
*
* @since ostermillerutils 1.00.00
*/
public static String[] defaultCommands() {
String[] exec = null;
if(System.getProperty("os.name").startsWith("Windows")) { //$NON-NLS-1$ //$NON-NLS-2$
exec = new String[] {
"rundll32 url.dll,FileProtocolHandler {0}", //$NON-NLS-1$
};
} else if(System.getProperty("os.name").startsWith("Mac")) { //$NON-NLS-1$ //$NON-NLS-2$
Vector<String> browsers = new Vector<String>();
try {
Process p = Runtime.getRuntime().exec("which open"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("open {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
if(browsers.size()>0) {
exec = browsers.toArray(new String[0]);
}
} else if(System.getProperty("os.name").startsWith("SunOS")) { //$NON-NLS-1$ //$NON-NLS-2$
exec = new String[] {"/usr/dt/bin/sdtwebclient {0}"}; //$NON-NLS-1$
} else {
Vector<String> browsers = new Vector<String>();
try {
Process p = Runtime.getRuntime().exec("which firebird"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("firebird -remote openURL({0})"); //$NON-NLS-1$
browsers.add("firebird {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which mozilla"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("mozilla -remote openURL({0})"); //$NON-NLS-1$
browsers.add("mozilla {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which opera"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("opera -remote openURL({0})"); //$NON-NLS-1$
browsers.add("opera {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which galeon"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("galeon {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which konqueror"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("konqueror {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which netscape"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("netscape -remote openURL({0})"); //$NON-NLS-1$
browsers.add("netscape {0}"); //$NON-NLS-1$
}
} catch(IOException e) {}
catch(InterruptedException e) {}
try {
Process p = Runtime.getRuntime().exec("which xterm"); //$NON-NLS-1$
if(p.waitFor()==0) {
p = Runtime.getRuntime().exec("which lynx"); //$NON-NLS-1$
if(p.waitFor()==0) {
browsers.add("xterm -e lynx {0}"); //$NON-NLS-1$
}
}
} catch(IOException e) {}
catch(InterruptedException e) {}
if(browsers.size()>0) {
exec = browsers.toArray(new String[0]);
}
}
return exec;
}
/**
* Display a URL in the system browser.
*
* Browser.init() should be called before calling this function or
* Browser.exec should be set explicitly.
*
* For security reasons, the URL will may not be passed directly to the
* browser as it is passed to this method. The URL may be made safe for
* the exec command by URLEncoding the URL before passing it.
*
* @param url the url to display
* @throws IOException if the url is not valid or the browser fails to star
*
* @since ostermillerutils 1.00.00
*/
public static void displayURL(String url) throws IOException {
if((exec==null)||(exec.length==0)) {
if(System.getProperty("os.name").startsWith("Mac")) { //$NON-NLS-1$ //$NON-NLS-2$
boolean success = false;
try {
Class<?> nSWorkspace;
if(new File("/System/Library/Java/com/apple/cocoa/application/NSWorkspace.class").exists()) { //$NON-NLS-1$
// Mac OS X has NSWorkspace, but it is not in the classpath, add it.
//ClassLoader classLoader = new URLClassLoader(new URL[]{new File("/System/Library/Java").toURL()}); //$NON-NLS-1$
ClassLoader classLoader = new URLClassLoader(new URL[] {new File("/System/Library/Java").toURI().toURL()}); //$NON-NLS-1$
nSWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace", true, classLoader); //$NON-NLS-1$
} else {
nSWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace"); //$NON-NLS-1$
}
Method sharedWorkspace = nSWorkspace.getMethod("sharedWorkspace", new Class[] {}); //$NON-NLS-1$
Object workspace = sharedWorkspace.invoke(null, new Object[] {});
Method openURL = nSWorkspace.getMethod("openURL", new Class[] {Class.forName("java.net.URL")}); //$NON-NLS-1$ //$NON-NLS-2$
success = ((Boolean) openURL.invoke(workspace, new Object[] {new java.net.URL(url)})).booleanValue();
//success = com.apple.cocoa.application.NSWorkspace.sharedWorkspace().openURL(new java.net.URL(url));
} catch(Exception x) {}
if(!success) {
try {
Class<?> mrjFileUtils = Class.forName("com.apple.mrj.MRJFileUtils"); //$NON-NLS-1$
Method openURL = mrjFileUtils.getMethod("openURL", new Class[] {Class.forName("java.lang.String")}); //$NON-NLS-1$ //$NON-NLS-2$
openURL.invoke(null, new Object[] {url});
//com.apple.mrj.MRJFileUtils.openURL(url);
} catch(Exception x) {
System.err.println(x.getMessage());
throw new IOException("Browser launch failed."); //$NON-NLS-1$
}
}
} else {
throw new IOException("Browser execute command not defined."); //$NON-NLS-1$
}
} else {
// for security, see if the url is valid.
// this is primarily to catch an attack in which the url
// starts with a - to fool the command line flags, bu
// it could catch other stuff as well, and will throw a
// MalformedURLException which will give the caller of this
// function useful information.
new URL(url);
// escape any weird characters in the url. This is primarily
// to prevent an attacker from putting in spaces
// that might fool exec into allowing
// the attacker to execute arbitrary code.
StringBuffer sb = new StringBuffer(url.length());
for(int i = 0; i<url.length(); i++) {
char c = url.charAt(i);
if(((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z'))||((c>='0')&&(c<='9'))||(c=='.')||(c==':')||(c=='&')||(c=='@')||(c=='/')||(c=='?')||(c=='%')||(c=='+')||(c=='=')||(c=='#')||(c=='-')||(c=='\\')) {
//characters that are necessary for URLs and should be safe
//to pass to exec. Exec uses a default string tokenizer with
//the default arguments (whitespace) to separate command line
//arguments, so there should be no problem with anything bu
//whitespace.
sb.append(c);
} else {
c = (char) (c&0xFF); // get the lowest 8 bits (URLEncoding)
if(c<0x10) {
sb.append("%0"+Integer.toHexString(c)); //$NON-NLS-1$
} else {
sb.append("%"+Integer.toHexString(c)); //$NON-NLS-1$
}
}
}
String[] messageArray = new String[1];
messageArray[0] = sb.toString();
String command = null;
boolean found = false;
// try each of the exec commands until something works
try {
for(int i = 0; (i<exec.length)&&!found; i++) {
try {
// stick the url into the command
command = MessageFormat.format(exec[i], (Object[]) messageArray);
// parse the command line.
Vector<String> argsVector = new Vector<String>();
BrowserCommandLexer lex = new BrowserCommandLexer(new StringReader(command));
String t;
while((t = lex.getNextToken())!=null) {
argsVector.add(t);
}
String[] args = new String[argsVector.size()];
args = argsVector.toArray(args);
// the windows url protocol handler doesn't work well with file URLs.
// Correct those problems here before continuing
// Java File.toURL() gives only one / following file: bu
// we need two.
// If there are escaped characters in the url, we will have
// to create an Internet shortcut and open that, as the command
// line version of the rundll doesn't like them.
if(args[0].equals("rundll32")&&args[1].equals("url.dll,FileProtocolHandler")) { //$NON-NLS-1$ //$NON-NLS-2$
if(args[2].startsWith("file:/")) { //$NON-NLS-1$
if(args[2].charAt(6)!='/') {
args[2] = "file://"+args[2].substring(6); //$NON-NLS-1$
}
if(args[2].charAt(7)!='/') {
args[2] = "file:///"+args[2].substring(7); //$NON-NLS-1$
}
} else if(args[2].toLowerCase().endsWith("html")||args[2].toLowerCase().endsWith("htm")) { //$NON-NLS-1$ //$NON-NLS-2$
}
}
// start the browser
Process p = Runtime.getRuntime().exec(args);
// give the browser a bit of time to fail.
// I have found that sometimes sleep doesn't work
// the first time, so do it twice. My tests
// seem to show that 1000 milliseconds is enough
// time for the browsers I'm using.
for(int j = 0; j<2; j++) {
try {
Thread.sleep(1000);
} catch(InterruptedException inte) {}
}
if(p.exitValue()==0) {
// this is a weird case. The browser exited after
// a couple seconds saying that it successfully
// displayed the url. Either the browser is lying
// or the user closed it *really* quickly. Oh well.
found = true;
}
} catch(IOException x) {
// the command was not a valid command.
System.err.println("Warning: "+x.getMessage()); //$NON-NLS-1$
}
}
if(!found) {
// we never found a command that didn't terminate with an error.
throw new IOException("Browser launch failed."); //$NON-NLS-1$
}
} catch(IllegalThreadStateException e) {
// the browser is still running. This is a good sign.
// lets just say that it is displaying the url right now!
}
}
}
/**
* Display the URLs, each in their own window, in the system browser.
*
* Browser.init() should be called before calling this function or
* Browser.exec should be set explicitly.
*
* If more than one URL is given an HTML page containing JavaScript will
* be written to the local drive, that page will be opened, and it will
* open the rest of the URLs.
*
* @param urls the list of urls to display
* @throws IOException if the url is not valid or the browser fails to star
*
* @since ostermillerutils 1.00.00
*/
public static void displayURLs(String[] urls) throws IOException {
if((urls==null)||(urls.length==0)) {
return;
}
if(urls.length==1) {
displayURL(urls[0]);
return;
}
File shortcut = File.createTempFile("DisplayURLs", ".html"); //$NON-NLS-1$ //$NON-NLS-2$
shortcut = shortcut.getCanonicalFile();
shortcut.deleteOnExit();
PrintWriter out = new PrintWriter(new FileWriter(shortcut));
out.println("<!-- saved from url=(0014)about:internet -->"); //$NON-NLS-1$
out.println("<html>"); //$NON-NLS-1$
out.println("<head>"); //$NON-NLS-1$
out.println("<title> Open URLs </title>"); //$NON-NLS-1$
out.println("<script language=\"javascript\" type=\"text/javascript\">"); //$NON-NLS-1$
out.println("function displayURLs(){"); //$NON-NLS-1$
for(int i = 1; i<urls.length; i++) {
out.println("window.open(\""+urls[i]+"\", \"_blank\", \"toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes\");"); //$NON-NLS-1$ //$NON-NLS-2$
}
out.println("location.href=\""+urls[0]+"\";"); //$NON-NLS-1$ //$NON-NLS-2$
out.println("}"); //$NON-NLS-1$
out.println("</script>"); //$NON-NLS-1$
out.println("</head>"); //$NON-NLS-1$
out.println("<body onload=\"javascript:displayURLs()\">"); //$NON-NLS-1$
out.println("<noscript>"); //$NON-NLS-1$
for(int i = 0; i<urls.length; i++) {
out.println("<a target=\"_blank\" href=\""+urls[i]+"\">"+urls[i]+"</a><br>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
out.println("</noscript>"); //$NON-NLS-1$
out.println("</body>"); //$NON-NLS-1$
out.println("</html>"); //$NON-NLS-1$
out.close();
displayURL(shortcut.toURI().toURL().toString());
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/