diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java index dca3b7b6e..4374aa80d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java @@ -43,6 +43,7 @@ import java.util.regex.Pattern; import java.util.zip.ZipEntry; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; @@ -1150,7 +1151,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { ClassNameInformation(String className) { name = className; if (name.contains("/")) { - final String[] tmp = className.toLowerCase().split("/"); + final String[] tmp = StringUtils.split(className.toLowerCase(), '/'); int start = 0; int end = 3; if ("com".equals(tmp[0]) || "org".equals(tmp[0])) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/IndexEntry.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/IndexEntry.java index 960729e85..54efbafd4 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/IndexEntry.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/IndexEntry.java @@ -20,6 +20,7 @@ package org.owasp.dependencycheck.data.cpe; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import org.apache.commons.lang3.StringUtils; /** * A CPE entry containing the name, vendor, product, and version. @@ -143,7 +144,8 @@ public class IndexEntry implements Serializable { */ public void parseName(String cpeName) throws UnsupportedEncodingException { if (cpeName != null && cpeName.length() > 7) { - final String[] data = cpeName.substring(7).split(":"); + final String cpeNameWithoutPrefix = cpeName.substring(7); + final String[] data = StringUtils.split(cpeNameWithoutPrefix, ':'); if (data.length >= 1) { vendor = URLDecoder.decode(data[0].replace("+", "%2B"), "UTF-8"); if (data.length >= 2) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java index ea63d3935..2617c959d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java @@ -23,8 +23,8 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,6 +35,7 @@ import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; +import javax.annotation.concurrent.ThreadSafe; import org.owasp.dependencycheck.data.cwe.CweDB; import org.owasp.dependencycheck.dependency.Reference; import org.owasp.dependencycheck.dependency.Vulnerability; @@ -47,12 +48,17 @@ import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*; + /** * The database holding information about the NVD CVE data. + * This class is safe to be accessed from multiple threads in parallel, however + * internally only one connection will be used. * * @author Jeremy Long */ -public class CveDB { +@ThreadSafe +public final class CveDB { /** * The logger. @@ -61,18 +67,54 @@ public class CveDB { /** * Database connection */ - private Connection conn; + private Connection connection; /** * The bundle of statements used when accessing the database. */ - private ResourceBundle statementBundle = null; + private final ResourceBundle statementBundle; + /** + * Database properties object containing the 'properties' from the database + * table. + */ + private final DatabaseProperties databaseProperties; + /** + * Does the underlying connection support batch operations? + * Currently we do not support batch execution. + */ + private final boolean batchSupported = false; + /** + * The prepared statements. + */ + private final EnumMap preparedStatements; /** - * Creates a new CveDB object and opens the database connection. Note, the - * connection must be closed by the caller by calling the close method. - * ======= Does the underlying connection support batch operations? + * The enum value names must match the keys of the statements in the + * statement bundles "dbStatements*.properties". */ - private boolean batchSupported; + enum PreparedStatementCveDb { + CLEANUP_ORPHANS, + COUNT_CPE, + DELETE_REFERENCE, + DELETE_SOFTWARE, + DELETE_VULNERABILITY, + INSERT_CPE, + INSERT_PROPERTY, + INSERT_REFERENCE, + INSERT_SOFTWARE, + INSERT_VULNERABILITY, + MERGE_PROPERTY, + SELECT_CPE_ENTRIES, + SELECT_CPE_ID, + SELECT_CVE_FROM_SOFTWARE, + SELECT_PROPERTIES, + SELECT_REFERENCES, + SELECT_SOFTWARE, + SELECT_VENDOR_PRODUCT_LIST, + SELECT_VULNERABILITY, + SELECT_VULNERABILITY_ID, + UPDATE_PROPERTY, + UPDATE_VULNERABILITY + } /** * Creates a new CveDB object and opens the database connection. Note, the @@ -82,24 +124,28 @@ public class CveDB { * database. */ public CveDB() throws DatabaseException { - super(); + open(); + final String databaseProductName = determineDatabaseProductName(); + statementBundle = databaseProductName != null ? + ResourceBundle.getBundle("data/dbStatements", new Locale(databaseProductName)) : + ResourceBundle.getBundle("data/dbStatements"); + preparedStatements = prepareStatements(); + databaseProperties = new DatabaseProperties(this); + } + + /** + * Tries to determine the product name of the database. + * + * @return the product name of the database if successful, {@code null} else + */ + private String determineDatabaseProductName() { try { - open(); - try { - final String databaseProductName = conn.getMetaData().getDatabaseProductName(); - LOGGER.debug("Database dialect: {}", databaseProductName); - final Locale dbDialect = new Locale(databaseProductName); - statementBundle = ResourceBundle.getBundle("data/dbStatements", dbDialect); - if ("mysql".equalsIgnoreCase(databaseProductName)) { - batchSupported = false; - } - } catch (SQLException se) { - LOGGER.warn("Problem loading database specific dialect!", se); - statementBundle = ResourceBundle.getBundle("data/dbStatements"); - } - databaseProperties = new DatabaseProperties(this); - } catch (DatabaseException ex) { - throw ex; + final String databaseProductName = getConnection().getMetaData().getDatabaseProductName(); + LOGGER.debug("Database product: {}", databaseProductName); + return databaseProductName; + } catch (SQLException se) { + LOGGER.warn("Problem determining database product!", se); + return null; } } @@ -108,8 +154,8 @@ public class CveDB { * * @return the database connection */ - protected Connection getConnection() { - return conn; + private Connection getConnection() { + return connection; } /** @@ -119,9 +165,9 @@ public class CveDB { * @throws DatabaseException thrown if there is an error opening the * database connection */ - public final synchronized void open() throws DatabaseException { + public synchronized void open() throws DatabaseException { if (!isOpen()) { - conn = ConnectionFactory.getConnection(); + connection = ConnectionFactory.getConnection(); } } @@ -130,9 +176,10 @@ public class CveDB { * is done being used. */ public synchronized void close() { - if (conn != null) { + if (isOpen()) { + closeStatements(); try { - conn.close(); + getConnection().close(); } catch (SQLException ex) { LOGGER.error("There was an error attempting to close the CveDB, see the log for more details."); LOGGER.debug("", ex); @@ -140,7 +187,7 @@ public class CveDB { LOGGER.error("There was an exception attempting to close the CveDB, see the log for more details."); LOGGER.debug("", ex); } - conn = null; + connection = null; } } @@ -149,8 +196,57 @@ public class CveDB { * * @return whether the database connection is open or closed */ - public synchronized boolean isOpen() { - return conn != null; + private boolean isOpen() { + return getConnection() != null; + } + + /** + * Prepares all statements to be used and returns them. + * + * @return the prepared statements + * @throws DatabaseException thrown if there is an error preparing the statements + */ + private EnumMap prepareStatements() + throws DatabaseException { + + final EnumMap result = new EnumMap(PreparedStatementCveDb.class); + for (PreparedStatementCveDb key : values()) { + final String statementString = statementBundle.getString(key.name()); + final PreparedStatement preparedStatement; + try { + if (key == INSERT_VULNERABILITY || key == INSERT_CPE) { + preparedStatement = getConnection().prepareStatement(statementString, new String[]{"id"}); + } else { + preparedStatement = getConnection().prepareStatement(statementString); + } + } catch (SQLException exception) { + throw new DatabaseException(exception); + } + result.put(key, preparedStatement); + } + return result; + } + + /** + * Closes all prepared statements. + */ + private void closeStatements() { + for (PreparedStatement preparedStatement : preparedStatements.values()) { + DBUtils.closeStatement(preparedStatement); + } + } + + /** + * Returns the specified prepared statement. + * + * @param key the prepared statement from {@link PreparedStatementCveDb} to return + * @return the prepared statement + * @throws SQLException thrown if a SQL Exception occurs + */ + private PreparedStatement getPreparedStatement(PreparedStatementCveDb key) throws SQLException { + final PreparedStatement preparedStatement = preparedStatements.get(key); + preparedStatement.clearParameters(); + return preparedStatement; } /** @@ -160,8 +256,8 @@ public class CveDB { */ public synchronized void commit() throws SQLException { //temporary remove this as autocommit is on. - //if (conn != null) { - // conn.commit(); + //if (isOpen()) { + // getConnection().commit(); //} } @@ -177,11 +273,6 @@ public class CveDB { close(); super.finalize(); } - /** - * Database properties object containing the 'properties' from the database - * table. - */ - private DatabaseProperties databaseProperties; /** * Get the value of databaseProperties. @@ -205,9 +296,8 @@ public class CveDB { public synchronized Set getCPEs(String vendor, String product) { final Set cpe = new HashSet(); ResultSet rs = null; - PreparedStatement ps = null; try { - ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ENTRIES")); + final PreparedStatement ps = getPreparedStatement(SELECT_CPE_ENTRIES); ps.setString(1, vendor); ps.setString(2, product); rs = ps.executeQuery(); @@ -222,7 +312,6 @@ public class CveDB { LOGGER.debug("", ex); } finally { DBUtils.closeResultSet(rs); - DBUtils.closeStatement(ps); } return cpe; } @@ -237,9 +326,8 @@ public class CveDB { public synchronized Set> getVendorProductList() throws DatabaseException { final Set> data = new HashSet>(); ResultSet rs = null; - PreparedStatement ps = null; try { - ps = getConnection().prepareStatement(statementBundle.getString("SELECT_VENDOR_PRODUCT_LIST")); + final PreparedStatement ps = getPreparedStatement(SELECT_VENDOR_PRODUCT_LIST); rs = ps.executeQuery(); while (rs.next()) { data.add(new Pair(rs.getString(1), rs.getString(2))); @@ -249,7 +337,6 @@ public class CveDB { throw new DatabaseException(msg, ex); } finally { DBUtils.closeResultSet(rs); - DBUtils.closeStatement(ps); } return data; } @@ -261,10 +348,9 @@ public class CveDB { */ public synchronized Properties getProperties() { final Properties prop = new Properties(); - PreparedStatement ps = null; ResultSet rs = null; try { - ps = getConnection().prepareStatement(statementBundle.getString("SELECT_PROPERTIES")); + final PreparedStatement ps = getPreparedStatement(SELECT_PROPERTIES); rs = ps.executeQuery(); while (rs.next()) { prop.setProperty(rs.getString(1), rs.getString(2)); @@ -273,7 +359,6 @@ public class CveDB { LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details."); LOGGER.debug("", ex); } finally { - DBUtils.closeStatement(ps); DBUtils.closeResultSet(rs); } return prop; @@ -288,31 +373,20 @@ public class CveDB { public synchronized void saveProperty(String key, String value) { try { try { - final PreparedStatement mergeProperty = getConnection().prepareStatement(statementBundle.getString("MERGE_PROPERTY")); - try { - mergeProperty.setString(1, key); - mergeProperty.setString(2, value); - mergeProperty.executeUpdate(); - } finally { - DBUtils.closeStatement(mergeProperty); - } + final PreparedStatement mergeProperty = getPreparedStatement(MERGE_PROPERTY); + mergeProperty.setString(1, key); + mergeProperty.setString(2, value); + mergeProperty.executeUpdate(); } catch (MissingResourceException mre) { // No Merge statement, so doing an Update/Insert... - PreparedStatement updateProperty = null; - PreparedStatement insertProperty = null; - try { - updateProperty = getConnection().prepareStatement(statementBundle.getString("UPDATE_PROPERTY")); - updateProperty.setString(1, value); - updateProperty.setString(2, key); - if (updateProperty.executeUpdate() == 0) { - insertProperty = getConnection().prepareStatement(statementBundle.getString("INSERT_PROPERTY")); - insertProperty.setString(1, key); - insertProperty.setString(2, value); - insertProperty.executeUpdate(); - } - } finally { - DBUtils.closeStatement(updateProperty); - DBUtils.closeStatement(insertProperty); + final PreparedStatement updateProperty = getPreparedStatement(UPDATE_PROPERTY); + updateProperty.setString(1, value); + updateProperty.setString(2, key); + if (updateProperty.executeUpdate() == 0) { + final PreparedStatement insertProperty = getPreparedStatement(INSERT_PROPERTY); + insertProperty.setString(1, key); + insertProperty.setString(2, value); + insertProperty.executeUpdate(); } } } catch (SQLException ex) { @@ -338,10 +412,9 @@ public class CveDB { final DependencyVersion detectedVersion = parseDependencyVersion(cpe); final List vulnerabilities = new ArrayList(); - PreparedStatement ps = null; ResultSet rs = null; try { - ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CVE_FROM_SOFTWARE")); + final PreparedStatement ps = getPreparedStatement(SELECT_CVE_FROM_SOFTWARE); ps.setString(1, cpe.getVendor()); ps.setString(2, cpe.getProduct()); rs = ps.executeQuery(); @@ -377,7 +450,6 @@ public class CveDB { throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex); } finally { DBUtils.closeResultSet(rs); - DBUtils.closeStatement(ps); } return vulnerabilities; } @@ -390,16 +462,13 @@ public class CveDB { * @throws DatabaseException if an exception occurs */ public synchronized Vulnerability getVulnerability(String cve) throws DatabaseException { - PreparedStatement psV = null; - PreparedStatement psR = null; - PreparedStatement psS = null; ResultSet rsV = null; ResultSet rsR = null; ResultSet rsS = null; Vulnerability vuln = null; try { - psV = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY")); + final PreparedStatement psV = getPreparedStatement(SELECT_VULNERABILITY); psV.setString(1, cve); rsV = psV.executeQuery(); if (rsV.next()) { @@ -423,13 +492,14 @@ public class CveDB { vuln.setCvssIntegrityImpact(rsV.getString(9)); vuln.setCvssAvailabilityImpact(rsV.getString(10)); - psR = getConnection().prepareStatement(statementBundle.getString("SELECT_REFERENCES")); + final PreparedStatement psR = getPreparedStatement(SELECT_REFERENCES); psR.setInt(1, cveId); rsR = psR.executeQuery(); while (rsR.next()) { vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3)); } - psS = getConnection().prepareStatement(statementBundle.getString("SELECT_SOFTWARE")); + + final PreparedStatement psS = getPreparedStatement(SELECT_SOFTWARE); psS.setInt(1, cveId); rsS = psS.executeQuery(); while (rsS.next()) { @@ -448,9 +518,6 @@ public class CveDB { DBUtils.closeResultSet(rsV); DBUtils.closeResultSet(rsR); DBUtils.closeResultSet(rsS); - DBUtils.closeStatement(psV); - DBUtils.closeStatement(psR); - DBUtils.closeStatement(psS); } return vuln; } @@ -463,52 +530,31 @@ public class CveDB { * @throws DatabaseException is thrown if the database */ public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException { - PreparedStatement selectVulnerabilityId = null; - PreparedStatement deleteVulnerability = null; - PreparedStatement deleteReferences = null; - PreparedStatement deleteSoftware = null; - PreparedStatement updateVulnerability = null; - PreparedStatement insertVulnerability = null; - PreparedStatement insertReference = null; - PreparedStatement selectCpeId = null; - PreparedStatement insertCpe = null; - PreparedStatement insertSoftware = null; - try { - selectVulnerabilityId = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY_ID")); - deleteVulnerability = getConnection().prepareStatement(statementBundle.getString("DELETE_VULNERABILITY")); - deleteReferences = getConnection().prepareStatement(statementBundle.getString("DELETE_REFERENCE")); - deleteSoftware = getConnection().prepareStatement(statementBundle.getString("DELETE_SOFTWARE")); - updateVulnerability = getConnection().prepareStatement(statementBundle.getString("UPDATE_VULNERABILITY")); - final String[] ids = {"id"}; - insertVulnerability = getConnection().prepareStatement(statementBundle.getString("INSERT_VULNERABILITY"), - //Statement.RETURN_GENERATED_KEYS); - ids); - insertReference = getConnection().prepareStatement(statementBundle.getString("INSERT_REFERENCE")); - selectCpeId = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ID")); - insertCpe = getConnection().prepareStatement(statementBundle.getString("INSERT_CPE"), - //Statement.RETURN_GENERATED_KEYS); - ids); - insertSoftware = getConnection().prepareStatement(statementBundle.getString("INSERT_SOFTWARE")); int vulnerabilityId = 0; + final PreparedStatement selectVulnerabilityId = getPreparedStatement(SELECT_VULNERABILITY_ID); selectVulnerabilityId.setString(1, vuln.getName()); ResultSet rs = selectVulnerabilityId.executeQuery(); if (rs.next()) { vulnerabilityId = rs.getInt(1); // first delete any existing vulnerability info. We don't know what was updated. yes, slower but atm easier. - deleteReferences.setInt(1, vulnerabilityId); - deleteReferences.execute(); + final PreparedStatement deleteReference = getPreparedStatement(DELETE_REFERENCE); + deleteReference.setInt(1, vulnerabilityId); + deleteReference.execute(); + + final PreparedStatement deleteSoftware = getPreparedStatement(DELETE_SOFTWARE); deleteSoftware.setInt(1, vulnerabilityId); deleteSoftware.execute(); } DBUtils.closeResultSet(rs); - rs = null; if (vulnerabilityId != 0) { if (vuln.getDescription().contains("** REJECT **")) { + final PreparedStatement deleteVulnerability = getPreparedStatement(DELETE_VULNERABILITY); deleteVulnerability.setInt(1, vulnerabilityId); deleteVulnerability.executeUpdate(); } else { + final PreparedStatement updateVulnerability = getPreparedStatement(UPDATE_VULNERABILITY); updateVulnerability.setString(1, vuln.getDescription()); updateVulnerability.setString(2, vuln.getCwe()); updateVulnerability.setFloat(3, vuln.getCvssScore()); @@ -522,6 +568,7 @@ public class CveDB { updateVulnerability.executeUpdate(); } } else { + final PreparedStatement insertVulnerability = getPreparedStatement(INSERT_VULNERABILITY); insertVulnerability.setString(1, vuln.getName()); insertVulnerability.setString(2, vuln.getDescription()); insertVulnerability.setString(3, vuln.getCwe()); @@ -542,10 +589,13 @@ public class CveDB { throw new DatabaseException(msg, ex); } finally { DBUtils.closeResultSet(rs); - rs = null; } } + final PreparedStatement insertReference = getPreparedStatement(INSERT_REFERENCE); + if (batchSupported) { + insertReference.clearBatch(); + } for (Reference r : vuln.getReferences()) { insertReference.setInt(1, vulnerabilityId); insertReference.setString(2, r.getName()); @@ -563,8 +613,13 @@ public class CveDB { insertReference.executeBatch(); } + final PreparedStatement insertSoftware = getPreparedStatement(INSERT_SOFTWARE); + if (batchSupported) { + insertSoftware.clearBatch(); + } for (VulnerableSoftware s : vuln.getVulnerableSoftware()) { int cpeProductId = 0; + final PreparedStatement selectCpeId = getPreparedStatement(SELECT_CPE_ID); selectCpeId.setString(1, s.getName()); try { rs = selectCpeId.executeQuery(); @@ -575,10 +630,10 @@ public class CveDB { throw new DatabaseException("Unable to get primary key for new cpe: " + s.getName(), ex); } finally { DBUtils.closeResultSet(rs); - rs = null; } if (cpeProductId == 0) { + final PreparedStatement insertCpe = getPreparedStatement(INSERT_CPE); insertCpe.setString(1, s.getName()); insertCpe.setString(2, s.getVendor()); insertCpe.setString(3, s.getProduct()); @@ -605,7 +660,7 @@ public class CveDB { } catch (SQLException ex) { if (ex.getMessage().contains("Duplicate entry")) { final String msg = String.format("Duplicate software key identified in '%s:%s'", vuln.getName(), s.getName()); - LOGGER.debug(msg, ex); + LOGGER.info(msg, ex); } else { throw ex; } @@ -619,17 +674,6 @@ public class CveDB { final String msg = String.format("Error updating '%s'", vuln.getName()); LOGGER.debug(msg, ex); throw new DatabaseException(msg, ex); - } finally { - DBUtils.closeStatement(selectVulnerabilityId); - DBUtils.closeStatement(deleteReferences); - DBUtils.closeStatement(deleteSoftware); - DBUtils.closeStatement(updateVulnerability); - DBUtils.closeStatement(deleteVulnerability); - DBUtils.closeStatement(insertVulnerability); - DBUtils.closeStatement(insertReference); - DBUtils.closeStatement(selectCpeId); - DBUtils.closeStatement(insertCpe); - DBUtils.closeStatement(insertSoftware); } } @@ -639,17 +683,16 @@ public class CveDB { * @return true if data exists; otherwise false */ public synchronized boolean dataExists() { - Statement cs = null; ResultSet rs = null; try { - cs = conn.createStatement(); - rs = cs.executeQuery("SELECT COUNT(*) records FROM cpeEntry"); + final PreparedStatement cs = getPreparedStatement(COUNT_CPE); + rs = cs.executeQuery(); if (rs.next()) { if (rs.getInt(1) > 0) { return true; } } - } catch (SQLException ex) { + } catch (Exception ex) { String dd; try { dd = Settings.getDataDirectory().getAbsolutePath(); @@ -664,7 +707,6 @@ public class CveDB { LOGGER.debug("", ex); } finally { DBUtils.closeResultSet(rs); - DBUtils.closeStatement(cs); } return false; } @@ -675,17 +717,14 @@ public class CveDB { * ensure orphan entries are removed. */ public synchronized void cleanupDatabase() { - PreparedStatement ps = null; try { - ps = getConnection().prepareStatement(statementBundle.getString("CLEANUP_ORPHANS")); + final PreparedStatement ps = getPreparedStatement(CLEANUP_ORPHANS); if (ps != null) { ps.executeUpdate(); } } catch (SQLException ex) { LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details."); LOGGER.debug("", ex); - } finally { - DBUtils.closeStatement(ps); } } @@ -819,7 +858,6 @@ public class CveDB { ps.executeUpdate(); } catch (SQLException ex) { LOGGER.error("Unable to delete CPE dictionary entries", ex); - } finally { DBUtils.closeStatement(ps); } } @@ -844,7 +882,6 @@ public class CveDB { ps.executeUpdate(); } catch (SQLException ex) { LOGGER.error("Unable to add CPE dictionary entry", ex); - } finally { DBUtils.closeStatement(ps); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java index 8283fc5f2..a3b2aa86c 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/DatabaseProperties.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeMap; +import javax.annotation.concurrent.ThreadSafe; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -31,9 +32,11 @@ import org.slf4j.LoggerFactory; /** * This is a wrapper around a set of properties that are stored in the database. + * This class is safe to be accessed from multiple threads in parallel. * * @author Jeremy Long */ +@ThreadSafe public class DatabaseProperties { /** diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/Cpe.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/Cpe.java index 1d4c3cb33..980552df1 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/Cpe.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/Cpe.java @@ -17,6 +17,7 @@ */ package org.owasp.dependencycheck.data.update.cpe; +import org.apache.commons.lang3.StringUtils; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import org.owasp.dependencycheck.data.update.exception.InvalidDataException; @@ -36,7 +37,8 @@ public class Cpe { */ public Cpe(String value) throws UnsupportedEncodingException, InvalidDataException { this.value = value; - final String[] data = value.substring(7).split(":"); + final String valueWithoutPrefix = value.substring(7); + final String[] data = StringUtils.split(valueWithoutPrefix, ':'); if (data.length >= 2) { vendor = URLDecoder.decode(data[0].replace("+", "%2B"), "UTF-8"); product = URLDecoder.decode(data[1].replace("+", "%2B"), "UTF-8"); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/NvdCve20Handler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/NvdCve20Handler.java index e5d5ce0b8..96b64de9d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/NvdCve20Handler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/NvdCve20Handler.java @@ -33,6 +33,8 @@ import org.xml.sax.SAXException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.helpers.DefaultHandler; +import static org.owasp.dependencycheck.data.update.nvd.NvdCve20Handler.AttributeValues.*; + /** * A SAX Handler that will parse the NVD CVE XML (schema version 2.0). * @@ -48,6 +50,18 @@ public class NvdCve20Handler extends DefaultHandler { * the current supported schema version. */ private static final String CURRENT_SCHEMA_VERSION = "2.0"; + /** + * a possible attribute value of the {@link AttributeValues#XML_LANG} attribute + */ + private static final String EN = "en"; + /** + * the prefix of the node text of a CPE + */ + private static final String CPE_NODE_TEXT_PREFIX = "cpe:/a:"; + /** + * the node text of an entry marked for deletion + */ + private static final String REJECT_NODE_TEXT = "** REJECT **"; /** * the current element. */ @@ -111,30 +125,30 @@ public class NvdCve20Handler extends DefaultHandler { if (current.isEntryNode()) { hasApplicationCpe = false; vulnerability = new Vulnerability(); - vulnerability.setName(attributes.getValue("id")); + vulnerability.setName(attributes.getValue(ID)); } else if (current.isVulnProductNode()) { nodeText = new StringBuilder(100); } else if (current.isVulnReferencesNode()) { - final String lang = attributes.getValue("xml:lang"); - if ("en".equals(lang)) { + final String lang = attributes.getValue(XML_LANG); + if (EN.equals(lang)) { reference = new Reference(); } else { reference = null; } } else if (reference != null && current.isVulnReferenceNode()) { - reference.setUrl(attributes.getValue("href")); + reference.setUrl(attributes.getValue(HREF)); nodeText = new StringBuilder(130); } else if (reference != null && current.isVulnSourceNode()) { nodeText = new StringBuilder(30); } else if (current.isVulnSummaryNode()) { nodeText = new StringBuilder(500); } else if (current.isNVDNode()) { - final String nvdVer = attributes.getValue("nvd_xml_version"); + final String nvdVer = attributes.getValue(NVD_XML_VERSION); if (!CURRENT_SCHEMA_VERSION.equals(nvdVer)) { throw new SAXNotSupportedException("Schema version " + nvdVer + " is not supported"); } } else if (current.isVulnCWENode()) { - vulnerability.setCwe(attributes.getValue("id")); + vulnerability.setCwe(attributes.getValue(ID)); } else if (current.isCVSSScoreNode()) { nodeText = new StringBuilder(5); } else if (current.isCVSSAccessVectorNode()) { @@ -206,7 +220,7 @@ public class NvdCve20Handler extends DefaultHandler { nodeText = null; } else if (current.isVulnProductNode()) { final String cpe = nodeText.toString(); - if (cpe.startsWith("cpe:/a:")) { + if (cpe.startsWith(CPE_NODE_TEXT_PREFIX)) { hasApplicationCpe = true; vulnerability.addVulnerableSoftware(cpe); } @@ -222,7 +236,7 @@ public class NvdCve20Handler extends DefaultHandler { nodeText = null; } else if (current.isVulnSummaryNode()) { vulnerability.setDescription(nodeText.toString()); - if (nodeText.indexOf("** REJECT **") >= 0) { + if (nodeText.indexOf(REJECT_NODE_TEXT) >= 0) { hasApplicationCpe = true; //ensure we process this to delete the vuln } nodeText = null; @@ -492,4 +506,27 @@ public class NvdCve20Handler extends DefaultHandler { } } // + + /** + * A simple class to maintain information about the attribute values encountered while parsing the NVD CVE XML. + */ + protected static class AttributeValues { + + /** + * An attribute in the NVD CVE Schema 2.0 + */ + protected static final String ID = "id"; + /** + * An attribute in the NVD CVE Schema 2.0 + */ + protected static final String XML_LANG = "xml:lang"; + /** + * An attribute in the NVD CVE Schema 2.0 + */ + protected static final String HREF = "href"; + /** + * An attribute in the NVD CVE Schema 2.0 + */ + protected static final String NVD_XML_VERSION = "nvd_xml_version"; + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java index 013c8db79..b03001a53 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java @@ -230,10 +230,9 @@ public class Vulnerability implements Serializable, Comparable { * Adds an entry for vulnerable software. * * @param cpe string representation of a CPE entry - * @return if the add succeeded */ - public boolean addVulnerableSoftware(String cpe) { - return addVulnerableSoftware(cpe, null); + public void addVulnerableSoftware(String cpe) { + addVulnerableSoftware(cpe, null); } /** @@ -242,28 +241,26 @@ public class Vulnerability implements Serializable, Comparable { * @param cpe string representation of a cpe * @param previousVersion the previous version (previousVersion - cpe would * be considered vulnerable) - * @return if the add succeeded */ - public boolean addVulnerableSoftware(String cpe, String previousVersion) { + public void addVulnerableSoftware(String cpe, String previousVersion) { final VulnerableSoftware vs = new VulnerableSoftware(); vs.setCpe(cpe); if (previousVersion != null) { vs.setPreviousVersion(previousVersion); } - return updateVulnerableSoftware(vs); + updateVulnerableSoftware(vs); } /** * Adds or updates a vulnerable software entry. * * @param vulnSoftware the vulnerable software - * @return if the update succeeded */ - public boolean updateVulnerableSoftware(VulnerableSoftware vulnSoftware) { + public void updateVulnerableSoftware(VulnerableSoftware vulnSoftware) { if (vulnerableSoftware.contains(vulnSoftware)) { vulnerableSoftware.remove(vulnSoftware); } - return vulnerableSoftware.add(vulnSoftware); + vulnerableSoftware.add(vulnSoftware); } /** diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/VulnerableSoftware.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/VulnerableSoftware.java index 12a1121d7..9c3570914 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/VulnerableSoftware.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/VulnerableSoftware.java @@ -20,6 +20,7 @@ package org.owasp.dependencycheck.dependency; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import org.apache.commons.lang3.StringUtils; import org.owasp.dependencycheck.data.cpe.IndexEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,7 +74,8 @@ public class VulnerableSoftware extends IndexEntry implements Serializable, Comp public void parseName(String cpeName) throws UnsupportedEncodingException { this.name = cpeName; if (cpeName != null && cpeName.length() > 7) { - final String[] data = cpeName.substring(7).split(":"); + final String cpeNameWithoutPrefix = cpeName.substring(7); + final String[] data = StringUtils.split(cpeNameWithoutPrefix, ':'); if (data.length >= 1) { this.setVendor(urlDecode(data[0])); } @@ -172,8 +174,8 @@ public class VulnerableSoftware extends IndexEntry implements Serializable, Comp @Override public int compareTo(VulnerableSoftware vs) { int result = 0; - final String[] left = this.name.split(":"); - final String[] right = vs.getName().split(":"); + final String[] left = StringUtils.split(this.name, ':'); + final String[] right = StringUtils.split(vs.getName(), ':'); final int max = (left.length <= right.length) ? left.length : right.length; if (max > 0) { for (int i = 0; result == 0 && i < max; i++) { diff --git a/dependency-check-core/src/main/resources/data/dbStatements.properties b/dependency-check-core/src/main/resources/data/dbStatements.properties index 6849503d1..d7c4eb276 100644 --- a/dependency-check-core/src/main/resources/data/dbStatements.properties +++ b/dependency-check-core/src/main/resources/data/dbStatements.properties @@ -19,6 +19,7 @@ DELETE_REFERENCE=DELETE FROM reference WHERE cveid = ? DELETE_SOFTWARE=DELETE FROM software WHERE cveid = ? DELETE_VULNERABILITY=DELETE FROM vulnerability WHERE id = ? CLEANUP_ORPHANS=DELETE FROM cpeEntry WHERE id not in (SELECT CPEEntryId FROM software); +COUNT_CPE=SELECT COUNT(*) records FROM cpeEntry INSERT_REFERENCE=INSERT INTO reference (cveid, name, url, source) VALUES (?, ?, ?, ?) INSERT_SOFTWARE=INSERT INTO software (cveid, cpeEntryId, previousVersion) VALUES (?, ?, ?) INSERT_CPE=INSERT INTO cpeEntry (cpe, vendor, product) VALUES (?, ?, ?) @@ -38,6 +39,6 @@ INSERT_PROPERTY=INSERT INTO properties (id, value) VALUES (?, ?) UPDATE_PROPERTY=UPDATE properties SET value = ? WHERE id = ? DELETE_PROPERTY=DELETE FROM properties WHERE id = ? -#the following two statements are unused and are only referenecd in dead code +#the following two statements are unused and are only referenced in dead code DELETE_UNUSED_DICT_CPE=DELETE FROM cpeEntry WHERE dictionaryEntry=true AND id NOT IN (SELECT cpeEntryId FROM software) ADD_DICT_CPE=MERGE INTO cpeEntry (cpe, vendor, product, dictionaryEntry) KEY(cpe) VALUES(?,?,?,true) diff --git a/dependency-check-core/src/main/resources/data/dbStatements_oracle.properties b/dependency-check-core/src/main/resources/data/dbStatements_oracle.properties index 9c6ce7fff..2fdabe3f7 100644 --- a/dependency-check-core/src/main/resources/data/dbStatements_oracle.properties +++ b/dependency-check-core/src/main/resources/data/dbStatements_oracle.properties @@ -1 +1,15 @@ +# Copyright 2015 OWASP. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + CLEANUP_ORPHANS=DELETE FROM cpeEntry WHERE id not in (SELECT CPEEntryId FROM software) diff --git a/dependency-check-maven/src/it/629-jackson-datafromat/invoker.properties b/dependency-check-maven/src/it/629-jackson-dataformat/invoker.properties similarity index 100% rename from dependency-check-maven/src/it/629-jackson-datafromat/invoker.properties rename to dependency-check-maven/src/it/629-jackson-dataformat/invoker.properties diff --git a/dependency-check-maven/src/it/629-jackson-datafromat/pom.xml b/dependency-check-maven/src/it/629-jackson-dataformat/pom.xml similarity index 100% rename from dependency-check-maven/src/it/629-jackson-datafromat/pom.xml rename to dependency-check-maven/src/it/629-jackson-dataformat/pom.xml diff --git a/dependency-check-maven/src/it/629-jackson-datafromat/postbuild.groovy b/dependency-check-maven/src/it/629-jackson-dataformat/postbuild.groovy similarity index 100% rename from dependency-check-maven/src/it/629-jackson-datafromat/postbuild.groovy rename to dependency-check-maven/src/it/629-jackson-dataformat/postbuild.groovy diff --git a/dependency-check-maven/src/it/629-jackson-datafromat/prebuild.groovy b/dependency-check-maven/src/it/629-jackson-dataformat/prebuild.groovy similarity index 100% rename from dependency-check-maven/src/it/629-jackson-datafromat/prebuild.groovy rename to dependency-check-maven/src/it/629-jackson-dataformat/prebuild.groovy