-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
RetireJSDataSource.java
182 lines (173 loc) · 7.81 KB
/
RetireJSDataSource.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2018 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.data.update;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.annotation.concurrent.ThreadSafe;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.exception.WriteLockException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.ResourceNotFoundException;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.TooManyRequestsException;
import org.owasp.dependencycheck.utils.WriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Downloads a local copy of the RetireJS repository.
*
* @author Jeremy Long
*/
@ThreadSafe
public class RetireJSDataSource implements CachedWebDataSource {
/**
* Static logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(RetireJSDataSource.class);
/**
* The property key indicating when the last update occurred.
*/
public static final String RETIREJS_UPDATED_ON = "RetireJSUpdatedOn";
/**
* The configured settings.
*/
private Settings settings;
/**
* The properties obtained from the database.
*/
private DatabaseProperties dbProperties = null;
/**
* The default URL to the RetireJS JavaScript repository.
*/
public static final String DEFAULT_JS_URL = "https://raw.githubusercontent.com/Retirejs/retire.js/master/repository/jsrepository.json";
/**
* Constructs a new engine version check utility.
*/
public RetireJSDataSource() {
}
/**
* Downloads the current RetireJS data source.
*
* @param engine a reference to the ODC Engine
* @return returns false as no updates are made to the database
* @throws UpdateException thrown if the update failed
*/
@Override
public boolean update(Engine engine) throws UpdateException {
this.settings = engine.getSettings();
this.dbProperties = engine.getDatabase().getDatabaseProperties();
final String configuredUrl = settings.getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, DEFAULT_JS_URL);
final boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
final boolean forceupdate = settings.getBoolean(Settings.KEYS.ANALYZER_RETIREJS_FORCEUPDATE, false);
final boolean enabled = settings.getBoolean(Settings.KEYS.ANALYZER_RETIREJS_ENABLED, true);
try {
final URL url = new URL(configuredUrl);
final File filepath = new File(url.getPath());
final File repoFile = new File(settings.getDataDirectory(), filepath.getName());
final boolean proceed = enabled && (forceupdate || (autoupdate && shouldUpdate(repoFile)));
if (proceed) {
LOGGER.debug("Begin RetireJS Update");
initializeRetireJsRepo(settings, url, repoFile);
dbProperties.save(DatabaseProperties.RETIRE_LAST_CHECKED, Long.toString(System.currentTimeMillis() / 1000));
}
} catch (MalformedURLException ex) {
throw new UpdateException(String.format("Invalid URL for RetireJS repository (%s)", configuredUrl), ex);
} catch (IOException ex) {
throw new UpdateException("Unable to get the data directory", ex);
}
return false;
}
/**
* Determines if the we should update the RetireJS database.
*
* @param repo the retire JS repository.
* @return <code>true</code> if an updated to the RetireJS database should
* be performed; otherwise <code>false</code>
* @throws NumberFormatException thrown if an invalid value is contained in
* the database properties
*/
protected boolean shouldUpdate(File repo) throws NumberFormatException {
boolean proceed = true;
if (repo != null && repo.isFile()) {
final int validForHours = settings.getInt(Settings.KEYS.ANALYZER_RETIREJS_REPO_VALID_FOR_HOURS, 0);
long lastUpdatedOn = dbProperties.getPropertyInSeconds(DatabaseProperties.RETIRE_LAST_CHECKED);
if (lastUpdatedOn <= 0) {
//fall back on conversion from file last modified to storing in the db.
lastUpdatedOn = repo.lastModified();
}
final long now = System.currentTimeMillis();
LOGGER.debug("Last updated: {}", lastUpdatedOn);
LOGGER.debug("Now: {}", now);
final long msValid = validForHours * 60L * 60L * 1000L;
proceed = (now - lastUpdatedOn) > msValid;
if (!proceed) {
LOGGER.info("Skipping RetireJS update since last update was within {} hours.", validForHours);
}
}
return proceed;
}
/**
* Initializes the local RetireJS repository
*
* @param settings a reference to the dependency-check settings
* @param repoUrl the URL to the RetireJS repository to use
* @param repoFile the filename to use for the RetireJS repository
* @throws UpdateException thrown if there is an exception during
* initialization
*/
@SuppressWarnings("try")
private void initializeRetireJsRepo(Settings settings, URL repoUrl, File repoFile) throws UpdateException {
try (WriteLock lock = new WriteLock(settings, true, repoFile.getName() + ".lock")) {
LOGGER.debug("RetireJS Repo URL: {}", repoUrl.toExternalForm());
final Downloader downloader = new Downloader(settings);
downloader.fetchFile(repoUrl, repoFile, Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_USER, Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_PASSWORD);
} catch (IOException | TooManyRequestsException | ResourceNotFoundException | WriteLockException ex) {
throw new UpdateException("Failed to initialize the RetireJS repo", ex);
}
}
@Override
@SuppressWarnings("try")
public boolean purge(Engine engine) {
this.settings = engine.getSettings();
boolean result = true;
try {
final File dataDir = engine.getSettings().getDataDirectory();
final URL repoUrl = new URL(engine.getSettings().getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, DEFAULT_JS_URL));
final String filename = repoUrl.getFile().substring(repoUrl.getFile().lastIndexOf("/") + 1);
final File repo = new File(dataDir, filename);
if (repo.exists()) {
try (WriteLock lock = new WriteLock(settings, true, filename + ".lock")) {
if (repo.delete()) {
LOGGER.info("RetireJS repo removed successfully");
} else {
LOGGER.error("Unable to delete '{}'; please delete the file manually", repo.getAbsolutePath());
result = false;
}
}
}
} catch (WriteLockException | IOException ex) {
LOGGER.error("Unable to delete the RetireJS repo - invalid configuration");
result = false;
}
return result;
}
}