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