diff --git a/src/main/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzer.java b/src/main/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzer.java
new file mode 100644
index 000000000..b1c42ddf8
--- /dev/null
+++ b/src/main/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzer.java
@@ -0,0 +1,48 @@
+package org.codesecure.dependencycheck.scanner;
+/*
+ * This file is part of DependencyCheck.
+ *
+ * DependencyCheck is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DependencyCheck 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
+ *
+ * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
+ */
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ *
+ * @author Jeremy Long (jeremy.long@gmail.com)
+ */
+public abstract class AbstractAnalyzer implements Analyzer {
+
+ /**
+ * Utility method to help in the creation of the extensions set. This constructs
+ * a new Set that can be used in a final static declaration.
+ *
+ * This implementation was copied from http://stackoverflow.com/questions/2041778/initialize-java-hashset-values-by-construction
+ *
+ * @param strings a list of strings to add to the set.
+ * @return a Set of strings.
+ */
+ protected static Set newHashSet(String... strings) {
+ Set set = new HashSet();
+
+ //yes, in Java7 we could use Array.toList(...) - but I'm trying to keep this Java 6 compliant.
+ for (String s : strings) {
+ set.add(s);
+ }
+ return set;
+ }
+}
diff --git a/src/main/java/org/codesecure/dependencycheck/scanner/Analyzer.java b/src/main/java/org/codesecure/dependencycheck/scanner/Analyzer.java
index 2dc938815..a128eb05e 100644
--- a/src/main/java/org/codesecure/dependencycheck/scanner/Analyzer.java
+++ b/src/main/java/org/codesecure/dependencycheck/scanner/Analyzer.java
@@ -20,6 +20,7 @@ package org.codesecure.dependencycheck.scanner;
import java.io.File;
import java.io.IOException;
+import java.util.Set;
/**
* An interface that defines an Analyzer that is used to identify Dependencies.
@@ -38,4 +39,26 @@ public interface Analyzer {
* @throws IOException is thrown if there is an error reading the dependency file
*/
Dependency insepct(File file) throws IOException;
+
+ /**
+ * Returns a list of supported file extensions. An example would be an analyzer
+ * that inpected java jar files. The getSupportedExtensions function would return
+ * a set with a single element "jar".
+ *
+ * @return The file extensions supported by this analyzer.
+ */
+ Set getSupportedExtensions();
+
+ /**
+ * Returns the name of the analyzer.
+ * @return the name of the analyzer.
+ */
+ String getName();
+
+ /**
+ * Returns whether or not this analyzer can process the given extension.
+ * @param extension the file extension to test for support.
+ * @return whether or not the specified file extension is supported by tihs analyzer.
+ */
+ boolean supportsExtension(String extension);
}
diff --git a/src/main/java/org/codesecure/dependencycheck/scanner/AnalyzerService.java b/src/main/java/org/codesecure/dependencycheck/scanner/AnalyzerService.java
new file mode 100644
index 000000000..d4b8579f0
--- /dev/null
+++ b/src/main/java/org/codesecure/dependencycheck/scanner/AnalyzerService.java
@@ -0,0 +1,57 @@
+package org.codesecure.dependencycheck.scanner;
+/*
+ * This file is part of DependencyCheck.
+ *
+ * DependencyCheck is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DependencyCheck 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
+ *
+ * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
+ */
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ *
+ * @author Jeremy Long (jeremy.long@gmail.com)
+ */
+public class AnalyzerService {
+ private static AnalyzerService service;
+ private ServiceLoader loader;
+
+ /**
+ * Creates a new instance of AnalyzerService
+ */
+ private AnalyzerService() {
+ loader = ServiceLoader.load(Analyzer.class);
+ }
+
+ /**
+ * Retrieve the singleton instance of AnalyzerService.
+ * @return a singleton AnalyzerService.
+ */
+ public static synchronized AnalyzerService getInstance() {
+ if (service == null) {
+ service = new AnalyzerService();
+ }
+ return service;
+ }
+
+ /**
+ * Returns an Iterator for all instances of the Analyzer interface.
+ * @return an iterator of Analyzers.
+ */
+ public Iterator getAnalyzers() {
+ return loader.iterator();
+ }
+}
diff --git a/src/main/java/org/codesecure/dependencycheck/scanner/JarAnalyzer.java b/src/main/java/org/codesecure/dependencycheck/scanner/JarAnalyzer.java
index 936ac309c..c4df214d4 100644
--- a/src/main/java/org/codesecure/dependencycheck/scanner/JarAnalyzer.java
+++ b/src/main/java/org/codesecure/dependencycheck/scanner/JarAnalyzer.java
@@ -23,9 +23,12 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -47,22 +50,29 @@ import org.codesecure.dependencycheck.utils.Checksum;
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
-public class JarAnalyzer implements Analyzer {
+public class JarAnalyzer extends AbstractAnalyzer {
- private static List IGNORE_LIST;
-
- public JarAnalyzer() {
- IGNORE_LIST = new ArrayList();
- IGNORE_LIST.add("built-by");
- IGNORE_LIST.add("created-by");
- IGNORE_LIST.add("license");
- IGNORE_LIST.add("build-jdk");
- IGNORE_LIST.add("ant-version");
- IGNORE_LIST.add("import-package");
- IGNORE_LIST.add("export-package");
- IGNORE_LIST.add("sealed");
- IGNORE_LIST.add("manifest-version");
- }
+ private final static String ANALYZER_NAME = "Jar Analyzer";
+
+ /**
+ * A list of elements in the manifest to ignore.
+ */
+ private final static Set IGNORE_LIST = newHashSet(
+ "built-by",
+ "created-by",
+ "license",
+ "build-jdk",
+ "ant-version",
+ "import-package",
+ "export-package",
+ "sealed",
+ "manifest-version",
+ "archiver-version",
+ "classpath",
+ "bundle-manifestversion");
+
+ private final static Set extensions = newHashSet("jar");
+
/**
* item in some manifest, should be considered medium confidence.
*/
@@ -80,6 +90,31 @@ public class JarAnalyzer implements Analyzer {
*/
private static final String BUNDLE_VENDOR = "Bundle-Vendor"; //: Apache Software Foundation
+ /**
+ * Returns a list of file extensions supported by this analyzer.
+ * @return a list of file extensions supported by this analyzer.
+ */
+ public Set getSupportedExtensions() {
+ return extensions;
+ }
+
+ /**
+ * Returns the name of the analyzer.
+ * @return the name of the analyzer.
+ */
+ public String getName() {
+ return ANALYZER_NAME;
+ }
+
+ /**
+ * Returns whether or not this analyzer can process the given extension.
+ * @param extension the file extension to test for support.
+ * @return whether or not the specified file extension is supported by tihs analyzer.
+ */
+ public boolean supportsExtension(String extension) {
+ return extensions.contains(extension);
+ }
+
/**
* An enumeration to keep track of the characters in a string as it is being
* read in one character at a time.
@@ -331,7 +366,7 @@ public class JarAnalyzer implements Analyzer {
}
/**
- *
Reads the manifest from the JAR file and collects the:
+ *
Reads the manifest from the JAR file and collects the entries. Some key entries are:
*
Implementation Title
*
Implementation Version
*
Implementation Vendor
@@ -342,7 +377,8 @@ public class JarAnalyzer implements Analyzer {
*
Bundle Description
*
Main Class
*
- *
+ * However, all but a handful of specific entries are read in.
+ *
* @param dependency A reference to the dependency.
* @throws IOException if there is an issue reading the JAR file.
*/
@@ -382,7 +418,7 @@ public class JarAnalyzer implements Analyzer {
} else {
key = key.toLowerCase();
- if (!IGNORE_LIST.contains(key)) {
+ if (!IGNORE_LIST.contains(key) && !key.contains("license") && !key.endsWith("jdk")) {
if (key.contains("version")) {
versionEvidence.addEvidence(source, key, value, Evidence.Confidence.MEDIUM);
} else if (key.contains("title")) {
diff --git a/src/main/java/org/codesecure/dependencycheck/scanner/Scanner.java b/src/main/java/org/codesecure/dependencycheck/scanner/Scanner.java
index f851e9de3..5947f3dbc 100644
--- a/src/main/java/org/codesecure/dependencycheck/scanner/Scanner.java
+++ b/src/main/java/org/codesecure/dependencycheck/scanner/Scanner.java
@@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@@ -44,9 +45,9 @@ public class Scanner {
*/
protected List dependencies = new ArrayList();
/**
- * A Map of analyzers - the key is the file extension.
+ * A List of analyzers.
*/
- protected Map analyzers = new HashMap();
+ protected List analyzers = new ArrayList();
/**
* Creates a new Scanner.
@@ -59,56 +60,20 @@ public class Scanner {
* Loads the analyzers specified in the configuration file (or system properties).
*/
private void loadAnalyzers() {
- Map associations = Settings.getPropertiesByPrefix(KEYS.FILE_EXTENSION_ANALYZER_ASSOCIATION_PREFIX);
- for (Map.Entry entry : associations.entrySet()) {
- addAnalyzer(entry.getKey(), entry.getValue());
+ AnalyzerService service = AnalyzerService.getInstance();
+ Iterator iterator = service.getAnalyzers();
+ while(iterator.hasNext()) {
+ Analyzer a = iterator.next();
+ analyzers.add(a);
}
}
/**
- * Adds an Analyzer to the collection of analyzers and associates the
- * analyzer with a file extension.
- *
- * If the specified class does not implement 'org.codesecure.dependencycheck.detect.Analyzer'
- * the load will fail mostly silently - only writting the failure to the log file.
- *
- * @param extension the file extension that this analyzer can analyze.
- * @param className the fully qualified classname of the Analyzer.
- */
- public final void addAnalyzer(String extension, String className) {
-
- ClassLoader loader = this.getClass().getClassLoader();
- try {
- Class analyzer = loader.loadClass(className);
- boolean implmnts = false;
- for (Class p : analyzer.getInterfaces()) {
- if (org.codesecure.dependencycheck.scanner.Analyzer.class.isAssignableFrom(p)) {
- implmnts = true;
- break;
- }
- }
-
- if (implmnts) {
- this.analyzers.put(extension, (Analyzer) analyzer.newInstance());
- } else {
- String msg = String.format("Class '%s' does not implement org.codesecure.dependencycheck.scanner.Analyzer and cannot be loaded as an Analyzer for extension '%s'.", className, extension);
- Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
- }
- } catch (ClassNotFoundException ex) {
- Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
- } catch (InstantiationException ex) {
- Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
- } catch (IllegalAccessException ex) {
- Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- /**
- * Get the Map of the analyzers.
+ * Get the List of the analyzers.
*
* @return the analyzers loaded
*/
- public Map getAnalyzers() {
+ public List getAnalyzers() {
return analyzers;
}
@@ -170,24 +135,24 @@ public class Scanner {
String fileName = file.getName();
String extension = getFileExtension(fileName);
if (extension != null) {
- if (analyzers.containsKey(extension)) {
- Analyzer a = analyzers.get(extension);
- try {
- Dependency dependency = a.insepct(file);
- dependencies.add(dependency);
- } catch (IOException ex) {
- String msg = String.format("IOException occured while scanning the file '%s'.", file.toString());
- Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, msg, ex);
+ for (Analyzer a : analyzers) {
+ if (a.supportsExtension(extension)) {
+ try {
+ Dependency dependency = a.insepct(file);
+ if (dependency != null) {
+ dependencies.add(dependency);
+ break;
+ }
+ } catch (IOException ex) {
+ String msg = String.format("IOException occured while scanning the file '%s'.", file.toString());
+ Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, msg, ex);
+ }
}
- } else {
- String msg = String.format("No analyzer is configured for files of type '%s'. The file, '%s', was not analyzed.", extension, file.toString());
- Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
}
} else {
String msg = String.format("No files extension found on file '%s'. The file was not analyzed.", file.toString());
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
}
-
}
/**
diff --git a/src/main/java/org/codesecure/dependencycheck/utils/Settings.java b/src/main/java/org/codesecure/dependencycheck/utils/Settings.java
index 15e6e90df..4fa896eef 100644
--- a/src/main/java/org/codesecure/dependencycheck/utils/Settings.java
+++ b/src/main/java/org/codesecure/dependencycheck/utils/Settings.java
@@ -64,8 +64,17 @@ public class Settings {
* The properties key prefix for the analyzer assocations.
*/
public static final String FILE_EXTENSION_ANALYZER_ASSOCIATION_PREFIX = "file.extension.analyzer.association.";
+ /**
+ * The properties key for the proxy url.
+ */
public static final String PROXY_URL = "proxy.url";
+ /**
+ * The properties key for the proxy port - this must be an integer value.
+ */
public static final String PROXY_PORT = "proxy.port";
+ /**
+ * The properties key for the connection timeout.
+ */
public static final String CONNECTION_TIMEOUT = "connection.timeout";
}
private static final String PROPERTIES_FILE = "dependencycheck.properties";
@@ -124,44 +133,7 @@ public class Settings {
public static String getString(String key) {
return System.getProperty(key, instance.props.getProperty(key));
}
-
- /**
- * Returns a map of properties selected by a given prefix. For isntance
- * if you have five properties that started off with "org.codesecure.name"
- * you could get a collection of those properties by calling this method.
- *
- * NOTE: The prefix is removed from the given properties when returned.
- *
- * @param prefix the prefix used to search the property collections for.
- * @return a Map of properties found.
- */
- public static Map getPropertiesByPrefix(String prefix) {
- Map ret = new HashMap();
-
- Properties properties = instance.props;
- for (Enumeration