Coverage Report - org.owasp.dependencycheck.data.nvdcve.CveDB
 
Classes in this File Line Coverage Branch Coverage Complexity
CveDB
47%
206/437
57%
80/140
5.636
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  *
 16  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 17  
  */
 18  
 package org.owasp.dependencycheck.data.nvdcve;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.sql.Connection;
 23  
 import java.sql.PreparedStatement;
 24  
 import java.sql.ResultSet;
 25  
 import java.sql.SQLException;
 26  
 import java.sql.Statement;
 27  
 import java.util.ArrayList;
 28  
 import java.util.HashMap;
 29  
 import java.util.HashSet;
 30  
 import java.util.List;
 31  
 import java.util.Locale;
 32  
 import java.util.Map;
 33  
 import java.util.Map.Entry;
 34  
 import java.util.MissingResourceException;
 35  
 import java.util.Properties;
 36  
 import java.util.ResourceBundle;
 37  
 import java.util.Set;
 38  
 import org.owasp.dependencycheck.data.cwe.CweDB;
 39  
 import org.owasp.dependencycheck.dependency.Reference;
 40  
 import org.owasp.dependencycheck.dependency.Vulnerability;
 41  
 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
 42  
 import org.owasp.dependencycheck.utils.DBUtils;
 43  
 import org.owasp.dependencycheck.utils.DependencyVersion;
 44  
 import org.owasp.dependencycheck.utils.DependencyVersionUtil;
 45  
 import org.owasp.dependencycheck.utils.Pair;
 46  
 import org.owasp.dependencycheck.utils.Settings;
 47  
 import org.slf4j.Logger;
 48  
 import org.slf4j.LoggerFactory;
 49  
 
 50  
 /**
 51  
  * The database holding information about the NVD CVE data.
 52  
  *
 53  
  * @author Jeremy Long
 54  
  */
 55  
 public class CveDB {
 56  
 
 57  
     /**
 58  
      * The logger.
 59  
      */
 60  1
     private static final Logger LOGGER = LoggerFactory.getLogger(CveDB.class);
 61  
     /**
 62  
      * Database connection
 63  
      */
 64  
     private Connection conn;
 65  
     /**
 66  
      * The bundle of statements used when accessing the database.
 67  
      */
 68  13
     private ResourceBundle statementBundle = null;
 69  
 
 70  
     /**
 71  
      * Creates a new CveDB object and opens the database connection. Note, the
 72  
      * connection must be closed by the caller by calling the close method.
 73  
      * ======= Does the underlying connection support batch operations?
 74  
      */
 75  
     private boolean batchSupported;
 76  
 
 77  
     /**
 78  
      * Creates a new CveDB object and opens the database connection. Note, the
 79  
      * connection must be closed by the caller by calling the close method.
 80  
      *
 81  
      * @throws DatabaseException thrown if there is an exception opening the
 82  
      * database.
 83  
      */
 84  
     public CveDB() throws DatabaseException {
 85  13
         super();
 86  
         try {
 87  13
             open();
 88  
             try {
 89  13
                 final String databaseProductName = conn.getMetaData().getDatabaseProductName();
 90  13
                 LOGGER.debug("Database dialect: {}", databaseProductName);
 91  13
                 final Locale dbDialect = new Locale(databaseProductName);
 92  13
                 statementBundle = ResourceBundle.getBundle("data/dbStatements", dbDialect);
 93  13
                 if ("mysql".equalsIgnoreCase(databaseProductName)) {
 94  0
                     batchSupported = false;
 95  
                 }
 96  0
             } catch (SQLException se) {
 97  0
                 LOGGER.warn("Problem loading database specific dialect!", se);
 98  0
                 statementBundle = ResourceBundle.getBundle("data/dbStatements");
 99  13
             }
 100  13
             databaseProperties = new DatabaseProperties(this);
 101  0
         } catch (DatabaseException ex) {
 102  0
             throw ex;
 103  13
         }
 104  13
     }
 105  
 
 106  
     /**
 107  
      * Returns the database connection.
 108  
      *
 109  
      * @return the database connection
 110  
      */
 111  
     protected Connection getConnection() {
 112  50
         return conn;
 113  
     }
 114  
 
 115  
     /**
 116  
      * Opens the database connection. If the database does not exist, it will
 117  
      * create a new one.
 118  
      *
 119  
      * @throws DatabaseException thrown if there is an error opening the
 120  
      * database connection
 121  
      */
 122  
     public final synchronized void open() throws DatabaseException {
 123  26
         if (!isOpen()) {
 124  13
             conn = ConnectionFactory.getConnection();
 125  
         }
 126  26
     }
 127  
 
 128  
     /**
 129  
      * Closes the DB4O database. Close should be called on this object when it
 130  
      * is done being used.
 131  
      */
 132  
     public synchronized void close() {
 133  26
         if (conn != null) {
 134  
             try {
 135  13
                 conn.close();
 136  0
             } catch (SQLException ex) {
 137  0
                 LOGGER.error("There was an error attempting to close the CveDB, see the log for more details.");
 138  0
                 LOGGER.debug("", ex);
 139  0
             } catch (Throwable ex) {
 140  0
                 LOGGER.error("There was an exception attempting to close the CveDB, see the log for more details.");
 141  0
                 LOGGER.debug("", ex);
 142  13
             }
 143  13
             conn = null;
 144  
         }
 145  26
     }
 146  
 
 147  
     /**
 148  
      * Returns whether the database connection is open or closed.
 149  
      *
 150  
      * @return whether the database connection is open or closed
 151  
      */
 152  
     public synchronized boolean isOpen() {
 153  26
         return conn != null;
 154  
     }
 155  
 
 156  
     /**
 157  
      * Commits all completed transactions.
 158  
      *
 159  
      * @throws SQLException thrown if a SQL Exception occurs
 160  
      */
 161  
     public synchronized void commit() throws SQLException {
 162  
         //temporary remove this as autocommit is on.
 163  
         //if (conn != null) {
 164  
         //    conn.commit();
 165  
         //}
 166  0
     }
 167  
 
 168  
     /**
 169  
      * Cleans up the object and ensures that "close" has been called.
 170  
      *
 171  
      * @throws Throwable thrown if there is a problem
 172  
      */
 173  
     @Override
 174  
     @SuppressWarnings("FinalizeDeclaration")
 175  
     protected void finalize() throws Throwable {
 176  13
         LOGGER.debug("Entering finalize");
 177  13
         close();
 178  13
         super.finalize();
 179  13
     }
 180  
     /**
 181  
      * Database properties object containing the 'properties' from the database
 182  
      * table.
 183  
      */
 184  
     private DatabaseProperties databaseProperties;
 185  
 
 186  
     /**
 187  
      * Get the value of databaseProperties.
 188  
      *
 189  
      * @return the value of databaseProperties
 190  
      */
 191  
     public DatabaseProperties getDatabaseProperties() {
 192  3
         return databaseProperties;
 193  
     }
 194  
 
 195  
     /**
 196  
      * Searches the CPE entries in the database and retrieves all entries for a
 197  
      * given vendor and product combination. The returned list will include all
 198  
      * versions of the product that are registered in the NVD CVE data.
 199  
      *
 200  
      * @param vendor the identified vendor name of the dependency being analyzed
 201  
      * @param product the identified name of the product of the dependency being
 202  
      * analyzed
 203  
      * @return a set of vulnerable software
 204  
      */
 205  
     public synchronized Set<VulnerableSoftware> getCPEs(String vendor, String product) {
 206  4
         final Set<VulnerableSoftware> cpe = new HashSet<VulnerableSoftware>();
 207  4
         ResultSet rs = null;
 208  4
         PreparedStatement ps = null;
 209  
         try {
 210  4
             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ENTRIES"));
 211  4
             ps.setString(1, vendor);
 212  4
             ps.setString(2, product);
 213  4
             rs = ps.executeQuery();
 214  
 
 215  152
             while (rs.next()) {
 216  148
                 final VulnerableSoftware vs = new VulnerableSoftware();
 217  148
                 vs.setCpe(rs.getString(1));
 218  148
                 cpe.add(vs);
 219  148
             }
 220  0
         } catch (SQLException ex) {
 221  0
             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
 222  0
             LOGGER.debug("", ex);
 223  
         } finally {
 224  4
             DBUtils.closeResultSet(rs);
 225  4
             DBUtils.closeStatement(ps);
 226  4
         }
 227  4
         return cpe;
 228  
     }
 229  
 
 230  
     /**
 231  
      * Returns the entire list of vendor/product combinations.
 232  
      *
 233  
      * @return the entire list of vendor/product combinations
 234  
      * @throws DatabaseException thrown when there is an error retrieving the
 235  
      * data from the DB
 236  
      */
 237  
     public synchronized Set<Pair<String, String>> getVendorProductList() throws DatabaseException {
 238  2
         final Set<Pair<String, String>> data = new HashSet<Pair<String, String>>();
 239  2
         ResultSet rs = null;
 240  2
         PreparedStatement ps = null;
 241  
         try {
 242  2
             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_VENDOR_PRODUCT_LIST"));
 243  2
             rs = ps.executeQuery();
 244  53116
             while (rs.next()) {
 245  53114
                 data.add(new Pair<String, String>(rs.getString(1), rs.getString(2)));
 246  
             }
 247  0
         } catch (SQLException ex) {
 248  0
             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
 249  0
             throw new DatabaseException(msg, ex);
 250  
         } finally {
 251  2
             DBUtils.closeResultSet(rs);
 252  2
             DBUtils.closeStatement(ps);
 253  2
         }
 254  2
         return data;
 255  
     }
 256  
 
 257  
     /**
 258  
      * Returns a set of properties.
 259  
      *
 260  
      * @return the properties from the database
 261  
      */
 262  
     synchronized Properties getProperties() {
 263  13
         final Properties prop = new Properties();
 264  13
         PreparedStatement ps = null;
 265  13
         ResultSet rs = null;
 266  
         try {
 267  13
             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_PROPERTIES"));
 268  13
             rs = ps.executeQuery();
 269  286
             while (rs.next()) {
 270  273
                 prop.setProperty(rs.getString(1), rs.getString(2));
 271  
             }
 272  0
         } catch (SQLException ex) {
 273  0
             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
 274  0
             LOGGER.debug("", ex);
 275  
         } finally {
 276  13
             DBUtils.closeStatement(ps);
 277  13
             DBUtils.closeResultSet(rs);
 278  13
         }
 279  13
         return prop;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Saves a property to the database.
 284  
      *
 285  
      * @param key the property key
 286  
      * @param value the property value
 287  
      */
 288  
     synchronized void saveProperty(String key, String value) {
 289  
         try {
 290  
             try {
 291  0
                 final PreparedStatement mergeProperty = getConnection().prepareStatement(statementBundle.getString("MERGE_PROPERTY"));
 292  
                 try {
 293  0
                     mergeProperty.setString(1, key);
 294  0
                     mergeProperty.setString(2, value);
 295  0
                     mergeProperty.executeUpdate();
 296  
                 } finally {
 297  0
                     DBUtils.closeStatement(mergeProperty);
 298  0
                 }
 299  0
             } catch (MissingResourceException mre) {
 300  
                 // No Merge statement, so doing an Update/Insert...
 301  0
                 PreparedStatement updateProperty = null;
 302  0
                 PreparedStatement insertProperty = null;
 303  
                 try {
 304  0
                     updateProperty = getConnection().prepareStatement(statementBundle.getString("UPDATE_PROPERTY"));
 305  0
                     updateProperty.setString(1, value);
 306  0
                     updateProperty.setString(2, key);
 307  0
                     if (updateProperty.executeUpdate() == 0) {
 308  0
                         insertProperty = getConnection().prepareStatement(statementBundle.getString("INSERT_PROPERTY"));
 309  0
                         insertProperty.setString(1, key);
 310  0
                         insertProperty.setString(2, value);
 311  0
                         insertProperty.executeUpdate();
 312  
                     }
 313  
                 } finally {
 314  0
                     DBUtils.closeStatement(updateProperty);
 315  0
                     DBUtils.closeStatement(insertProperty);
 316  0
                 }
 317  0
             }
 318  0
         } catch (SQLException ex) {
 319  0
             LOGGER.warn("Unable to save property '{}' with a value of '{}' to the database", key, value);
 320  0
             LOGGER.debug("", ex);
 321  0
         }
 322  0
     }
 323  
 
 324  
     /**
 325  
      * Retrieves the vulnerabilities associated with the specified CPE.
 326  
      *
 327  
      * @param cpeStr the CPE name
 328  
      * @return a list of Vulnerabilities
 329  
      * @throws DatabaseException thrown if there is an exception retrieving data
 330  
      */
 331  
     public synchronized List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
 332  4
         final VulnerableSoftware cpe = new VulnerableSoftware();
 333  
         try {
 334  4
             cpe.parseName(cpeStr);
 335  0
         } catch (UnsupportedEncodingException ex) {
 336  0
             LOGGER.trace("", ex);
 337  4
         }
 338  4
         final DependencyVersion detectedVersion = parseDependencyVersion(cpe);
 339  4
         final List<Vulnerability> vulnerabilities = new ArrayList<Vulnerability>();
 340  
 
 341  4
         PreparedStatement ps = null;
 342  4
         ResultSet rs = null;
 343  
         try {
 344  4
             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CVE_FROM_SOFTWARE"));
 345  4
             ps.setString(1, cpe.getVendor());
 346  4
             ps.setString(2, cpe.getProduct());
 347  4
             rs = ps.executeQuery();
 348  4
             String currentCVE = "";
 349  
 
 350  4
             final Map<String, Boolean> vulnSoftware = new HashMap<String, Boolean>();
 351  341
             while (rs.next()) {
 352  337
                 final String cveId = rs.getString(1);
 353  337
                 if (!currentCVE.equals(cveId)) { //check for match and add
 354  13
                     final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
 355  13
                     if (matchedCPE != null) {
 356  6
                         final Vulnerability v = getVulnerability(currentCVE);
 357  6
                         v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
 358  6
                         vulnerabilities.add(v);
 359  
                     }
 360  13
                     vulnSoftware.clear();
 361  13
                     currentCVE = cveId;
 362  
                 }
 363  
 
 364  337
                 final String cpeId = rs.getString(2);
 365  337
                 final String previous = rs.getString(3);
 366  337
                 final Boolean p = previous != null && !previous.isEmpty();
 367  337
                 vulnSoftware.put(cpeId, p);
 368  337
             }
 369  
             //remember to process the last set of CVE/CPE entries
 370  4
             final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
 371  4
             if (matchedCPE != null) {
 372  3
                 final Vulnerability v = getVulnerability(currentCVE);
 373  3
                 v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
 374  3
                 vulnerabilities.add(v);
 375  
             }
 376  0
         } catch (SQLException ex) {
 377  0
             throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex);
 378  
         } finally {
 379  4
             DBUtils.closeResultSet(rs);
 380  4
             DBUtils.closeStatement(ps);
 381  4
         }
 382  4
         return vulnerabilities;
 383  
     }
 384  
 
 385  
     /**
 386  
      * Gets a vulnerability for the provided CVE.
 387  
      *
 388  
      * @param cve the CVE to lookup
 389  
      * @return a vulnerability object
 390  
      * @throws DatabaseException if an exception occurs
 391  
      */
 392  
     public synchronized Vulnerability getVulnerability(String cve) throws DatabaseException {
 393  9
         PreparedStatement psV = null;
 394  9
         PreparedStatement psR = null;
 395  9
         PreparedStatement psS = null;
 396  9
         ResultSet rsV = null;
 397  9
         ResultSet rsR = null;
 398  9
         ResultSet rsS = null;
 399  9
         Vulnerability vuln = null;
 400  
 
 401  
         try {
 402  9
             psV = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY"));
 403  9
             psV.setString(1, cve);
 404  9
             rsV = psV.executeQuery();
 405  9
             if (rsV.next()) {
 406  9
                 vuln = new Vulnerability();
 407  9
                 vuln.setName(cve);
 408  9
                 vuln.setDescription(rsV.getString(2));
 409  9
                 String cwe = rsV.getString(3);
 410  9
                 if (cwe != null) {
 411  9
                     final String name = CweDB.getCweName(cwe);
 412  9
                     if (name != null) {
 413  8
                         cwe += ' ' + name;
 414  
                     }
 415  
                 }
 416  9
                 final int cveId = rsV.getInt(1);
 417  9
                 vuln.setCwe(cwe);
 418  9
                 vuln.setCvssScore(rsV.getFloat(4));
 419  9
                 vuln.setCvssAccessVector(rsV.getString(5));
 420  9
                 vuln.setCvssAccessComplexity(rsV.getString(6));
 421  9
                 vuln.setCvssAuthentication(rsV.getString(7));
 422  9
                 vuln.setCvssConfidentialityImpact(rsV.getString(8));
 423  9
                 vuln.setCvssIntegrityImpact(rsV.getString(9));
 424  9
                 vuln.setCvssAvailabilityImpact(rsV.getString(10));
 425  
 
 426  9
                 psR = getConnection().prepareStatement(statementBundle.getString("SELECT_REFERENCES"));
 427  9
                 psR.setInt(1, cveId);
 428  9
                 rsR = psR.executeQuery();
 429  80
                 while (rsR.next()) {
 430  71
                     vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3));
 431  
                 }
 432  9
                 psS = getConnection().prepareStatement(statementBundle.getString("SELECT_SOFTWARE"));
 433  9
                 psS.setInt(1, cveId);
 434  9
                 rsS = psS.executeQuery();
 435  277
                 while (rsS.next()) {
 436  268
                     final String cpe = rsS.getString(1);
 437  268
                     final String prevVersion = rsS.getString(2);
 438  268
                     if (prevVersion == null) {
 439  259
                         vuln.addVulnerableSoftware(cpe);
 440  
                     } else {
 441  9
                         vuln.addVulnerableSoftware(cpe, prevVersion);
 442  
                     }
 443  268
                 }
 444  
             }
 445  0
         } catch (SQLException ex) {
 446  0
             throw new DatabaseException("Error retrieving " + cve, ex);
 447  
         } finally {
 448  9
             DBUtils.closeResultSet(rsV);
 449  9
             DBUtils.closeResultSet(rsR);
 450  9
             DBUtils.closeResultSet(rsS);
 451  9
             DBUtils.closeStatement(psV);
 452  9
             DBUtils.closeStatement(psR);
 453  9
             DBUtils.closeStatement(psS);
 454  9
         }
 455  9
         return vuln;
 456  
     }
 457  
 
 458  
     /**
 459  
      * Updates the vulnerability within the database. If the vulnerability does
 460  
      * not exist it will be added.
 461  
      *
 462  
      * @param vuln the vulnerability to add to the database
 463  
      * @throws DatabaseException is thrown if the database
 464  
      */
 465  
     public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException {
 466  0
         PreparedStatement selectVulnerabilityId = null;
 467  0
         PreparedStatement deleteVulnerability = null;
 468  0
         PreparedStatement deleteReferences = null;
 469  0
         PreparedStatement deleteSoftware = null;
 470  0
         PreparedStatement updateVulnerability = null;
 471  0
         PreparedStatement insertVulnerability = null;
 472  0
         PreparedStatement insertReference = null;
 473  0
         PreparedStatement selectCpeId = null;
 474  0
         PreparedStatement insertCpe = null;
 475  0
         PreparedStatement insertSoftware = null;
 476  
 
 477  
         try {
 478  0
             selectVulnerabilityId = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY_ID"));
 479  0
             deleteVulnerability = getConnection().prepareStatement(statementBundle.getString("DELETE_VULNERABILITY"));
 480  0
             deleteReferences = getConnection().prepareStatement(statementBundle.getString("DELETE_REFERENCE"));
 481  0
             deleteSoftware = getConnection().prepareStatement(statementBundle.getString("DELETE_SOFTWARE"));
 482  0
             updateVulnerability = getConnection().prepareStatement(statementBundle.getString("UPDATE_VULNERABILITY"));
 483  0
             final String[] ids = {"id"};
 484  0
             insertVulnerability = getConnection().prepareStatement(statementBundle.getString("INSERT_VULNERABILITY"),
 485  
                     //Statement.RETURN_GENERATED_KEYS);
 486  
                     ids);
 487  0
             insertReference = getConnection().prepareStatement(statementBundle.getString("INSERT_REFERENCE"));
 488  0
             selectCpeId = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ID"));
 489  0
             insertCpe = getConnection().prepareStatement(statementBundle.getString("INSERT_CPE"),
 490  
                     //Statement.RETURN_GENERATED_KEYS);
 491  
                     ids);
 492  0
             insertSoftware = getConnection().prepareStatement(statementBundle.getString("INSERT_SOFTWARE"));
 493  0
             int vulnerabilityId = 0;
 494  0
             selectVulnerabilityId.setString(1, vuln.getName());
 495  0
             ResultSet rs = selectVulnerabilityId.executeQuery();
 496  0
             if (rs.next()) {
 497  0
                 vulnerabilityId = rs.getInt(1);
 498  
                 // first delete any existing vulnerability info. We don't know what was updated. yes, slower but atm easier.
 499  0
                 deleteReferences.setInt(1, vulnerabilityId);
 500  0
                 deleteReferences.execute();
 501  0
                 deleteSoftware.setInt(1, vulnerabilityId);
 502  0
                 deleteSoftware.execute();
 503  
             }
 504  0
             DBUtils.closeResultSet(rs);
 505  0
             rs = null;
 506  
 
 507  0
             if (vulnerabilityId != 0) {
 508  0
                 if (vuln.getDescription().contains("** REJECT **")) {
 509  0
                     deleteVulnerability.setInt(1, vulnerabilityId);
 510  0
                     deleteVulnerability.executeUpdate();
 511  
                 } else {
 512  0
                     updateVulnerability.setString(1, vuln.getDescription());
 513  0
                     updateVulnerability.setString(2, vuln.getCwe());
 514  0
                     updateVulnerability.setFloat(3, vuln.getCvssScore());
 515  0
                     updateVulnerability.setString(4, vuln.getCvssAccessVector());
 516  0
                     updateVulnerability.setString(5, vuln.getCvssAccessComplexity());
 517  0
                     updateVulnerability.setString(6, vuln.getCvssAuthentication());
 518  0
                     updateVulnerability.setString(7, vuln.getCvssConfidentialityImpact());
 519  0
                     updateVulnerability.setString(8, vuln.getCvssIntegrityImpact());
 520  0
                     updateVulnerability.setString(9, vuln.getCvssAvailabilityImpact());
 521  0
                     updateVulnerability.setInt(10, vulnerabilityId);
 522  0
                     updateVulnerability.executeUpdate();
 523  
                 }
 524  
             } else {
 525  0
                 insertVulnerability.setString(1, vuln.getName());
 526  0
                 insertVulnerability.setString(2, vuln.getDescription());
 527  0
                 insertVulnerability.setString(3, vuln.getCwe());
 528  0
                 insertVulnerability.setFloat(4, vuln.getCvssScore());
 529  0
                 insertVulnerability.setString(5, vuln.getCvssAccessVector());
 530  0
                 insertVulnerability.setString(6, vuln.getCvssAccessComplexity());
 531  0
                 insertVulnerability.setString(7, vuln.getCvssAuthentication());
 532  0
                 insertVulnerability.setString(8, vuln.getCvssConfidentialityImpact());
 533  0
                 insertVulnerability.setString(9, vuln.getCvssIntegrityImpact());
 534  0
                 insertVulnerability.setString(10, vuln.getCvssAvailabilityImpact());
 535  0
                 insertVulnerability.execute();
 536  
                 try {
 537  0
                     rs = insertVulnerability.getGeneratedKeys();
 538  0
                     rs.next();
 539  0
                     vulnerabilityId = rs.getInt(1);
 540  0
                 } catch (SQLException ex) {
 541  0
                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", vuln.getName());
 542  0
                     throw new DatabaseException(msg, ex);
 543  
                 } finally {
 544  0
                     DBUtils.closeResultSet(rs);
 545  0
                     rs = null;
 546  0
                 }
 547  
             }
 548  
 
 549  0
             for (Reference r : vuln.getReferences()) {
 550  0
                 insertReference.setInt(1, vulnerabilityId);
 551  0
                 insertReference.setString(2, r.getName());
 552  0
                 insertReference.setString(3, r.getUrl());
 553  0
                 insertReference.setString(4, r.getSource());
 554  
 
 555  0
                 if (batchSupported) {
 556  0
                     insertReference.addBatch();
 557  
                 } else {
 558  0
                     insertReference.execute();
 559  
                 }
 560  0
             }
 561  
 
 562  0
             if (batchSupported) {
 563  0
                 insertReference.executeBatch();
 564  
             }
 565  
 
 566  0
             for (VulnerableSoftware s : vuln.getVulnerableSoftware()) {
 567  0
                 int cpeProductId = 0;
 568  0
                 selectCpeId.setString(1, s.getName());
 569  
                 try {
 570  0
                     rs = selectCpeId.executeQuery();
 571  0
                     if (rs.next()) {
 572  0
                         cpeProductId = rs.getInt(1);
 573  
                     }
 574  0
                 } catch (SQLException ex) {
 575  0
                     throw new DatabaseException("Unable to get primary key for new cpe: " + s.getName(), ex);
 576  
                 } finally {
 577  0
                     DBUtils.closeResultSet(rs);
 578  0
                     rs = null;
 579  0
                 }
 580  
 
 581  0
                 if (cpeProductId == 0) {
 582  0
                     insertCpe.setString(1, s.getName());
 583  0
                     insertCpe.setString(2, s.getVendor());
 584  0
                     insertCpe.setString(3, s.getProduct());
 585  0
                     insertCpe.executeUpdate();
 586  0
                     cpeProductId = DBUtils.getGeneratedKey(insertCpe);
 587  
                 }
 588  0
                 if (cpeProductId == 0) {
 589  0
                     throw new DatabaseException("Unable to retrieve cpeProductId - no data returned");
 590  
                 }
 591  
 
 592  0
                 insertSoftware.setInt(1, vulnerabilityId);
 593  0
                 insertSoftware.setInt(2, cpeProductId);
 594  
 
 595  0
                 if (s.getPreviousVersion() == null) {
 596  0
                     insertSoftware.setNull(3, java.sql.Types.VARCHAR);
 597  
                 } else {
 598  0
                     insertSoftware.setString(3, s.getPreviousVersion());
 599  
                 }
 600  0
                 if (batchSupported) {
 601  0
                     insertSoftware.addBatch();
 602  
                 } else {
 603  
                     try {
 604  0
                         insertSoftware.execute();
 605  0
                     } catch (SQLException ex) {
 606  0
                         if (ex.getMessage().contains("Duplicate entry")) {
 607  0
                             final String msg = String.format("Duplicate software key identified in '%s:%s'", vuln.getName(), s.getName());
 608  0
                             LOGGER.debug(msg, ex);
 609  0
                         } else {
 610  0
                             throw ex;
 611  
                         }
 612  0
                     }
 613  
                 }
 614  0
             }
 615  0
             if (batchSupported) {
 616  0
                 insertSoftware.executeBatch();
 617  
             }
 618  0
         } catch (SQLException ex) {
 619  0
             final String msg = String.format("Error updating '%s'", vuln.getName());
 620  0
             LOGGER.debug(msg, ex);
 621  0
             throw new DatabaseException(msg, ex);
 622  
         } finally {
 623  0
             DBUtils.closeStatement(selectVulnerabilityId);
 624  0
             DBUtils.closeStatement(deleteReferences);
 625  0
             DBUtils.closeStatement(deleteSoftware);
 626  0
             DBUtils.closeStatement(updateVulnerability);
 627  0
             DBUtils.closeStatement(deleteVulnerability);
 628  0
             DBUtils.closeStatement(insertVulnerability);
 629  0
             DBUtils.closeStatement(insertReference);
 630  0
             DBUtils.closeStatement(selectCpeId);
 631  0
             DBUtils.closeStatement(insertCpe);
 632  0
             DBUtils.closeStatement(insertSoftware);
 633  0
         }
 634  0
     }
 635  
 
 636  
     /**
 637  
      * Checks to see if data exists so that analysis can be performed.
 638  
      *
 639  
      * @return <code>true</code> if data exists; otherwise <code>false</code>
 640  
      */
 641  
     public synchronized boolean dataExists() {
 642  2
         Statement cs = null;
 643  2
         ResultSet rs = null;
 644  
         try {
 645  2
             cs = conn.createStatement();
 646  2
             rs = cs.executeQuery("SELECT COUNT(*) records FROM cpeEntry");
 647  2
             if (rs.next()) {
 648  2
                 if (rs.getInt(1) > 0) {
 649  4
                     return true;
 650  
                 }
 651  
             }
 652  0
         } catch (SQLException ex) {
 653  
             String dd;
 654  
             try {
 655  0
                 dd = Settings.getDataDirectory().getAbsolutePath();
 656  0
             } catch (IOException ex1) {
 657  0
                 dd = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
 658  0
             }
 659  0
             LOGGER.error("Unable to access the local database.\n\nEnsure that '{}' is a writable directory. "
 660  
                     + "If the problem persist try deleting the files in '{}' and running {} again. If the problem continues, please "
 661  
                     + "create a log file (see documentation at http://jeremylong.github.io/DependencyCheck/) and open a ticket at "
 662  
                     + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.\n\n",
 663  0
                     dd, dd, Settings.getString(Settings.KEYS.APPLICATION_NAME));
 664  0
             LOGGER.debug("", ex);
 665  
         } finally {
 666  2
             DBUtils.closeResultSet(rs);
 667  2
             DBUtils.closeStatement(cs);
 668  0
         }
 669  0
         return false;
 670  
     }
 671  
 
 672  
     /**
 673  
      * It is possible that orphaned rows may be generated during database
 674  
      * updates. This should be called after all updates have been completed to
 675  
      * ensure orphan entries are removed.
 676  
      */
 677  
     public synchronized void cleanupDatabase() {
 678  0
         PreparedStatement ps = null;
 679  
         try {
 680  0
             ps = getConnection().prepareStatement(statementBundle.getString("CLEANUP_ORPHANS"));
 681  0
             if (ps != null) {
 682  0
                 ps.executeUpdate();
 683  
             }
 684  0
         } catch (SQLException ex) {
 685  0
             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
 686  0
             LOGGER.debug("", ex);
 687  
         } finally {
 688  0
             DBUtils.closeStatement(ps);
 689  0
         }
 690  0
     }
 691  
 
 692  
     /**
 693  
      * Determines if the given identifiedVersion is affected by the given cpeId
 694  
      * and previous version flag. A non-null, non-empty string passed to the
 695  
      * previous version argument indicates that all previous versions are
 696  
      * affected.
 697  
      *
 698  
      * @param vendor the vendor of the dependency being analyzed
 699  
      * @param product the product name of the dependency being analyzed
 700  
      * @param vulnerableSoftware a map of the vulnerable software with a boolean
 701  
      * indicating if all previous versions are affected
 702  
      * @param identifiedVersion the identified version of the dependency being
 703  
      * analyzed
 704  
      * @return true if the identified version is affected, otherwise false
 705  
      */
 706  
     Entry<String, Boolean> getMatchingSoftware(Map<String, Boolean> vulnerableSoftware, String vendor, String product,
 707  
             DependencyVersion identifiedVersion) {
 708  
 
 709  17
         final boolean isVersionTwoADifferentProduct = "apache".equals(vendor) && "struts".equals(product);
 710  
 
 711  17
         final Set<String> majorVersionsAffectingAllPrevious = new HashSet<String>();
 712  17
         final boolean matchesAnyPrevious = identifiedVersion == null || "-".equals(identifiedVersion.toString());
 713  17
         String majorVersionMatch = null;
 714  17
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 715  337
             final DependencyVersion v = parseDependencyVersion(entry.getKey());
 716  337
             if (v == null || "-".equals(v.toString())) { //all versions
 717  0
                 return entry;
 718  
             }
 719  337
             if (entry.getValue()) {
 720  9
                 if (matchesAnyPrevious) {
 721  0
                     return entry;
 722  
                 }
 723  9
                 if (identifiedVersion != null && identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) {
 724  7
                     majorVersionMatch = v.getVersionParts().get(0);
 725  
                 }
 726  9
                 majorVersionsAffectingAllPrevious.add(v.getVersionParts().get(0));
 727  
             }
 728  337
         }
 729  17
         if (matchesAnyPrevious) {
 730  0
             return null;
 731  
         }
 732  
 
 733  17
         final boolean canSkipVersions = majorVersionMatch != null && majorVersionsAffectingAllPrevious.size() > 1;
 734  
         //yes, we are iterating over this twice. The first time we are skipping versions those that affect all versions
 735  
         //then later we process those that affect all versions. This could be done with sorting...
 736  17
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 737  295
             if (!entry.getValue()) {
 738  286
                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
 739  
                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
 740  286
                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
 741  8
                     continue;
 742  
                 }
 743  
                 //this can't dereference a null 'identifiedVersion' because if it was null we would have exited
 744  
                 //in the above loop or just after loop (if matchesAnyPrevious return null).
 745  278
                 if (identifiedVersion.equals(v)) {
 746  8
                     return entry;
 747  
                 }
 748  
             }
 749  279
         }
 750  9
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 751  105
             if (entry.getValue()) {
 752  1
                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
 753  
                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
 754  1
                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
 755  0
                     continue;
 756  
                 }
 757  
                 //this can't dereference a null 'identifiedVersion' because if it was null we would have exited
 758  
                 //in the above loop or just after loop (if matchesAnyPrevious return null).
 759  1
                 if (entry.getValue() && identifiedVersion.compareTo(v) <= 0) {
 760  1
                     if (!(isVersionTwoADifferentProduct && !identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0)))) {
 761  1
                         return entry;
 762  
                     }
 763  
                 }
 764  
             }
 765  104
         }
 766  8
         return null;
 767  
     }
 768  
 
 769  
     /**
 770  
      * Parses the version (including revision) from a CPE identifier. If no
 771  
      * version is identified then a '-' is returned.
 772  
      *
 773  
      * @param cpeStr a cpe identifier
 774  
      * @return a dependency version
 775  
      */
 776  
     private DependencyVersion parseDependencyVersion(String cpeStr) {
 777  624
         final VulnerableSoftware cpe = new VulnerableSoftware();
 778  
         try {
 779  624
             cpe.parseName(cpeStr);
 780  0
         } catch (UnsupportedEncodingException ex) {
 781  
             //never going to happen.
 782  0
             LOGGER.trace("", ex);
 783  624
         }
 784  624
         return parseDependencyVersion(cpe);
 785  
     }
 786  
 
 787  
     /**
 788  
      * Takes a CPE and parses out the version number. If no version is
 789  
      * identified then a '-' is returned.
 790  
      *
 791  
      * @param cpe a cpe object
 792  
      * @return a dependency version
 793  
      */
 794  
     private DependencyVersion parseDependencyVersion(VulnerableSoftware cpe) {
 795  
         final DependencyVersion cpeVersion;
 796  628
         if (cpe.getVersion() != null && !cpe.getVersion().isEmpty()) {
 797  
             final String versionText;
 798  628
             if (cpe.getUpdate() != null && !cpe.getUpdate().isEmpty()) {
 799  113
                 versionText = String.format("%s.%s", cpe.getVersion(), cpe.getUpdate());
 800  
             } else {
 801  515
                 versionText = cpe.getVersion();
 802  
             }
 803  628
             cpeVersion = DependencyVersionUtil.parseVersion(versionText);
 804  628
         } else {
 805  0
             cpeVersion = new DependencyVersion("-");
 806  
         }
 807  628
         return cpeVersion;
 808  
     }
 809  
 
 810  
     /**
 811  
      * This method is only referenced in unused code.
 812  
      *
 813  
      * Deletes unused dictionary entries from the database.
 814  
      */
 815  
     public synchronized void deleteUnusedCpe() {
 816  0
         PreparedStatement ps = null;
 817  
         try {
 818  0
             ps = getConnection().prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE"));
 819  0
             ps.executeUpdate();
 820  0
         } catch (SQLException ex) {
 821  0
             LOGGER.error("Unable to delete CPE dictionary entries", ex);
 822  
         } finally {
 823  0
             DBUtils.closeStatement(ps);
 824  0
         }
 825  0
     }
 826  
 
 827  
     /**
 828  
      * This method is only referenced in unused code and will likely break on
 829  
      * MySQL if ever used due to the MERGE statement.
 830  
      *
 831  
      * Merges CPE entries into the database.
 832  
      *
 833  
      * @param cpe the CPE identifier
 834  
      * @param vendor the CPE vendor
 835  
      * @param product the CPE product
 836  
      */
 837  
     public synchronized void addCpe(String cpe, String vendor, String product) {
 838  0
         PreparedStatement ps = null;
 839  
         try {
 840  0
             ps = getConnection().prepareStatement(statementBundle.getString("ADD_DICT_CPE"));
 841  0
             ps.setString(1, cpe);
 842  0
             ps.setString(2, vendor);
 843  0
             ps.setString(3, product);
 844  0
             ps.executeUpdate();
 845  0
         } catch (SQLException ex) {
 846  0
             LOGGER.error("Unable to add CPE dictionary entry", ex);
 847  
         } finally {
 848  0
             DBUtils.closeStatement(ps);
 849  0
         }
 850  0
     }
 851  
 }