licensing cleanup (GPLv2)
[org.ibex.wildebeest.git] / src / org / xwt / shoehorn3 / ShoeHorn.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the GNU General Public License version 2 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.xwt.shoehorn3;
6
7 import java.applet.*;
8 import java.util.*;
9 import java.lang.reflect.*;
10 import java.io.*;
11 import java.net.*;
12 import java.util.zip.*;
13 import java.awt.*;
14 import java.math.*;
15
16 /** This class is XWT's presence on the user's computer; it must be run as trusted code */
17 public class ShoeHorn extends Applet {
18
19     // Startup Phase ////////////////////////////////////////////////////////////////////
20     private String build = null;
21     public ShoeHorn() { log("*** constructor invoked for " + this.getClass().getName()); }
22
23     public final String getParameter(String arg) { return super.getParameter(arg); }
24     public final void main(String[] s) { new ShoeHorn().start(); }
25     private void log(String s) { System.out.println(s); }
26
27     /** this just ensures that we are running with full privileges */
28     public final void start() {
29         build = getParameter("build");
30         new Thread() { public void run() { 
31             log("ShoeHorn thread spawned");
32             try {
33
34                 if (System.getProperty("java.vendor", "").startsWith("Netscape")) {
35                     log("Detected Navigator 4.x");
36                     Method m = Class.forName("netscape.security.PrivilegeManager").getMethod("enablePrivilege", new Class[] { String.class });
37                     m.invoke(null, new Object[] { "MarimbaInternalTarget" });
38                     m.invoke(null, new Object[] { "UniversalExecAccess" });
39                     m.invoke(null, new Object[] { "UniversalPropertyRead" });
40                     go();
41
42                 } else if (System.getProperty("java.vendor", "").startsWith("Microsoft")) {
43                     //com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.SYSTEM);
44                     Class permissionIdClass = Class.forName("com.ms.security.PermissionID");
45                     Object o = permissionIdClass.getField("SYSTEM").get(null);
46                     Method m = Class.forName("com.ms.security.PolicyEngine").getMethod("assertPermission", new Class[] { permissionIdClass });
47                     m.invoke(null, new Object[] { o });
48                     go();
49
50                 } else {
51                     log("Detected non-Navigator JVM");
52                     Method m = Class.forName("org.xwt.shoehorn3.ShoeHorn$Java12").getMethod("run", new Class[] { Object.class });
53                     m.invoke(null, new Object[] { ShoeHorn.this });
54                 }
55             } catch (Throwable e) {
56                 if (e instanceof InvocationTargetException) e = ((InvocationTargetException)e).getTargetException();
57                 e.printStackTrace();
58                 update(-1.0, "Error; please check the Java console");
59             }
60         } }.start();
61     }
62
63     /** ask Java Plugin for privs */
64     private static class Java12 {
65         public static void run(final Object a) {
66             java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {
67                     public Object run() {
68                         ((ShoeHorn)a).go();
69                         return null;
70                     }
71                 });
72         }
73     }
74
75
76     // Implantation ////////////////////////////////////////////////////////////////////
77
78     /** inserts the required entries into the user's ~/.java.policy */
79     private void modifyPolicyFile() throws IOException {
80         log("Adjusting ~/.java.policy");
81         File policy = new File(System.getProperty("user.home") + File.separatorChar + ".java.policy");
82         if (policy.exists()) {
83             BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(policy)));
84             String s = null;
85             while((s = br.readLine()) != null)
86                 if (s.startsWith("// XWT_MARKER:")) {
87                     log("Java policy file has already been adjusted");
88                     return;
89                 }
90         }
91         FileOutputStream fos = new FileOutputStream(policy.getAbsolutePath(), true);
92         PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos));
93         pw.println("");
94         pw.println("// XWT_MARKER: this line and the following two grant blocks are required for XWT; DO NOT REMOVE THEM.");
95         pw.println("grant {");
96         pw.println("    permission java.io.FilePermission \"${user.home}${/}.xwt${/}shoehorn.jar\", \"read\";");
97         pw.println("};");
98         pw.println("grant codebase \"file:${user.home}${/}.xwt${/}shoehorn.jar\" {");
99         pw.println("    permission java.security.AllPermission;");
100         pw.println("};");
101         pw.println("// END_XWT_MARKER");
102         pw.println("");
103         pw.flush();
104         pw.close();
105     }
106
107     /** read ourselves out of the resources and write a jarfile to some trusted place */
108     private void implantSelf() throws IOException {
109         InputStream manifest = getClass().getClassLoader().getResourceAsStream("META-INF/manifest.mf");
110         log("my classloader is " + getClass().getClassLoader().getClass().getName());
111         ClassLoader loader = getClass().getClassLoader();
112         if (manifest == null || loader == null ||
113             (loader.getClass().getName().indexOf("Applet") == -1 && loader.getClass().getName().indexOf("Plugin") == -1))
114             return;
115         BufferedReader br = new BufferedReader(new InputStreamReader(manifest));
116         Vector entries = new Vector();
117         String s = null;
118         while((s = br.readLine()) != null)
119             if (s.startsWith("Name: "))
120                 entries.addElement(s.substring(6));
121
122         String ext_dirs = System.getProperty("java.ext.dirs");
123         log("java.ext.dirs = " + ext_dirs);
124         ext_dirs = ext_dirs + File.pathSeparatorChar + System.getProperty("user.home") + File.separatorChar + ".xwt";
125         StringTokenizer st = new StringTokenizer(ext_dirs, File.pathSeparatorChar + "");
126         while(st.hasMoreTokens()) {
127             String dir = st.nextToken();
128             new File(dir).mkdirs();
129             try {
130                 // we have to modify the policy file BEFORE implanting in ~/.xwt to ensure that the policy mods work
131                 if (!st.hasMoreTokens()) modifyPolicyFile();
132                 implantInDirectory(dir, entries);
133                 return;
134             } catch (IOException e) {
135                 log("Failed to implant in " + dir + " due to " + e);
136             }
137         }
138         log("Failed to implant self!");
139     }
140
141     private void implantInDirectory(String dir, Vector entries) throws IOException {
142         File f = new File(dir + File.separatorChar + "shoehorn.tmp");
143         ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(f));
144         for(int i=0; i<entries.size(); i++) {
145             zos.putNextEntry(new ZipEntry(entries.elementAt(i).toString()));
146             InputStream is = this.getClass().getClassLoader().getResourceAsStream(entries.elementAt(i).toString());
147             byte[] buf = new byte[1024];
148             while(true) {
149                 int read = is.read(buf, 0, buf.length);
150                 if (read == -1) break;
151                 zos.write(buf, 0, read);
152             }
153             is.close();
154         }
155         zos.close();
156         f.renameTo(new File(dir + File.separatorChar + "shoehorn.jar"));
157         log("Succeeded in implanting in " + dir);
158     }
159
160
161     // Startup Phase ////////////////////////////////////////////////////////////////////
162
163     public final void go() {
164         try {
165             update(0.0, "");
166             implantSelf();
167             
168             File file;
169             String os_name = System.getProperty("os.name", "");
170             log("os.name == " + os_name);
171             Vector command = new Vector();
172             
173             String arch = null;
174             if (os_name.indexOf("Linux") != -1) {
175                 arch = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("/bin/uname -m").getInputStream())).readLine();
176                 log("arch is " + arch);
177             }
178             
179             if (os_name.indexOf("Linux") != -1 && arch.charAt(0) == 'i' && arch.substring(2, 4).equals("86")) {
180                 file = fetch("xwt-" + build + ".linux.gz");
181                 safeWaitFor(Runtime.getRuntime().exec("/bin/chmod +x " + file.getAbsolutePath()));
182                 command.addElement(file.getAbsolutePath());
183                 
184             } else if (os_name.indexOf("Windows") != -1) {
185                 file = fetch("xwt-" + build + ".cab");
186                 command.addElement(file.getAbsolutePath());
187                 
188             } else {
189                 file = fetch("xwt-" + build + ".jar");
190                 command.addElement(findJvmBinary());
191                 command.addElement("-jar");
192                 command.addElement(file.getAbsolutePath());
193             }
194             
195             if ("true".equals(getParameter("showrenders"))) command.addElement("-s");
196             if ("true".equals(getParameter("verbose"))) command.addElement("-v");
197             if (getParameter("log") != null) {
198                 command.addElement("-l");
199                 command.addElement(getParameter("log"));
200             }
201             command.addElement(getParameter("xwar"));
202             spawn(command);
203         } catch (Exception e) {
204             update(-1.0, "Error; please check the Java console");
205             e.printStackTrace();
206         }
207     }
208
209     /** searches for the JVM binary in the usual places */
210     private String findJvmBinary() throws IOException {
211         String jvmBinary = null;
212
213         // HACK: prefer jdk1.3 on OSX; it has better fonts
214         if (new File("/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Commands/java").exists())
215             jvmBinary = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Commands/java";
216
217         // check JAVA_HOME
218         String javaHome = getEnv("JAVA_HOME");
219         if (jvmBinary == null && javaHome != null && !javaHome.equals("")) {
220             jvmBinary = javaHome + File.separatorChar + "bin" + File.separatorChar + "java";
221             if (!new File(jvmBinary).exists()) jvmBinary = null;
222         }
223         
224         // check common locations
225         if (jvmBinary == null)
226             for(int i=0; i<commonJavaLocations.length; i++)
227                 if (new File(commonJavaLocations[i]).exists()) {
228                     jvmBinary = commonJavaLocations[i];
229                     break;
230                 }
231         
232         // check PATH
233         if (jvmBinary == null) {
234             String path = getEnv("PATH");
235             StringTokenizer st = new StringTokenizer(path, File.pathSeparatorChar + "");
236             while(st.hasMoreTokens()) {
237                 String s = st.nextToken();
238                 if (new File(s + File.separatorChar + "java").exists()) {
239                     jvmBinary = s + File.separatorChar + "java";
240                     break;
241                 }
242             }
243         }
244         
245         // check ${java.home}
246         javaHome = System.getProperty("java.home");
247         if (jvmBinary == null && javaHome != null && !javaHome.equals("")) {
248             jvmBinary = javaHome + File.separatorChar + "bin" + File.separatorChar + "java";
249             if (!new File(jvmBinary).exists()) jvmBinary = null;
250         }
251         
252         if (jvmBinary == null)
253             throw new Error("couldn't find JVM binary! JAVA_HOME=" + getEnv("JAVA_HOME") + " PATH=" + getEnv("PATH"));
254         else
255             return jvmBinary;
256     }
257
258     private void spawn(Vector command) throws IOException {
259         String proxy = detectProxy();
260         log("proxy settings: " + proxy);
261
262         String[] command_vec;
263         command.copyInto(command_vec = new String[command.size()]);
264         log("executing:");
265         for(int i=0; i<command_vec.length; i++) log("    \"" + command_vec[i] + "\"");
266
267         Process p;
268         if (proxy == null) {
269             p = Runtime.getRuntime().exec(command_vec);
270         } else if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
271             // hrm, win32 doesn't like it when we probe the environment; that doesn't matter, since the
272             // win32 environ vars are pretty much useless anyways.
273             p = Runtime.getRuntime().exec(command_vec, new String[] { proxy });
274         } else {
275             p = Runtime.getRuntime().exec(command_vec, dumpEnv(proxy));
276         }
277         BufferedReader stderr = new BufferedReader(new InputStreamReader(p.getErrorStream()));
278         
279         if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
280             update(1.0, "XWT Loaded");
281         } else {
282             String s = stderr.readLine();
283             update(1.0, "XWT Loaded");
284             while(s != null) {
285                 log(s);
286                 s = stderr.readLine();
287             }
288         }
289         log("exiting...");
290     }
291
292
293     // Utility Functions /////////////////////////////////////////////////////////////////////
294     
295     /** Voodoo to extract the proxy settings from Sun's Java Plugin */
296     private String detectProxy() {
297         try {
298             Vector ret = new Vector();
299             
300             Class PluginProxyHandler = Class.forName("sun.plugin.protocol.PluginProxyHandler");
301             Method getDefaultProxyHandler = PluginProxyHandler.getMethod("getDefaultProxyHandler", new Class[] { });
302             Object proxyHandler = getDefaultProxyHandler.invoke(null, new Object[] { });
303             
304             Class ProxyHandler = Class.forName("sun.plugin.protocol.ProxyHandler");
305             Method getProxyInfo = ProxyHandler.getMethod("getProxyInfo", new Class[] { URL.class });
306             Object proxyInfo = getProxyInfo.invoke(proxyHandler, new Object[] { new URL("http://www.xwt.org") });
307             
308             Class ProxyInfo = Class.forName("sun.plugin.protocol.ProxyInfo");
309             
310             if (((Boolean)ProxyInfo.getMethod("isProxyUsed", new Class[] { }).invoke(proxyInfo, new Object[] { })).booleanValue())
311                 return "http_proxy=" +
312                     (String)ProxyInfo.getMethod("getProxy", new Class[] { }).invoke(proxyInfo, new Object[] { }) + ":" +
313                     ((Integer)ProxyInfo.getMethod("getPort", new Class[] { }).invoke(proxyInfo, new Object[] { })).intValue();
314
315             if (((Boolean)ProxyInfo.getMethod("isSocksUsed", new Class[] { }).invoke(proxyInfo, new Object[] { })).booleanValue())
316                 return "socks_proxy=" +
317                     (String)ProxyInfo.getMethod("getSocksProxy", new Class[] { }).invoke(proxyInfo, new Object[] { }) + ":" +
318                     ((Integer)ProxyInfo.getMethod("getSocksPort", new Class[] { }).invoke(proxyInfo, new Object[] { })).intValue();
319
320             return null;
321             
322         } catch (Throwable e) {
323             log("exception while querying sun.plugin.protocol.PluginProxyHandler: " + e);
324             return null;
325         }
326     }
327
328     // The Netscape JVM has a bug which causes waitFor() to lock up, so we wait no more than 2000ms
329     private void safeWaitFor(final Process p) {
330         final Object o = new Object();
331         new Thread() {
332                 public void run() {
333                     try {
334                         p.waitFor();
335                     } catch (InterruptedException e) { }
336                     synchronized(o) { o.notify(); }
337                 }
338             }.start();
339         try {
340             synchronized(o) { o.wait(2000); }
341         } catch (InterruptedException e) { }
342     }
343
344     /** fetches a file from the distribution site, writing it to the appropriate place */
345     private File fetch(String filename) throws IOException {
346         String tmpdir = System.getProperty("user.home") + File.separatorChar + ".xwt";
347         new File(tmpdir).mkdirs();
348         URL u = new URL("http://dist.xwt.org/" + filename);
349         if (filename.endsWith(".gz")) filename = filename.substring(0, filename.length() - 3);
350         if (filename.endsWith(".cab")) filename = filename.substring(0, filename.length() - 4) + ".exe";
351         File target = new File(tmpdir + File.separatorChar + filename);
352         if (target.exists()) return target;
353         loadFromURL(u, target, filename);
354         return target;
355     }
356
357     /** loads a file from a url, verifying that it was properly signed */
358     private void loadFromURL(URL u, File target, String filename) throws IOException {
359
360         final URLConnection uc = u.openConnection();
361         final int signatureLength = 128;
362         final byte[] signature = new byte[signatureLength];
363
364         InputStream is = uc.getInputStream();
365         final int contentLength = uc.getContentLength();
366
367         // display the progress indicator as we go
368         InputStream dis = new FilterInputStream(is) {
369                 int total = 0;
370                 public int read() throws IOException {
371                     int ret = super.read();
372                     if (ret != -1) total++;
373                     double loaded = ((double)total) / ((double)uc.getContentLength());
374                     update(loaded, "Loading XWT: " + ((int)Math.ceil(loaded * 100)) + "%");
375                     return ret;
376                 }
377                 public int read(byte[] buf, int off, int len) throws IOException {
378                     int ret = super.read(buf, off, len);
379                     if (ret != -1) total += ret;
380                     double loaded = ((double)total) / ((double)uc.getContentLength());
381                     update(loaded, "Loading XWT: " + ((int)Math.ceil(loaded * 100)) + "%");
382                     return ret;
383                 }
384             };
385
386         // decompress if needed
387         if (u.toString().endsWith(".cab")) dis = CAB.getFileInputStream(dis, "xwt-" + build + ".exe");
388         else if (u.toString().endsWith(".gz")) dis = new GZIPInputStream(dis);
389
390         // digest and copy the file to target.tmp, omitting the last signatureLength bytes
391         SHA1Digest sha1 = new SHA1Digest();
392         OutputStream fos = new DigestOutputStream(new FileOutputStream(target + ".tmp"), sha1);
393         byte[] buf = new byte[1024 * 128];
394         int numread = 0;
395         while(true) {
396             while(numread < buf.length) {
397                 int read = dis.read(buf, numread, buf.length - numread);
398                 if (read == -1) break;
399                 numread += read;
400             }
401             if (numread < buf.length) {
402                 System.arraycopy(buf, numread - signatureLength, signature, 0, signatureLength);
403                 fos.write(buf, 0, numread - signatureLength);
404                 break;
405             } else {
406                 fos.write(buf, 0, buf.length - signatureLength);
407                 System.arraycopy(buf, buf.length - signatureLength, buf, 0, signatureLength);
408                 numread = signatureLength;
409             }
410         }
411         fos.close();
412
413         // construct our hash
414         byte[] hash = new byte[sha1.getDigestSize()];
415         sha1.doFinal(hash, 0);
416
417         // decrypt the signature hash
418         DERInputStream deris = new DERInputStream(new ByteArrayInputStream(Base64.decode(XWT_foundation_public_key)));
419         SubjectPublicKeyInfo pki = new SubjectPublicKeyInfo((DERConstructedSequence)deris.readObject());
420         RSAPublicKeyStructure rsa_pks = new RSAPublicKeyStructure((DERConstructedSequence)pki.getPublicKey());
421         BigInteger modulus = rsa_pks.getModulus();
422         BigInteger exponent = rsa_pks.getPublicExponent();
423         AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
424         rsa.init(false, new RSAKeyParameters(false, modulus, exponent));
425
426         // compare
427         try {
428             byte[] decryptedHash = rsa.processBlock(signature, 0, signature.length);
429             for(int i=0; i<hash.length; i++)
430                 if (decryptedHash[i] != hash[i]) {
431                     new File(target + ".tmp").delete();
432                     throw new Error("invalid signature on " + filename);
433                 }
434         } catch (InvalidCipherTextException e) {
435             new File(target + ".tmp").delete();
436             throw new Error(e);
437         }
438
439         // good to go; rename the file
440         new File(target + ".tmp").renameTo(target);
441     }
442
443     /** retrieves an environment variable */
444     private static String getEnv(String key) throws IOException {
445         Process p = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", "echo $" + key });
446         BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
447         return br.readLine();
448     }
449
450     /** dumps the environment plus an additional variable */
451     private static String[] dumpEnv(String newvar) throws IOException {
452         Vector v = new Vector();
453         String os = System.getProperty("os.name").toLowerCase();
454         Process p;
455         if (os.indexOf("windows 9") > -1) {
456             p = Runtime.getRuntime().exec("command.com /c set");
457         } else if ((os.indexOf("nt") > -1) || (os.indexOf("windows 2000") > -1) ) {
458             p = Runtime.getRuntime().exec("cmd.exe /c set");
459         } else {  
460             p = Runtime.getRuntime().exec("env");
461         }
462         BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
463         String s;
464         while ((s = br.readLine()) != null) v.addElement(s);
465         v.addElement(newvar);
466         String[] ret;
467         v.copyInto(ret = new String[v.size()]);
468         return ret;
469     }
470
471
472
473     // Applet Painting Functions ////////////////////////////////////////////////////////
474
475     private Image backbuffer = null;
476     public final void paint(Graphics g) { if (backbuffer != null) g.drawImage(backbuffer, 0, 0, null); }
477
478     public final Graphics getGraphics() { return super.getGraphics(); }
479     public final Dimension getSize() { return super.getSize(); }
480
481     private void update(double loaded, String text) {
482         // indicates we should paint ourselves
483         Graphics g2 = getGraphics();
484         String s = text;
485         if (backbuffer == null || backbuffer.getWidth(null) != getSize().width || backbuffer.getHeight(null) != getSize().height)
486             backbuffer = createImage(getSize().width, getSize().height);
487         if (backbuffer == null) return;
488         Graphics g = backbuffer.getGraphics();
489
490         g.setColor(loaded < 0 ? Color.red : Color.blue);
491         loaded = Math.abs(loaded);
492
493         int w = (int)((double)getSize().width * loaded);
494         g.fillRect(0, 0, w, getSize().height);
495         g.setColor(Color.darkGray);
496         g.fillRect(w, 0, getSize().width - w, getSize().height);
497
498         Font f = new Font("Sans-serif", Font.BOLD, 12);
499         FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f);
500         g.setFont(f);
501
502         int x = (getSize().width - fm.stringWidth(s)) / 2;
503         int y = ((getSize().height - fm.getMaxAscent() - fm.getMaxDescent()) / 2) + fm.getMaxAscent();
504         g.setColor(Color.white);
505         g.drawString(s, x, y);
506
507         if (g2 != null) {
508             g2.setClip(0, 0, getSize().width, getSize().height);
509             g2.drawImage(backbuffer, 0, 0, null);
510         }
511     }
512
513
514     // Misc Constants ///////////////////////////////////////////////////////////
515
516     private static final String XWT_foundation_public_key =
517         "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoVweDZ+h3jcN2Qz1YwWJR8gVF0m/IE" +
518         "BasLIcvey9y1VkyN9jY6b9qm/Z2UoVtvAlgezd4CsJedUCIMe7uURyHGnqI4MLrozxLz3" +
519         "zqx5EYChsJt+Ju/f44KYnMx7upUQ4irfxOj6RpHy3E5GbW4XO96WwFlOuaR8+HRwKCXGP" +
520         "QvQIDAQAB";
521
522     private static final String[] commonJavaLocations = new String[] {
523         "/usr/bin/java",
524         "/usr/java/bin/java",
525         "/usr/local/bin/java",
526         "/usr/local/java/bin/java",
527         "/usr/lib/j2sdk1.4/bin/java",
528         "/usr/lib/j2sdk1.3/bin/java",
529         "/usr/lib/j2sdk1.2/bin/java"
530     };
531
532 }
533
534
535
536
537 /*
538
539 /// NSJVM Notes ///////////////////////////////////////////////////////////////////
540
541   PrivilegeManager.enablePrivilege("MarimbaInternalTarget");
542   PrivilegeManager.enablePrivilege("UniversalSystemClipboardAccess");
543   netscape.security.PrivilegeManager.enablePrivilege("UniversalConnect"); (sockets)
544   netscape.security.PrivilegeManager.enablePrivilege("UniversalSystemClipboardAccess");
545   netscape.security.PrivilegeManager.enablePrivilege("UniversalTopLevelWindow");
546
547   - Netscape's ClassLoader.getResource() is broken, see http://developer.netscape.com/docs/technote/java/getresource/getresource.html
548   - this will fix it: netscape.security.PrivilegeManager.enablePrivilege("UniversalPropertyRead");
549
550   - If you create a classloader, include
551         public boolean checkMatchPrincipalAlways(int i) {
552             return ((AppletClassLoader)this.getClass().getClassLoader()).checkMatchPrincipalAlways(i);
553         }
554         
555   - protected int _modifiersToButtonNumber(int modifiers) {
556         if ((modifiers & (InputEvent.BUTTON1_MASK & 15)) == (InputEvent.BUTTON1_MASK & 15)) return 1;
557         if ((modifiers & (InputEvent.BUTTON2_MASK & 15)) == (InputEvent.BUTTON2_MASK & 15)) return 3;
558         if ((modifiers & (InputEvent.BUTTON3_MASK & 15)) == (InputEvent.BUTTON3_MASK & 15)) return 2;
559
560
561 /// MSJVM Notes ///////////////////////////////////////////////////////////////////
562
563 - To sniff the JVM, check if (window.clientInformation.javaEnabled()), or if
564   (navigator.javaEnabled()).  See also http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndetect/html/detectvm.asp
565
566 - MSJVM note: you have to write to the getGraphics() of a Frame before you can setIconImage() it.
567
568 - How to create trusted classes within an already-running MSJVM:
569
570     class MicrosoftClassLoader extends SecurityClassLoader {
571         static PermissionSet ps;  // a PermissionSet that allows <i>everything</i>
572         static {
573                 PermissionDataSet pds = new PermissionDataSet();
574                     IPermission perm = new IPermission() {
575                     public void check(Object request) { }
576                     public IPermission combine(IPermission other) { return this; }
577                     public IPermission copy() { return this; }
578                     public int compareSet(Object o) { return SetComparison.DISJOINT; }
579                 };
580             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ClientStoragePermission"), perm);
581             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ExecutionPermission"), perm);
582             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.FileIOPermission"), perm);
583             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.MultimediaPermission"), perm);
584             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.NetIOPermission"), perm);
585             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.PrintingPermission"), perm);
586             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.PropertyPermission"), perm);
587             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ReflectionPermission"), perm);
588             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.SecurityPermission"), perm);
589             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.SystemStreamsPermission"), perm);
590             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ThreadPermission"), perm);
591             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.UIPermission"), perm);
592             pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.UserFileIOPermission"), perm);
593             ps = new PermissionSet(pds);
594         }
595         public Class defineClass(String name, byte[] b) {
596             Class c = super.defineClass(name, b, 0, b.length, ps, com.ms.security.PolicyEngine.getPrincipalOfClass(Resources.class));
597             if (name.startsWith("net")) System.out.println("defineClass " + name + " yielded " + c);
598             // for some reason, the MSJVM doesn't request resolves properly, so we have to do it manually on every class-load
599             resolveClass(c);
600             return c;
601         }
602
603 - How to inhibit background-clearing on the MSJVM
604
605         // Used to ensure that getPeer() doesn't go off in an infinite loop
606         boolean ok = false;
607
608         // The MSJVM needs us to occasionally falsify getPeer() in order to thwart unneeded background-clearing
609         public ComponentPeer getPeer() {
610             if (Thread.currentThread() == Platform.fastPathThread) return super.getPeer();
611             if (ok) return super.getPeer();
612
613             // to prevent recursive stack-dumping... =)
614             ok = true;
615             Dimension d = getSize();
616             ok = false;
617             if (last != null && last.width == d.width && last.height == d.height)
618                 return super.getPeer();
619
620             String s = com.ms.wfc.util.Debug.getStackTraceText();
621             for(int i = s.indexOf('d'); i != -1; i = s.indexOf('d', i + 1)) {
622                 if (s.regionMatches(i, "doClearAndPaint", 0, 15)) {
623                     last = getSize(); 
624                     update(null);
625                     return null;
626                 }
627             }
628             return super.getPeer();
629         }
630
631 */