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 e = properties.keys(); e.hasMoreElements(); ) { - Object o = e.nextElement(); - if (o instanceof String) { - String key = (String) o; - if (key.startsWith(prefix)) { - String ext = key.substring(prefix.length()); - ret.put(ext, properties.getProperty(key)); - } - } - } - properties = System.getProperties(); - for (Enumeration e = properties.keys(); e.hasMoreElements(); ) { - Object o = e.nextElement(); - if (o instanceof String) { - String key = (String) o; - if (key.startsWith(prefix)) { - String ext = key.substring(prefix.length() + 1); - ret.put(ext, properties.getProperty(key)); - } - } - } - return ret; - } + /** * Returns a integer value from the properties file. If the value was specified as a * system property or passed in via the -Dprop=value argument - this method diff --git a/src/main/resources/dependencycheck.properties b/src/main/resources/META-INF/dependencycheck.properties similarity index 71% rename from src/main/resources/dependencycheck.properties rename to src/main/resources/META-INF/dependencycheck.properties index e989860af..7527480e0 100644 --- a/src/main/resources/dependencycheck.properties +++ b/src/main/resources/META-INF/dependencycheck.properties @@ -7,4 +7,3 @@ cpe.downloadfrequency=1 cve=store/cve osvdb=store/osvdb -file.extension.analyzer.association.jar=org.codesecure.dependencycheck.scanner.JarAnalyzer diff --git a/src/main/resources/META-INF/services/org.codesecure.dependencycheck.scanner.Analyzer b/src/main/resources/META-INF/services/org.codesecure.dependencycheck.scanner.Analyzer new file mode 100644 index 000000000..a0a72861e --- /dev/null +++ b/src/main/resources/META-INF/services/org.codesecure.dependencycheck.scanner.Analyzer @@ -0,0 +1 @@ +org.codesecure.dependencycheck.scanner.JarAnalyzer \ No newline at end of file diff --git a/src/test/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzerTest.java b/src/test/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzerTest.java new file mode 100644 index 000000000..264a71bb1 --- /dev/null +++ b/src/test/java/org/codesecure/dependencycheck/scanner/AbstractAnalyzerTest.java @@ -0,0 +1,51 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.codesecure.dependencycheck.scanner; + +import java.util.Set; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Jeremy Long (jeremy.long@gmail.com) + */ +public class AbstractAnalyzerTest { + + public AbstractAnalyzerTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of newHashSet method, of class AbstractAnalyzer. + */ + @Test + public void testNewHashSet() { + System.out.println("newHashSet"); + Set result = AbstractAnalyzer.newHashSet("one","two"); + assertEquals(2, result.size()); + assertTrue(result.contains("one")); + assertTrue(result.contains("two")); + } +} diff --git a/src/test/java/org/codesecure/dependencycheck/scanner/AnalyzerServiceTest.java b/src/test/java/org/codesecure/dependencycheck/scanner/AnalyzerServiceTest.java new file mode 100644 index 000000000..1156d4f57 --- /dev/null +++ b/src/test/java/org/codesecure/dependencycheck/scanner/AnalyzerServiceTest.java @@ -0,0 +1,60 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.codesecure.dependencycheck.scanner; + +import java.util.Set; +import java.util.Iterator; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Jeremy Long (jeremy.long@gmail.com) + */ +public class AnalyzerServiceTest { + + public AnalyzerServiceTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getAnalyzers method, of class AnalyzerService. + */ + @Test + public void testGetAnalyzers() { + System.out.println("getAnalyzers"); + AnalyzerService instance = AnalyzerService.getInstance(); + Iterator result = instance.getAnalyzers(); + + boolean found = false; + while (result.hasNext()) { + Analyzer a = result.next(); + Set e = a.getSupportedExtensions(); + if (e.contains("jar")) { + found = true; + } + } + assertTrue("JarAnalyzer loaded", found); + } +} diff --git a/src/test/java/org/codesecure/dependencycheck/scanner/JarAnalyzerTest.java b/src/test/java/org/codesecure/dependencycheck/scanner/JarAnalyzerTest.java index 126c848f7..0d8b4de47 100644 --- a/src/test/java/org/codesecure/dependencycheck/scanner/JarAnalyzerTest.java +++ b/src/test/java/org/codesecure/dependencycheck/scanner/JarAnalyzerTest.java @@ -4,7 +4,9 @@ */ package org.codesecure.dependencycheck.scanner; +import java.util.HashSet; import java.io.File; +import java.util.Set; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -52,4 +54,42 @@ public class JarAnalyzerTest { assertTrue(result.getVendorEvidence().toString().toLowerCase().contains("apache")); assertTrue(result.getVendorEvidence().getWeighting().contains("apache")); } + + /** + * Test of getSupportedExtensions method, of class JarAnalyzer. + */ + @Test + public void testGetSupportedExtensions() { + System.out.println("getSupportedExtensions"); + JarAnalyzer instance = new JarAnalyzer(); + Set expResult = new HashSet(); + expResult.add("jar"); + Set result = instance.getSupportedExtensions(); + assertEquals(expResult, result); + } + + /** + * Test of getName method, of class JarAnalyzer. + */ + @Test + public void testGetName() { + System.out.println("getName"); + JarAnalyzer instance = new JarAnalyzer(); + String expResult = "Jar Analyzer"; + String result = instance.getName(); + assertEquals(expResult, result); + } + + /** + * Test of supportsExtension method, of class JarAnalyzer. + */ + @Test + public void testSupportsExtension() { + System.out.println("supportsExtension"); + String extension = "jar"; + JarAnalyzer instance = new JarAnalyzer(); + boolean expResult = true; + boolean result = instance.supportsExtension(extension); + assertEquals(expResult, result); + } }