1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.update;
19
20 import org.owasp.dependencycheck.Engine;
21 import org.owasp.dependencycheck.data.update.exception.UpdateException;
22 import org.owasp.dependencycheck.exception.WriteLockException;
23 import org.owasp.dependencycheck.utils.Downloader;
24 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
25 import org.owasp.dependencycheck.utils.Settings;
26 import org.owasp.dependencycheck.utils.TooManyRequestsException;
27 import org.owasp.dependencycheck.utils.WriteLock;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.nio.file.Files;
36
37 public class HostedSuppressionsDataSource extends LocalDataSource {
38
39
40
41
42 private static final Logger LOGGER = LoggerFactory.getLogger(HostedSuppressionsDataSource.class);
43
44
45
46
47 private Settings settings;
48
49
50
51 public static final String DEFAULT_SUPPRESSIONS_URL = "https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml";
52
53
54
55
56
57
58
59
60
61 @Override
62 public boolean update(Engine engine) throws UpdateException {
63 this.settings = engine.getSettings();
64 final String configuredUrl = settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, DEFAULT_SUPPRESSIONS_URL);
65 final boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
66 final boolean forceupdate = settings.getBoolean(Settings.KEYS.HOSTED_SUPPRESSIONS_FORCEUPDATE, false);
67 final boolean cpeSuppressionEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_CPE_SUPPRESSION_ENABLED, true);
68 final boolean vulnSuppressionEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_VULNERABILITY_SUPPRESSION_ENABLED, true);
69 boolean enabled = settings.getBoolean(Settings.KEYS.HOSTED_SUPPRESSIONS_ENABLED, true);
70 enabled = enabled && (cpeSuppressionEnabled || vulnSuppressionEnabled);
71 try {
72 final URL url = new URL(configuredUrl);
73 final File filepath = new File(url.getPath());
74 final File repoFile = new File(settings.getDataDirectory(), filepath.getName());
75 final boolean proceed = enabled && (forceupdate || (autoupdate && shouldUpdate(repoFile)));
76 if (proceed) {
77 LOGGER.debug("Begin Hosted Suppressions file update");
78 fetchHostedSuppressions(settings, url, repoFile);
79 saveLastUpdated(repoFile, System.currentTimeMillis() / 1000);
80 }
81 } catch (UpdateException ex) {
82
83 LOGGER.warn("Failed to update hosted suppressions file, results may contain false positives already resolved by the "
84 + "DependencyCheck project", ex);
85 } catch (MalformedURLException ex) {
86 throw new UpdateException(String.format("Invalid URL for Hosted Suppressions file (%s)", configuredUrl), ex);
87 } catch (IOException ex) {
88 throw new UpdateException("Unable to get the data directory", ex);
89 }
90 return false;
91 }
92
93
94
95
96
97
98
99
100
101
102 protected boolean shouldUpdate(File repo) throws NumberFormatException {
103 boolean proceed = true;
104 if (repo != null && repo.isFile()) {
105 final int validForHours = settings.getInt(Settings.KEYS.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS, 2);
106 final long lastUpdatedOn = getLastUpdated(repo);
107 final long now = System.currentTimeMillis();
108 LOGGER.debug("Last updated: {}", lastUpdatedOn);
109 LOGGER.debug("Now: {}", now);
110 final long msValid = validForHours * 60L * 60L * 1000L;
111 proceed = (now - lastUpdatedOn) > msValid;
112 if (!proceed) {
113 LOGGER.info("Skipping Hosted Suppressions file update since last update was within {} hours.", validForHours);
114 }
115 }
116 return proceed;
117 }
118
119
120
121
122
123
124
125
126
127
128
129 @SuppressWarnings("try")
130 private void fetchHostedSuppressions(Settings settings, URL repoUrl, File repoFile) throws UpdateException {
131 try (WriteLock ignored = new WriteLock(settings, true, repoFile.getName() + ".lock")) {
132 if (LOGGER.isDebugEnabled()) {
133 LOGGER.debug("Hosted Suppressions URL: {}", repoUrl.toExternalForm());
134 }
135 Downloader.getInstance().fetchFile(repoUrl, repoFile);
136 } catch (IOException | TooManyRequestsException | ResourceNotFoundException | WriteLockException ex) {
137 throw new UpdateException("Failed to update the hosted suppressions file", ex);
138 }
139 }
140
141 @Override
142 @SuppressWarnings("try")
143 public boolean purge(Engine engine) {
144 this.settings = engine.getSettings();
145 boolean result = true;
146 try {
147 final URL repoUrl = new URL(settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL,
148 DEFAULT_SUPPRESSIONS_URL));
149 final String filename = new File(repoUrl.getPath()).getName();
150 final File repo = new File(settings.getDataDirectory(), filename);
151 if (repo.exists()) {
152 try (WriteLock ignored = new WriteLock(settings, true, filename + ".lock")) {
153 result = deleteCachedFile(repo);
154 }
155 }
156 } catch (WriteLockException | IOException ex) {
157 LOGGER.error("Unable to delete the Hosted suppression file - invalid configuration");
158 result = false;
159 }
160 return result;
161 }
162
163 private boolean deleteCachedFile(final File repo) {
164 boolean deleted = true;
165 try {
166 Files.delete(repo.toPath());
167 LOGGER.info("Hosted suppression file removed successfully");
168 } catch (IOException ex) {
169 LOGGER.error("Unable to delete '{}'; please delete the file manually", repo.getAbsolutePath(), ex);
170 deleted = false;
171 }
172 return deleted;
173 }
174 }