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 java.io.File;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.ListIterator;
24 import java.util.Set;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 import org.owasp.dependencycheck.Engine;
28 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.Identifier;
31 import org.owasp.dependencycheck.utils.DependencyVersion;
32 import org.owasp.dependencycheck.utils.DependencyVersionUtil;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36
37
38
39
40
41
42
43
44
45
46 public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Analyzer {
47
48
49
50
51 private static final Logger LOGGER = LoggerFactory.getLogger(DependencyBundlingAnalyzer.class);
52
53
54
55
56
57 private static final Pattern STARTING_TEXT_PATTERN = Pattern.compile("^[a-zA-Z0-9]*");
58
59
60
61 private boolean analyzed = false;
62
63
64
65
66
67 private static final String ANALYZER_NAME = "Dependency Bundling Analyzer";
68
69
70
71 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.PRE_FINDING_ANALYSIS;
72
73
74
75
76
77
78 @Override
79 public String getName() {
80 return ANALYZER_NAME;
81 }
82
83
84
85
86
87
88 @Override
89 public AnalysisPhase getAnalysisPhase() {
90 return ANALYSIS_PHASE;
91 }
92
93
94
95
96
97
98
99
100
101
102 @Override
103 public void analyze(Dependency ignore, Engine engine) throws AnalysisException {
104 if (!analyzed) {
105 analyzed = true;
106 final Set<Dependency> dependenciesToRemove = new HashSet<Dependency>();
107 final ListIterator<Dependency> mainIterator = engine.getDependencies().listIterator();
108
109 while (mainIterator.hasNext()) {
110 final Dependency dependency = mainIterator.next();
111 if (mainIterator.hasNext() && !dependenciesToRemove.contains(dependency)) {
112 final ListIterator<Dependency> subIterator = engine.getDependencies().listIterator(mainIterator.nextIndex());
113 while (subIterator.hasNext()) {
114 final Dependency nextDependency = subIterator.next();
115 if (hashesMatch(dependency, nextDependency) && !containedInWar(dependency.getFilePath())
116 && !containedInWar(nextDependency.getFilePath())) {
117 if (firstPathIsShortest(dependency.getFilePath(), nextDependency.getFilePath())) {
118 mergeDependencies(dependency, nextDependency, dependenciesToRemove);
119 } else {
120 mergeDependencies(nextDependency, dependency, dependenciesToRemove);
121 break;
122 }
123 } else if (isShadedJar(dependency, nextDependency)) {
124 if (dependency.getFileName().toLowerCase().endsWith("pom.xml")) {
125 mergeDependencies(nextDependency, dependency, dependenciesToRemove);
126 nextDependency.getRelatedDependencies().remove(dependency);
127 break;
128 } else {
129 mergeDependencies(dependency, nextDependency, dependenciesToRemove);
130 dependency.getRelatedDependencies().remove(nextDependency);
131 }
132 } else if (cpeIdentifiersMatch(dependency, nextDependency)
133 && hasSameBasePath(dependency, nextDependency)
134 && fileNameMatch(dependency, nextDependency)) {
135 if (isCore(dependency, nextDependency)) {
136 mergeDependencies(dependency, nextDependency, dependenciesToRemove);
137 } else {
138 mergeDependencies(nextDependency, dependency, dependenciesToRemove);
139 break;
140 }
141 }
142 }
143 }
144 }
145
146
147 engine.getDependencies().removeAll(dependenciesToRemove);
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160 private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set<Dependency> dependenciesToRemove) {
161 dependency.addRelatedDependency(relatedDependency);
162 final Iterator<Dependency> i = relatedDependency.getRelatedDependencies().iterator();
163 while (i.hasNext()) {
164 dependency.addRelatedDependency(i.next());
165 i.remove();
166 }
167 if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
168 dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
169 }
170 dependenciesToRemove.add(relatedDependency);
171 }
172
173
174
175
176
177
178
179 private String getBaseRepoPath(final String path) {
180 int pos = path.indexOf("repository" + File.separator) + 11;
181 if (pos < 0) {
182 return path;
183 }
184 int tmp = path.indexOf(File.separator, pos);
185 if (tmp <= 0) {
186 return path;
187 }
188 if (tmp > 0) {
189 pos = tmp + 1;
190 }
191 tmp = path.indexOf(File.separator, pos);
192 if (tmp > 0) {
193 pos = tmp + 1;
194 }
195 return path.substring(0, pos);
196 }
197
198
199
200
201
202
203
204
205 private boolean fileNameMatch(Dependency dependency1, Dependency dependency2) {
206 if (dependency1 == null || dependency1.getFileName() == null
207 || dependency2 == null || dependency2.getFileName() == null) {
208 return false;
209 }
210 final String fileName1 = dependency1.getActualFile().getName();
211 final String fileName2 = dependency2.getActualFile().getName();
212
213
214 final DependencyVersion version1 = DependencyVersionUtil.parseVersion(fileName1);
215 final DependencyVersion version2 = DependencyVersionUtil.parseVersion(fileName2);
216 if (version1 != null && version2 != null && !version1.equals(version2)) {
217 return false;
218 }
219
220
221 final Matcher match1 = STARTING_TEXT_PATTERN.matcher(fileName1);
222 final Matcher match2 = STARTING_TEXT_PATTERN.matcher(fileName2);
223 if (match1.find() && match2.find()) {
224 return match1.group().equals(match2.group());
225 }
226
227 return false;
228 }
229
230
231
232
233
234
235
236
237 private boolean cpeIdentifiersMatch(Dependency dependency1, Dependency dependency2) {
238 if (dependency1 == null || dependency1.getIdentifiers() == null
239 || dependency2 == null || dependency2.getIdentifiers() == null) {
240 return false;
241 }
242 boolean matches = false;
243 int cpeCount1 = 0;
244 int cpeCount2 = 0;
245 for (Identifier i : dependency1.getIdentifiers()) {
246 if ("cpe".equals(i.getType())) {
247 cpeCount1 += 1;
248 }
249 }
250 for (Identifier i : dependency2.getIdentifiers()) {
251 if ("cpe".equals(i.getType())) {
252 cpeCount2 += 1;
253 }
254 }
255 if (cpeCount1 > 0 && cpeCount1 == cpeCount2) {
256 for (Identifier i : dependency1.getIdentifiers()) {
257 if ("cpe".equals(i.getType())) {
258 matches |= dependency2.getIdentifiers().contains(i);
259 if (!matches) {
260 break;
261 }
262 }
263 }
264 }
265 LOGGER.debug("IdentifiersMatch={} ({}, {})", matches, dependency1.getFileName(), dependency2.getFileName());
266 return matches;
267 }
268
269
270
271
272
273
274
275
276 private boolean hasSameBasePath(Dependency dependency1, Dependency dependency2) {
277 if (dependency1 == null || dependency2 == null) {
278 return false;
279 }
280 final File lFile = new File(dependency1.getFilePath());
281 String left = lFile.getParent();
282 final File rFile = new File(dependency2.getFilePath());
283 String right = rFile.getParent();
284 if (left == null) {
285 return right == null;
286 }
287 if (left.equalsIgnoreCase(right)) {
288 return true;
289 }
290 if (left.matches(".*[/\\\\]repository[/\\\\].*") && right.matches(".*[/\\\\]repository[/\\\\].*")) {
291 left = getBaseRepoPath(left);
292 right = getBaseRepoPath(right);
293 }
294 if (left.equalsIgnoreCase(right)) {
295 return true;
296 }
297
298 for (Dependency child : dependency2.getRelatedDependencies()) {
299 if (hasSameBasePath(dependency1, child)) {
300 return true;
301 }
302 }
303 return false;
304 }
305
306
307
308
309
310
311
312
313
314 boolean isCore(Dependency left, Dependency right) {
315 final String leftName = left.getFileName().toLowerCase();
316 final String rightName = right.getFileName().toLowerCase();
317
318 final boolean returnVal;
319 if (!rightName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+") && leftName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+")
320 || rightName.contains("core") && !leftName.contains("core")
321 || rightName.contains("kernel") && !leftName.contains("kernel")) {
322 returnVal = false;
323 } else if (rightName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+") && !leftName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+")
324 || !rightName.contains("core") && leftName.contains("core")
325 || !rightName.contains("kernel") && leftName.contains("kernel")) {
326 returnVal = true;
327
328
329
330
331 } else {
332
333
334
335
336
337
338
339
340
341 returnVal = leftName.length() <= rightName.length();
342 }
343 LOGGER.debug("IsCore={} ({}, {})", returnVal, left.getFileName(), right.getFileName());
344 return returnVal;
345 }
346
347
348
349
350
351
352
353
354 private boolean hashesMatch(Dependency dependency1, Dependency dependency2) {
355 if (dependency1 == null || dependency2 == null || dependency1.getSha1sum() == null || dependency2.getSha1sum() == null) {
356 return false;
357 }
358 return dependency1.getSha1sum().equals(dependency2.getSha1sum());
359 }
360
361
362
363
364
365
366
367
368
369 private boolean isShadedJar(Dependency dependency, Dependency nextDependency) {
370 final String mainName = dependency.getFileName().toLowerCase();
371 final String nextName = nextDependency.getFileName().toLowerCase();
372 if (mainName.endsWith(".jar") && nextName.endsWith("pom.xml")) {
373 return dependency.getIdentifiers().containsAll(nextDependency.getIdentifiers());
374 } else if (nextName.endsWith(".jar") && mainName.endsWith("pom.xml")) {
375 return nextDependency.getIdentifiers().containsAll(dependency.getIdentifiers());
376 }
377 return false;
378 }
379
380
381
382
383
384
385
386
387
388 protected boolean firstPathIsShortest(String left, String right) {
389 final String leftPath = left.replace('\\', '/');
390 final String rightPath = right.replace('\\', '/');
391
392 final int leftCount = countChar(leftPath, '/');
393 final int rightCount = countChar(rightPath, '/');
394 if (leftCount == rightCount) {
395 return leftPath.compareTo(rightPath) <= 0;
396 } else {
397 return leftCount < rightCount;
398 }
399 }
400
401
402
403
404
405
406
407
408 private int countChar(String string, char c) {
409 int count = 0;
410 final int max = string.length();
411 for (int i = 0; i < max; i++) {
412 if (c == string.charAt(i)) {
413 count++;
414 }
415 }
416 return count;
417 }
418
419
420
421
422
423
424
425 private boolean containedInWar(String filePath) {
426 return filePath == null ? false : filePath.matches(".*\\.(ear|war)[\\\\/].*");
427 }
428 }