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