1 /*
   2  * Copyright (c) 1998, 2008, 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 com.sun.tools.extcheck;
  27 
  28 import java.util.*;
  29 import java.net.MalformedURLException;
  30 import java.util.Vector;
  31 import java.io.*;
  32 import java.util.StringTokenizer;
  33 import java.net.URL;
  34 import java.util.jar.JarFile;
  35 import java.util.jar.JarEntry;
  36 import java.util.jar.Manifest;
  37 import java.util.jar.Attributes;
  38 import java.util.jar.Attributes.Name;
  39 import java.util.zip.ZipException;
  40 import java.net.URLConnection;
  41 import java.security.Permission;
  42 import java.util.jar.*;
  43 import java.net.JarURLConnection;
  44 import sun.net.www.ParseUtil;
  45 
  46 /**
  47  * ExtCheck reports on clashes between a specified (target)
  48  * jar file and jar files already installed in the extensions
  49  * directory.
  50  *
  51  * @author Benedict Gomes
  52  * @since 1.2
  53  */
  54 
  55 public class ExtCheck {
  56 
  57     private static final boolean DEBUG = false;
  58 
  59     // The following strings hold the values of the version variables
  60     // for the target jar file
  61     private String targetSpecTitle;
  62     private String targetSpecVersion;
  63     private String targetSpecVendor;
  64     private String targetImplTitle;
  65     private String targetImplVersion;
  66     private String targetImplVendor;
  67     private String targetsealed;
  68 
  69     /* Flag to indicate whether extra information should be dumped to stdout */
  70     private boolean verboseFlag;
  71 
  72     /*
  73      * Create a new instance of the jar reporting tool for a particular
  74      * targetFile.
  75      * @param targetFile is the file to compare against.
  76      * @param verbose indicates whether to dump filenames and manifest
  77      *                information (on conflict) to the standard output.
  78      */
  79     static ExtCheck create(File targetFile, boolean verbose) {
  80         return new ExtCheck(targetFile, verbose);
  81     }
  82 
  83     private ExtCheck(File targetFile, boolean verbose) {
  84         verboseFlag = verbose;
  85         investigateTarget(targetFile);
  86     }
  87 
  88 
  89     private void investigateTarget(File targetFile) {
  90         verboseMessage("Target file:" + targetFile);
  91         Manifest targetManifest = null;
  92         try {
  93             File canon = new File(targetFile.getCanonicalPath());
  94             URL url = ParseUtil.fileToEncodedURL(canon);
  95             if (url != null){
  96                 JarLoader loader = new JarLoader(url);
  97                 JarFile jarFile = loader.getJarFile();
  98                 targetManifest = jarFile.getManifest();
  99             }
 100         } catch (MalformedURLException e){
 101             error("Malformed URL ");
 102         } catch (IOException e) {
 103             error("IO Exception ");
 104         }
 105         if (targetManifest == null)
 106             error("No manifest available in "+targetFile);
 107         Attributes attr = targetManifest.getMainAttributes();
 108         if (attr != null) {
 109             targetSpecTitle   = attr.getValue(Name.SPECIFICATION_TITLE);
 110             targetSpecVersion = attr.getValue(Name.SPECIFICATION_VERSION);
 111             targetSpecVendor  = attr.getValue(Name.SPECIFICATION_VENDOR);
 112             targetImplTitle   = attr.getValue(Name.IMPLEMENTATION_TITLE);
 113             targetImplVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
 114             targetImplVendor  = attr.getValue(Name.IMPLEMENTATION_VENDOR);
 115             targetsealed      = attr.getValue(Name.SEALED);
 116         } else {
 117             error("No attributes available in the manifest");
 118         }
 119         if (targetSpecTitle == null)
 120             error("The target file does not have a specification title");
 121         if (targetSpecVersion == null)
 122             error("The target file does not have a specification version");
 123         verboseMessage("Specification title:" + targetSpecTitle);
 124         verboseMessage("Specification version:" + targetSpecVersion);
 125         if (targetSpecVendor != null)
 126             verboseMessage("Specification vendor:" + targetSpecVendor);
 127         if (targetImplVersion != null)
 128             verboseMessage("Implementation version:" + targetImplVersion);
 129         if (targetImplVendor != null)
 130             verboseMessage("Implementation vendor:" + targetImplVendor);
 131         verboseMessage("");
 132     }
 133 
 134     /**
 135      * Verify that none of the jar files in the install directory
 136      * has the same specification-title and the same or a newer
 137      * specification-version.
 138      *
 139      * @return Return true if the target jar file is newer
 140      *        than any installed jar file with the same specification-title,
 141      *        otherwise return false
 142      */
 143     boolean checkInstalledAgainstTarget(){
 144         String s = System.getProperty("java.ext.dirs");
 145         File [] dirs;
 146         if (s != null) {
 147             StringTokenizer st =
 148                 new StringTokenizer(s, File.pathSeparator);
 149             int count = st.countTokens();
 150             dirs = new File[count];
 151             for (int i = 0; i < count; i++) {
 152                 dirs[i] = new File(st.nextToken());
 153             }
 154         } else {
 155             dirs = new File[0];
 156         }
 157 
 158         boolean result = true;
 159         for (int i = 0; i < dirs.length; i++) {
 160             String[] files = dirs[i].list();
 161             if (files != null) {
 162                 for (int j = 0; j < files.length; j++) {
 163                     try {
 164                         File f = new File(dirs[i],files[j]);
 165                         File canon = new File(f.getCanonicalPath());
 166                         URL url = ParseUtil.fileToEncodedURL(canon);
 167                         if (url != null){
 168                             result = result && checkURLRecursively(1,url);
 169                         }
 170                     } catch (MalformedURLException e){
 171                         error("Malformed URL");
 172                     } catch (IOException e) {
 173                         error("IO Exception");
 174                     }
 175                 }
 176             }
 177         }
 178         if (result) {
 179             generalMessage("No conflicting installed jar found.");
 180         } else {
 181             generalMessage("Conflicting installed jar found. "
 182                            + " Use -verbose for more information.");
 183         }
 184         return result;
 185     }
 186 
 187     /**
 188      * Recursively verify that a jar file, and any urls mentioned
 189      * in its class path, do not conflict with the target jar file.
 190      *
 191      * @param indent is the current nesting level
 192      * @param url is the path to the jar file being checked.
 193      * @return true if there is no newer URL, otherwise false
 194      */
 195     private boolean checkURLRecursively(int indent, URL url)
 196         throws IOException
 197     {
 198         verboseMessage("Comparing with " + url);
 199         JarLoader jarloader = null;
 200         try {
 201             jarloader = new JarLoader(url);
 202         } catch (ZipException ze) {
 203             verboseMessage("... skipping non-jar file " + url);
 204             return true;
 205         }
 206         JarFile j = jarloader.getJarFile();
 207         Manifest man = j.getManifest();
 208         if (man != null) {
 209             Attributes attr = man.getMainAttributes();
 210             if (attr != null){
 211                 String title   = attr.getValue(Name.SPECIFICATION_TITLE);
 212                 String version = attr.getValue(Name.SPECIFICATION_VERSION);
 213                 String vendor  = attr.getValue(Name.SPECIFICATION_VENDOR);
 214                 String implTitle   = attr.getValue(Name.IMPLEMENTATION_TITLE);
 215                 String implVersion
 216                     = attr.getValue(Name.IMPLEMENTATION_VERSION);
 217                 String implVendor  = attr.getValue(Name.IMPLEMENTATION_VENDOR);
 218                 String sealed      = attr.getValue(Name.SEALED);
 219                 if (title != null){
 220                     if (title.equals(targetSpecTitle)){
 221                         if (version != null){
 222                             if (version.equals(targetSpecVersion) ||
 223                                 isNotOlderThan(version,targetSpecVersion)){
 224                                 verboseMessage("");
 225                                 verboseMessage("CONFLICT DETECTED ");
 226                                 verboseMessage("Conflicting file:"+ url);
 227                                 verboseMessage("Installed Version:" +
 228                                                version);
 229                                 if (implTitle != null)
 230                                     verboseMessage("Implementation Title:"+
 231                                                    implTitle);
 232                                 if (implVersion != null)
 233                                     verboseMessage("Implementation Version:"+
 234                                                    implVersion);
 235                                 if (implVendor != null)
 236                                     verboseMessage("Implementation Vendor:"+
 237                                                    implVendor);
 238                                 return false;
 239                             }
 240                         }
 241                     }
 242                 }
 243             }
 244         }
 245         boolean result = true;
 246         URL[] loaderList = jarloader.getClassPath();
 247         if (loaderList != null) {
 248             for(int i=0; i < loaderList.length; i++){
 249                 if (url != null){
 250                     boolean res =  checkURLRecursively(indent+1,loaderList[i]);
 251                     result = res && result;
 252                 }
 253             }
 254         }
 255         return result;
 256     }
 257 
 258     /**
 259      *  See comment in method java.lang.Package.isCompatibleWith.
 260      *  Return true if already is not older than target. i.e. the
 261      *  target file may be superseded by a file already installed
 262      */
 263     private boolean isNotOlderThan(String already,String target)
 264         throws NumberFormatException
 265     {
 266             if (already == null || already.length() < 1) {
 267             throw new NumberFormatException("Empty version string");
 268         }
 269 
 270             // Until it matches scan and compare numbers
 271             StringTokenizer dtok = new StringTokenizer(target, ".", true);
 272             StringTokenizer stok = new StringTokenizer(already, ".", true);
 273         while (dtok.hasMoreTokens() || stok.hasMoreTokens()) {
 274             int dver;
 275             int sver;
 276             if (dtok.hasMoreTokens()) {
 277                 dver = Integer.parseInt(dtok.nextToken());
 278             } else
 279                 dver = 0;
 280 
 281             if (stok.hasMoreTokens()) {
 282                 sver = Integer.parseInt(stok.nextToken());
 283             } else
 284                 sver = 0;
 285 
 286                 if (sver < dver)
 287                         return false;                // Known to be incompatible
 288                 if (sver > dver)
 289                         return true;                // Known to be compatible
 290 
 291                 // Check for and absorb separators
 292                 if (dtok.hasMoreTokens())
 293                         dtok.nextToken();
 294                 if (stok.hasMoreTokens())
 295                         stok.nextToken();
 296                 // Compare next component
 297             }
 298             // All components numerically equal
 299         return true;
 300     }
 301 
 302 
 303     /**
 304      * Prints out message if the verboseFlag is set
 305      */
 306     void verboseMessage(String message){
 307         if (verboseFlag) {
 308             System.err.println(message);
 309         }
 310     }
 311 
 312     void generalMessage(String message){
 313         System.err.println(message);
 314     }
 315 
 316     /**
 317      * Throws a RuntimeException with a message describing the error.
 318      */
 319     static void error(String message) throws RuntimeException {
 320         throw new RuntimeException(message);
 321     }
 322 
 323 
 324     /**
 325      * Inner class used to represent a loader of resources and classes
 326      * from a base URL. Somewhat modified version of code in
 327      * sun.misc.URLClassPath.JarLoader
 328      */
 329     private static class JarLoader {
 330         private final URL base;
 331         private JarFile jar;
 332         private URL csu;
 333 
 334         /*
 335          * Creates a new Loader for the specified URL.
 336          */
 337         JarLoader(URL url) throws IOException {
 338             String urlName = url + "!/";
 339             URL tmpBaseURL = null;
 340             try {
 341                 tmpBaseURL = new URL("jar","",urlName);
 342                 jar = findJarFile(url);
 343                 csu = url;
 344             } catch (MalformedURLException e) {
 345                 ExtCheck.error("Malformed url "+urlName);
 346             }
 347             base = tmpBaseURL;
 348         }
 349 
 350         /*
 351          * Returns the base URL for this Loader.
 352          */
 353         URL getBaseURL() {
 354             return base;
 355         }
 356 
 357         JarFile getJarFile() {
 358             return jar;
 359         }
 360 
 361         private JarFile findJarFile(URL url) throws IOException {
 362              // Optimize case where url refers to a local jar file
 363              if ("file".equals(url.getProtocol())) {
 364                  String path = url.getFile().replace('/', File.separatorChar);
 365                  File file = new File(path);
 366                  if (!file.exists()) {
 367                      throw new FileNotFoundException(path);
 368                  }
 369                  return new JarFile(path);
 370              }
 371              URLConnection uc = getBaseURL().openConnection();
 372              //uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
 373              return ((JarURLConnection)uc).getJarFile();
 374          }
 375 
 376 
 377         /*
 378          * Returns the JAR file local class path, or null if none.
 379          */
 380         URL[] getClassPath() throws IOException {
 381             Manifest man = jar.getManifest();
 382             if (man != null) {
 383                 Attributes attr = man.getMainAttributes();
 384                 if (attr != null) {
 385                     String value = attr.getValue(Name.CLASS_PATH);
 386                     if (value != null) {
 387                         return parseClassPath(csu, value);
 388                     }
 389                 }
 390             }
 391             return null;
 392         }
 393 
 394         /*
 395          * Parses value of the Class-Path manifest attribute and returns
 396          * an array of URLs relative to the specified base URL.
 397          */
 398         private URL[] parseClassPath(URL base, String value)
 399             throws MalformedURLException
 400         {
 401             StringTokenizer st = new StringTokenizer(value);
 402             URL[] urls = new URL[st.countTokens()];
 403             int i = 0;
 404             while (st.hasMoreTokens()) {
 405                 String path = st.nextToken();
 406                 urls[i] = new URL(base, path);
 407                 i++;
 408             }
 409             return urls;
 410         }
 411     }
 412 }