411 lines
15 KiB
Java
411 lines
15 KiB
Java
/*
|
|
* Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package com.sun.tools.extcheck;
|
|
|
|
import java.util.*;
|
|
import java.net.MalformedURLException;
|
|
import java.util.Vector;
|
|
import java.io.*;
|
|
import java.util.StringTokenizer;
|
|
import java.net.URL;
|
|
import java.util.jar.JarFile;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.Manifest;
|
|
import java.util.jar.Attributes;
|
|
import java.util.jar.Attributes.Name;
|
|
import java.net.URLConnection;
|
|
import java.security.Permission;
|
|
import java.util.jar.*;
|
|
import java.net.JarURLConnection;
|
|
import sun.net.www.ParseUtil;
|
|
|
|
/**
|
|
* ExtCheck reports on clashes between a specified (target)
|
|
* jar file and jar files already installed in the extensions
|
|
* directory.
|
|
*
|
|
* @author Benedict Gomes
|
|
* @since 1.2
|
|
*/
|
|
|
|
public class ExtCheck {
|
|
|
|
private static final boolean DEBUG = false;
|
|
|
|
// The following strings hold the values of the version variables
|
|
// for the target jar file
|
|
private String targetSpecTitle;
|
|
private String targetSpecVersion;
|
|
private String targetSpecVendor;
|
|
private String targetImplTitle;
|
|
private String targetImplVersion;
|
|
private String targetImplVendor;
|
|
private String targetsealed;
|
|
|
|
/* Flag to indicate whether extra information should be dumped to stdout */
|
|
private boolean verboseFlag;
|
|
|
|
/*
|
|
* Create a new instance of the jar reporting tool for a particular
|
|
* targetFile.
|
|
* @param targetFile is the file to compare against.
|
|
* @param verbose indicates whether to dump filenames and manifest
|
|
* information (on conflict) to the standard output.
|
|
*/
|
|
static ExtCheck create(File targetFile, boolean verbose) {
|
|
return new ExtCheck(targetFile, verbose);
|
|
}
|
|
|
|
private ExtCheck(File targetFile, boolean verbose) {
|
|
verboseFlag = verbose;
|
|
investigateTarget(targetFile);
|
|
}
|
|
|
|
|
|
private void investigateTarget(File targetFile) {
|
|
verboseMessage("Target file:" + targetFile);
|
|
Manifest targetManifest = null;
|
|
try {
|
|
File canon = new File(targetFile.getCanonicalPath());
|
|
URL url = ParseUtil.fileToEncodedURL(canon);
|
|
if (url != null){
|
|
JarLoader loader = new JarLoader(url);
|
|
JarFile jarFile = loader.getJarFile();
|
|
targetManifest = jarFile.getManifest();
|
|
}
|
|
} catch (MalformedURLException e){
|
|
error("Malformed URL ");
|
|
} catch (IOException e) {
|
|
error("IO Exception ");
|
|
}
|
|
if (targetManifest == null)
|
|
error("No manifest available in "+targetFile);
|
|
Attributes attr = targetManifest.getMainAttributes();
|
|
if (attr != null) {
|
|
targetSpecTitle = attr.getValue(Name.SPECIFICATION_TITLE);
|
|
targetSpecVersion = attr.getValue(Name.SPECIFICATION_VERSION);
|
|
targetSpecVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
|
|
targetImplTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
|
|
targetImplVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
|
|
targetImplVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
|
|
targetsealed = attr.getValue(Name.SEALED);
|
|
} else {
|
|
error("No attributes available in the manifest");
|
|
}
|
|
if (targetSpecTitle == null)
|
|
error("The target file does not have a specification title");
|
|
if (targetSpecVersion == null)
|
|
error("The target file does not have a specification version");
|
|
verboseMessage("Specification title:" + targetSpecTitle);
|
|
verboseMessage("Specification version:" + targetSpecVersion);
|
|
if (targetSpecVendor != null)
|
|
verboseMessage("Specification vendor:" + targetSpecVendor);
|
|
if (targetImplVersion != null)
|
|
verboseMessage("Implementation version:" + targetImplVersion);
|
|
if (targetImplVendor != null)
|
|
verboseMessage("Implementation vendor:" + targetImplVendor);
|
|
verboseMessage("");
|
|
}
|
|
|
|
/**
|
|
* Verify that none of the jar files in the install directory
|
|
* has the same specification-title and the same or a newer
|
|
* specification-version.
|
|
*
|
|
* @return Return true if the target jar file is newer
|
|
* than any installed jar file with the same specification-title,
|
|
* otherwise return false
|
|
*/
|
|
boolean checkInstalledAgainstTarget(){
|
|
String s = System.getProperty("java.ext.dirs");
|
|
File [] dirs;
|
|
if (s != null) {
|
|
StringTokenizer st =
|
|
new StringTokenizer(s, File.pathSeparator);
|
|
int count = st.countTokens();
|
|
dirs = new File[count];
|
|
for (int i = 0; i < count; i++) {
|
|
dirs[i] = new File(st.nextToken());
|
|
}
|
|
} else {
|
|
dirs = new File[0];
|
|
}
|
|
|
|
boolean result = true;
|
|
for (int i = 0; i < dirs.length; i++) {
|
|
String[] files = dirs[i].list();
|
|
if (files != null) {
|
|
for (int j = 0; j < files.length; j++) {
|
|
try {
|
|
File f = new File(dirs[i],files[j]);
|
|
File canon = new File(f.getCanonicalPath());
|
|
URL url = ParseUtil.fileToEncodedURL(canon);
|
|
if (url != null){
|
|
result = result && checkURLRecursively(1,url);
|
|
}
|
|
} catch (MalformedURLException e){
|
|
error("Malformed URL");
|
|
} catch (IOException e) {
|
|
error("IO Exception");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result) {
|
|
generalMessage("No conflicting installed jar found.");
|
|
} else {
|
|
generalMessage("Conflicting installed jar found. "
|
|
+ " Use -verbose for more information.");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Recursively verify that a jar file, and any urls mentioned
|
|
* in its class path, do not conflict with the target jar file.
|
|
*
|
|
* @param indent is the current nesting level
|
|
* @param url is the path to the jar file being checked.
|
|
* @return true if there is no newer URL, otherwise false
|
|
*/
|
|
private boolean checkURLRecursively(int indent, URL url)
|
|
throws IOException
|
|
{
|
|
verboseMessage("Comparing with " + url);
|
|
JarLoader jarloader = new JarLoader(url);
|
|
JarFile j = jarloader.getJarFile();
|
|
Manifest man = j.getManifest();
|
|
if (man != null) {
|
|
Attributes attr = man.getMainAttributes();
|
|
if (attr != null){
|
|
String title = attr.getValue(Name.SPECIFICATION_TITLE);
|
|
String version = attr.getValue(Name.SPECIFICATION_VERSION);
|
|
String vendor = attr.getValue(Name.SPECIFICATION_VENDOR);
|
|
String implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
|
|
String implVersion
|
|
= attr.getValue(Name.IMPLEMENTATION_VERSION);
|
|
String implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
|
|
String sealed = attr.getValue(Name.SEALED);
|
|
if (title != null){
|
|
if (title.equals(targetSpecTitle)){
|
|
if (version != null){
|
|
if (version.equals(targetSpecVersion) ||
|
|
isNotOlderThan(version,targetSpecVersion)){
|
|
verboseMessage("");
|
|
verboseMessage("CONFLICT DETECTED ");
|
|
verboseMessage("Conflicting file:"+ url);
|
|
verboseMessage("Installed Version:" +
|
|
version);
|
|
if (implTitle != null)
|
|
verboseMessage("Implementation Title:"+
|
|
implTitle);
|
|
if (implVersion != null)
|
|
verboseMessage("Implementation Version:"+
|
|
implVersion);
|
|
if (implVendor != null)
|
|
verboseMessage("Implementation Vendor:"+
|
|
implVendor);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
boolean result = true;
|
|
URL[] loaderList = jarloader.getClassPath();
|
|
if (loaderList != null) {
|
|
for(int i=0; i < loaderList.length; i++){
|
|
if (url != null){
|
|
boolean res = checkURLRecursively(indent+1,loaderList[i]);
|
|
result = res && result;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* See comment in method java.lang.Package.isCompatibleWith.
|
|
* Return true if already is not older than target. i.e. the
|
|
* target file may be superseded by a file already installed
|
|
*/
|
|
private boolean isNotOlderThan(String already,String target)
|
|
throws NumberFormatException
|
|
{
|
|
if (already == null || already.length() < 1) {
|
|
throw new NumberFormatException("Empty version string");
|
|
}
|
|
|
|
// Until it matches scan and compare numbers
|
|
StringTokenizer dtok = new StringTokenizer(target, ".", true);
|
|
StringTokenizer stok = new StringTokenizer(already, ".", true);
|
|
while (dtok.hasMoreTokens() || stok.hasMoreTokens()) {
|
|
int dver;
|
|
int sver;
|
|
if (dtok.hasMoreTokens()) {
|
|
dver = Integer.parseInt(dtok.nextToken());
|
|
} else
|
|
dver = 0;
|
|
|
|
if (stok.hasMoreTokens()) {
|
|
sver = Integer.parseInt(stok.nextToken());
|
|
} else
|
|
sver = 0;
|
|
|
|
if (sver < dver)
|
|
return false; // Known to be incompatible
|
|
if (sver > dver)
|
|
return true; // Known to be compatible
|
|
|
|
// Check for and absorb separators
|
|
if (dtok.hasMoreTokens())
|
|
dtok.nextToken();
|
|
if (stok.hasMoreTokens())
|
|
stok.nextToken();
|
|
// Compare next component
|
|
}
|
|
// All components numerically equal
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints out message if the verboseFlag is set
|
|
*/
|
|
void verboseMessage(String message){
|
|
if (verboseFlag) {
|
|
System.err.println(message);
|
|
}
|
|
}
|
|
|
|
void generalMessage(String message){
|
|
System.err.println(message);
|
|
}
|
|
|
|
/**
|
|
* Throws a RuntimeException with a message describing the error.
|
|
*/
|
|
static void error(String message) throws RuntimeException {
|
|
throw new RuntimeException(message);
|
|
}
|
|
|
|
|
|
/**
|
|
* Inner class used to represent a loader of resources and classes
|
|
* from a base URL. Somewhat modified version of code in
|
|
* sun.misc.URLClassPath.JarLoader
|
|
*/
|
|
private static class JarLoader {
|
|
private final URL base;
|
|
private JarFile jar;
|
|
private URL csu;
|
|
|
|
/*
|
|
* Creates a new Loader for the specified URL.
|
|
*/
|
|
JarLoader(URL url) {
|
|
String urlName = url + "!/";
|
|
URL tmpBaseURL = null;
|
|
try {
|
|
tmpBaseURL = new URL("jar","",urlName);
|
|
jar = findJarFile(url);
|
|
csu = url;
|
|
} catch (MalformedURLException e) {
|
|
ExtCheck.error("Malformed url "+urlName);
|
|
} catch (IOException e) {
|
|
ExtCheck.error("IO Exception occurred");
|
|
}
|
|
base = tmpBaseURL;
|
|
|
|
}
|
|
|
|
/*
|
|
* Returns the base URL for this Loader.
|
|
*/
|
|
URL getBaseURL() {
|
|
return base;
|
|
}
|
|
|
|
JarFile getJarFile() {
|
|
return jar;
|
|
}
|
|
|
|
private JarFile findJarFile(URL url) throws IOException {
|
|
// Optimize case where url refers to a local jar file
|
|
if ("file".equals(url.getProtocol())) {
|
|
String path = url.getFile().replace('/', File.separatorChar);
|
|
File file = new File(path);
|
|
if (!file.exists()) {
|
|
throw new FileNotFoundException(path);
|
|
}
|
|
return new JarFile(path);
|
|
}
|
|
URLConnection uc = getBaseURL().openConnection();
|
|
//uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
|
|
return ((JarURLConnection)uc).getJarFile();
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the JAR file local class path, or null if none.
|
|
*/
|
|
URL[] getClassPath() throws IOException {
|
|
Manifest man = jar.getManifest();
|
|
if (man != null) {
|
|
Attributes attr = man.getMainAttributes();
|
|
if (attr != null) {
|
|
String value = attr.getValue(Name.CLASS_PATH);
|
|
if (value != null) {
|
|
return parseClassPath(csu, value);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Parses value of the Class-Path manifest attribute and returns
|
|
* an array of URLs relative to the specified base URL.
|
|
*/
|
|
private URL[] parseClassPath(URL base, String value)
|
|
throws MalformedURLException
|
|
{
|
|
StringTokenizer st = new StringTokenizer(value);
|
|
URL[] urls = new URL[st.countTokens()];
|
|
int i = 0;
|
|
while (st.hasMoreTokens()) {
|
|
String path = st.nextToken();
|
|
urls[i] = new URL(base, path);
|
|
i++;
|
|
}
|
|
return urls;
|
|
}
|
|
}
|
|
|
|
|
|
}
|