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