1 /* 2 * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.applet; 27 28 import java.io.BufferedInputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.InvocationTargetException; 35 import java.net.URL; 36 import java.net.MalformedURLException; 37 import java.util.Enumeration; 38 import java.util.Properties; 39 import java.util.Vector; 40 import sun.net.www.ParseUtil; 41 42 /** 43 * The main entry point into AppletViewer. 44 */ 45 public class Main { 46 /** 47 * The file which contains all of the AppletViewer specific properties. 48 */ 49 static File theUserPropertiesFile; 50 51 /** 52 * The default key/value pairs for the required user-specific properties. 53 */ 54 static final String [][] avDefaultUserProps = { 55 // There's a bootstrapping problem here. If we don't have a proxyHost, 56 // then we will not be able to connect to a URL outside the firewall; 57 // however, there's no way for us to set the proxyHost without starting 58 // AppletViewer. This problem existed before the re-write. 59 {"http.proxyHost", ""}, 60 {"http.proxyPort", "80"}, 61 {"package.restrict.access.sun", "true"} 62 }; 63 64 static { 65 File userHome = new File(System.getProperty("user.home")); 66 // make sure we can write to this location 67 userHome.canWrite(); 68 69 theUserPropertiesFile = new File(userHome, ".appletviewer"); 70 } 71 72 // i18n 73 private static AppletMessageHandler amh = new AppletMessageHandler("appletviewer"); 74 75 /** 76 * Member variables set according to options passed in to AppletViewer. 77 */ 78 private boolean debugFlag = false; 79 private boolean helpFlag = false; 80 private String encoding = null; 81 private boolean noSecurityFlag = false; 82 private static boolean cmdLineTestFlag = false; 83 84 /** 85 * The list of valid URLs passed in to AppletViewer. 86 */ 87 private static Vector urlList = new Vector(1); 88 89 // This is used in init(). Getting rid of this is desirable but depends 90 // on whether the property that uses it is necessary/standard. 91 public static final String theVersion = System.getProperty("java.version"); 92 93 /** 94 * The main entry point into AppletViewer. 95 */ 96 public static void main(String [] args) { 97 Main m = new Main(); 98 int ret = m.run(args); 99 100 // Exit immediately if we got some sort of error along the way. 101 // For debugging purposes, if we have passed in "-XcmdLineTest" we 102 // force a premature exit. 103 if ((ret != 0) || (cmdLineTestFlag)) 104 System.exit(ret); 105 } 106 107 private int run(String [] args) { 108 // DECODE ARGS 109 try { 110 if (args.length == 0) { 111 usage(); 112 return 0; 113 } 114 for (int i = 0; i < args.length; ) { 115 int j = decodeArg(args, i); 116 if (j == 0) { 117 throw new ParseException(lookup("main.err.unrecognizedarg", 118 args[i])); 119 } 120 i += j; 121 } 122 } catch (ParseException e) { 123 System.err.println(e.getMessage()); 124 return 1; 125 } 126 127 // CHECK ARGUMENTS 128 if (helpFlag) { 129 usage(); 130 return 0; 131 } 132 133 if (urlList.size() == 0) { 134 System.err.println(lookup("main.err.inputfile")); 135 return 1; 136 } 137 138 if (debugFlag) { 139 // START A DEBUG SESSION 140 // Given the current architecture, we will end up decoding the 141 // arguments again, but at least we are guaranteed to have 142 // arguments which are valid. 143 return invokeDebugger(args); 144 } 145 146 // INSTALL THE SECURITY MANAGER (if necessary) 147 if (!noSecurityFlag && (System.getSecurityManager() == null)) 148 init(); 149 150 // LAUNCH APPLETVIEWER FOR EACH URL 151 for (int i = 0; i < urlList.size(); i++) { 152 try { 153 // XXX 5/17 this parsing method should be changed/fixed so that 154 // it doesn't do both parsing of the html file and launching of 155 // the AppletPanel 156 AppletViewer.parse((URL) urlList.elementAt(i), encoding); 157 } catch (IOException e) { 158 System.err.println(lookup("main.err.io", e.getMessage())); 159 return 1; 160 } 161 } 162 return 0; 163 } 164 165 private static void usage() { 166 System.out.println(lookup("usage")); 167 } 168 169 /** 170 * Decode a single argument in an array and return the number of elements 171 * used. 172 * 173 * @param args The array of arguments. 174 * @param i The argument to decode. 175 * @return The number of array elements used when the argument was 176 * decoded. 177 * @exception ParseException 178 * Thrown when there is a problem with something in the 179 * argument array. 180 */ 181 private int decodeArg(String [] args, int i) throws ParseException { 182 String arg = args[i]; 183 int argc = args.length; 184 185 if ("-help".equalsIgnoreCase(arg) || "-?".equals(arg)) { 186 helpFlag = true; 187 return 1; 188 } else if ("-encoding".equals(arg) && (i < argc - 1)) { 189 if (encoding != null) 190 throw new ParseException(lookup("main.err.dupoption", arg)); 191 encoding = args[++i]; 192 return 2; 193 } else if ("-debug".equals(arg)) { 194 debugFlag = true; 195 return 1; 196 } else if ("-Xnosecurity".equals(arg)) { 197 // This is an undocumented (and, in the future, unsupported) 198 // flag which prevents AppletViewer from installing its own 199 // SecurityManager. 200 201 System.err.println(); 202 System.err.println(lookup("main.warn.nosecmgr")); 203 System.err.println(); 204 205 noSecurityFlag = true; 206 return 1; 207 } else if ("-XcmdLineTest".equals(arg)) { 208 // This is an internal flag which should be used for command-line 209 // testing. It instructs AppletViewer to force a premature exit 210 // immediately after the applet has been launched. 211 cmdLineTestFlag = true; 212 return 1; 213 } else if (arg.startsWith("-")) { 214 throw new ParseException(lookup("main.err.unsupportedopt", arg)); 215 } else { 216 // we found what we hope is a url 217 URL url = parseURL(arg); 218 if (url != null) { 219 urlList.addElement(url); 220 return 1; 221 } 222 } 223 return 0; 224 } 225 226 /** 227 * Following the relevant RFC, construct a valid URL based on the passed in 228 * string. 229 * 230 * @param url a string which represents either a relative or absolute URL. 231 * @return a URL when the passed in string can be interpreted according 232 * to the RFC, <code>null</code> otherwise. 233 * @exception ParseException 234 * Thrown when we are unable to construct a proper URL from the 235 * passed in string. 236 */ 237 private URL parseURL(String url) throws ParseException { 238 URL u = null; 239 // prefix of the urls with 'file' scheme 240 String prefix = "file:"; 241 242 try { 243 if (url.indexOf(':') <= 1) 244 { 245 // appletviewer accepts only unencoded filesystem paths 246 u = ParseUtil.fileToEncodedURL(new File(url)); 247 } else if (url.startsWith(prefix) && 248 url.length() != prefix.length() && 249 !(new File(url.substring(prefix.length())).isAbsolute())) 250 { 251 // relative file URL, like this "file:index.html" 252 // ensure that this file URL is absolute 253 // ParseUtil.fileToEncodedURL should be done last (see 6329251) 254 String path = ParseUtil.fileToEncodedURL(new File(System.getProperty("user.dir"))).getPath() + 255 url.substring(prefix.length()); 256 u = new URL("file", "", path); 257 } else { 258 // appletviewer accepts only encoded urls 259 u = new URL(url); 260 } 261 } catch (MalformedURLException e) { 262 throw new ParseException(lookup("main.err.badurl", 263 url, e.getMessage())); 264 } 265 266 return u; 267 } 268 269 /** 270 * Invoke the debugger with the arguments passed in to appletviewer. 271 * 272 * @param args The arguments passed into the debugger. 273 * @return <code>0</code> if the debugger is invoked successfully, 274 * <code>1</code> otherwise. 275 */ 276 private int invokeDebugger(String [] args) { 277 // CONSTRUCT THE COMMAND LINE 278 String [] newArgs = new String[args.length + 1]; 279 int current = 0; 280 281 // Add a -classpath argument that prevents 282 // the debugger from launching appletviewer with the default of 283 // ".". appletviewer's classpath should never contain valid 284 // classes since they will result in security exceptions. 285 // Ideally, the classpath should be set to "", but the VM won't 286 // allow an empty classpath, so a phony directory name is used. 287 String phonyDir = System.getProperty("java.home") + 288 File.separator + "phony"; 289 newArgs[current++] = "-Djava.class.path=" + phonyDir; 290 291 // Appletviewer's main class is the debuggee 292 newArgs[current++] = "sun.applet.Main"; 293 294 // Append all the of the original appletviewer arguments, 295 // leaving out the "-debug" option. 296 for (int i = 0; i < args.length; i++) { 297 if (!("-debug".equals(args[i]))) { 298 newArgs[current++] = args[i]; 299 } 300 } 301 302 // LAUNCH THE DEBUGGER 303 // Reflection is used for two reasons: 304 // 1) The debugger classes are on classpath and thus must be loaded 305 // by the application class loader. (Currently, appletviewer are 306 // loaded through the boot class path out of rt.jar.) 307 // 2) Reflection removes any build dependency between appletviewer 308 // and jdb. 309 try { 310 Class c = Class.forName("com.sun.tools.example.debug.tty.TTY", true, 311 ClassLoader.getSystemClassLoader()); 312 Method m = c.getDeclaredMethod("main", 313 new Class[] { String[].class }); 314 m.invoke(null, new Object[] { newArgs }); 315 } catch (ClassNotFoundException cnfe) { 316 System.err.println(lookup("main.debug.cantfinddebug")); 317 return 1; 318 } catch (NoSuchMethodException nsme) { 319 System.err.println(lookup("main.debug.cantfindmain")); 320 return 1; 321 } catch (InvocationTargetException ite) { 322 System.err.println(lookup("main.debug.exceptionindebug")); 323 return 1; 324 } catch (IllegalAccessException iae) { 325 System.err.println(lookup("main.debug.cantaccess")); 326 return 1; 327 } 328 return 0; 329 } 330 331 private void init() { 332 // GET APPLETVIEWER USER-SPECIFIC PROPERTIES 333 Properties avProps = getAVProps(); 334 335 // ADD OTHER RANDOM PROPERTIES 336 // XXX 5/18 need to revisit why these are here, is there some 337 // standard for what is available? 338 339 // Standard browser properties 340 avProps.put("browser", "sun.applet.AppletViewer"); 341 avProps.put("browser.version", "1.06"); 342 avProps.put("browser.vendor", "Oracle Corporation"); 343 avProps.put("http.agent", "Java(tm) 2 SDK, Standard Edition v" + theVersion); 344 345 // Define which packages can be extended by applets 346 // XXX 5/19 probably not needed, not checked in AppletSecurity 347 avProps.put("package.restrict.definition.java", "true"); 348 avProps.put("package.restrict.definition.sun", "true"); 349 350 // Define which properties can be read by applets. 351 // A property named by "key" can be read only when its twin 352 // property "key.applet" is true. The following ten properties 353 // are open by default. Any other property can be explicitly 354 // opened up by the browser user by calling appletviewer with 355 // -J-Dkey.applet=true 356 avProps.put("java.version.applet", "true"); 357 avProps.put("java.vendor.applet", "true"); 358 avProps.put("java.vendor.url.applet", "true"); 359 avProps.put("java.class.version.applet", "true"); 360 avProps.put("os.name.applet", "true"); 361 avProps.put("os.version.applet", "true"); 362 avProps.put("os.arch.applet", "true"); 363 avProps.put("file.separator.applet", "true"); 364 avProps.put("path.separator.applet", "true"); 365 avProps.put("line.separator.applet", "true"); 366 367 // Read in the System properties. If something is going to be 368 // over-written, warn about it. 369 Properties sysProps = System.getProperties(); 370 for (Enumeration e = sysProps.propertyNames(); e.hasMoreElements(); ) { 371 String key = (String) e.nextElement(); 372 String val = (String) sysProps.getProperty(key); 373 String oldVal; 374 if ((oldVal = (String) avProps.setProperty(key, val)) != null) 375 System.err.println(lookup("main.warn.prop.overwrite", key, 376 oldVal, val)); 377 } 378 379 // INSTALL THE PROPERTY LIST 380 System.setProperties(avProps); 381 382 // Create and install the security manager 383 if (!noSecurityFlag) { 384 System.setSecurityManager(new AppletSecurity()); 385 } else { 386 System.err.println(lookup("main.nosecmgr")); 387 } 388 389 // REMIND: Create and install a socket factory! 390 } 391 392 /** 393 * Read the AppletViewer user-specific properties. Typically, these 394 * properties should reside in the file $USER/.appletviewer. If this file 395 * does not exist, one will be created. Information for this file will 396 * be gleaned from $USER/.hotjava/properties. If that file does not exist, 397 * then default values will be used. 398 * 399 * @return A Properties object containing all of the AppletViewer 400 * user-specific properties. 401 */ 402 private Properties getAVProps() { 403 Properties avProps = new Properties(); 404 405 File dotAV = theUserPropertiesFile; 406 if (dotAV.exists()) { 407 // we must have already done the conversion 408 if (dotAV.canRead()) { 409 // just read the file 410 avProps = getAVProps(dotAV); 411 } else { 412 // send out warning and use defaults 413 System.err.println(lookup("main.warn.cantreadprops", 414 dotAV.toString())); 415 avProps = setDefaultAVProps(); 416 } 417 } else { 418 // create the $USER/.appletviewer file 419 420 // see if $USER/.hotjava/properties exists 421 File userHome = new File(System.getProperty("user.home")); 422 File dotHJ = new File(userHome, ".hotjava"); 423 dotHJ = new File(dotHJ, "properties"); 424 if (dotHJ.exists()) { 425 // just read the file 426 avProps = getAVProps(dotHJ); 427 } else { 428 // send out warning and use defaults 429 System.err.println(lookup("main.warn.cantreadprops", 430 dotHJ.toString())); 431 avProps = setDefaultAVProps(); 432 } 433 434 // SAVE THE FILE 435 try { 436 FileOutputStream out = new FileOutputStream(dotAV); 437 avProps.store(out, lookup("main.prop.store")); 438 out.close(); 439 } catch (IOException e) { 440 System.err.println(lookup("main.err.prop.cantsave", 441 dotAV.toString())); 442 } 443 } 444 return avProps; 445 } 446 447 /** 448 * Set the AppletViewer user-specific properties to be the default values. 449 * 450 * @return A Properties object containing all of the AppletViewer 451 * user-specific properties, set to the default values. 452 */ 453 private Properties setDefaultAVProps() { 454 Properties avProps = new Properties(); 455 for (int i = 0; i < avDefaultUserProps.length; i++) { 456 avProps.setProperty(avDefaultUserProps[i][0], 457 avDefaultUserProps[i][1]); 458 } 459 return avProps; 460 } 461 462 /** 463 * Given a file, find only the properties that are setable by AppletViewer. 464 * 465 * @param inFile A Properties file from which we select the properties of 466 * interest. 467 * @return A Properties object containing all of the AppletViewer 468 * user-specific properties. 469 */ 470 private Properties getAVProps(File inFile) { 471 Properties avProps = new Properties(); 472 473 // read the file 474 Properties tmpProps = new Properties(); 475 try { 476 FileInputStream in = new FileInputStream(inFile); 477 tmpProps.load(new BufferedInputStream(in)); 478 in.close(); 479 } catch (IOException e) { 480 System.err.println(lookup("main.err.prop.cantread", 481 inFile.toString())); 482 } 483 484 // pick off the properties we care about 485 for (int i = 0; i < avDefaultUserProps.length; i++) { 486 String value = tmpProps.getProperty(avDefaultUserProps[i][0]); 487 if (value != null) { 488 // the property exists in the file, so replace the default 489 avProps.setProperty(avDefaultUserProps[i][0], value); 490 } else { 491 // just use the default 492 avProps.setProperty(avDefaultUserProps[i][0], 493 avDefaultUserProps[i][1]); 494 } 495 } 496 return avProps; 497 } 498 499 /** 500 * Methods for easier i18n handling. 501 */ 502 503 private static String lookup(String key) { 504 return amh.getMessage(key); 505 } 506 507 private static String lookup(String key, String arg0) { 508 return amh.getMessage(key, arg0); 509 } 510 511 private static String lookup(String key, String arg0, String arg1) { 512 return amh.getMessage(key, arg0, arg1); 513 } 514 515 private static String lookup(String key, String arg0, String arg1, 516 String arg2) { 517 return amh.getMessage(key, arg0, arg1, arg2); 518 } 519 520 class ParseException extends RuntimeException 521 { 522 public ParseException(String msg) { 523 super(msg); 524 } 525 526 public ParseException(Throwable t) { 527 super(t.getMessage()); 528 this.t = t; 529 } 530 531 Throwable t = null; 532 } 533 }