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.misc;
  27 
  28 import java.io.File;
  29 import java.io.FilenameFilter;
  30 import java.io.IOException;
  31 import java.io.FileNotFoundException;
  32 import java.util.StringTokenizer;
  33 import java.util.Vector;
  34 import java.util.Enumeration;
  35 import java.util.jar.JarFile;
  36 import java.util.jar.Manifest;
  37 import java.util.jar.Attributes;
  38 import java.util.jar.Attributes.Name;
  39 import java.security.AccessController;
  40 import java.security.PrivilegedAction;
  41 import java.security.PrivilegedExceptionAction;
  42 import java.security.PrivilegedActionException;
  43 import java.net.URL;
  44 import java.net.MalformedURLException;
  45 import sun.net.www.ParseUtil;
  46 
  47 /**
  48  * <p>
  49  * This class checks dependent extensions a particular jar file may have
  50  * declared through its manifest attributes.
  51  * </p>
  52  * Jar file declared dependent extensions through the extension-list
  53  * attribute. The extension-list contains a list of keys used to
  54  * fetch the other attributes describing the required extension.
  55  * If key is the extension key declared in the extension-list
  56  * attribute, the following describing attribute can be found in
  57  * the manifest :
  58  * key-Extension-Name:  (Specification package name)
  59  * key-Specification-Version: (Specification-Version)
  60  * key-Implementation-Version: (Implementation-Version)
  61  * key-Implementation-Vendor-Id: (Imlementation-Vendor-Id)
  62  * key-Implementation-Version: (Implementation version)
  63  * key-Implementation-URL: (URL to download the requested extension)
  64  * <p>
  65  * This class also maintain versioning consistency of installed
  66  * extensions dependencies declared in jar file manifest.
  67  * </p>
  68  *
  69  * @deprecated this class will be removed in a future release.
  70  * @author  Jerome Dochez
  71  */
  72 @Deprecated
  73 public class ExtensionDependency {
  74 
  75     /* Callbak interfaces to delegate installation of missing extensions */
  76     private static Vector<ExtensionInstallationProvider> providers;
  77 
  78     /**
  79      * <p>
  80      * Register an ExtensionInstallationProvider. The provider is responsible
  81      * for handling the installation (upgrade) of any missing extensions.
  82      * </p>
  83      * @param eip ExtensionInstallationProvider implementation
  84      */
  85     public synchronized static void addExtensionInstallationProvider
  86         (ExtensionInstallationProvider eip)
  87     {
  88         if (providers == null) {
  89             providers = new Vector<>();
  90         }
  91         providers.add(eip);
  92     }
  93 
  94     /**
  95      * <p>
  96      * Unregister a previously installed installation provider
  97      * </p>
  98      */
  99     public synchronized static void removeExtensionInstallationProvider
 100         (ExtensionInstallationProvider eip)
 101     {
 102         providers.remove(eip);
 103     }
 104 
 105     /**
 106      * <p>
 107      * Checks the dependencies of the jar file on installed extension.
 108      * </p>
 109      * @param jarFile containing the attriutes declaring the dependencies
 110      */
 111     public static boolean checkExtensionsDependencies(JarFile jar)
 112     {
 113         if (providers == null) {
 114             // no need to bother, nobody is registered to install missing
 115             // extensions
 116             return true;
 117         }
 118 
 119         try {
 120             ExtensionDependency extDep = new ExtensionDependency();
 121             return extDep.checkExtensions(jar);
 122         } catch (ExtensionInstallationException e) {
 123             debug(e.getMessage());
 124         }
 125         return false;
 126     }
 127 
 128     /*
 129      * Check for all declared required extensions in the jar file
 130      * manifest.
 131      */
 132     protected boolean checkExtensions(JarFile jar)
 133         throws ExtensionInstallationException
 134     {
 135         Manifest man;
 136         try {
 137             man = jar.getManifest();
 138         } catch (IOException e) {
 139             return false;
 140         }
 141 
 142         if (man == null) {
 143             // The applet does not define a manifest file, so
 144             // we just assume all dependencies are satisfied.
 145             return true;
 146         }
 147 
 148         boolean result = true;
 149         Attributes attr = man.getMainAttributes();
 150         if (attr != null) {
 151             // Let's get the list of declared dependencies
 152             String value = attr.getValue(Name.EXTENSION_LIST);
 153             if (value != null) {
 154                 StringTokenizer st = new StringTokenizer(value);
 155                 // Iterate over all declared dependencies
 156                 while (st.hasMoreTokens()) {
 157                     String extensionName = st.nextToken();
 158                     debug("The file " + jar.getName() +
 159                           " appears to depend on " + extensionName);
 160                     // Sanity Check
 161                     String extName = extensionName + "-" +
 162                         Name.EXTENSION_NAME.toString();
 163                     if (attr.getValue(extName) == null) {
 164                         debug("The jar file " + jar.getName() +
 165                               " appers to depend on "
 166                               + extensionName + " but does not define the " +
 167                               extName + " attribute in its manifest ");
 168 
 169                     } else {
 170                         if (!checkExtension(extensionName, attr)) {
 171                             debug("Failed installing " + extensionName);
 172                             result = false;
 173                         }
 174                     }
 175                 }
 176             } else {
 177                 debug("No dependencies for " + jar.getName());
 178             }
 179         }
 180         return result;
 181     }
 182 
 183 
 184     /*
 185      * <p>
 186      * Check that a particular dependency on an extension is satisfied.
 187      * </p>
 188      * @param extensionName is the key used for the attributes in the manifest
 189      * @param attr is the attributes of the manifest file
 190      *
 191      * @return true if the dependency is satisfied by the installed extensions
 192      */
 193     protected synchronized boolean checkExtension(final String extensionName,
 194                                      final Attributes attr)
 195         throws ExtensionInstallationException
 196     {
 197         debug("Checking extension " + extensionName);
 198         if (checkExtensionAgainstInstalled(extensionName, attr))
 199             return true;
 200 
 201         debug("Extension not currently installed ");
 202         ExtensionInfo reqInfo = new ExtensionInfo(extensionName, attr);
 203         return installExtension(reqInfo, null);
 204     }
 205 
 206     /*
 207      * <p>
 208      * Check if a particular extension is part of the currently installed
 209      * extensions.
 210      * </p>
 211      * @param extensionName is the key for the attributes in the manifest
 212      * @param attr is the attributes of the manifest
 213      *
 214      * @return true if the requested extension is already installed
 215      */
 216     boolean checkExtensionAgainstInstalled(String extensionName,
 217                                            Attributes attr)
 218         throws ExtensionInstallationException
 219     {
 220         File fExtension = checkExtensionExists(extensionName);
 221 
 222         if (fExtension != null) {
 223         // Extension already installed, just check against this one
 224             try {
 225                 if (checkExtensionAgainst(extensionName, attr, fExtension))
 226                     return true;
 227             } catch (FileNotFoundException e) {
 228                 debugException(e);
 229             } catch (IOException e) {
 230                 debugException(e);
 231             }
 232             return false;
 233 
 234         } else {
 235         // Not sure if extension is already installed, so check all the
 236         // installed extension jar files to see if we get a match
 237 
 238             File[] installedExts;
 239 
 240             try {
 241             // Get the list of installed extension jar files so we can
 242             // compare the installed versus the requested extension
 243                 installedExts = getInstalledExtensions();
 244             } catch(IOException e) {
 245                 debugException(e);
 246                 return false;
 247             }
 248 
 249             for (int i=0;i<installedExts.length;i++) {
 250                 try {
 251                     if (checkExtensionAgainst(extensionName, attr, installedExts[i]))
 252                         return true;
 253                 } catch (FileNotFoundException e) {
 254                     debugException(e);
 255                 } catch (IOException e) {
 256                     debugException(e);
 257                     // let's continue with the next installed extension
 258                 }
 259             }
 260         }
 261         return false;
 262     }
 263 
 264     /*
 265      * <p>
 266      * Check if the requested extension described by the attributes
 267      * in the manifest under the key extensionName is compatible with
 268      * the jar file.
 269      * </p>
 270      *
 271      * @param extensionName key in the attribute list
 272      * @param attr manifest file attributes
 273      * @param file installed extension jar file to compare the requested
 274      * extension against.
 275      */
 276     protected boolean checkExtensionAgainst(String extensionName,
 277                                             Attributes attr,
 278                                             final File file)
 279         throws IOException,
 280                FileNotFoundException,
 281                ExtensionInstallationException
 282     {
 283 
 284         debug("Checking extension " + extensionName +
 285               " against " + file.getName());
 286 
 287         // Load the jar file ...
 288         Manifest man;
 289         try {
 290             man = AccessController.doPrivileged(
 291                 new PrivilegedExceptionAction<Manifest>() {
 292                     public Manifest run()
 293                             throws IOException, FileNotFoundException {
 294                          if (!file.exists())
 295                              throw new FileNotFoundException(file.getName());
 296                          JarFile jarFile =  new JarFile(file);
 297                          return jarFile.getManifest();
 298                      }
 299                  });
 300         } catch(PrivilegedActionException e) {
 301             if (e.getException() instanceof FileNotFoundException)
 302                 throw (FileNotFoundException) e.getException();
 303             throw (IOException) e.getException();
 304         }
 305 
 306         // Construct the extension information object
 307         ExtensionInfo reqInfo = new ExtensionInfo(extensionName, attr);
 308         debug("Requested Extension : " + reqInfo);
 309 
 310         int isCompatible = ExtensionInfo.INCOMPATIBLE;
 311         ExtensionInfo instInfo = null;
 312 
 313         if (man != null) {
 314             Attributes instAttr = man.getMainAttributes();
 315             if (instAttr != null) {
 316                 instInfo = new ExtensionInfo(null, instAttr);
 317                 debug("Extension Installed " + instInfo);
 318                 isCompatible = instInfo.isCompatibleWith(reqInfo);
 319                 switch(isCompatible) {
 320                 case ExtensionInfo.COMPATIBLE:
 321                     debug("Extensions are compatible");
 322                     return true;
 323 
 324                 case ExtensionInfo.INCOMPATIBLE:
 325                     debug("Extensions are incompatible");
 326                     return false;
 327 
 328                 default:
 329                     // everything else
 330                     debug("Extensions require an upgrade or vendor switch");
 331                     return installExtension(reqInfo, instInfo);
 332 
 333                 }
 334             }
 335         }
 336         return false;
 337     }
 338 
 339     /*
 340      * <p>
 341      * An required extension is missing, if an ExtensionInstallationProvider is
 342      * registered, delegate the installation of that particular extension to it.
 343      * </p>
 344      *
 345      * @param reqInfo Missing extension information
 346      * @param instInfo Older installed version information
 347      *
 348      * @return true if the installation is successful
 349      */
 350     protected boolean installExtension(ExtensionInfo reqInfo,
 351                                        ExtensionInfo instInfo)
 352         throws ExtensionInstallationException
 353     {
 354         Vector<ExtensionInstallationProvider> currentProviders;
 355         synchronized(providers) {
 356             @SuppressWarnings("unchecked")
 357             Vector<ExtensionInstallationProvider> tmp =
 358                 (Vector<ExtensionInstallationProvider>) providers.clone();
 359             currentProviders = tmp;
 360         }
 361         for (Enumeration<ExtensionInstallationProvider> e = currentProviders.elements();
 362                 e.hasMoreElements();) {
 363             ExtensionInstallationProvider eip = e.nextElement();
 364 
 365             if (eip!=null) {
 366                 // delegate the installation to the provider
 367                 if (eip.installExtension(reqInfo, instInfo)) {
 368                     debug(reqInfo.name + " installation successful");
 369                     Launcher.ExtClassLoader cl = (Launcher.ExtClassLoader)
 370                         Launcher.getLauncher().getClassLoader().getParent();
 371                     addNewExtensionsToClassLoader(cl);
 372                     return true;
 373                 }
 374             }
 375         }
 376         // We have tried all of our providers, noone could install this
 377         // extension, we just return failure at this point
 378         debug(reqInfo.name + " installation failed");
 379         return false;
 380     }
 381 
 382     /**
 383      * <p>
 384      * Checks if the extension, that is specified in the extension-list in
 385      * the applet jar manifest, is already installed (i.e. exists in the
 386      * extension directory).
 387      * </p>
 388      *
 389      * @param extensionName extension name in the extension-list
 390      *
 391      * @return the extension if it exists in the extension directory
 392      */
 393     private File checkExtensionExists(String extensionName) {
 394         // Function added to fix bug 4504166
 395         final String extName = extensionName;
 396         final String[] fileExt = {".jar", ".zip"};
 397 
 398         return AccessController.doPrivileged(
 399             new PrivilegedAction<File>() {
 400                 public File run() {
 401                     try {
 402                         File fExtension;
 403                         File[] dirs = getExtDirs();
 404 
 405                         // Search the extension directories for the extension that is specified
 406                         // in the attribute extension-list in the applet jar manifest
 407                         for (int i=0;i<dirs.length;i++) {
 408                             for (int j=0;j<fileExt.length;j++) {
 409                                 if (extName.toLowerCase().endsWith(fileExt[j])) {
 410                                     fExtension = new File(dirs[i], extName);
 411                                 } else {
 412                                     fExtension = new File(dirs[i], extName+fileExt[j]);
 413                                 }
 414                                 debug("checkExtensionExists:fileName " + fExtension.getName());
 415                                 if (fExtension.exists()) {
 416                                     return fExtension;
 417                                 }
 418                             }
 419                         }
 420                         return null;
 421 
 422                     } catch(Exception e) {
 423                          debugException(e);
 424                          return null;
 425                     }
 426                 }
 427             });
 428     }
 429 
 430     /**
 431      * <p>
 432      * @return the java.ext.dirs property as a list of directory
 433      * </p>
 434      */
 435     private static File[] getExtDirs() {
 436         String s = java.security.AccessController.doPrivileged(
 437                 new sun.security.action.GetPropertyAction("java.ext.dirs"));
 438 
 439         File[] dirs;
 440         if (s != null) {
 441             StringTokenizer st =
 442                 new StringTokenizer(s, File.pathSeparator);
 443             int count = st.countTokens();
 444             debug("getExtDirs count " + count);
 445             dirs = new File[count];
 446             for (int i = 0; i < count; i++) {
 447                 dirs[i] = new File(st.nextToken());
 448                 debug("getExtDirs dirs["+i+"] "+ dirs[i]);
 449             }
 450         } else {
 451             dirs = new File[0];
 452             debug("getExtDirs dirs " + dirs);
 453         }
 454         debug("getExtDirs dirs.length " + dirs.length);
 455         return dirs;
 456     }
 457 
 458     /*
 459      * <p>
 460      * Scan the directories and return all files installed in those
 461      * </p>
 462      * @param dirs list of directories to scan
 463      *
 464      * @return the list of files installed in all the directories
 465      */
 466     private static File[] getExtFiles(File[] dirs) throws IOException {
 467         Vector<File> urls = new Vector<File>();
 468         for (int i = 0; i < dirs.length; i++) {
 469             String[] files = dirs[i].list(new JarFilter());
 470             if (files != null) {
 471                 debug("getExtFiles files.length " + files.length);
 472                 for (int j = 0; j < files.length; j++) {
 473                     File f = new File(dirs[i], files[j]);
 474                     urls.add(f);
 475                     debug("getExtFiles f["+j+"] "+ f);
 476                 }
 477             }
 478         }
 479         File[] ua = new File[urls.size()];
 480         urls.copyInto(ua);
 481         debug("getExtFiles ua.length " + ua.length);
 482         return ua;
 483     }
 484 
 485     /*
 486      * <p>
 487      * @return the list of installed extensions jar files
 488      * </p>
 489      */
 490     private File[] getInstalledExtensions() throws IOException {
 491         return AccessController.doPrivileged(
 492             new PrivilegedAction<File[]>() {
 493                 public File[] run() {
 494                      try {
 495                          return getExtFiles(getExtDirs());
 496                      } catch(IOException e) {
 497                          debug("Cannot get list of installed extensions");
 498                          debugException(e);
 499                         return new File[0];
 500                      }
 501                  }
 502             });
 503     }
 504 
 505     /*
 506      * <p>
 507      * Add the newly installed jar file to the extension class loader.
 508      * </p>
 509      *
 510      * @param cl the current installed extension class loader
 511      *
 512      * @return true if successful
 513      */
 514     private Boolean addNewExtensionsToClassLoader(Launcher.ExtClassLoader cl) {
 515         try {
 516             File[] installedExts = getInstalledExtensions();
 517             for (int i=0;i<installedExts.length;i++) {
 518                 final File instFile = installedExts[i];
 519                 URL instURL = AccessController.doPrivileged(
 520                     new PrivilegedAction<URL>() {
 521                         public URL run() {
 522                             try {
 523                                 return ParseUtil.fileToEncodedURL(instFile);
 524                             } catch (MalformedURLException e) {
 525                                 debugException(e);
 526                                 return null;
 527                             }
 528                         }
 529                     });
 530                 if (instURL != null) {
 531                     URL[] urls = cl.getURLs();
 532                     boolean found=false;
 533                     for (int j = 0; j<urls.length; j++) {
 534                         debug("URL["+j+"] is " + urls[j] + " looking for "+
 535                                            instURL);
 536                         if (urls[j].toString().compareToIgnoreCase(
 537                                     instURL.toString())==0) {
 538                             found=true;
 539                             debug("Found !");
 540                         }
 541                     }
 542                     if (!found) {
 543                         debug("Not Found ! adding to the classloader " +
 544                               instURL);
 545                         cl.addExtURL(instURL);
 546                     }
 547                 }
 548             }
 549         } catch (MalformedURLException e) {
 550             e.printStackTrace();
 551         } catch (IOException e) {
 552             e.printStackTrace();
 553             // let's continue with the next installed extension
 554         }
 555         return Boolean.TRUE;
 556     }
 557 
 558     // True to display all debug and trace messages
 559     static final boolean DEBUG = false;
 560 
 561     private static void debug(String s) {
 562         if (DEBUG) {
 563             System.err.println(s);
 564         }
 565     }
 566 
 567     private void debugException(Throwable e) {
 568         if (DEBUG) {
 569             e.printStackTrace();
 570         }
 571     }
 572 
 573 }