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 }