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) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.update;
19  
20  import java.io.IOException;
21  import java.net.HttpURLConnection;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import org.apache.commons.io.IOUtils;
25  import org.owasp.dependencycheck.data.nvdcve.CveDB;
26  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
27  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
28  import org.owasp.dependencycheck.data.update.exception.UpdateException;
29  import org.owasp.dependencycheck.utils.DateUtil;
30  import org.owasp.dependencycheck.utils.DependencyVersion;
31  import org.owasp.dependencycheck.utils.Settings;
32  import org.owasp.dependencycheck.utils.URLConnectionFactory;
33  import org.owasp.dependencycheck.utils.URLConnectionFailureException;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   *
39   * @author Jeremy Long
40   */
41  public class EngineVersionCheck implements CachedWebDataSource {
42  
43      /**
44       * Static logger.
45       */
46      private static final Logger LOGGER = LoggerFactory.getLogger(EngineVersionCheck.class);
47      /**
48       * The property key indicating when the last version check occurred.
49       */
50      public static final String ENGINE_VERSION_CHECKED_ON = "VersionCheckOn";
51      /**
52       * The property key indicating when the last version check occurred.
53       */
54      public static final String CURRENT_ENGINE_RELEASE = "CurrentEngineRelease";
55      /**
56       * Reference to the Cve Database.
57       */
58      private CveDB cveDB = null;
59  
60      /**
61       * The version retrieved from the database properties or web to check against.
62       */
63      private String updateToVersion;
64  
65      /**
66       * Getter for updateToVersion - only used for testing. Represents the version retrieved from the database.
67       *
68       * @return the version to test
69       */
70      protected String getUpdateToVersion() {
71          return updateToVersion;
72      }
73  
74      /**
75       * Setter for updateToVersion - only used for testing. Represents the version retrieved from the database.
76       *
77       * @param version the version to test
78       */
79      protected void setUpdateToVersion(String version) {
80          updateToVersion = version;
81      }
82  
83      @Override
84      public void update() throws UpdateException {
85          try {
86              openDatabase();
87              LOGGER.debug("Begin Engine Version Check");
88              final DatabaseProperties properties = cveDB.getDatabaseProperties();
89              final long lastChecked = Long.parseLong(properties.getProperty(ENGINE_VERSION_CHECKED_ON, "0"));
90              final long now = System.currentTimeMillis();
91              updateToVersion = properties.getProperty(CURRENT_ENGINE_RELEASE, "");
92              final String currentVersion = Settings.getString(Settings.KEYS.APPLICATION_VERSION, "0.0.0");
93              LOGGER.debug("Last checked: {}", lastChecked);
94              LOGGER.debug("Now: {}", now);
95              LOGGER.debug("Current version: {}", currentVersion);
96              final boolean updateNeeded = shouldUpdate(lastChecked, now, properties, currentVersion);
97              if (updateNeeded) {
98                  LOGGER.warn("A new version of dependency-check is available. Consider updating to version {}.",
99                          updateToVersion);
100             }
101         } catch (DatabaseException ex) {
102             LOGGER.debug("Database Exception opening databases to retrieve properties", ex);
103             throw new UpdateException("Error occured updating database properties.");
104         } finally {
105             closeDatabase();
106         }
107     }
108 
109     /**
110      * Determines if a new version of the dependency-check engine has been released.
111      *
112      * @param lastChecked the epoch time of the last version check
113      * @param now the current epoch time
114      * @param properties the database properties object
115      * @param currentVersion the current version of dependency-check
116      * @return <code>true</code> if a newer version of the database has been released; otherwise <code>false</code>
117      * @throws UpdateException thrown if there is an error connecting to the github documentation site or accessing the local
118      * database.
119      */
120     protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties,
121             String currentVersion) throws UpdateException {
122         //check every 30 days if we know there is an update, otherwise check every 7 days
123         int checkRange = 30;
124         if (updateToVersion.isEmpty()) {
125             checkRange = 7;
126         }
127         if (!DateUtil.withinDateRange(lastChecked, now, checkRange)) {
128             LOGGER.debug("Checking web for new version.");
129             final String currentRelease = getCurrentReleaseVersion();
130             if (currentRelease != null) {
131                 final DependencyVersion v = new DependencyVersion(currentRelease);
132                 if (v.getVersionParts() != null && v.getVersionParts().size() >= 3) {
133                     updateToVersion = v.toString();
134                     if (!currentRelease.equals(updateToVersion)) {
135                         properties.save(CURRENT_ENGINE_RELEASE, updateToVersion);
136                     } else {
137                         properties.save(CURRENT_ENGINE_RELEASE, "");
138                     }
139                     properties.save(ENGINE_VERSION_CHECKED_ON, Long.toString(now));
140                 }
141             }
142             LOGGER.debug("Current Release: {}", updateToVersion);
143         }
144         final DependencyVersion running = new DependencyVersion(currentVersion);
145         final DependencyVersion released = new DependencyVersion(updateToVersion);
146         if (running.compareTo(released) < 0) {
147             LOGGER.debug("Upgrade recommended");
148             return true;
149         }
150         LOGGER.debug("Upgrade not needed");
151         return false;
152     }
153 
154     /**
155      * Opens the CVE and CPE data stores.
156      *
157      * @throws DatabaseException thrown if a data store cannot be opened
158      */
159     protected final void openDatabase() throws DatabaseException {
160         if (cveDB != null) {
161             return;
162         }
163         cveDB = new CveDB();
164         cveDB.open();
165     }
166 
167     /**
168      * Closes the CVE and CPE data stores.
169      */
170     protected void closeDatabase() {
171         if (cveDB != null) {
172             try {
173                 cveDB.close();
174                 cveDB = null;
175             } catch (Throwable ignore) {
176                 LOGGER.trace("Error closing the cveDB", ignore);
177             }
178         }
179     }
180 
181     /**
182      * Retrieves the current released version number from the github documentation site.
183      *
184      * @return the current released version number
185      */
186     protected String getCurrentReleaseVersion() {
187         HttpURLConnection conn = null;
188         try {
189             final String str = Settings.getString(Settings.KEYS.ENGINE_VERSION_CHECK_URL, "http://jeremylong.github.io/DependencyCheck/current.txt");
190             final URL url = new URL(str);
191             conn = URLConnectionFactory.createHttpURLConnection(url);
192             conn.connect();
193             if (conn.getResponseCode() != 200) {
194                 return null;
195             }
196             final String releaseVersion = IOUtils.toString(conn.getInputStream(), "UTF-8");
197             if (releaseVersion != null) {
198                 return releaseVersion.trim();
199             }
200         } catch (MalformedURLException ex) {
201             LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
202         } catch (URLConnectionFailureException ex) {
203             LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
204         } catch (IOException ex) {
205             LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
206         } finally {
207             if (conn != null) {
208                 conn.disconnect();
209             }
210         }
211         return null;
212     }
213 }