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) 2018 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.nvdcve;
19  //CSOFF: AvoidStarImport
20  
21  import com.google.common.io.Resources;
22  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
23  import io.github.jeremylong.openvulnerability.client.nvd.Config;
24  import io.github.jeremylong.openvulnerability.client.nvd.CpeMatch;
25  import org.apache.commons.collections.map.ReferenceMap;
26  import org.owasp.dependencycheck.dependency.Vulnerability;
27  import org.owasp.dependencycheck.dependency.VulnerableSoftware;
28  import org.owasp.dependencycheck.utils.*;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import javax.annotation.concurrent.ThreadSafe;
33  import java.io.IOException;
34  import java.net.URL;
35  import java.nio.charset.StandardCharsets;
36  import java.sql.CallableStatement;
37  import java.sql.Connection;
38  import java.sql.JDBCType;
39  import java.sql.PreparedStatement;
40  import java.sql.ResultSet;
41  import java.sql.SQLException;
42  import java.sql.Statement;
43  import java.util.*;
44  import java.util.stream.Collectors;
45  import org.anarres.jdiagnostics.DefaultQuery;
46  
47  import static org.apache.commons.collections.map.AbstractReferenceMap.HARD;
48  import static org.apache.commons.collections.map.AbstractReferenceMap.SOFT;
49  import org.owasp.dependencycheck.analyzer.exception.LambdaExceptionWrapper;
50  import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
51  import io.github.jeremylong.openvulnerability.client.nvd.DefCveItem;
52  import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*;
53  import org.owasp.dependencycheck.data.update.cpe.CpeEcosystemCache;
54  import org.owasp.dependencycheck.data.update.cpe.CpePlus;
55  import io.github.jeremylong.openvulnerability.client.nvd.CvssV2;
56  import io.github.jeremylong.openvulnerability.client.nvd.CvssV2Data;
57  import io.github.jeremylong.openvulnerability.client.nvd.CvssV3;
58  import io.github.jeremylong.openvulnerability.client.nvd.CvssV3Data;
59  import io.github.jeremylong.openvulnerability.client.nvd.LangString;
60  import io.github.jeremylong.openvulnerability.client.nvd.Node;
61  import io.github.jeremylong.openvulnerability.client.nvd.Reference;
62  import io.github.jeremylong.openvulnerability.client.nvd.Weakness;
63  import org.owasp.dependencycheck.dependency.VulnerableSoftwareBuilder;
64  import us.springett.parsers.cpe.Cpe;
65  import us.springett.parsers.cpe.CpeBuilder;
66  import us.springett.parsers.cpe.CpeParser;
67  import us.springett.parsers.cpe.exceptions.CpeParsingException;
68  import us.springett.parsers.cpe.exceptions.CpeValidationException;
69  
70  /**
71   * The database holding information about the NVD CVE data. This class is safe
72   * to be accessed from multiple threads in parallel, however internally only one
73   * connection will be used.
74   *
75   * @author Jeremy Long
76   */
77  @ThreadSafe
78  public final class CveDB implements AutoCloseable {
79  
80      /**
81       * The logger.
82       */
83      private static final Logger LOGGER = LoggerFactory.getLogger(CveDB.class);
84  
85      /**
86       * Resource location for SQL file containing updates to the ecosystem cache.
87       */
88      public static final String DB_ECOSYSTEM_CACHE = "data/dbEcosystemCacheUpdates.sql";
89  
90      /**
91       * The database connection manager.
92       */
93      private final DatabaseManager databaseManager;
94  
95      /**
96       * The bundle of statements used when accessing the database.
97       */
98      private ResourceBundle statementBundle;
99      /**
100      * Database properties object containing the 'properties' from the database
101      * table.
102      */
103     private DatabaseProperties databaseProperties;
104     /**
105      * The filter for 2.3 CPEs in the CVEs - we don't import unless we get a
106      * match.
107      */
108     private final String cpeStartsWithFilter;
109     /**
110      * Cache for CVE lookup; used to speed up the vulnerability search process.
111      */
112     @SuppressWarnings("unchecked")
113     private final Map<String, List<Vulnerability>> vulnerabilitiesForCpeCache = Collections.synchronizedMap(new ReferenceMap(HARD, SOFT));
114     /**
115      * The configured settings
116      */
117     private final Settings settings;
118 
119     /**
120      * Utility to extract information from
121      * {@linkplain org.owasp.dependencycheck.data.nvd.json.DefCveItem}.
122      */
123     private final CveItemOperator cveItemConverter;
124     /**
125      * Flag indicating if the database is Oracle.
126      */
127     private boolean isOracle = false;
128     /**
129      * Flag indicating if the database is H2.
130      */
131     private boolean isH2 = false;
132 
133     /**
134      * Updates the EcoSystem Cache.
135      *
136      * @return The number of records updated by the DB_ECOSYSTEM_CACHE update
137      * script.
138      */
139     public int updateEcosystemCache() {
140         LOGGER.debug("Updating the ecosystem cache");
141         int updateCount = 0;
142         try {
143             final URL url = Resources.getResource(DB_ECOSYSTEM_CACHE);
144             final List<String> sql = Resources.readLines(url, StandardCharsets.UTF_8);
145 
146             try (Connection conn = databaseManager.getConnection(); Statement statement = conn.createStatement()) {
147                 for (String single : sql) {
148                     updateCount += statement.executeUpdate(single);
149                 }
150             } catch (SQLException ex) {
151                 LOGGER.debug("", ex);
152                 throw new DatabaseException("Unable to update the ecosystem cache", ex);
153             }
154         } catch (IOException ex) {
155             throw new DatabaseException("Unable to update the ecosystem cache", ex);
156         } catch (LinkageError ex) {
157             LOGGER.debug(new DefaultQuery(ex).call().toString());
158         }
159         return updateCount;
160     }
161 
162     /**
163      * The enumeration value names must match the keys of the statements in the
164      * statement bundles "dbStatements*.properties".
165      */
166     enum PreparedStatementCveDb {
167         /**
168          * Key for SQL Statement.
169          */
170         CLEANUP_ORPHANS,
171         /**
172          * Key for update ecosystem.
173          */
174         UPDATE_ECOSYSTEM,
175         /**
176          * Key for update ecosystem.
177          */
178         UPDATE_ECOSYSTEM2,
179         /**
180          * Key for SQL Statement.
181          */
182         COUNT_CPE,
183         /**
184          * Key for SQL Statement.
185          */
186         DELETE_VULNERABILITY,
187         /**
188          * Key for SQL Statement.
189          */
190         INSERT_PROPERTY,
191         /**
192          * Key for SQL Statement.
193          */
194         INSERT_CWE,
195         /**
196          * Key for SQL Statement.
197          */
198         INSERT_REFERENCE,
199         /**
200          * Key for SQL Statement.
201          */
202         INSERT_SOFTWARE,
203         /**
204          * Key for SQL Statement.
205          */
206         MERGE_PROPERTY,
207         /**
208          * Key for SQL Statement.
209          */
210         SELECT_CPE_ENTRIES,
211         /**
212          * Key for SQL Statement.
213          */
214         SELECT_CVE_FROM_SOFTWARE,
215         /**
216          * Key for SQL Statement.
217          */
218         SELECT_PROPERTIES,
219         /**
220          * Key for SQL Statement.
221          */
222         SELECT_VULNERABILITY_CWE,
223         /**
224          * Key for SQL Statement.
225          */
226         SELECT_REFERENCES,
227         /**
228          * Key for SQL Statement.
229          */
230         SELECT_SOFTWARE,
231         /**
232          * Key for SQL Statement.
233          */
234         SELECT_VENDOR_PRODUCT_LIST,
235         /**
236          * Key for SQL Statement.
237          */
238         SELECT_VENDOR_PRODUCT_LIST_FOR_NODE,
239         /**
240          * Key for SQL Statement.
241          */
242         SELECT_VULNERABILITY,
243         /**
244          * Key for SQL Statement.
245          */
246         UPDATE_PROPERTY,
247         /**
248          * Key for SQL Statement.
249          */
250         UPDATE_VULNERABILITY,
251         /**
252          * Key for SQL Statement.
253          */
254         SELECT_CPE_ECOSYSTEM,
255         /**
256          * Key for SQL Statement.
257          */
258         MERGE_CPE_ECOSYSTEM,
259         /**
260          * Key for SQL Statement.
261          */
262         DELETE_UNUSED_DICT_CPE,
263         /**
264          * Key for SQL Statement.
265          */
266         ADD_DICT_CPE,
267         /**
268          * Key for SQL Statement.
269          */
270         SELECT_KNOWN_EXPLOITED_VULNERABILITIES,
271         /**
272          * Key for SQL Statement.
273          */
274         MERGE_KNOWN_EXPLOITED
275     }
276 
277     /**
278      * Creates a new CveDB object and opens the database connection. Note, the
279      * connection must be closed by the caller by calling the close method.
280      *
281      * @param settings the configured settings
282      * @throws DatabaseException thrown if there is an exception opening the
283      * database.
284      */
285     public CveDB(Settings settings) throws DatabaseException {
286         this.settings = settings;
287         this.cpeStartsWithFilter = settings.getString(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, "cpe:2.3:a:");
288         this.cveItemConverter = new CveItemOperator(cpeStartsWithFilter);
289         databaseManager = new DatabaseManager(settings);
290         statementBundle = databaseManager.getSqlStatements();
291         isOracle = databaseManager.isOracle();
292         isH2 = databaseManager.isH2Connection();
293     }
294 
295     /**
296      * Opens the database connection pool.
297      */
298     public void open() {
299         databaseManager.open();
300         databaseProperties = new DatabaseProperties(this);
301     }
302 
303     /**
304      * Closes the database connection. Close should be called on this object
305      * when it is done being used.
306      */
307     @Override
308     public void close() {
309         if (isOpen()) {
310             LOGGER.debug("Closing database");
311             clearCache();
312             LOGGER.debug("Cache cleared");
313             try {
314                 databaseManager.close();
315                 LOGGER.debug("Connection closed");
316             } catch (Throwable ex) {
317                 LOGGER.error("There was an exception attempting to close the CveDB, see the log for more details.");
318                 LOGGER.debug("", ex);
319             }
320             releaseResources();
321             LOGGER.debug("Resources released");
322             databaseManager.cleanup();
323         }
324     }
325 
326     /**
327      * Releases the resources used by CveDB.
328      */
329     private void releaseResources() {
330         statementBundle = null;
331         databaseProperties = null;
332     }
333 
334     /**
335      * Returns whether the database connection is open or closed.
336      *
337      * @return whether the database connection is open or closed
338      */
339     public boolean isOpen() {
340         return databaseManager.isOpen();
341     }
342 
343     /**
344      * Creates a prepared statement from the given key. The SQL is stored in a
345      * properties file and the key is used to lookup the specific query.
346      *
347      * @param connection the database connection
348      * @param key the key to select the prepared statement from the properties
349      * file
350      * @param parameter the first parameter to pass into the statement
351      * @return the prepared statement
352      * @throws DatabaseException throw if there is an error generating the
353      * prepared statement
354      */
355     private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key, String parameter)
356             throws DatabaseException, SQLException {
357         final PreparedStatement preparedStatement = getPreparedStatement(connection, key);
358         preparedStatement.setString(1, parameter);
359         return preparedStatement;
360     }
361 
362     /**
363      * Creates a prepared statement from the given key. The SQL is stored in a
364      * properties file and the key is used to lookup the specific query.
365      *
366      * @param connection the database connection
367      * @param key the key to select the prepared statement from the properties
368      * file
369      * @param parameter the first parameter to pass into the statement
370      * @return the prepared statement
371      * @throws DatabaseException throw if there is an error generating the
372      * prepared statement
373      */
374     private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key, int parameter)
375             throws DatabaseException, SQLException {
376         final PreparedStatement preparedStatement = getPreparedStatement(connection, key);
377         preparedStatement.setInt(1, parameter);
378         return preparedStatement;
379     }
380 
381     /**
382      * Creates a prepared statement from the given key. The SQL is stored in a
383      * properties file and the key is used to lookup the specific query.
384      *
385      * @param connection the database connection
386      * @param key the key to select the prepared statement from the properties
387      * file
388      * @return the prepared statement
389      * @throws DatabaseException throw if there is an error generating the
390      * prepared statement
391      */
392     private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key) throws DatabaseException {
393         PreparedStatement preparedStatement = null;
394         try {
395             final String statementString = statementBundle.getString(key.name());
396             if (isOracle && key == UPDATE_VULNERABILITY) {
397                 preparedStatement = connection.prepareCall(statementString);
398 //            } else if (key == INSERT_CPE) {
399 //                final String[] returnedColumns = {"id"};
400 //                preparedStatement = connection.prepareStatement(statementString, returnedColumns);
401             } else {
402                 preparedStatement = connection.prepareStatement(statementString);
403             }
404             if (isOracle) {
405                 // Oracle has a default fetch-size of 10; MariaDB, MySQL, SQLServer and PostgreSQL by default cache the full
406                 // resultset at the client https://venkatsadasivam.com/2009/02/01/jdbc-performance-tuning-with-optimal-fetch-size/
407                 preparedStatement.setFetchSize(10_000);
408             }
409         } catch (SQLException ex) {
410             throw new DatabaseException(ex);
411         } catch (MissingResourceException ex) {
412             if (!ex.getMessage().contains("key MERGE_PROPERTY")) {
413                 throw new DatabaseException(ex);
414             }
415         }
416         return preparedStatement;
417     }
418 
419     /**
420      * Cleans up the object and ensures that "close" has been called.
421      *
422      * @throws Throwable thrown if there is a problem
423      */
424     @Override
425     @SuppressWarnings("FinalizeDeclaration")
426     protected void finalize() throws Throwable {
427         LOGGER.debug("Entering finalize");
428         close();
429         super.finalize();
430     }
431 
432     /**
433      * Get the value of databaseProperties.
434      *
435      * @return the value of databaseProperties
436      */
437     public DatabaseProperties getDatabaseProperties() {
438         return databaseProperties;
439     }
440 
441     /**
442      * Used within the unit tests to reload the database properties.
443      *
444      * @return the database properties
445      */
446     DatabaseProperties reloadProperties() {
447         databaseProperties = new DatabaseProperties(this);
448         return databaseProperties;
449     }
450 
451     /**
452      * Searches the CPE entries in the database and retrieves all entries for a
453      * given vendor and product combination. The returned list will include all
454      * versions of the product that are registered in the NVD CVE data.
455      *
456      * @param vendor the identified vendor name of the dependency being analyzed
457      * @param product the identified name of the product of the dependency being
458      * analyzed
459      * @return a set of vulnerable software
460      */
461     public Set<CpePlus> getCPEs(String vendor, String product) {
462         final Set<CpePlus> cpe = new HashSet<>();
463         try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, SELECT_CPE_ENTRIES)) {
464             //part, vendor, product, version, update_version, edition,
465             //lang, sw_edition, target_sw, target_hw, other, ecosystem
466             ps.setString(1, vendor);
467             ps.setString(2, product);
468             try (ResultSet rs = ps.executeQuery()) {
469                 final CpeBuilder builder = new CpeBuilder();
470                 while (rs.next()) {
471                     final Cpe entry = builder
472                             .part(rs.getString(1))
473                             .vendor(rs.getString(2))
474                             .product(rs.getString(3))
475                             .version(rs.getString(4))
476                             .update(rs.getString(5))
477                             .edition(rs.getString(6))
478                             .language(rs.getString(7))
479                             .swEdition(rs.getString(8))
480                             .targetSw(rs.getString(9))
481                             .targetHw(rs.getString(10))
482                             .other(rs.getString(11)).build();
483                     final CpePlus plus = new CpePlus(entry, rs.getString(12));
484                     cpe.add(plus);
485                 }
486             }
487         } catch (SQLException | CpeParsingException | CpeValidationException ex) {
488             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
489             LOGGER.debug("", ex);
490         }
491         return cpe;
492     }
493 
494     /**
495      * Returns the entire list of vendor/product combinations.
496      *
497      * @return the entire list of vendor/product combinations
498      * @throws DatabaseException thrown when there is an error retrieving the
499      * data from the DB
500      */
501     public Set<Pair<String, String>> getVendorProductList() throws DatabaseException {
502         final Set<Pair<String, String>> data = new HashSet<>();
503         try (Connection conn = databaseManager.getConnection();
504                 PreparedStatement ps = getPreparedStatement(conn, SELECT_VENDOR_PRODUCT_LIST);
505                 ResultSet rs = ps.executeQuery()) {
506             while (rs.next()) {
507                 data.add(new Pair<>(rs.getString(1), rs.getString(2)));
508             }
509         } catch (SQLException ex) {
510             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
511             throw new DatabaseException(msg, ex);
512         }
513         return data;
514     }
515 
516     /**
517      * Returns the entire list of vendor/product combinations filtered for just
518      * Node JS related products.
519      *
520      * @return the list of vendor/product combinations that are known to be
521      * related to Node JS
522      * @throws DatabaseException thrown when there is an error retrieving the
523      * data from the DB
524      */
525     public Set<Pair<String, String>> getVendorProductListForNode() throws DatabaseException {
526         final Set<Pair<String, String>> data = new HashSet<>();
527         try (Connection conn = databaseManager.getConnection();
528                 PreparedStatement ps = getPreparedStatement(conn, SELECT_VENDOR_PRODUCT_LIST_FOR_NODE);
529                 ResultSet rs = ps.executeQuery()) {
530             while (rs.next()) {
531                 data.add(new Pair<>(rs.getString(1), rs.getString(2)));
532             }
533         } catch (SQLException ex) {
534             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
535             throw new DatabaseException(msg, ex);
536         }
537         return data;
538     }
539 
540     /**
541      * Returns a set of properties.
542      *
543      * @return the properties from the database
544      */
545     public Properties getProperties() {
546         final Properties prop = new Properties();
547         try (Connection conn = databaseManager.getConnection();
548                 PreparedStatement ps = getPreparedStatement(conn, SELECT_PROPERTIES);
549                 ResultSet rs = ps.executeQuery()) {
550             while (rs.next()) {
551                 prop.setProperty(rs.getString(1), rs.getString(2));
552             }
553         } catch (SQLException ex) {
554             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
555             LOGGER.debug("", ex);
556         }
557         return prop;
558     }
559 
560     /**
561      * Saves a property to the database.
562      *
563      * @param key the property key
564      * @param value the property value
565      */
566     public void saveProperty(String key, String value) {
567         clearCache();
568         try (Connection conn = databaseManager.getConnection(); PreparedStatement mergeProperty = getPreparedStatement(conn, MERGE_PROPERTY)) {
569             if (mergeProperty != null) {
570                 mergeProperty.setString(1, key);
571                 mergeProperty.setString(2, value);
572                 mergeProperty.execute();
573             } else {
574                 // No Merge statement, so doing an Update/Insert...
575                 try (PreparedStatement updateProperty = getPreparedStatement(conn, UPDATE_PROPERTY)) {
576                     updateProperty.setString(1, value);
577                     updateProperty.setString(2, key);
578                     if (updateProperty.executeUpdate() == 0) {
579                         try (PreparedStatement insertProperty = getPreparedStatement(conn, INSERT_PROPERTY)) {
580                             insertProperty.setString(1, key);
581                             insertProperty.setString(2, value);
582                             insertProperty.executeUpdate();
583                         }
584                     }
585                 }
586             }
587         } catch (SQLException ex) {
588             LOGGER.warn("Unable to save property '{}' with a value of '{}' to the database", key, value);
589             LOGGER.debug("", ex);
590         }
591     }
592 
593     /**
594      * Clears cache. Should be called whenever something is modified. While this
595      * is not the optimal cache eviction strategy, this is good enough for
596      * typical usage (update DB and then only read) and it is easier to maintain
597      * the code.
598      * <p>
599      * It should be also called when DB is closed.
600      * </p>
601      */
602     private void clearCache() {
603         vulnerabilitiesForCpeCache.clear();
604     }
605 
606     /**
607      * Retrieves the vulnerabilities associated with the specified CPE.
608      *
609      * @param cpe the CPE to retrieve vulnerabilities for
610      * @return a list of Vulnerabilities
611      * @throws DatabaseException thrown if there is an exception retrieving data
612      */
613     public List<Vulnerability> getVulnerabilities(Cpe cpe) throws DatabaseException {
614         final List<Vulnerability> cachedVulnerabilities = vulnerabilitiesForCpeCache.get(cpe.toCpe23FS());
615         if (cachedVulnerabilities != null) {
616             LOGGER.debug("Cache hit for {}", cpe.toCpe23FS());
617             return cachedVulnerabilities;
618         } else {
619             LOGGER.debug("Cache miss for {}", cpe.toCpe23FS());
620         }
621 
622         final List<Vulnerability> vulnerabilities = new ArrayList<>();
623         try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, SELECT_CVE_FROM_SOFTWARE)) {
624             ps.setString(1, cpe.getVendor());
625             ps.setString(2, cpe.getProduct());
626             try (ResultSet rs = ps.executeQuery()) {
627                 String currentCVE = "";
628                 final Set<VulnerableSoftware> vulnSoftware = new HashSet<>();
629                 final VulnerableSoftwareBuilder vulnerableSoftwareBuilder = new VulnerableSoftwareBuilder();
630                 while (rs.next()) {
631                     final String cveId = rs.getString(1);
632                     if (currentCVE.isEmpty()) {
633                         //first loop we don't have the cveId
634                         currentCVE = cveId;
635                     }
636                     if (!vulnSoftware.isEmpty() && !currentCVE.equals(cveId)) { //check for match and add
637                         final VulnerableSoftware matchedCPE = getMatchingSoftware(cpe, vulnSoftware);
638                         if (matchedCPE != null) {
639                             final Vulnerability v = getVulnerability(currentCVE, conn);
640                             if (v != null) {
641                                 v.setMatchedVulnerableSoftware(matchedCPE);
642                                 v.setSource(Vulnerability.Source.NVD);
643                                 vulnerabilities.add(v);
644                             }
645                         }
646                         vulnSoftware.clear();
647                         currentCVE = cveId;
648                     }
649                     // 1 cve, 2 part, 3 vendor, 4 product, 5 version, 6 update_version, 7 edition,
650                     // 8 lang, 9 sw_edition, 10 target_sw, 11 target_hw, 12 other, 13 versionEndExcluding,
651                     //14 versionEndIncluding, 15 versionStartExcluding, 16 versionStartIncluding, 17 vulnerable
652                     final VulnerableSoftware vs;
653                     try {
654                         vs = vulnerableSoftwareBuilder.part(rs.getString(2)).vendor(rs.getString(3))
655                                 .product(rs.getString(4)).version(rs.getString(5)).update(rs.getString(6))
656                                 .edition(rs.getString(7)).language(rs.getString(8)).swEdition(rs.getString(9))
657                                 .targetSw(rs.getString(10)).targetHw(rs.getString(11)).other(rs.getString(12))
658                                 .versionEndExcluding(rs.getString(13)).versionEndIncluding(rs.getString(14))
659                                 .versionStartExcluding(rs.getString(15)).versionStartIncluding(rs.getString(16))
660                                 .vulnerable(rs.getBoolean(17)).build();
661                     } catch (CpeParsingException | CpeValidationException ex) {
662                         throw new DatabaseException("Database contains an invalid Vulnerable Software Entry", ex);
663                     }
664                     vulnSoftware.add(vs);
665                 }
666 
667                 //remember to process the last set of CVE/CPE entries
668                 final VulnerableSoftware matchedCPE = getMatchingSoftware(cpe, vulnSoftware);
669                 if (matchedCPE != null) {
670                     final Vulnerability v = getVulnerability(currentCVE, conn);
671                     if (v != null) {
672                         v.setMatchedVulnerableSoftware(matchedCPE);
673                         v.setSource(Vulnerability.Source.NVD);
674                         vulnerabilities.add(v);
675                     }
676                 }
677             }
678         } catch (SQLException ex) {
679             throw new DatabaseException("Exception retrieving vulnerability for " + cpe.toCpe23FS(), ex);
680         }
681         vulnerabilitiesForCpeCache.put(cpe.toCpe23FS(), vulnerabilities);
682         return vulnerabilities;
683     }
684 
685     /**
686      * Gets a vulnerability for the provided CVE.
687      *
688      * @param cve the CVE to lookup
689      * @return a vulnerability object
690      * @throws DatabaseException if an exception occurs
691      */
692     public Vulnerability getVulnerability(String cve) throws DatabaseException {
693         try (Connection conn = databaseManager.getConnection()) {
694             return getVulnerability(cve, conn);
695         } catch (SQLException ex) {
696             throw new DatabaseException("Error retrieving " + cve, ex);
697         }
698     }
699 
700     /**
701      * Gets a vulnerability for the provided CVE.
702      *
703      * @param cve the CVE to lookup
704      * @param conn already active database connection
705      * @return a vulnerability object
706      * @throws DatabaseException if an exception occurs
707      */
708     public Vulnerability getVulnerability(String cve, Connection conn) throws DatabaseException {
709         final int cveId;
710         final VulnerableSoftwareBuilder vulnerableSoftwareBuilder = new VulnerableSoftwareBuilder();
711         Vulnerability vuln = null;
712         try {
713             try (PreparedStatement psV = getPreparedStatement(conn, SELECT_VULNERABILITY, cve); ResultSet rsV = psV.executeQuery()) {
714                 if (rsV.next()) {
715                     //1.id, 2.description,
716                     cveId = rsV.getInt(1);
717                     vuln = new Vulnerability();
718                     vuln.setSource(Vulnerability.Source.NVD);
719                     vuln.setName(cve);
720                     vuln.setDescription(rsV.getString(2));
721 
722                     //3.v2Severity, 4.v2ExploitabilityScore, 5.v2ImpactScore, 6.v2AcInsufInfo, 7.v2ObtainAllPrivilege,
723                     //8.v2ObtainUserPrivilege, 9.v2ObtainOtherPrivilege, 10.v2UserInteractionRequired, 11.v2Score,
724                     //12.v2AccessVector, 13.v2AccessComplexity, 14.v2Authentication, 15.v2ConfidentialityImpact,
725                     //16.v2IntegrityImpact, 17.v2AvailabilityImpact, 18.v2Version,
726                     if (rsV.getObject(11) != null) {
727 
728                         final CvssV2Data.AccessVectorType accessVector = CvssV2Data.AccessVectorType.fromValue(rsV.getString(12));
729                         final CvssV2Data.AccessComplexityType accessComplexity = CvssV2Data.AccessComplexityType.fromValue(rsV.getString(13));
730                         final CvssV2Data.AuthenticationType authentication = CvssV2Data.AuthenticationType.fromValue(rsV.getString(14));
731                         final CvssV2Data.CiaType confidentialityImpact = CvssV2Data.CiaType.fromValue(rsV.getString(15));
732                         final CvssV2Data.CiaType integrityImpact = CvssV2Data.CiaType.fromValue(rsV.getString(16));
733                         final CvssV2Data.CiaType availabilityImpact = CvssV2Data.CiaType.fromValue(rsV.getString(17));
734                         final String vector = String.format("/AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s",
735                                 accessVector == null ? "" : accessVector.value().substring(0, 1),
736                                 accessComplexity == null ? "" : accessComplexity.value().substring(0, 1),
737                                 authentication == null ? "" : authentication.value().substring(0, 1),
738                                 confidentialityImpact == null ? "" : confidentialityImpact.value().substring(0, 1),
739                                 integrityImpact == null ? "" : integrityImpact.value().substring(0, 1),
740                                 availabilityImpact == null ? "" : availabilityImpact.value().substring(0, 1));
741 
742                         //some older test data may not correctly have the version set.
743                         String cveVersion = "2.0";
744                         if (rsV.getString(18) != null) {
745                             cveVersion = rsV.getString(18);
746                         }
747                         final CvssV2Data cvssData = new CvssV2Data(cveVersion, vector, accessVector,
748                                 accessComplexity, authentication, confidentialityImpact,
749                                 integrityImpact, availabilityImpact, rsV.getDouble(11), rsV.getString(3),
750                                 null, null, null, null, null, null, null, null, null, null);
751                         final CvssV2 cvss = new CvssV2(null, CvssV2.Type.PRIMARY, cvssData, rsV.getString(3),
752                                 rsV.getDouble(4), rsV.getDouble(5), rsV.getBoolean(6), rsV.getBoolean(7),
753                                 rsV.getBoolean(8), rsV.getBoolean(9), rsV.getBoolean(10));
754                         vuln.setCvssV2(cvss);
755                     }
756                     //19.v3ExploitabilityScore, 20.v3ImpactScore, 21.v3AttackVector, 22.v3AttackComplexity, 23.v3PrivilegesRequired,
757                     //24.v3UserInteraction, 25.v3Scope, 26.v3ConfidentialityImpact, 27.v3IntegrityImpact, 28.v3AvailabilityImpact,
758                     //29.v3BaseScore, 30.v3BaseSeverity, 31.v3Version
759                     if (rsV.getObject(21) != null) {
760                         //some older test data may not correctly have the version set.
761                         String cveVersion = "3.1";
762                         if (rsV.getString(31) != null) {
763                             cveVersion = rsV.getString(31);
764                         }
765                         final CvssV3Data.Version version = CvssV3Data.Version.fromValue(cveVersion);
766                         final CvssV3Data.AttackVectorType attackVector = CvssV3Data.AttackVectorType.fromValue(rsV.getString(21));
767                         final CvssV3Data.AttackComplexityType attackComplexity = CvssV3Data.AttackComplexityType.fromValue(rsV.getString(22));
768                         final CvssV3Data.PrivilegesRequiredType privilegesRequired = CvssV3Data.PrivilegesRequiredType.fromValue(rsV.getString(23));
769                         final CvssV3Data.UserInteractionType userInteraction = CvssV3Data.UserInteractionType.fromValue(rsV.getString(24));
770                         final CvssV3Data.ScopeType scope = CvssV3Data.ScopeType.fromValue(rsV.getString(25));
771                         final CvssV3Data.CiaType confidentialityImpact = CvssV3Data.CiaType.fromValue(rsV.getString(26));
772                         final CvssV3Data.CiaType integrityImpact = CvssV3Data.CiaType.fromValue(rsV.getString(27));
773                         final CvssV3Data.CiaType availabilityImpact = CvssV3Data.CiaType.fromValue(rsV.getString(28));
774                         final CvssV3Data.SeverityType baseSeverity = CvssV3Data.SeverityType.fromValue(rsV.getString(30));
775                         final String vector = String.format("CVSS:%s/AV:%s/AC:%s/PR:%s/UI:%s/S:%s/C:%s/I:%s/A:%s",
776                                 version == null ? "" : version,
777                                 attackVector == null ? "" : attackVector.value().substring(0, 1),
778                                 attackComplexity == null ? "" : attackComplexity.value().substring(0, 1),
779                                 privilegesRequired == null ? "" : privilegesRequired.value().substring(0, 1),
780                                 userInteraction == null ? "" : userInteraction.value().substring(0, 1),
781                                 scope == null ? "" : scope.value().substring(0, 1),
782                                 confidentialityImpact == null ? "" : confidentialityImpact.value().substring(0, 1),
783                                 integrityImpact == null ? "" : integrityImpact.value().substring(0, 1),
784                                 availabilityImpact == null ? "" : availabilityImpact.value().substring(0, 1));
785 
786                         final CvssV3Data cvssData = new CvssV3Data(version, vector, attackVector, attackComplexity, privilegesRequired,
787                                 userInteraction, scope, confidentialityImpact, integrityImpact, availabilityImpact,
788                                 rsV.getDouble(29), baseSeverity, CvssV3Data.ExploitCodeMaturityType.PROOF_OF_CONCEPT,
789                                 CvssV3Data.RemediationLevelType.NOT_DEFINED, CvssV3Data.ConfidenceType.REASONABLE, 0.0,
790                                 CvssV3Data.SeverityType.MEDIUM, CvssV3Data.CiaRequirementType.NOT_DEFINED,
791                                 CvssV3Data.CiaRequirementType.NOT_DEFINED, CvssV3Data.CiaRequirementType.NOT_DEFINED,
792                                 CvssV3Data.ModifiedAttackVectorType.ADJACENT_NETWORK, CvssV3Data.ModifiedAttackComplexityType.NOT_DEFINED,
793                                 CvssV3Data.ModifiedPrivilegesRequiredType.NOT_DEFINED, CvssV3Data.ModifiedUserInteractionType.NOT_DEFINED,
794                                 CvssV3Data.ModifiedScopeType.NOT_DEFINED, CvssV3Data.ModifiedCiaType.NOT_DEFINED,
795                                 CvssV3Data.ModifiedCiaType.NOT_DEFINED, CvssV3Data.ModifiedCiaType.NOT_DEFINED, 1.0,
796                                 CvssV3Data.SeverityType.NONE);
797                         final CvssV3 cvss = new CvssV3(null, null, cvssData, rsV.getDouble(19), rsV.getDouble(20));
798                         vuln.setCvssV3(cvss);
799                     }
800                 } else {
801                     LOGGER.debug(cve + " does not exist in the database");
802                     return null;
803                 }
804             }
805             try (PreparedStatement psCWE = getPreparedStatement(conn, SELECT_VULNERABILITY_CWE, cveId); ResultSet rsC = psCWE.executeQuery()) {
806                 while (rsC.next()) {
807                     vuln.addCwe(rsC.getString(1));
808                 }
809             }
810             try (PreparedStatement psR = getPreparedStatement(conn, SELECT_REFERENCES, cveId); ResultSet rsR = psR.executeQuery()) {
811                 while (rsR.next()) {
812                     vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3));
813                 }
814             }
815             try (PreparedStatement psS = getPreparedStatement(conn, SELECT_SOFTWARE, cveId); ResultSet rsS = psS.executeQuery()) {
816                 //1 part, 2 vendor, 3 product, 4 version, 5 update_version, 6 edition, 7 lang,
817                 //8 sw_edition, 9 target_sw, 10 target_hw, 11 other, 12 versionEndExcluding,
818                 //13 versionEndIncluding, 14 versionStartExcluding, 15 versionStartIncluding, 16 vulnerable
819                 while (rsS.next()) {
820                     vulnerableSoftwareBuilder.part(rsS.getString(1))
821                             .vendor(rsS.getString(2))
822                             .product(rsS.getString(3))
823                             .version(rsS.getString(4))
824                             .update(rsS.getString(5))
825                             .edition(rsS.getString(6))
826                             .language(rsS.getString(7))
827                             .swEdition(rsS.getString(8))
828                             .targetSw(rsS.getString(9))
829                             .targetHw(rsS.getString(10))
830                             .other(rsS.getString(11))
831                             .versionEndExcluding(rsS.getString(12))
832                             .versionEndIncluding(rsS.getString(13))
833                             .versionStartExcluding(rsS.getString(14))
834                             .versionStartIncluding(rsS.getString(15))
835                             .vulnerable(rsS.getBoolean(16));
836                     vuln.addVulnerableSoftware(vulnerableSoftwareBuilder.build());
837                 }
838             }
839         } catch (SQLException ex) {
840             throw new DatabaseException("Error retrieving " + cve, ex);
841         } catch (CpeParsingException | CpeValidationException ex) {
842             throw new DatabaseException("The database contains an invalid Vulnerable Software Entry", ex);
843         }
844         return vuln;
845     }
846 
847     /**
848      * Updates the vulnerability within the database. If the vulnerability does
849      * not exist it will be added.
850      *
851      * @param cve the vulnerability from the NVD CVE Data Feed to add to the
852      * database
853      * @param baseEcosystem the ecosystem the CVE belongs to; this is based off
854      * of things like the CVE description
855      * @throws DatabaseException is thrown if the database
856      */
857     public void updateVulnerability(DefCveItem cve, String baseEcosystem) {
858         clearCache();
859         final String cveId = cve.getCve().getId();
860         try {
861             if (cve.getCve().getVulnStatus().toUpperCase().startsWith("REJECT")) {
862                 deleteVulnerability(cveId);
863             } else {
864                 if (cveItemConverter.testCveCpeStartWithFilter(cve)) {
865                     final String description = cveItemConverter.extractDescription(cve);
866                     final int vulnerabilityId = updateOrInsertVulnerability(cve, description);
867                     updateVulnerabilityInsertCwe(vulnerabilityId, cve);
868                     updateVulnerabilityInsertReferences(vulnerabilityId, cve);
869 
870                     final List<VulnerableSoftware> software = parseCpes(cve);
871                     updateVulnerabilityInsertSoftware(vulnerabilityId, cveId, software, baseEcosystem);
872                 }
873             }
874         } catch (SQLException ex) {
875             final String msg = String.format("Error updating '%s'; %s", cveId, ex.getMessage());
876             LOGGER.debug(msg, ex);
877             throw new DatabaseException(msg);
878         } catch (CpeValidationException ex) {
879             final String msg = String.format("Error parsing CPE entry from '%s'; %s", cveId, ex.getMessage());
880             LOGGER.debug(msg, ex);
881             throw new DatabaseException(msg);
882         }
883     }
884 
885     private void loadCpeEcosystemCache() {
886         final Map<Pair<String, String>, String> map = new HashMap<>();
887         try (Connection conn = databaseManager.getConnection();
888                 PreparedStatement ps = getPreparedStatement(conn, SELECT_CPE_ECOSYSTEM);
889                 ResultSet rs = ps.executeQuery()) {
890             while (rs.next()) {
891                 final Pair<String, String> key = new Pair<>(rs.getString(1), rs.getString(2));
892                 final String value = rs.getString(3);
893                 map.put(key, value);
894             }
895         } catch (SQLException ex) {
896             final String msg = String.format("Error loading the Cpe Ecosystem Cache: %s", ex.getMessage());
897             LOGGER.debug(msg, ex);
898             throw new DatabaseException(msg, ex);
899         }
900         CpeEcosystemCache.setCache(map);
901     }
902 
903     private void saveCpeEcosystemCache() {
904         final Map<Pair<String, String>, String> map = CpeEcosystemCache.getChanged();
905         if (map != null && !map.isEmpty()) {
906             try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, MERGE_CPE_ECOSYSTEM)) {
907                 for (Map.Entry<Pair<String, String>, String> entry : map.entrySet()) {
908                     ps.setString(1, entry.getKey().getLeft());
909                     ps.setString(2, entry.getKey().getRight());
910                     ps.setString(3, entry.getValue());
911                     if (isBatchInsertEnabled()) {
912                         ps.addBatch();
913                     } else {
914                         ps.execute();
915                     }
916                 }
917                 if (isBatchInsertEnabled()) {
918                     ps.executeBatch();
919                 }
920             } catch (SQLException ex) {
921                 final String msg = String.format("Error saving the Cpe Ecosystem Cache: %s", ex.getMessage());
922                 LOGGER.debug(msg, ex);
923                 throw new DatabaseException(msg, ex);
924             }
925         }
926     }
927 
928     /**
929      * Used when updating a vulnerability - this method inserts the
930      * vulnerability entry itself.
931      *
932      * @param cve the CVE data
933      * @param description the description of the CVE entry
934      * @return the vulnerability ID
935      */
936     private int updateOrInsertVulnerability(DefCveItem cve, String description) {
937         if (CpeEcosystemCache.isEmpty()) {
938             loadCpeEcosystemCache();
939         }
940         final int vulnerabilityId;
941         try (Connection conn = databaseManager.getConnection(); PreparedStatement callUpdate = getPreparedStatement(conn, UPDATE_VULNERABILITY)) {
942 //            String 1.cve, String 2.description, String 3.v2Severity, Float 4.v2ExploitabilityScore,
943 //            Float 5.v2ImpactScore, Boolean 6.v2AcInsufInfo, Boolean 7.v2ObtainAllPrivilege,
944 //            Boolean 8.v2ObtainUserPrivilege, Boolean 9.v2ObtainOtherPrivilege, Boolean 10.v2UserInteractionRequired,
945 //            Float 11.v2Score, String 12.v2AccessVector, String 13.v2AccessComplexity,
946 //            String 14.v2Authentication, String 15.v2ConfidentialityImpact, String 16.v2IntegrityImpact,
947 //            String 17.v2AvailabilityImpact, String 18.v2Version, Float 19.v3ExploitabilityScore,
948 //            Float 20.v3ImpactScore, String 21.v3AttackVector, String 22.v3AttackComplexity,
949 //            String 23.v3PrivilegesRequired, String 24.v3UserInteraction, String 25.v3Scope,
950 //            String 26.v3ConfidentialityImpact, String 27.v3IntegrityImpact, String 28.v3AvailabilityImpact,
951 //            Float 29.v3BaseScore, String 30.v3BaseSeverity, String 31.v3Version
952             callUpdate.setString(1, cve.getCve().getId());
953             callUpdate.setString(2, description);
954             Optional<CvssV2> optCvssv2 = null;
955             if (cve.getCve().getMetrics() != null && cve.getCve().getMetrics().getCvssMetricV2() != null) {
956                 optCvssv2 = cve.getCve().getMetrics().getCvssMetricV2().stream().sorted(Comparator.comparing(CvssV2::getType)).findFirst();
957             }
958             if (optCvssv2 != null && optCvssv2.isPresent()) {
959                 final CvssV2 cvssv2 = optCvssv2.get();
960                 setUpdateColumn(callUpdate, 3, cvssv2.getBaseSeverity());
961                 setUpdateColumn(callUpdate, 4, cvssv2.getExploitabilityScore());
962                 setUpdateColumn(callUpdate, 5, cvssv2.getImpactScore());
963                 setUpdateColumn(callUpdate, 6, cvssv2.getAcInsufInfo());
964                 setUpdateColumn(callUpdate, 7, cvssv2.getObtainAllPrivilege());
965                 setUpdateColumn(callUpdate, 8, cvssv2.getObtainUserPrivilege());
966                 setUpdateColumn(callUpdate, 9, cvssv2.getObtainOtherPrivilege());
967                 setUpdateColumn(callUpdate, 10, cvssv2.getUserInteractionRequired());
968                 setUpdateColumn(callUpdate, 11, cvssv2.getCvssData().getBaseScore());
969                 setUpdateColumn(callUpdate, 12, cvssv2.getCvssData().getAccessVector());
970                 setUpdateColumn(callUpdate, 13, cvssv2.getCvssData().getAccessComplexity());
971                 setUpdateColumn(callUpdate, 14, cvssv2.getCvssData().getAuthentication());
972                 setUpdateColumn(callUpdate, 15, cvssv2.getCvssData().getConfidentialityImpact());
973                 setUpdateColumn(callUpdate, 16, cvssv2.getCvssData().getIntegrityImpact());
974                 setUpdateColumn(callUpdate, 17, cvssv2.getCvssData().getAvailabilityImpact());
975                 setUpdateColumn(callUpdate, 18, cvssv2.getCvssData().getVersion());
976             } else {
977                 callUpdate.setNull(3, java.sql.Types.VARCHAR);
978                 callUpdate.setNull(4, java.sql.Types.DOUBLE);
979                 callUpdate.setNull(5, java.sql.Types.DOUBLE);
980                 callUpdate.setNull(6, java.sql.Types.VARCHAR);
981                 //TODO this is may also be an issue for MS SQL, if an issue is created we'll just need
982                 // to create an isMsSQL flag. See todo below in setUpdateColum
983                 if (isOracle) {
984                     callUpdate.setNull(7, java.sql.Types.BIT);
985                     callUpdate.setNull(8, java.sql.Types.BIT);
986                     callUpdate.setNull(9, java.sql.Types.BIT);
987                     callUpdate.setNull(10, java.sql.Types.BIT);
988                 } else {
989                     callUpdate.setNull(7, java.sql.Types.BOOLEAN);
990                     callUpdate.setNull(8, java.sql.Types.BOOLEAN);
991                     callUpdate.setNull(9, java.sql.Types.BOOLEAN);
992                     callUpdate.setNull(10, java.sql.Types.BOOLEAN);
993                 }
994                 callUpdate.setNull(11, java.sql.Types.DOUBLE);
995                 callUpdate.setNull(12, java.sql.Types.VARCHAR);
996                 callUpdate.setNull(13, java.sql.Types.VARCHAR);
997                 callUpdate.setNull(14, java.sql.Types.VARCHAR);
998                 callUpdate.setNull(15, java.sql.Types.VARCHAR);
999                 callUpdate.setNull(16, java.sql.Types.VARCHAR);
1000                 callUpdate.setNull(17, java.sql.Types.VARCHAR);
1001                 callUpdate.setNull(18, java.sql.Types.VARCHAR);
1002             }
1003             Optional<CvssV3> optCvssv30 = Optional.empty();
1004             if (cve.getCve().getMetrics() != null && cve.getCve().getMetrics().getCvssMetricV30() != null) {
1005                 optCvssv30 = cve.getCve().getMetrics().getCvssMetricV30().stream().sorted(Comparator.comparing(CvssV3::getType)).findFirst();
1006             }
1007             Optional<CvssV3> optCvssv31 = Optional.empty();
1008             if (cve.getCve().getMetrics() != null && cve.getCve().getMetrics().getCvssMetricV31() != null) {
1009                 optCvssv31 = cve.getCve().getMetrics().getCvssMetricV31().stream().sorted(Comparator.comparing(CvssV3::getType)).findFirst();
1010             }
1011 
1012             CvssV3 cvssv3 = null;
1013             if (optCvssv31.isPresent()) {
1014                 cvssv3 = optCvssv31.get();
1015             } else if (optCvssv30.isPresent()) {
1016                 cvssv3 = optCvssv30.get();
1017             }
1018             if (cvssv3 != null) {
1019                 setUpdateColumn(callUpdate, 19, cvssv3.getExploitabilityScore());
1020                 setUpdateColumn(callUpdate, 20, cvssv3.getImpactScore());
1021                 setUpdateColumn(callUpdate, 21, cvssv3.getCvssData().getAttackVector());
1022                 setUpdateColumn(callUpdate, 22, cvssv3.getCvssData().getAttackComplexity());
1023                 setUpdateColumn(callUpdate, 23, cvssv3.getCvssData().getPrivilegesRequired());
1024                 setUpdateColumn(callUpdate, 24, cvssv3.getCvssData().getUserInteraction());
1025                 setUpdateColumn(callUpdate, 25, cvssv3.getCvssData().getScope());
1026                 setUpdateColumn(callUpdate, 26, cvssv3.getCvssData().getConfidentialityImpact());
1027                 setUpdateColumn(callUpdate, 27, cvssv3.getCvssData().getIntegrityImpact());
1028                 setUpdateColumn(callUpdate, 28, cvssv3.getCvssData().getAvailabilityImpact());
1029                 setUpdateColumn(callUpdate, 29, cvssv3.getCvssData().getBaseScore());
1030                 setUpdateColumn(callUpdate, 30, cvssv3.getCvssData().getBaseSeverity());
1031                 setUpdateColumn(callUpdate, 31, cvssv3.getCvssData().getVersion());
1032             } else {
1033                 callUpdate.setNull(19, java.sql.Types.DOUBLE);
1034                 callUpdate.setNull(20, java.sql.Types.DOUBLE);
1035                 callUpdate.setNull(21, java.sql.Types.VARCHAR);
1036                 callUpdate.setNull(22, java.sql.Types.VARCHAR);
1037                 callUpdate.setNull(23, java.sql.Types.VARCHAR);
1038                 callUpdate.setNull(24, java.sql.Types.VARCHAR);
1039                 callUpdate.setNull(25, java.sql.Types.VARCHAR);
1040                 callUpdate.setNull(26, java.sql.Types.VARCHAR);
1041                 callUpdate.setNull(27, java.sql.Types.VARCHAR);
1042                 callUpdate.setNull(28, java.sql.Types.VARCHAR);
1043                 callUpdate.setNull(29, java.sql.Types.DOUBLE);
1044                 callUpdate.setNull(30, java.sql.Types.VARCHAR);
1045                 callUpdate.setNull(31, java.sql.Types.VARCHAR);
1046             }
1047             if (isOracle) {
1048                 try {
1049                     final CallableStatement cs = (CallableStatement) callUpdate;
1050                     cs.registerOutParameter(32, JDBCType.INTEGER);
1051                     cs.executeUpdate();
1052                     vulnerabilityId = cs.getInt(32);
1053                 } catch (SQLException ex) {
1054                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", cve.getCve().getId());
1055                     throw new DatabaseException(msg, ex);
1056                 }
1057             } else {
1058                 try (ResultSet rs = callUpdate.executeQuery()) {
1059                     rs.next();
1060                     vulnerabilityId = rs.getInt(1);
1061                 } catch (SQLException ex) {
1062                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", cve.getCve().getId());
1063                     throw new DatabaseException(msg, ex);
1064                 }
1065             }
1066         } catch (SQLException ex) {
1067             throw new UnexpectedAnalysisException(ex);
1068         }
1069         return vulnerabilityId;
1070     }
1071 
1072     /**
1073      * Used when updating a vulnerability - this method inserts the CWE entries.
1074      *
1075      * @param vulnerabilityId the vulnerability ID
1076      * @param cve the CVE entry that contains the CWE entries to insert
1077      * @throws SQLException thrown if there is an error inserting the data
1078      */
1079     private void updateVulnerabilityInsertCwe(int vulnerabilityId, DefCveItem cve) throws SQLException {
1080         if (cve.getCve() != null && cve.getCve().getWeaknesses() != null) {
1081             try (Connection conn = databaseManager.getConnection();
1082                     PreparedStatement insertCWE = getPreparedStatement(conn, INSERT_CWE, vulnerabilityId)) {
1083                 for (Weakness weakness : cve.getCve().getWeaknesses()) {
1084                     for (LangString desc : weakness.getDescription()) {
1085                         if ("en".equals(desc.getLang())) {
1086                             insertCWE.setString(2, desc.getValue());
1087                             if (isBatchInsertEnabled()) {
1088                                 insertCWE.addBatch();
1089                             } else {
1090                                 insertCWE.execute();
1091                             }
1092                         }
1093                     }
1094                 }
1095                 if (isBatchInsertEnabled()) {
1096                     insertCWE.executeBatch();
1097                 }
1098             }
1099         }
1100     }
1101 
1102     /**
1103      * Used when updating a vulnerability - in some cases a CVE needs to be
1104      * removed.
1105      *
1106      * @param cve the vulnerability CVE
1107      * @throws SQLException thrown if there is an error deleting the
1108      * vulnerability
1109      */
1110     private void deleteVulnerability(String cve) throws SQLException {
1111         try (Connection conn = databaseManager.getConnection();
1112                 PreparedStatement deleteVulnerability = getPreparedStatement(conn, DELETE_VULNERABILITY, cve)) {
1113             deleteVulnerability.executeUpdate();
1114         }
1115     }
1116 
1117     /**
1118      * Merges the list of known exploited vulnerabilities into the database.
1119      *
1120      * @param vulnerabilities the list of known exploited vulnerabilities
1121      * @throws DatabaseException thrown if there is an exception... duh..
1122      * @throws SQLException thrown if there is an exception... duh..
1123      */
1124     public void updateKnownExploitedVulnerabilities(
1125             List<org.owasp.dependencycheck.data.knownexploited.json.Vulnerability> vulnerabilities)
1126             throws DatabaseException, SQLException {
1127         try (Connection conn = databaseManager.getConnection();
1128                 PreparedStatement mergeKnownVulnerability = getPreparedStatement(conn, MERGE_KNOWN_EXPLOITED)) {
1129             int ctr = 0;
1130             for (org.owasp.dependencycheck.data.knownexploited.json.Vulnerability v : vulnerabilities) {
1131                 mergeKnownVulnerability.setString(1, v.getCveID());
1132                 addNullableStringParameter(mergeKnownVulnerability, 2, v.getVendorProject());
1133                 addNullableStringParameter(mergeKnownVulnerability, 3, v.getProduct());
1134                 addNullableStringParameter(mergeKnownVulnerability, 4, v.getVulnerabilityName());
1135                 addNullableStringParameter(mergeKnownVulnerability, 5, v.getDateAdded());
1136                 addNullableStringParameter(mergeKnownVulnerability, 6, v.getShortDescription());
1137                 addNullableStringParameter(mergeKnownVulnerability, 7, v.getRequiredAction());
1138                 addNullableStringParameter(mergeKnownVulnerability, 8, v.getDueDate());
1139                 addNullableStringParameter(mergeKnownVulnerability, 9, v.getNotes());
1140                 if (isBatchInsertEnabled()) {
1141                     mergeKnownVulnerability.addBatch();
1142                     ctr++;
1143                     if (ctr >= getBatchSize()) {
1144                         mergeKnownVulnerability.executeBatch();
1145                         ctr = 0;
1146                     }
1147                 } else {
1148                     try {
1149                         mergeKnownVulnerability.execute();
1150                     } catch (SQLException ex) {
1151                         if (ex.getMessage().contains("Duplicate entry")) {
1152                             final String msg = String.format("Duplicate known exploited vulnerability key identified in '%s'", v.getCveID());
1153                             LOGGER.info(msg, ex);
1154                         } else {
1155                             throw ex;
1156                         }
1157                     }
1158                 }
1159             }
1160             if (isBatchInsertEnabled()) {
1161                 mergeKnownVulnerability.executeBatch();
1162             }
1163         }
1164     }
1165 
1166     /**
1167      * Used when updating a vulnerability - this method inserts the list of
1168      * vulnerable software.
1169      *
1170      * @param vulnerabilityId the vulnerability id
1171      * @param cveId the CVE ID - used for reporting
1172      * @param software the list of vulnerable software
1173      * @param baseEcosystem the ecosystem based off of the vulnerability
1174      * description
1175      * @throws DatabaseException thrown if there is an error inserting the data
1176      * @throws SQLException thrown if there is an error inserting the data
1177      */
1178     private void updateVulnerabilityInsertSoftware(int vulnerabilityId, String cveId,
1179             List<VulnerableSoftware> software, String baseEcosystem)
1180             throws DatabaseException, SQLException {
1181         try (Connection conn = databaseManager.getConnection(); PreparedStatement insertSoftware = getPreparedStatement(conn, INSERT_SOFTWARE)) {
1182             for (VulnerableSoftware parsedCpe : software) {
1183                 insertSoftware.setInt(1, vulnerabilityId);
1184                 insertSoftware.setString(2, parsedCpe.getPart().getAbbreviation());
1185                 insertSoftware.setString(3, parsedCpe.getVendor());
1186                 insertSoftware.setString(4, parsedCpe.getProduct());
1187                 insertSoftware.setString(5, parsedCpe.getVersion());
1188                 insertSoftware.setString(6, parsedCpe.getUpdate());
1189                 insertSoftware.setString(7, parsedCpe.getEdition());
1190                 insertSoftware.setString(8, parsedCpe.getLanguage());
1191                 insertSoftware.setString(9, parsedCpe.getSwEdition());
1192                 insertSoftware.setString(10, parsedCpe.getTargetSw());
1193                 insertSoftware.setString(11, parsedCpe.getTargetHw());
1194                 insertSoftware.setString(12, parsedCpe.getOther());
1195                 final String ecosystem = CpeEcosystemCache.getEcosystem(parsedCpe.getVendor(), parsedCpe.getProduct(),
1196                         cveItemConverter.extractEcosystem(baseEcosystem, parsedCpe));
1197 
1198                 addNullableStringParameter(insertSoftware, 13, ecosystem);
1199                 addNullableStringParameter(insertSoftware, 14, parsedCpe.getVersionEndExcluding());
1200                 addNullableStringParameter(insertSoftware, 15, parsedCpe.getVersionEndIncluding());
1201                 addNullableStringParameter(insertSoftware, 16, parsedCpe.getVersionStartExcluding());
1202                 addNullableStringParameter(insertSoftware, 17, parsedCpe.getVersionStartIncluding());
1203                 insertSoftware.setBoolean(18, parsedCpe.isVulnerable());
1204 
1205                 if (isBatchInsertEnabled()) {
1206                     insertSoftware.addBatch();
1207                 } else {
1208                     try {
1209                         insertSoftware.execute();
1210                     } catch (SQLException ex) {
1211                         if (ex.getMessage().contains("Duplicate entry")) {
1212                             final String msg = String.format("Duplicate software key identified in '%s'", cveId);
1213                             LOGGER.info(msg, ex);
1214                         } else {
1215                             throw ex;
1216                         }
1217                     }
1218                 }
1219             }
1220             if (isBatchInsertEnabled()) {
1221                 executeBatch(cveId, insertSoftware);
1222             }
1223         }
1224     }
1225 
1226     /**
1227      * Used when updating a vulnerability - this method inserts the list of
1228      * references.
1229      *
1230      * @param vulnerabilityId the vulnerability id
1231      * @param cve the CVE entry that contains the list of references
1232      * @throws SQLException thrown if there is an error inserting the data
1233      */
1234     private void updateVulnerabilityInsertReferences(int vulnerabilityId, DefCveItem cve) throws SQLException {
1235         try (Connection conn = databaseManager.getConnection(); PreparedStatement insertReference = getPreparedStatement(conn, INSERT_REFERENCE)) {
1236             if (cve.getCve().getReferences() != null) {
1237                 for (Reference r : cve.getCve().getReferences()) {
1238                     insertReference.setInt(1, vulnerabilityId);
1239                     String name = null;
1240                     if (r.getTags() != null) {
1241                         name = r.getTags().stream().sorted().collect(Collectors.joining(",")).toUpperCase().replaceAll("\\s", "_");
1242                     }
1243                     if (name != null) {
1244                         insertReference.setString(2, name);
1245                     } else {
1246                         insertReference.setNull(2, java.sql.Types.VARCHAR);
1247                     }
1248                     if (r.getUrl() != null && !r.getUrl().isEmpty()) {
1249                         insertReference.setString(3, r.getUrl());
1250                     } else {
1251                         insertReference.setNull(3, java.sql.Types.VARCHAR);
1252                     }
1253                     if (r.getSource() != null && !r.getSource().isEmpty()) {
1254                         insertReference.setString(4, r.getSource());
1255                     } else {
1256                         insertReference.setNull(4, java.sql.Types.VARCHAR);
1257                     }
1258                     if (isBatchInsertEnabled()) {
1259                         insertReference.addBatch();
1260                     } else {
1261                         insertReference.execute();
1262                     }
1263                 }
1264             }
1265             if (isBatchInsertEnabled()) {
1266                 insertReference.executeBatch();
1267             }
1268         }
1269     }
1270 
1271     /**
1272      * Parses the configuration entries from the CVE entry into a list of
1273      * VulnerableSoftware objects.
1274      *
1275      * @param cve the CVE to parse the vulnerable software entries from
1276      * @return the list of vulnerable software
1277      * @throws CpeValidationException if an invalid CPE is present
1278      */
1279     private List<VulnerableSoftware> parseCpes(DefCveItem cve) throws CpeValidationException {
1280         final List<VulnerableSoftware> software = new ArrayList<>();
1281 
1282         final List<CpeMatch> cpeEntries = cve.getCve().getConfigurations().stream()
1283                 .map(Config::getNodes)
1284                 .flatMap(List::stream)
1285                 .map(Node::getCpeMatch)
1286                 .flatMap(List::stream)
1287                 .filter(predicate -> predicate.getCriteria() != null)
1288                 .filter(predicate -> predicate.getCriteria().startsWith(cpeStartsWithFilter))
1289                 //this single CPE entry causes nearly 100% FP - so filtering it at the source.
1290                 .filter(entry -> !("CVE-2009-0754".equals(cve.getCve().getId())
1291                 && "cpe:2.3:a:apache:apache:*:*:*:*:*:*:*:*".equals(entry.getCriteria())))
1292                 .collect(Collectors.toList());
1293         final VulnerableSoftwareBuilder builder = new VulnerableSoftwareBuilder();
1294 
1295         try {
1296             cpeEntries.forEach(entry -> {
1297                 builder.cpe(parseCpe(entry, cve.getCve().getId()))
1298                         .versionEndExcluding(entry.getVersionEndExcluding())
1299                         .versionStartExcluding(entry.getVersionStartExcluding())
1300                         .versionEndIncluding(entry.getVersionEndIncluding())
1301                         .versionStartIncluding(entry.getVersionStartIncluding())
1302                         .vulnerable(entry.getVulnerable());
1303                 try {
1304                     software.add(builder.build());
1305                 } catch (CpeValidationException ex) {
1306                     throw new LambdaExceptionWrapper(ex);
1307                 }
1308             });
1309         } catch (LambdaExceptionWrapper ex) {
1310             throw (CpeValidationException) ex.getCause();
1311         }
1312         return software;
1313     }
1314 
1315     /**
1316      * Helper method to convert a CpeMatch (generated code used in parsing the
1317      * NVD JSON) into a CPE object.
1318      *
1319      * @param cpe the CPE Match
1320      * @param cveId the CVE associated with the CPEMatch - used for error
1321      * reporting
1322      * @return the resulting CPE object
1323      * @throws DatabaseException thrown if there is an error converting the
1324      * CpeMatch into a CPE object
1325      */
1326     private Cpe parseCpe(CpeMatch cpe, String cveId) throws DatabaseException {
1327         final Cpe parsedCpe;
1328         try {
1329             //the replace is a hack as the NVD does not properly escape backslashes in their JSON
1330             parsedCpe = CpeParser.parse(cpe.getCriteria(), true);
1331         } catch (CpeParsingException ex) {
1332             LOGGER.debug("NVD (" + cveId + ") contain an invalid 2.3 CPE: " + cpe.getCriteria());
1333             throw new DatabaseException("Unable to parse CPE: " + cpe.getCriteria(), ex);
1334         }
1335         return parsedCpe;
1336     }
1337 
1338     /**
1339      * Returns the size of the batch.
1340      *
1341      * @return the size of the batch
1342      */
1343     private int getBatchSize() {
1344         int max;
1345         try {
1346             max = settings.getInt(Settings.KEYS.MAX_BATCH_SIZE);
1347         } catch (InvalidSettingException pE) {
1348             max = 1000;
1349         }
1350         return max;
1351     }
1352 
1353     /**
1354      * Determines whether or not batch insert is enabled.
1355      *
1356      * @return <code>true</code> if batch insert is enabled; otherwise
1357      * <code>false</code>
1358      */
1359     private boolean isBatchInsertEnabled() {
1360         boolean batch;
1361         try {
1362             batch = settings.getBoolean(Settings.KEYS.ENABLE_BATCH_UPDATES);
1363         } catch (InvalidSettingException pE) {
1364             //If there's no configuration, default is to not perform batch inserts
1365             batch = false;
1366         }
1367         return batch;
1368     }
1369 
1370     /**
1371      * Executes batch inserts of vulnerabilities when property
1372      * database.batchinsert.maxsize is reached.
1373      *
1374      * @param vulnId the vulnerability ID
1375      * @param statement the prepared statement to batch execute
1376      * @throws SQLException thrown when the batch cannot be executed
1377      */
1378     private void executeBatch(String vulnId, PreparedStatement statement)
1379             throws SQLException {
1380         try {
1381             statement.executeBatch();
1382         } catch (SQLException ex) {
1383             if (ex.getMessage().contains("Duplicate entry")) {
1384                 final String msg = String.format("Duplicate software key identified in '%s'",
1385                         vulnId);
1386                 LOGGER.info(msg, ex);
1387             } else {
1388                 throw ex;
1389             }
1390         }
1391     }
1392 
1393     /**
1394      * Checks to see if data exists so that analysis can be performed.
1395      *
1396      * @return <code>true</code> if data exists; otherwise <code>false</code>
1397      */
1398     public boolean dataExists() {
1399         try (Connection conn = databaseManager.getConnection();
1400                 PreparedStatement cs = getPreparedStatement(conn, COUNT_CPE);
1401                 ResultSet rs = cs.executeQuery()) {
1402             if (rs.next() && rs.getInt(1) > 0) {
1403                 return true;
1404             }
1405         } catch (Exception ex) {
1406             String dd;
1407             try {
1408                 dd = settings.getDataDirectory().getAbsolutePath();
1409             } catch (IOException ex1) {
1410                 dd = settings.getString(Settings.KEYS.DATA_DIRECTORY);
1411             }
1412             LOGGER.error("Unable to access the local database.\n\nEnsure that '{}' is a writable directory. "
1413                     + "If the problem persist try deleting the files in '{}' and running {} again. If the problem continues, please "
1414                     + "create a log file (see documentation at https://jeremylong.github.io/DependencyCheck/) and open a ticket at "
1415                     + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.\n\n",
1416                     dd, dd, settings.getString(Settings.KEYS.APPLICATION_NAME));
1417             LOGGER.debug("", ex);
1418         }
1419         return false;
1420     }
1421 
1422     /**
1423      * It is possible that orphaned rows may be generated during database
1424      * updates. This should be called after all updates have been completed to
1425      * ensure orphan entries are removed.
1426      */
1427     public void cleanupDatabase() {
1428         LOGGER.info("Begin database maintenance");
1429         final long start = System.currentTimeMillis();
1430         try (Connection conn = databaseManager.getConnection();
1431                 PreparedStatement psOrphans = getPreparedStatement(conn, CLEANUP_ORPHANS);
1432                 PreparedStatement psEcosystem = getPreparedStatement(conn, UPDATE_ECOSYSTEM);
1433                 PreparedStatement psEcosystem2 = getPreparedStatement(conn, UPDATE_ECOSYSTEM2)) {
1434             if (psEcosystem != null) {
1435                 final int count = psEcosystem.executeUpdate();
1436                 if (count > 0) {
1437                     LOGGER.info("Updated the CPE ecosystem on {} NVD records", count);
1438                 }
1439             }
1440             if (psEcosystem2 != null) {
1441                 final int count = psEcosystem2.executeUpdate();
1442                 if (count > 0) {
1443                     LOGGER.info("Removed the CPE ecosystem on {} NVD records", count);
1444                 }
1445             }
1446             if (psOrphans != null) {
1447                 final int count = psOrphans.executeUpdate();
1448                 if (count > 0) {
1449                     LOGGER.info("Cleaned up {} orphaned NVD records", count);
1450                 }
1451             }
1452             final long millis = System.currentTimeMillis() - start;
1453             //final long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
1454             LOGGER.info("End database maintenance ({} ms)", millis);
1455         } catch (SQLException ex) {
1456             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
1457             LOGGER.debug("", ex);
1458             throw new DatabaseException("Unexpected SQL Exception", ex);
1459         }
1460     }
1461 
1462     /**
1463      * Persist the EcosystemCache into the database.
1464      */
1465     public void persistEcosystemCache() {
1466         saveCpeEcosystemCache();
1467         clearCache();
1468     }
1469 
1470     /**
1471      * If the database is using an H2 file based database calling
1472      * <code>defrag()</code> will de-fragment the database.
1473      */
1474     public void defrag() {
1475         if (isH2) {
1476             final long start = System.currentTimeMillis();
1477             try (Connection conn = databaseManager.getConnection(); CallableStatement psCompaxt = conn.prepareCall("SHUTDOWN DEFRAG")) {
1478                 LOGGER.info("Begin database defrag");
1479                 psCompaxt.execute();
1480                 final long millis = System.currentTimeMillis() - start;
1481                 //final long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
1482                 LOGGER.info("End database defrag ({} ms)", millis);
1483             } catch (SQLException ex) {
1484                 LOGGER.error("An unexpected SQL Exception occurred compacting the database; please see the verbose log for more details.");
1485                 LOGGER.debug("", ex);
1486             }
1487         }
1488     }
1489 
1490     /**
1491      * Determines if the given identifiedVersion is affected by the given cpeId
1492      * and previous version flag. A non-null, non-empty string passed to the
1493      * previous version argument indicates that all previous versions are
1494      * affected.
1495      *
1496      * @param cpe the CPE for the given dependency
1497      * @param vulnerableSoftware a set of the vulnerable software
1498      * @return true if the identified version is affected, otherwise false
1499      */
1500     VulnerableSoftware getMatchingSoftware(Cpe cpe, Set<VulnerableSoftware> vulnerableSoftware) {
1501         VulnerableSoftware matched = null;
1502         for (VulnerableSoftware vs : vulnerableSoftware) {
1503             if (vs.matches(cpe)) {
1504                 if (matched == null) {
1505                     matched = vs;
1506                 } else {
1507                     if ("*".equals(vs.getWellFormedUpdate()) && !"*".equals(matched.getWellFormedUpdate())) {
1508                         matched = vs;
1509                     }
1510                 }
1511             }
1512         }
1513         return matched;
1514     }
1515 
1516     /**
1517      * This method is only referenced in unused code.
1518      * <p>
1519      * Deletes unused dictionary entries from the database.
1520      * </p>
1521      */
1522     public void deleteUnusedCpe() {
1523         clearCache();
1524         try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, DELETE_UNUSED_DICT_CPE)) {
1525             ps.executeUpdate();
1526         } catch (SQLException ex) {
1527             LOGGER.error("Unable to delete CPE dictionary entries", ex);
1528         }
1529     }
1530 
1531     /**
1532      * This method is only referenced in unused code and will likely break on
1533      * MySQL if ever used due to the MERGE statement.
1534      * <p>
1535      * Merges CPE entries into the database.
1536      * </p>
1537      *
1538      * @param cpe the CPE identifier
1539      * @param vendor the CPE vendor
1540      * @param product the CPE product
1541      */
1542     public void addCpe(String cpe, String vendor, String product) {
1543         clearCache();
1544         try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, ADD_DICT_CPE)) {
1545             ps.setString(1, cpe);
1546             ps.setString(2, vendor);
1547             ps.setString(3, product);
1548             ps.executeUpdate();
1549         } catch (SQLException ex) {
1550             LOGGER.error("Unable to add CPE dictionary entry", ex);
1551         }
1552     }
1553 
1554     /**
1555      * Returns a map of known exploited vulnerabilities.
1556      *
1557      * @return a map of known exploited vulnerabilities
1558      */
1559     public Map<String, org.owasp.dependencycheck.data.knownexploited.json.Vulnerability> getknownExploitedVulnerabilities() {
1560         final Map<String, org.owasp.dependencycheck.data.knownexploited.json.Vulnerability> known = new HashMap<>();
1561 
1562         try (Connection conn = databaseManager.getConnection();
1563                 PreparedStatement ps = getPreparedStatement(conn, SELECT_KNOWN_EXPLOITED_VULNERABILITIES);
1564                 ResultSet rs = ps.executeQuery()) {
1565 
1566             while (rs.next()) {
1567                 final org.owasp.dependencycheck.data.knownexploited.json.Vulnerability kev =
1568                         new org.owasp.dependencycheck.data.knownexploited.json.Vulnerability();
1569                 kev.setCveID(rs.getString(1));
1570                 kev.setVendorProject(rs.getString(2));
1571                 kev.setProduct(rs.getString(3));
1572                 kev.setVulnerabilityName(rs.getString(4));
1573                 kev.setDateAdded(rs.getString(5));
1574                 kev.setShortDescription(rs.getString(6));
1575                 kev.setRequiredAction(rs.getString(7));
1576                 kev.setDueDate(rs.getString(8));
1577                 kev.setNotes(rs.getString(9));
1578                 known.put(kev.getCveID(), kev);
1579             }
1580 
1581         } catch (SQLException ex) {
1582             throw new DatabaseException(ex);
1583         }
1584         return known;
1585     }
1586 
1587     /**
1588      * Helper method to add a nullable string parameter.
1589      *
1590      * @param ps the prepared statement
1591      * @param pos the position of the parameter
1592      * @param value the value of the parameter
1593      * @throws SQLException thrown if there is an error setting the parameter.
1594      */
1595     private void addNullableStringParameter(PreparedStatement ps, int pos, String value) throws SQLException {
1596         if (value == null || value.isEmpty()) {
1597             ps.setNull(pos, java.sql.Types.VARCHAR);
1598         } else {
1599             ps.setString(pos, value);
1600         }
1601     }
1602 
1603     private void setUpdateColumn(PreparedStatement ps, int i, Double value) throws SQLException {
1604         if (value == null) {
1605             ps.setNull(i, java.sql.Types.DOUBLE);
1606         } else {
1607             ps.setDouble(i, value);
1608         }
1609     }
1610 
1611     private void setUpdateColumn(PreparedStatement ps, int i, CvssV2Data.AuthenticationType value) throws SQLException {
1612         if (value == null) {
1613             ps.setNull(i, java.sql.Types.VARCHAR);
1614         } else {
1615             ps.setString(i, value.value());
1616         }
1617     }
1618 
1619     private void setUpdateColumn(PreparedStatement ps, int i, CvssV2Data.CiaType value) throws SQLException {
1620         if (value == null) {
1621             ps.setNull(i, java.sql.Types.VARCHAR);
1622         } else {
1623             ps.setString(i, value.value());
1624         }
1625     }
1626 
1627     private void setUpdateColumn(PreparedStatement ps, int i, CvssV2Data.Version value) throws SQLException {
1628         if (value == null) {
1629             ps.setNull(i, java.sql.Types.VARCHAR);
1630         } else {
1631             ps.setString(i, value.value());
1632         }
1633     }
1634 
1635     private void setUpdateColumn(PreparedStatement ps, int i, CvssV2Data.AccessComplexityType value) throws SQLException {
1636         if (value == null) {
1637             ps.setNull(i, java.sql.Types.VARCHAR);
1638         } else {
1639             ps.setString(i, value.value());
1640         }
1641     }
1642 
1643     private void setUpdateColumn(PreparedStatement ps, int i, CvssV2Data.AccessVectorType value) throws SQLException {
1644         if (value == null) {
1645             ps.setNull(i, java.sql.Types.VARCHAR);
1646         } else {
1647             ps.setString(i, value.value());
1648         }
1649     }
1650 
1651     private void setUpdateColumn(PreparedStatement ps, int i, String value) throws SQLException {
1652         if (value == null) {
1653             ps.setNull(i, java.sql.Types.VARCHAR);
1654         } else {
1655             ps.setString(i, value);
1656         }
1657     }
1658 
1659     private void setUpdateColumn(PreparedStatement ps, int i, Boolean value) throws SQLException {
1660         if (value == null) {
1661             //TODO this is may also be an issue for MS SQL, if an issue is created we'll just need
1662             // to create an isMsSQL flag. See todo above in updateOrInsertVulnerability.
1663             if (isOracle) {
1664                 ps.setNull(i, java.sql.Types.BIT);
1665             } else {
1666                 ps.setNull(i, java.sql.Types.BOOLEAN);
1667             }
1668         } else {
1669             ps.setBoolean(i, value);
1670         }
1671     }
1672 
1673     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.AttackVectorType value) throws SQLException {
1674         if (value == null) {
1675             ps.setNull(i, java.sql.Types.VARCHAR);
1676         } else {
1677             ps.setString(i, value.value());
1678         }
1679     }
1680 
1681     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.AttackComplexityType value) throws SQLException {
1682         if (value == null) {
1683             ps.setNull(i, java.sql.Types.VARCHAR);
1684         } else {
1685             ps.setString(i, value.value());
1686         }
1687     }
1688 
1689     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.PrivilegesRequiredType value) throws SQLException {
1690         if (value == null) {
1691             ps.setNull(i, java.sql.Types.VARCHAR);
1692         } else {
1693             ps.setString(i, value.value());
1694         }
1695     }
1696 
1697     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.UserInteractionType value) throws SQLException {
1698         if (value == null) {
1699             ps.setNull(i, java.sql.Types.VARCHAR);
1700         } else {
1701             ps.setString(i, value.value());
1702         }
1703     }
1704 
1705     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.ScopeType value) throws SQLException {
1706         if (value == null) {
1707             ps.setNull(i, java.sql.Types.VARCHAR);
1708         } else {
1709             ps.setString(i, value.value());
1710         }
1711     }
1712 
1713     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.SeverityType value) throws SQLException {
1714         if (value == null) {
1715             ps.setNull(i, java.sql.Types.VARCHAR);
1716         } else {
1717             ps.setString(i, value.value());
1718         }
1719     }
1720 
1721     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.CiaType value) throws SQLException {
1722         if (value == null) {
1723             ps.setNull(i, java.sql.Types.VARCHAR);
1724         } else {
1725             ps.setString(i, value.value());
1726         }
1727     }
1728 
1729     private void setUpdateColumn(PreparedStatement ps, int i, CvssV3Data.Version value) throws SQLException {
1730         if (value == null) {
1731             ps.setNull(i, java.sql.Types.VARCHAR);
1732         } else {
1733             ps.setString(i, value.value());
1734         }
1735     }
1736 
1737     /**
1738      * Sets the float parameter on a prepared statement from a properties map.
1739      *
1740      * @param ps a prepared statement
1741      * @param i the index of the property
1742      * @param props the property collection
1743      * @param key the property key
1744      * @throws SQLException thrown if there is an error adding the property
1745      */
1746     private void setFloatValue(PreparedStatement ps, int i, Map<String, Object> props, String key) throws SQLException {
1747         if (props != null && props.containsKey(key)) {
1748             try {
1749                 ps.setFloat(i, Float.parseFloat(props.get(key).toString()));
1750             } catch (NumberFormatException nfe) {
1751                 ps.setNull(i, java.sql.Types.FLOAT);
1752             }
1753         } else {
1754             ps.setNull(i, java.sql.Types.FLOAT);
1755         }
1756     }
1757 
1758     /**
1759      * Sets the string parameter on a prepared statement from a properties map.
1760      *
1761      * @param ps a prepared statement
1762      * @param i the index of the property
1763      * @param props the property collection
1764      * @param key the property key
1765      * @throws SQLException thrown if there is an error adding the property
1766      */
1767     private void setStringValue(PreparedStatement ps, int i, Map<String, Object> props, String key) throws SQLException {
1768         if (props != null && props.containsKey(key)) {
1769             ps.setString(i, props.get(key).toString());
1770         } else {
1771             ps.setNull(i, java.sql.Types.VARCHAR);
1772         }
1773     }
1774 
1775     /**
1776      * Sets the boolean parameter on a prepared statement from a properties map.
1777      *
1778      * @param ps a prepared statement
1779      * @param i the index of the property
1780      * @param props the property collection
1781      * @param key the property key
1782      * @throws SQLException thrown if there is an error adding the property
1783      */
1784     private void setBooleanValue(PreparedStatement ps, int i, Map<String, Object> props, String key) throws SQLException {
1785         if (props != null && props.containsKey(key)) {
1786             ps.setBoolean(i, Boolean.parseBoolean(props.get(key).toString()));
1787         } else {
1788             ps.setNull(i, java.sql.Types.BOOLEAN);
1789         }
1790     }
1791 
1792     /**
1793      * Returns the Boolean value for the given index; if the value is null then
1794      * null is returned.
1795      *
1796      * @param rs the record set
1797      * @param index the parameter index
1798      * @return the Boolean value; or null
1799      * @throws SQLException thrown if there is an error obtaining the value
1800      */
1801     @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
1802     private Boolean getBooleanValue(ResultSet rs, int index) throws SQLException {
1803         if (rs.getObject(index) == null) {
1804             return null;
1805         }
1806         return rs.getBoolean(index);
1807     }
1808 
1809     /**
1810      * Returns the Float value for the given index; if the value is null then
1811      * null is returned.
1812      *
1813      * @param rs the record set
1814      * @param index the parameter index
1815      * @return the Float value; or null
1816      * @throws SQLException thrown if there is an error obtaining the value
1817      */
1818     private Float getFloatValue(ResultSet rs, int index) throws SQLException {
1819         if (rs.getObject(index) == null) {
1820             return null;
1821         }
1822         return rs.getFloat(index);
1823     }
1824 }