Coverage Report - org.owasp.dependencycheck.data.update.StandardUpdate
 
Classes in this File Line Coverage Branch Coverage Complexity
StandardUpdate
0%
0/140
0%
0/52
6.875
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Dependency-check-core is free software: you can redistribute it and/or modify it
 5  
  * under the terms of the GNU General Public License as published by the Free
 6  
  * Software Foundation, either version 3 of the License, or (at your option) any
 7  
  * later version.
 8  
  *
 9  
  * Dependency-check-core is distributed in the hope that it will be useful, but
 10  
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  
  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 12  
  * details.
 13  
  *
 14  
  * You should have received a copy of the GNU General Public License along with
 15  
  * dependency-check-core. If not, see http://www.gnu.org/licenses/.
 16  
  *
 17  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 18  
  */
 19  
 package org.owasp.dependencycheck.data.update;
 20  
 
 21  
 import org.owasp.dependencycheck.data.update.task.ProcessTask;
 22  
 import org.owasp.dependencycheck.data.update.task.CallableDownloadTask;
 23  
 import org.owasp.dependencycheck.data.update.exception.UpdateException;
 24  
 import org.owasp.dependencycheck.data.update.exception.InvalidDataException;
 25  
 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
 26  
 import java.net.MalformedURLException;
 27  
 import java.util.Calendar;
 28  
 import java.util.Date;
 29  
 import java.util.HashSet;
 30  
 import java.util.Set;
 31  
 import java.util.concurrent.ExecutionException;
 32  
 import java.util.concurrent.ExecutorService;
 33  
 import java.util.concurrent.Executors;
 34  
 import java.util.concurrent.Future;
 35  
 import java.util.logging.Level;
 36  
 import java.util.logging.Logger;
 37  
 import org.owasp.dependencycheck.data.nvdcve.CveDB;
 38  
 import org.owasp.dependencycheck.utils.DownloadFailedException;
 39  
 import org.owasp.dependencycheck.utils.Settings;
 40  
 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
 41  
 import org.owasp.dependencycheck.utils.InvalidSettingException;
 42  
 import static org.owasp.dependencycheck.data.nvdcve.DatabaseProperties.MODIFIED;
 43  
 
 44  
 /**
 45  
  * Class responsible for updating the NVDCVE data store.
 46  
  *
 47  
  * @author Jeremy Long <jeremy.long@owasp.org>
 48  
  */
 49  
 public class StandardUpdate {
 50  
 
 51  
     /**
 52  
      * The max thread pool size to use when downloading files.
 53  
      */
 54  0
     public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3);
 55  
     /**
 56  
      * Information about the timestamps and URLs for data that needs to be
 57  
      * updated.
 58  
      */
 59  
     private DatabaseProperties properties;
 60  
     /**
 61  
      * A collection of updateable NVD CVE items.
 62  
      */
 63  
     private UpdateableNvdCve updateable;
 64  
     /**
 65  
      * Reference to the Cve Database.
 66  
      */
 67  0
     private CveDB cveDB = null;
 68  
 
 69  
     /**
 70  
      * Gets whether or not an update is needed.
 71  
      *
 72  
      * @return true or false depending on whether an update is needed
 73  
      */
 74  
     public boolean isUpdateNeeded() {
 75  0
         return updateable.isUpdateNeeded();
 76  
     }
 77  
 
 78  
     /**
 79  
      * Constructs a new Standard Update Task.
 80  
      *
 81  
      * @throws MalformedURLException thrown if a configured URL is malformed
 82  
      * @throws DownloadFailedException thrown if a timestamp cannot be checked
 83  
      * on a configured URL
 84  
      * @throws UpdateException thrown if there is an exception generating the
 85  
      * update task
 86  
      */
 87  0
     public StandardUpdate() throws MalformedURLException, DownloadFailedException, UpdateException {
 88  0
         openDataStores();
 89  0
         properties = cveDB.getDatabaseProperties();
 90  0
         updateable = updatesNeeded();
 91  0
     }
 92  
 
 93  
     /**
 94  
      * <p>Downloads the latest NVD CVE XML file from the web and imports it into
 95  
      * the current CVE Database.</p>
 96  
      *
 97  
      * @throws UpdateException is thrown if there is an error updating the
 98  
      * database
 99  
      */
 100  
     public void update() throws UpdateException {
 101  0
         int maxUpdates = 0;
 102  
         try {
 103  0
             for (NvdCveInfo cve : updateable) {
 104  0
                 if (cve.getNeedsUpdate()) {
 105  0
                     maxUpdates += 1;
 106  
                 }
 107  0
             }
 108  0
             if (maxUpdates <= 0) {
 109  
                 return;
 110  
             }
 111  0
             if (maxUpdates > 3) {
 112  0
                 Logger.getLogger(StandardUpdate.class.getName()).log(Level.INFO,
 113  
                         "NVD CVE requires several updates; this could take a couple of minutes.");
 114  
             }
 115  0
             if (maxUpdates > 0) {
 116  0
                 openDataStores();
 117  
             }
 118  
 
 119  0
             final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
 120  
 
 121  0
             final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
 122  0
             final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
 123  0
             final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
 124  0
             for (NvdCveInfo cve : updateable) {
 125  0
                 if (cve.getNeedsUpdate()) {
 126  0
                     final CallableDownloadTask call = new CallableDownloadTask(cve, processExecutor, cveDB);
 127  0
                     downloadFutures.add(downloadExecutors.submit(call));
 128  
                 }
 129  0
             }
 130  0
             downloadExecutors.shutdown();
 131  
 
 132  
             //next, move the future future processTasks to just future processTasks
 133  0
             final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
 134  0
             for (Future<Future<ProcessTask>> future : downloadFutures) {
 135  0
                 Future<ProcessTask> task = null;
 136  
                 try {
 137  0
                     task = future.get();
 138  0
                 } catch (InterruptedException ex) {
 139  0
                     downloadExecutors.shutdownNow();
 140  0
                     processExecutor.shutdownNow();
 141  
 
 142  0
                     Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Thread was interupted during download", ex);
 143  0
                     throw new UpdateException("The download was interupted", ex);
 144  0
                 } catch (ExecutionException ex) {
 145  0
                     downloadExecutors.shutdownNow();
 146  0
                     processExecutor.shutdownNow();
 147  
 
 148  0
                     Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Thread was interupted during download execution", ex);
 149  0
                     throw new UpdateException("The execution of the download was interupted", ex);
 150  0
                 }
 151  0
                 if (task == null) {
 152  0
                     downloadExecutors.shutdownNow();
 153  0
                     processExecutor.shutdownNow();
 154  0
                     Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Thread was interupted during download");
 155  0
                     throw new UpdateException("The download was interupted; unable to complete the update");
 156  
                 } else {
 157  0
                     processFutures.add(task);
 158  
                 }
 159  0
             }
 160  
 
 161  0
             for (Future<ProcessTask> future : processFutures) {
 162  
                 try {
 163  0
                     final ProcessTask task = future.get();
 164  0
                     if (task.getException() != null) {
 165  0
                         throw task.getException();
 166  
                     }
 167  0
                 } catch (InterruptedException ex) {
 168  0
                     processExecutor.shutdownNow();
 169  0
                     Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Thread was interupted during processing", ex);
 170  0
                     throw new UpdateException(ex);
 171  0
                 } catch (ExecutionException ex) {
 172  0
                     processExecutor.shutdownNow();
 173  0
                     Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Execution Exception during process", ex);
 174  0
                     throw new UpdateException(ex);
 175  
                 } finally {
 176  0
                     processExecutor.shutdown();
 177  0
                 }
 178  0
             }
 179  
 
 180  0
             if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it)
 181  0
                 properties.save(updateable.get(MODIFIED));
 182  0
                 cveDB.cleanupDatabase();
 183  
             }
 184  
         } finally {
 185  0
             closeDataStores();
 186  0
         }
 187  0
     }
 188  
 
 189  
     /**
 190  
      * Determines if the index needs to be updated. This is done by fetching the
 191  
      * NVD CVE meta data and checking the last update date. If the data needs to
 192  
      * be refreshed this method will return the NvdCveUrl for the files that
 193  
      * need to be updated.
 194  
      *
 195  
      * @return the collection of files that need to be updated
 196  
      * @throws MalformedURLException is thrown if the URL for the NVD CVE Meta
 197  
      * data is incorrect
 198  
      * @throws DownloadFailedException is thrown if there is an error.
 199  
      * downloading the NVD CVE download data file
 200  
      * @throws UpdateException Is thrown if there is an issue with the last
 201  
      * updated properties file
 202  
      */
 203  
     protected final UpdateableNvdCve updatesNeeded() throws MalformedURLException, DownloadFailedException, UpdateException {
 204  0
         UpdateableNvdCve updates = null;
 205  
         try {
 206  0
             updates = retrieveCurrentTimestampsFromWeb();
 207  0
         } catch (InvalidDataException ex) {
 208  0
             final String msg = "Unable to retrieve valid timestamp from nvd cve downloads page";
 209  0
             Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, msg, ex);
 210  0
             throw new DownloadFailedException(msg, ex);
 211  0
         } catch (InvalidSettingException ex) {
 212  0
             Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Invalid setting found when retrieving timestamps", ex);
 213  0
             throw new DownloadFailedException("Invalid settings", ex);
 214  0
         }
 215  
 
 216  0
         if (updates == null) {
 217  0
             throw new DownloadFailedException("Unable to retrieve the timestamps of the currently published NVD CVE data");
 218  
         }
 219  0
         if (!properties.isEmpty()) {
 220  
             try {
 221  0
                 final long lastUpdated = Long.parseLong(properties.getProperty(DatabaseProperties.LAST_UPDATED, "0"));
 222  0
                 final Date now = new Date();
 223  0
                 final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
 224  0
                 if (lastUpdated == updates.getTimeStamp(MODIFIED)) {
 225  0
                     updates.clear(); //we don't need to update anything.
 226  0
                 } else if (withinRange(lastUpdated, now.getTime(), days)) {
 227  0
                     for (NvdCveInfo entry : updates) {
 228  0
                         if (MODIFIED.equals(entry.getId())) {
 229  0
                             entry.setNeedsUpdate(true);
 230  
                         } else {
 231  0
                             entry.setNeedsUpdate(false);
 232  
                         }
 233  0
                     }
 234  
                 } else { //we figure out which of the several XML files need to be downloaded.
 235  0
                     for (NvdCveInfo entry : updates) {
 236  0
                         if (MODIFIED.equals(entry.getId())) {
 237  0
                             entry.setNeedsUpdate(true);
 238  
                         } else {
 239  0
                             long currentTimestamp = 0;
 240  
                             try {
 241  0
                                 currentTimestamp = Long.parseLong(properties.getProperty(DatabaseProperties.LAST_UPDATED_BASE + entry.getId(), "0"));
 242  0
                             } catch (NumberFormatException ex) {
 243  0
                                 final String msg = String.format("Error parsing '%s' '%s' from nvdcve.lastupdated",
 244  
                                         DatabaseProperties.LAST_UPDATED_BASE, entry.getId());
 245  0
                                 Logger
 246  
                                         .getLogger(StandardUpdate.class
 247  
                                         .getName()).log(Level.FINE, msg, ex);
 248  0
                             }
 249  0
                             if (currentTimestamp == entry.getTimestamp()) {
 250  0
                                 entry.setNeedsUpdate(false);
 251  
                             }
 252  
                         }
 253  0
                     }
 254  
                 }
 255  0
             } catch (NumberFormatException ex) {
 256  0
                 final String msg = "An invalid schema version or timestamp exists in the data.properties file.";
 257  0
                 Logger
 258  
                         .getLogger(StandardUpdate.class
 259  
                         .getName()).log(Level.WARNING, msg);
 260  0
                 Logger.getLogger(StandardUpdate.class
 261  
                         .getName()).log(Level.FINE, null, ex);
 262  0
             }
 263  
         }
 264  0
         return updates;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Retrieves the timestamps from the NVD CVE meta data file.
 269  
      *
 270  
      * @return the timestamp from the currently published nvdcve downloads page
 271  
      * @throws MalformedURLException thrown if the URL for the NVD CCE Meta data
 272  
      * is incorrect.
 273  
      * @throws DownloadFailedException thrown if there is an error downloading
 274  
      * the nvd cve meta data file
 275  
      * @throws InvalidDataException thrown if there is an exception parsing the
 276  
      * timestamps
 277  
      * @throws InvalidSettingException thrown if the settings are invalid
 278  
      */
 279  
     private UpdateableNvdCve retrieveCurrentTimestampsFromWeb()
 280  
             throws MalformedURLException, DownloadFailedException, InvalidDataException, InvalidSettingException {
 281  
 
 282  0
         final UpdateableNvdCve updates = new UpdateableNvdCve();
 283  0
         updates.add(MODIFIED, Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL),
 284  
                 Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL),
 285  
                 false);
 286  
 
 287  0
         final int start = Settings.getInt(Settings.KEYS.CVE_START_YEAR);
 288  0
         final int end = Calendar.getInstance().get(Calendar.YEAR);
 289  0
         final String baseUrl20 = Settings.getString(Settings.KEYS.CVE_SCHEMA_2_0);
 290  0
         final String baseUrl12 = Settings.getString(Settings.KEYS.CVE_SCHEMA_1_2);
 291  0
         for (int i = start; i <= end; i++) {
 292  0
             updates.add(Integer.toString(i), String.format(baseUrl20, i),
 293  
                     String.format(baseUrl12, i),
 294  
                     true);
 295  
         }
 296  
 
 297  0
         return updates;
 298  
     }
 299  
 
 300  
     /**
 301  
      * Closes the CVE and CPE data stores.
 302  
      */
 303  
     protected void closeDataStores() {
 304  0
         if (cveDB != null) {
 305  
             try {
 306  0
                 cveDB.close();
 307  0
             } catch (Exception ignore) {
 308  0
                 Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINEST, "Error closing the cveDB", ignore);
 309  0
             }
 310  
         }
 311  0
     }
 312  
 
 313  
     /**
 314  
      * Opens the CVE and CPE data stores.
 315  
      *
 316  
      * @throws UpdateException thrown if a data store cannot be opened
 317  
      */
 318  
     protected final void openDataStores() throws UpdateException {
 319  0
         if (cveDB != null) {
 320  0
             return;
 321  
         }
 322  
         try {
 323  0
             cveDB = new CveDB();
 324  0
             cveDB.open();
 325  0
         } catch (DatabaseException ex) {
 326  0
             closeDataStores();
 327  0
             Logger.getLogger(StandardUpdate.class.getName()).log(Level.FINE, "Database Exception opening databases", ex);
 328  0
             throw new UpdateException("Error updating the CPE/CVE data, please see the log file for more details.");
 329  0
         }
 330  0
     }
 331  
 
 332  
     /**
 333  
      * Determines if the epoch date is within the range specified of the
 334  
      * compareTo epoch time. This takes the (compareTo-date)/1000/60/60/24 to
 335  
      * get the number of days. If the calculated days is less then the range the
 336  
      * date is considered valid.
 337  
      *
 338  
      * @param date the date to be checked.
 339  
      * @param compareTo the date to compare to.
 340  
      * @param range the range in days to be considered valid.
 341  
      * @return whether or not the date is within the range.
 342  
      */
 343  
     protected boolean withinRange(long date, long compareTo, int range) {
 344  0
         final double differenceInDays = (compareTo - date) / 1000.0 / 60.0 / 60.0 / 24.0;
 345  0
         return differenceInDays < range;
 346  
     }
 347  
 }