1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import com.google.common.annotations.VisibleForTesting;
21 import org.owasp.dependencycheck.Engine;
22 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23 import org.owasp.dependencycheck.dependency.Dependency;
24 import org.owasp.dependencycheck.dependency.Evidence;
25 import org.owasp.dependencycheck.dependency.EvidenceType;
26 import org.owasp.dependencycheck.exception.InitializationException;
27 import org.owasp.dependencycheck.utils.DownloadFailedException;
28 import org.owasp.dependencycheck.utils.Downloader;
29 import org.owasp.dependencycheck.utils.FileUtils;
30 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
31 import org.owasp.dependencycheck.utils.Settings;
32 import org.owasp.dependencycheck.utils.TooManyRequestsException;
33 import org.owasp.dependencycheck.xml.hints.EvidenceMatcher;
34 import org.owasp.dependencycheck.xml.hints.HintParseException;
35 import org.owasp.dependencycheck.xml.hints.HintParser;
36 import org.owasp.dependencycheck.xml.hints.HintRule;
37 import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule;
38 import org.owasp.dependencycheck.xml.suppression.PropertyType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.xml.sax.SAXException;
42
43 import javax.annotation.concurrent.ThreadSafe;
44 import java.io.File;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.net.MalformedURLException;
48 import java.net.URL;
49 import java.nio.file.Files;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.regex.Pattern;
53
54
55
56
57
58
59
60 @ThreadSafe
61 public class HintAnalyzer extends AbstractAnalyzer {
62
63
64
65
66 private static final Logger LOGGER = LoggerFactory.getLogger(HintAnalyzer.class);
67
68
69
70 private static final String HINT_RULE_FILE_NAME = "dependencycheck-base-hint.xml";
71
72
73
74 private HintRule[] hints = null;
75
76
77
78 private VendorDuplicatingHintRule[] vendorHints;
79
80
81
82 private static final String ANALYZER_NAME = "Hint Analyzer";
83
84
85
86 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION2;
87
88
89
90
91
92
93 @Override
94 public String getName() {
95 return ANALYZER_NAME;
96 }
97
98
99
100
101
102
103 @Override
104 public AnalysisPhase getAnalysisPhase() {
105 return ANALYSIS_PHASE;
106 }
107
108
109
110
111
112
113
114 @Override
115 protected String getAnalyzerEnabledSettingKey() {
116 return Settings.KEYS.ANALYZER_HINT_ENABLED;
117 }
118
119
120
121
122
123
124
125 @Override
126 public void prepareAnalyzer(Engine engine) throws InitializationException {
127 try {
128 loadHintRules();
129 } catch (HintParseException ex) {
130 LOGGER.debug("Unable to parse hint file", ex);
131 throw new InitializationException("Unable to parse the hint file", ex);
132 }
133 }
134
135
136
137
138
139
140
141
142
143
144 @Override
145 @SuppressWarnings("StringSplitter")
146 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
147 for (HintRule hint : hints) {
148 boolean matchFound = false;
149 for (EvidenceMatcher given : hint.getGivenVendor()) {
150 if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VENDOR), given)) {
151 matchFound = true;
152 break;
153 }
154 }
155 if (!matchFound) {
156 for (EvidenceMatcher given : hint.getGivenProduct()) {
157 if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.PRODUCT), given)) {
158 matchFound = true;
159 break;
160 }
161 }
162 }
163 if (!matchFound) {
164 for (EvidenceMatcher given : hint.getGivenVersion()) {
165 if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VERSION), given)) {
166 matchFound = true;
167 break;
168 }
169 }
170 }
171 if (!matchFound) {
172 for (PropertyType pt : hint.getFileNames()) {
173 if (pt.matches(dependency.getFileName())) {
174 matchFound = true;
175 break;
176 }
177 }
178 }
179 if (matchFound) {
180 hint.getAddVendor().forEach((e) -> {
181 dependency.addEvidence(EvidenceType.VENDOR, e);
182 for (String weighting : e.getValue().split(" ")) {
183 dependency.addVendorWeighting(weighting);
184 }
185 });
186 hint.getAddProduct().forEach((e) -> {
187 dependency.addEvidence(EvidenceType.PRODUCT, e);
188 for (String weighting : e.getValue().split(" ")) {
189 dependency.addProductWeighting(weighting);
190 }
191 });
192 hint.getAddVersion().forEach((e) -> dependency.addEvidence(EvidenceType.VERSION, e));
193
194 hint.getRemoveVendor().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VENDOR, e));
195 hint.getRemoveProduct().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.PRODUCT, e));
196 hint.getRemoveVersion().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VERSION, e));
197 }
198 }
199
200 for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
201 for (VendorDuplicatingHintRule dhr : vendorHints) {
202 if (dhr.getValue().equalsIgnoreCase(e.getValue())) {
203 dependency.addEvidence(EvidenceType.VENDOR, new Evidence(e.getSource() + " (hint)",
204 e.getName(), dhr.getDuplicate(), e.getConfidence(), true));
205 }
206 }
207 }
208 }
209
210
211
212
213
214
215
216
217 private boolean hasMatchingEvidence(Set<Evidence> evidences, EvidenceMatcher criterion) {
218 for (Evidence evidence : evidences) {
219 if (criterion.matches(evidence)) {
220 return true;
221 }
222 }
223 return false;
224 }
225
226
227
228
229
230
231
232
233 private void removeMatchingEvidences(Dependency dependency, EvidenceType type, EvidenceMatcher e) {
234 for (Evidence evidence : dependency.getEvidence(type)) {
235 if (e.matches(evidence)) {
236 dependency.removeEvidence(type, evidence);
237 }
238 }
239 }
240
241
242
243
244
245
246 private void loadHintRules() throws HintParseException {
247 final List<HintRule> localHints;
248 final List<VendorDuplicatingHintRule> localVendorHints;
249 final HintParser parser = new HintParser();
250 File file = null;
251 try (InputStream in = FileUtils.getResourceAsStream(HINT_RULE_FILE_NAME)) {
252 parser.parseHints(in);
253 } catch (SAXException | IOException ex) {
254 throw new HintParseException("Error parsing hints: " + ex.getMessage(), ex);
255 }
256 localHints = parser.getHintRules();
257 localVendorHints = parser.getVendorDuplicatingHintRules();
258
259 final String filePath = getSettings().getString(Settings.KEYS.HINTS_FILE);
260 if (filePath != null) {
261 boolean deleteTempFile = false;
262 try {
263 final Pattern uriRx = Pattern.compile("^(https?|file):.*", Pattern.CASE_INSENSITIVE);
264 if (uriRx.matcher(filePath).matches()) {
265 deleteTempFile = true;
266 file = getSettings().getTempFile("hint", "xml");
267 final URL url = new URL(filePath);
268 try {
269 Downloader.getInstance().fetchFile(url, file, false);
270 } catch (DownloadFailedException ex) {
271 try {
272 Thread.sleep(500);
273 Downloader.getInstance().fetchFile(url, file, true);
274 } catch (TooManyRequestsException ex1) {
275 throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex1);
276 } catch (ResourceNotFoundException ex1) {
277 throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex1);
278 } catch (InterruptedException ex1) {
279 Thread.currentThread().interrupt();
280 throw new HintParseException("Unable to download hint file `" + file + "`", ex1);
281 }
282 } catch (TooManyRequestsException ex) {
283 throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex);
284 } catch (ResourceNotFoundException ex) {
285 throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex);
286 }
287 } else {
288 file = new File(filePath);
289 if (!file.exists()) {
290 try (InputStream fromClasspath = FileUtils.getResourceAsStream(filePath)) {
291 deleteTempFile = true;
292 file = getSettings().getTempFile("hint", "xml");
293 Files.copy(fromClasspath, file.toPath());
294 } catch (IOException ex) {
295 throw new HintParseException("Unable to locate hints file in classpath", ex);
296 }
297 }
298 }
299
300 try {
301 parser.parseHints(file);
302 if (parser.getHintRules() != null && !parser.getHintRules().isEmpty()) {
303 localHints.addAll(parser.getHintRules());
304 }
305 if (parser.getVendorDuplicatingHintRules() != null && !parser.getVendorDuplicatingHintRules().isEmpty()) {
306 localVendorHints.addAll(parser.getVendorDuplicatingHintRules());
307 }
308 } catch (HintParseException ex) {
309 LOGGER.warn("Unable to parse hint rule xml file '{}'", file.getPath());
310 LOGGER.warn(ex.getMessage());
311 LOGGER.debug("", ex);
312 throw ex;
313 }
314 } catch (DownloadFailedException ex) {
315 throw new HintParseException("Unable to fetch the configured hint file", ex);
316 } catch (MalformedURLException ex) {
317 throw new HintParseException("Configured hint file has an invalid URL", ex);
318 } catch (IOException ex) {
319 throw new HintParseException("Unable to create temp file for hints", ex);
320 } finally {
321 if (deleteTempFile && file != null) {
322 FileUtils.delete(file);
323 }
324 }
325 }
326 hints = localHints.toArray(new HintRule[0]);
327 vendorHints = localVendorHints.toArray(new VendorDuplicatingHintRule[0]);
328 LOGGER.debug("{} hint rules were loaded.", hints.length);
329 LOGGER.debug("{} duplicating hint rules were loaded.", vendorHints.length);
330 }
331
332 @VisibleForTesting
333 HintRule[] getHintRules() {
334 return hints;
335 }
336
337 @VisibleForTesting
338 VendorDuplicatingHintRule[] getVendorDuplicatingHintRules() {
339 return vendorHints;
340 }
341 }