1 /*
   2  * Copyright (c) 1999, 2013, 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 = 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 (FileOutputStream out = new FileOutputStream(dotAV)) {
 436                 avProps.store(out, lookup("main.prop.store"));
 437             } catch (IOException e) {
 438                 System.err.println(lookup("main.err.prop.cantsave",
 439                                           dotAV.toString()));
 440             }
 441         }
 442         return avProps;
 443     }
 444 
 445     /**
 446      * Set the AppletViewer user-specific properties to be the default values.
 447      *
 448      * @return     A Properties object containing all of the AppletViewer
 449      *             user-specific properties, set to the default values.
 450      */
 451     private Properties setDefaultAVProps() {
 452         Properties avProps = new Properties();
 453         for (int i = 0; i < avDefaultUserProps.length; i++) {
 454             avProps.setProperty(avDefaultUserProps[i][0],
 455                                 avDefaultUserProps[i][1]);
 456         }
 457         return avProps;
 458     }
 459 
 460     /**
 461      * Given a file, find only the properties that are setable by AppletViewer.
 462      *
 463      * @param inFile A Properties file from which we select the properties of
 464      *             interest.
 465      * @return     A Properties object containing all of the AppletViewer
 466      *             user-specific properties.
 467      */
 468     private Properties getAVProps(File inFile) {
 469         Properties avProps  = new Properties();
 470 
 471         // read the file
 472         Properties tmpProps = new Properties();
 473         try (FileInputStream in = new FileInputStream(inFile)) {
 474             tmpProps.load(new BufferedInputStream(in));
 475         } catch (IOException e) {
 476             System.err.println(lookup("main.err.prop.cantread", inFile.toString()));
 477         }
 478 
 479         // pick off the properties we care about
 480         for (int i = 0; i < avDefaultUserProps.length; i++) {
 481             String value = tmpProps.getProperty(avDefaultUserProps[i][0]);
 482             if (value != null) {
 483                 // the property exists in the file, so replace the default
 484                 avProps.setProperty(avDefaultUserProps[i][0], value);
 485             } else {
 486                 // just use the default
 487                 avProps.setProperty(avDefaultUserProps[i][0],
 488                                     avDefaultUserProps[i][1]);
 489             }
 490         }
 491         return avProps;
 492     }
 493 
 494     /**
 495      * Methods for easier i18n handling.
 496      */
 497 
 498     private static String lookup(String key) {
 499         return amh.getMessage(key);
 500     }
 501 
 502     private static String lookup(String key, String arg0) {
 503         return amh.getMessage(key, arg0);
 504     }
 505 
 506     private static String lookup(String key, String arg0, String arg1) {
 507         return amh.getMessage(key, arg0, arg1);
 508     }
 509 
 510     private static String lookup(String key, String arg0, String arg1,
 511                                  String arg2) {
 512         return amh.getMessage(key, arg0, arg1, arg2);
 513     }
 514 
 515     class ParseException extends RuntimeException
 516     {
 517         public ParseException(String msg) {
 518             super(msg);
 519         }
 520 
 521         public ParseException(Throwable t) {
 522             super(t.getMessage());
 523             this.t = t;
 524         }
 525 
 526         Throwable t = null;
 527     }
 528 }