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) {
217 if (!version1.equals(version2)) {
218 return false;
219 }
220 }
221
222
223 final Matcher match1 = STARTING_TEXT_PATTERN.matcher(fileName1);
224 final Matcher match2 = STARTING_TEXT_PATTERN.matcher(fileName2);
225 if (match1.find() && match2.find()) {
226 return match1.group().equals(match2.group());
227 }
228
229 return false;
230 }
231
232
233
234
235
236
237
238
239 private boolean cpeIdentifiersMatch(Dependency dependency1, Dependency dependency2) {
240 if (dependency1 == null || dependency1.getIdentifiers() == null
241 || dependency2 == null || dependency2.getIdentifiers() == null) {
242 return false;
243 }
244 boolean matches = false;
245 int cpeCount1 = 0;
246 int cpeCount2 = 0;
247 for (Identifier i : dependency1.getIdentifiers()) {
248 if ("cpe".equals(i.getType())) {
249 cpeCount1 += 1;
250 }
251 }
252 for (Identifier i : dependency2.getIdentifiers()) {
253 if ("cpe".equals(i.getType())) {
254 cpeCount2 += 1;
255 }
256 }
257 if (cpeCount1 > 0 && cpeCount1 == cpeCount2) {
258 for (Identifier i : dependency1.getIdentifiers()) {
259 if ("cpe".equals(i.getType())) {
260 matches |= dependency2.getIdentifiers().contains(i);
261 if (!matches) {
262 break;
263 }
264 }
265 }
266 }
267 LOGGER.debug("IdentifiersMatch={} ({}, {})", matches, dependency1.getFileName(), dependency2.getFileName());
268 return matches;
269 }
270
271
272
273
274
275
276
277
278 private boolean hasSameBasePath(Dependency dependency1, Dependency dependency2) {
279 if (dependency1 == null || dependency2 == null) {
280 return false;
281 }
282 final File lFile = new File(dependency1.getFilePath());
283 String left = lFile.getParent();
284 final File rFile = new File(dependency2.getFilePath());
285 String right = rFile.getParent();
286 if (left == null) {
287 return right == null;
288 }
289 if (left.equalsIgnoreCase(right)) {
290 return true;
291 }
292 if (left.matches(".*[/\\\\]repository[/\\\\].*") && right.matches(".*[/\\\\]repository[/\\\\].*")) {
293 left = getBaseRepoPath(left);
294 right = getBaseRepoPath(right);
295 }
296 if (left.equalsIgnoreCase(right)) {
297 return true;
298 }
299
300 for (Dependency child : dependency2.getRelatedDependencies()) {
301 if (hasSameBasePath(dependency1, child)) {
302 return true;
303 }
304 }
305 return false;
306 }
307
308
309
310
311
312
313
314
315
316 boolean isCore(Dependency left, Dependency right) {
317 final String leftName = left.getFileName().toLowerCase();
318 final String rightName = right.getFileName().toLowerCase();
319
320 final boolean returnVal;
321 if (!rightName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+") && leftName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+")
322 || rightName.contains("core") && !leftName.contains("core")
323 || rightName.contains("kernel") && !leftName.contains("kernel")) {
324 returnVal = false;
325 } else if (rightName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+") && !leftName.matches(".*\\.(tar|tgz|gz|zip|ear|war).+")
326 || !rightName.contains("core") && leftName.contains("core")
327 || !rightName.contains("kernel") && leftName.contains("kernel")) {
328 returnVal = true;
329
330
331
332
333 } else {
334
335
336
337
338
339
340
341
342
343 returnVal = leftName.length() <= rightName.length();
344 }
345 LOGGER.debug("IsCore={} ({}, {})", returnVal, left.getFileName(), right.getFileName());
346 return returnVal;
347 }
348
349
350
351
352
353
354
355
356 private boolean hashesMatch(Dependency dependency1, Dependency dependency2) {
357 if (dependency1 == null || dependency2 == null || dependency1.getSha1sum() == null || dependency2.getSha1sum() == null) {
358 return false;
359 }
360 return dependency1.getSha1sum().equals(dependency2.getSha1sum());
361 }
362
363
364
365
366
367
368
369
370
371 private boolean isShadedJar(Dependency dependency, Dependency nextDependency) {
372 final String mainName = dependency.getFileName().toLowerCase();
373 final String nextName = nextDependency.getFileName().toLowerCase();
374 if (mainName.endsWith(".jar") && nextName.endsWith("pom.xml")) {
375 return dependency.getIdentifiers().containsAll(nextDependency.getIdentifiers());
376 } else if (nextName.endsWith(".jar") && mainName.endsWith("pom.xml")) {
377 return nextDependency.getIdentifiers().containsAll(dependency.getIdentifiers());
378 }
379 return false;
380 }
381
382
383
384
385
386
387
388
389
390 protected boolean firstPathIsShortest(String left, String right) {
391 final String leftPath = left.replace('\\', '/');
392 final String rightPath = right.replace('\\', '/');
393
394 final int leftCount = countChar(leftPath, '/');
395 final int rightCount = countChar(rightPath, '/');
396 if (leftCount == rightCount) {
397 return leftPath.compareTo(rightPath) <= 0;
398 } else {
399 return leftCount < rightCount;
400 }
401 }
402
403
404
405
406
407
408
409
410 private int countChar(String string, char c) {
411 int count = 0;
412 final int max = string.length();
413 for (int i = 0; i < max; i++) {
414 if (c == string.charAt(i)) {
415 count++;
416 }
417 }
418 return count;
419 }
420
421
422
423
424
425
426
427 private boolean containedInWar(String filePath) {
428 return filePath == null ? false : filePath.matches(".*\\.(ear|war)[\\\\/].*");
429 }
430 }