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