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.io.FileFilter;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.OutputStream;
27 import java.io.Reader;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Properties;
37 import java.util.Set;
38 import java.util.StringTokenizer;
39 import java.util.jar.Attributes;
40 import java.util.jar.JarEntry;
41 import java.util.jar.JarFile;
42 import java.util.jar.Manifest;
43 import java.util.regex.Pattern;
44 import java.util.zip.ZipEntry;
45 import org.apache.commons.compress.utils.IOUtils;
46 import org.apache.commons.io.FilenameUtils;
47 import org.jsoup.Jsoup;
48 import org.owasp.dependencycheck.Engine;
49 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
50 import org.owasp.dependencycheck.dependency.Confidence;
51 import org.owasp.dependencycheck.dependency.Dependency;
52 import org.owasp.dependencycheck.dependency.EvidenceCollection;
53 import org.owasp.dependencycheck.utils.FileFilterBuilder;
54 import org.owasp.dependencycheck.xml.pom.License;
55 import org.owasp.dependencycheck.xml.pom.PomUtils;
56 import org.owasp.dependencycheck.xml.pom.Model;
57 import org.owasp.dependencycheck.utils.FileUtils;
58 import org.owasp.dependencycheck.utils.Settings;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62
63
64
65
66
67 public class JarAnalyzer extends AbstractFileTypeAnalyzer {
68
69
70
71
72
73 private static final Logger LOGGER = LoggerFactory.getLogger(JarAnalyzer.class);
74
75
76
77 private static int dirCount = 0;
78
79
80
81 private static final String NEWLINE = System.getProperty("line.separator");
82
83
84
85 private static final Set<String> IGNORE_VALUES = newHashSet(
86 "Sun Java System Application Server");
87
88
89
90 private static final Set<String> IGNORE_KEYS = newHashSet(
91 "built-by",
92 "created-by",
93 "builtby",
94 "createdby",
95 "build-jdk",
96 "buildjdk",
97 "ant-version",
98 "antversion",
99 "dynamicimportpackage",
100 "dynamicimport-package",
101 "dynamic-importpackage",
102 "dynamic-import-package",
103 "import-package",
104 "ignore-package",
105 "export-package",
106 "importpackage",
107 "ignorepackage",
108 "exportpackage",
109 "sealed",
110 "manifest-version",
111 "archiver-version",
112 "manifestversion",
113 "archiverversion",
114 "classpath",
115 "class-path",
116 "tool",
117 "bundle-manifestversion",
118 "bundlemanifestversion",
119 "bundle-vendor",
120 "include-resource",
121 "embed-dependency",
122 "ipojo-components",
123 "ipojo-extension",
124 "eclipse-sourcereferences");
125
126
127
128 @SuppressWarnings("deprecation")
129 private static final String IMPLEMENTATION_VENDOR_ID = Attributes.Name.IMPLEMENTATION_VENDOR_ID
130 .toString();
131
132
133
134 private static final String BUNDLE_VERSION = "Bundle-Version";
135
136
137
138 private static final String BUNDLE_DESCRIPTION = "Bundle-Description";
139
140
141
142 private static final String BUNDLE_NAME = "Bundle-Name";
143
144
145
146 private static final Pattern HTML_DETECTION_PATTERN = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);
147
148
149
150
151
152 public JarAnalyzer() {
153 }
154
155
156
157
158
159 private static final String ANALYZER_NAME = "Jar Analyzer";
160
161
162
163 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
164
165
166
167 private static final String[] EXTENSIONS = {"jar", "war"};
168
169
170
171
172 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
173
174
175
176
177
178
179 @Override
180 protected FileFilter getFileFilter() {
181 return FILTER;
182 }
183
184
185
186
187
188
189 @Override
190 public String getName() {
191 return ANALYZER_NAME;
192 }
193
194
195
196
197
198
199 @Override
200 public AnalysisPhase getAnalysisPhase() {
201 return ANALYSIS_PHASE;
202 }
203
204
205
206
207
208
209
210 @Override
211 protected String getAnalyzerEnabledSettingKey() {
212 return Settings.KEYS.ANALYZER_JAR_ENABLED;
213 }
214
215
216
217
218
219
220
221
222
223 @Override
224 public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
225 try {
226 final List<ClassNameInformation> classNames = collectClassNames(dependency);
227 final String fileName = dependency.getFileName().toLowerCase();
228 if (classNames.isEmpty()
229 && (fileName.endsWith("-sources.jar")
230 || fileName.endsWith("-javadoc.jar")
231 || fileName.endsWith("-src.jar")
232 || fileName.endsWith("-doc.jar"))) {
233 engine.getDependencies().remove(dependency);
234 }
235 final boolean hasManifest = parseManifest(dependency, classNames);
236 final boolean hasPOM = analyzePOM(dependency, classNames, engine);
237 final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
238 analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
239 } catch (IOException ex) {
240 throw new AnalysisException("Exception occurred reading the JAR file.", ex);
241 }
242 }
243
244
245
246
247
248
249
250
251
252
253
254 protected boolean analyzePOM(Dependency dependency, List<ClassNameInformation> classes, Engine engine) throws AnalysisException {
255 boolean foundSomething = false;
256 final JarFile jar;
257 try {
258 jar = new JarFile(dependency.getActualFilePath());
259 } catch (IOException ex) {
260 LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
261 LOGGER.trace("", ex);
262 return false;
263 }
264 List<String> pomEntries;
265 try {
266 pomEntries = retrievePomListing(jar);
267 } catch (IOException ex) {
268 LOGGER.warn("Unable to read Jar file entries in '{}'.", dependency.getActualFilePath());
269 LOGGER.trace("", ex);
270 return false;
271 }
272 File externalPom = null;
273 if (pomEntries.isEmpty()) {
274 final String pomPath = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
275 externalPom = new File(pomPath);
276 if (externalPom.isFile()) {
277 pomEntries.add(pomPath);
278 } else {
279 return false;
280 }
281 }
282 for (String path : pomEntries) {
283 LOGGER.debug("Reading pom entry: {}", path);
284 Properties pomProperties = null;
285 try {
286 if (externalPom == null) {
287 pomProperties = retrievePomProperties(path, jar);
288 }
289 } catch (IOException ex) {
290 LOGGER.trace("ignore this, failed reading a non-existent pom.properties", ex);
291 }
292 Model pom = null;
293 try {
294 if (pomEntries.size() > 1) {
295
296 final Dependency newDependency = new Dependency();
297 pom = extractPom(path, jar, newDependency);
298
299 final String displayPath = String.format("%s%s%s",
300 dependency.getFilePath(),
301 File.separator,
302 path);
303 final String displayName = String.format("%s%s%s",
304 dependency.getFileName(),
305 File.separator,
306 path);
307
308 newDependency.setFileName(displayName);
309 newDependency.setFilePath(displayPath);
310 pom.processProperties(pomProperties);
311 setPomEvidence(newDependency, pom, null);
312 engine.getDependencies().add(newDependency);
313 Collections.sort(engine.getDependencies());
314 } else {
315 if (externalPom == null) {
316 pom = PomUtils.readPom(path, jar);
317 } else {
318 pom = PomUtils.readPom(externalPom);
319 }
320 pom.processProperties(pomProperties);
321 foundSomething |= setPomEvidence(dependency, pom, classes);
322 }
323 } catch (AnalysisException ex) {
324 LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
325 LOGGER.trace("", ex);
326 }
327 }
328 return foundSomething;
329 }
330
331
332
333
334
335
336
337
338
339 private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
340 Properties pomProperties = null;
341 final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
342 final ZipEntry propEntry = jar.getEntry(propPath);
343 if (propEntry != null) {
344 Reader reader = null;
345 try {
346 reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
347 pomProperties = new Properties();
348 pomProperties.load(reader);
349 LOGGER.debug("Read pom.properties: {}", propPath);
350 } finally {
351 if (reader != null) {
352 try {
353 reader.close();
354 } catch (IOException ex) {
355 LOGGER.trace("close error", ex);
356 }
357 }
358 }
359 }
360 return pomProperties;
361 }
362
363
364
365
366
367
368
369
370 private List<String> retrievePomListing(final JarFile jar) throws IOException {
371 final List<String> pomEntries = new ArrayList<String>();
372 final Enumeration<JarEntry> entries = jar.entries();
373 while (entries.hasMoreElements()) {
374 final JarEntry entry = entries.nextElement();
375 final String entryName = (new File(entry.getName())).getName().toLowerCase();
376 if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
377 LOGGER.trace("POM Entry found: {}", entry.getName());
378 pomEntries.add(entry.getName());
379 }
380 }
381 return pomEntries;
382 }
383
384
385
386
387
388
389
390
391
392
393
394 private Model extractPom(String path, JarFile jar, Dependency dependency) throws AnalysisException {
395 InputStream input = null;
396 FileOutputStream fos = null;
397 final File tmpDir = getNextTempDirectory();
398 final File file = new File(tmpDir, "pom.xml");
399 try {
400 final ZipEntry entry = jar.getEntry(path);
401 input = jar.getInputStream(entry);
402 fos = new FileOutputStream(file);
403 IOUtils.copy(input, fos);
404 dependency.setActualFilePath(file.getAbsolutePath());
405 } catch (IOException ex) {
406 LOGGER.warn("An error occurred reading '{}' from '{}'.", path, dependency.getFilePath());
407 LOGGER.error("", ex);
408 } finally {
409 closeStream(fos);
410 closeStream(input);
411 }
412 return PomUtils.readPom(file);
413 }
414
415
416
417
418
419
420 private void closeStream(InputStream stream) {
421 if (stream != null) {
422 try {
423 stream.close();
424 } catch (IOException ex) {
425 LOGGER.trace("", ex);
426 }
427 }
428 }
429
430
431
432
433
434
435 private void closeStream(OutputStream stream) {
436 if (stream != null) {
437 try {
438 stream.close();
439 } catch (IOException ex) {
440 LOGGER.trace("", ex);
441 }
442 }
443 }
444
445
446
447
448
449
450
451
452
453
454 public static boolean setPomEvidence(Dependency dependency, Model pom, List<ClassNameInformation> classes) {
455 boolean foundSomething = false;
456 boolean addAsIdentifier = true;
457 if (pom == null) {
458 return foundSomething;
459 }
460 String groupid = pom.getGroupId();
461 String parentGroupId = pom.getParentGroupId();
462 String artifactid = pom.getArtifactId();
463 String parentArtifactId = pom.getParentArtifactId();
464 String version = pom.getVersion();
465 String parentVersion = pom.getParentVersion();
466
467 if ("org.sonatype.oss".equals(parentGroupId) && "oss-parent".equals(parentArtifactId)) {
468 parentGroupId = null;
469 parentArtifactId = null;
470 parentVersion = null;
471 }
472
473 if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) {
474 groupid = parentGroupId;
475 }
476
477 final String originalGroupID = groupid;
478 if (groupid.startsWith("org.") || groupid.startsWith("com.")) {
479 groupid = groupid.substring(4);
480 }
481
482 if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) {
483 artifactid = parentArtifactId;
484 }
485
486 final String originalArtifactID = artifactid;
487 if (artifactid.startsWith("org.") || artifactid.startsWith("com.")) {
488 artifactid = artifactid.substring(4);
489 }
490
491 if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) {
492 version = parentVersion;
493 }
494
495 if (groupid != null && !groupid.isEmpty()) {
496 foundSomething = true;
497 dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST);
498 dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW);
499 addMatchingValues(classes, groupid, dependency.getVendorEvidence());
500 addMatchingValues(classes, groupid, dependency.getProductEvidence());
501 if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) {
502 dependency.getVendorEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
503 dependency.getProductEvidence().addEvidence("pom", "parent-groupid", parentGroupId, Confidence.LOW);
504 addMatchingValues(classes, parentGroupId, dependency.getVendorEvidence());
505 addMatchingValues(classes, parentGroupId, dependency.getProductEvidence());
506 }
507 } else {
508 addAsIdentifier = false;
509 }
510
511 if (artifactid != null && !artifactid.isEmpty()) {
512 foundSomething = true;
513 dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGHEST);
514 dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.LOW);
515 addMatchingValues(classes, artifactid, dependency.getVendorEvidence());
516 addMatchingValues(classes, artifactid, dependency.getProductEvidence());
517 if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) {
518 dependency.getProductEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM);
519 dependency.getVendorEvidence().addEvidence("pom", "parent-artifactid", parentArtifactId, Confidence.LOW);
520 addMatchingValues(classes, parentArtifactId, dependency.getVendorEvidence());
521 addMatchingValues(classes, parentArtifactId, dependency.getProductEvidence());
522 }
523 } else {
524 addAsIdentifier = false;
525 }
526
527 if (version != null && !version.isEmpty()) {
528 foundSomething = true;
529 dependency.getVersionEvidence().addEvidence("pom", "version", version, Confidence.HIGHEST);
530 if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) {
531 dependency.getVersionEvidence().addEvidence("pom", "parent-version", version, Confidence.LOW);
532 }
533 } else {
534 addAsIdentifier = false;
535 }
536
537 if (addAsIdentifier) {
538 dependency.addIdentifier("maven", String.format("%s:%s:%s", originalGroupID, originalArtifactID, version), null, Confidence.HIGH);
539 }
540
541
542 final String org = pom.getOrganization();
543 if (org != null && !org.isEmpty()) {
544 dependency.getVendorEvidence().addEvidence("pom", "organization name", org, Confidence.HIGH);
545 dependency.getProductEvidence().addEvidence("pom", "organization name", org, Confidence.LOW);
546 addMatchingValues(classes, org, dependency.getVendorEvidence());
547 addMatchingValues(classes, org, dependency.getProductEvidence());
548 }
549
550 final String pomName = pom.getName();
551 if (pomName
552 != null && !pomName.isEmpty()) {
553 foundSomething = true;
554 dependency.getProductEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
555 dependency.getVendorEvidence().addEvidence("pom", "name", pomName, Confidence.HIGH);
556 addMatchingValues(classes, pomName, dependency.getVendorEvidence());
557 addMatchingValues(classes, pomName, dependency.getProductEvidence());
558 }
559
560
561 final String description = pom.getDescription();
562 if (description != null && !description.isEmpty() && !description.startsWith("POM was created by")) {
563 foundSomething = true;
564 final String trimmedDescription = addDescription(dependency, description, "pom", "description");
565 addMatchingValues(classes, trimmedDescription, dependency.getVendorEvidence());
566 addMatchingValues(classes, trimmedDescription, dependency.getProductEvidence());
567 }
568
569 extractLicense(pom, dependency);
570 return foundSomething;
571 }
572
573
574
575
576
577
578
579
580
581 protected void analyzePackageNames(List<ClassNameInformation> classNames,
582 Dependency dependency, boolean addPackagesAsEvidence) {
583 final Map<String, Integer> vendorIdentifiers = new HashMap<String, Integer>();
584 final Map<String, Integer> productIdentifiers = new HashMap<String, Integer>();
585 analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
586
587 final int classCount = classNames.size();
588 final EvidenceCollection vendor = dependency.getVendorEvidence();
589 final EvidenceCollection product = dependency.getProductEvidence();
590
591 for (Map.Entry<String, Integer> entry : vendorIdentifiers.entrySet()) {
592 final float ratio = entry.getValue() / (float) classCount;
593 if (ratio > 0.5) {
594
595 vendor.addWeighting(entry.getKey());
596 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
597 vendor.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
598 }
599 }
600 }
601 for (Map.Entry<String, Integer> entry : productIdentifiers.entrySet()) {
602 final float ratio = entry.getValue() / (float) classCount;
603 if (ratio > 0.5) {
604 product.addWeighting(entry.getKey());
605 if (addPackagesAsEvidence && entry.getKey().length() > 1) {
606 product.addEvidence("jar", "package name", entry.getKey(), Confidence.LOW);
607 }
608 }
609 }
610 }
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626 protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation) throws IOException {
627 boolean foundSomething = false;
628 JarFile jar = null;
629 try {
630 jar = new JarFile(dependency.getActualFilePath());
631 final Manifest manifest = jar.getManifest();
632 if (manifest == null) {
633
634 if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
635 && !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
636 && !dependency.getFileName().toLowerCase().endsWith("-src.jar")
637 && !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
638 LOGGER.debug("Jar file '{}' does not contain a manifest.",
639 dependency.getFileName());
640 }
641 return false;
642 }
643 final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
644 final EvidenceCollection productEvidence = dependency.getProductEvidence();
645 final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
646
647 String source = "Manifest";
648 String specificationVersion = null;
649 boolean hasImplementationVersion = false;
650
651 Attributes atts = manifest.getMainAttributes();
652 for (Entry<Object, Object> entry : atts.entrySet()) {
653 String key = entry.getKey().toString();
654 String value = atts.getValue(key);
655 if (HTML_DETECTION_PATTERN.matcher(value).find()) {
656 value = Jsoup.parse(value).text();
657 }
658 if (IGNORE_VALUES.contains(value)) {
659 continue;
660 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
661 foundSomething = true;
662 productEvidence.addEvidence(source, key, value, Confidence.HIGH);
663 addMatchingValues(classInformation, value, productEvidence);
664 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
665 hasImplementationVersion = true;
666 foundSomething = true;
667 versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
668 } else if ("specification-version".equalsIgnoreCase(key)) {
669 specificationVersion = key;
670 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
671 foundSomething = true;
672 vendorEvidence.addEvidence(source, key, value, Confidence.HIGH);
673 addMatchingValues(classInformation, value, vendorEvidence);
674 } else if (key.equalsIgnoreCase(IMPLEMENTATION_VENDOR_ID)) {
675 foundSomething = true;
676 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
677 addMatchingValues(classInformation, value, vendorEvidence);
678 } else if (key.equalsIgnoreCase(BUNDLE_DESCRIPTION)) {
679 foundSomething = true;
680 addDescription(dependency, value, "manifest", key);
681
682 addMatchingValues(classInformation, value, productEvidence);
683 } else if (key.equalsIgnoreCase(BUNDLE_NAME)) {
684 foundSomething = true;
685 productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
686 addMatchingValues(classInformation, value, productEvidence);
687
688
689
690
691
692 } else if (key.equalsIgnoreCase(BUNDLE_VERSION)) {
693 foundSomething = true;
694 versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
695 } else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) {
696 continue;
697
698
699
700
701
702
703
704
705 } else {
706 key = key.toLowerCase();
707 if (!IGNORE_KEYS.contains(key)
708 && !key.endsWith("jdk")
709 && !key.contains("lastmodified")
710 && !key.endsWith("package")
711 && !key.endsWith("classpath")
712 && !key.endsWith("class-path")
713 && !key.endsWith("-scm")
714 && !key.startsWith("scm-")
715 && !value.trim().startsWith("scm:")
716 && !isImportPackage(key, value)
717 && !isPackage(key, value)) {
718
719 foundSomething = true;
720 if (key.contains("version")) {
721 if (!key.contains("specification")) {
722 versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
723 }
724 } else if ("build-id".equals(key)) {
725 int pos = value.indexOf('(');
726 if (pos >= 0) {
727 value = value.substring(0, pos - 1);
728 }
729 pos = value.indexOf('[');
730 if (pos >= 0) {
731 value = value.substring(0, pos - 1);
732 }
733 versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
734 } else if (key.contains("title")) {
735 productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
736 addMatchingValues(classInformation, value, productEvidence);
737 } else if (key.contains("vendor")) {
738 if (key.contains("specification")) {
739 vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
740 } else {
741 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
742 addMatchingValues(classInformation, value, vendorEvidence);
743 }
744 } else if (key.contains("name")) {
745 productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
746 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
747 addMatchingValues(classInformation, value, vendorEvidence);
748 addMatchingValues(classInformation, value, productEvidence);
749 } else if (key.contains("license")) {
750 addLicense(dependency, value);
751 } else {
752 if (key.contains("description")) {
753 addDescription(dependency, value, "manifest", key);
754 } else {
755 productEvidence.addEvidence(source, key, value, Confidence.LOW);
756 vendorEvidence.addEvidence(source, key, value, Confidence.LOW);
757 addMatchingValues(classInformation, value, vendorEvidence);
758 addMatchingValues(classInformation, value, productEvidence);
759 if (value.matches(".*\\d.*")) {
760 final StringTokenizer tokenizer = new StringTokenizer(value, " ");
761 while (tokenizer.hasMoreElements()) {
762 final String s = tokenizer.nextToken();
763 if (s.matches("^[0-9.]+$")) {
764 versionEvidence.addEvidence(source, key, s, Confidence.LOW);
765 }
766 }
767 }
768 }
769 }
770 }
771 }
772 }
773
774 final Map<String, Attributes> entries = manifest.getEntries();
775 for (Iterator<String> it = entries.keySet().iterator(); it.hasNext();) {
776 final String name = it.next();
777 source = "manifest: " + name;
778 atts = entries.get(name);
779 for (Entry<Object, Object> entry : atts.entrySet()) {
780 final String key = entry.getKey().toString();
781 final String value = atts.getValue(key);
782 if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
783 foundSomething = true;
784 productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
785 addMatchingValues(classInformation, value, productEvidence);
786 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
787 foundSomething = true;
788 versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
789 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
790 foundSomething = true;
791 vendorEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
792 addMatchingValues(classInformation, value, vendorEvidence);
793 } else if (key.equalsIgnoreCase(Attributes.Name.SPECIFICATION_TITLE.toString())) {
794 foundSomething = true;
795 productEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
796 addMatchingValues(classInformation, value, productEvidence);
797 }
798 }
799 }
800 if (specificationVersion != null && !hasImplementationVersion) {
801 foundSomething = true;
802 versionEvidence.addEvidence(source, "specification-version", specificationVersion, Confidence.HIGH);
803 }
804 } finally {
805 if (jar != null) {
806 jar.close();
807 }
808 }
809 return foundSomething;
810 }
811
812
813
814
815
816
817
818
819
820
821
822
823 public static String addDescription(Dependency dependency, String description, String source, String key) {
824 if (dependency.getDescription() == null) {
825 dependency.setDescription(description);
826 }
827 String desc;
828 if (HTML_DETECTION_PATTERN.matcher(description).find()) {
829 desc = Jsoup.parse(description).text();
830 } else {
831 desc = description;
832 }
833 dependency.setDescription(desc);
834 if (desc.length() > 100) {
835 desc = desc.replaceAll("\\s\\s+", " ");
836 final int posSuchAs = desc.toLowerCase().indexOf("such as ", 100);
837 final int posLike = desc.toLowerCase().indexOf("like ", 100);
838 final int posWillUse = desc.toLowerCase().indexOf("will use ", 100);
839 final int posUses = desc.toLowerCase().indexOf(" uses ", 100);
840 int pos = -1;
841 pos = Math.max(pos, posSuchAs);
842 if (pos >= 0 && posLike >= 0) {
843 pos = Math.min(pos, posLike);
844 } else {
845 pos = Math.max(pos, posLike);
846 }
847 if (pos >= 0 && posWillUse >= 0) {
848 pos = Math.min(pos, posWillUse);
849 } else {
850 pos = Math.max(pos, posWillUse);
851 }
852 if (pos >= 0 && posUses >= 0) {
853 pos = Math.min(pos, posUses);
854 } else {
855 pos = Math.max(pos, posUses);
856 }
857
858 if (pos > 0) {
859 desc = desc.substring(0, pos) + "...";
860 }
861 dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.LOW);
862 dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.LOW);
863 } else {
864 dependency.getProductEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
865 dependency.getVendorEvidence().addEvidence(source, key, desc, Confidence.MEDIUM);
866 }
867 return desc;
868 }
869
870
871
872
873
874
875
876 private void addLicense(Dependency d, String license) {
877 if (d.getLicense() == null) {
878 d.setLicense(license);
879 } else if (!d.getLicense().contains(license)) {
880 d.setLicense(d.getLicense() + NEWLINE + license);
881 }
882 }
883
884
885
886
887 private File tempFileLocation = null;
888
889
890
891
892
893
894 @Override
895 public void initializeFileTypeAnalyzer() throws Exception {
896 final File baseDir = Settings.getTempDirectory();
897 tempFileLocation = File.createTempFile("check", "tmp", baseDir);
898 if (!tempFileLocation.delete()) {
899 final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
900 throw new AnalysisException(msg);
901 }
902 if (!tempFileLocation.mkdirs()) {
903 final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
904 throw new AnalysisException(msg);
905 }
906 }
907
908
909
910
911 @Override
912 public void close() {
913 if (tempFileLocation != null && tempFileLocation.exists()) {
914 LOGGER.debug("Attempting to delete temporary files");
915 final boolean success = FileUtils.delete(tempFileLocation);
916 if (!success) {
917 LOGGER.warn("Failed to delete some temporary files, see the log for more details");
918 }
919 }
920 }
921
922
923
924
925
926
927
928
929 private boolean isImportPackage(String key, String value) {
930 final Pattern packageRx = Pattern.compile("^([a-zA-Z0-9_#\\$\\*\\.]+\\s*[,;]\\s*)+([a-zA-Z0-9_#\\$\\*\\.]+\\s*)?$");
931 final boolean matches = packageRx.matcher(value).matches();
932 return matches && (key.contains("import") || key.contains("include") || value.length() > 10);
933 }
934
935
936
937
938
939
940
941
942 private List<ClassNameInformation> collectClassNames(Dependency dependency) {
943 final List<ClassNameInformation> classNames = new ArrayList<ClassNameInformation>();
944 JarFile jar = null;
945 try {
946 jar = new JarFile(dependency.getActualFilePath());
947 final Enumeration<JarEntry> entries = jar.entries();
948 while (entries.hasMoreElements()) {
949 final JarEntry entry = entries.nextElement();
950 final String name = entry.getName().toLowerCase();
951
952 if (name.endsWith(".class") && !name.matches("^javax?\\..*$")) {
953 final ClassNameInformation className = new ClassNameInformation(name.substring(0, name.length() - 6));
954 classNames.add(className);
955 }
956 }
957 } catch (IOException ex) {
958 LOGGER.warn("Unable to open jar file '{}'.", dependency.getFileName());
959 LOGGER.debug("", ex);
960 } finally {
961 if (jar != null) {
962 try {
963 jar.close();
964 } catch (IOException ex) {
965 LOGGER.trace("", ex);
966 }
967 }
968 }
969 return classNames;
970 }
971
972
973
974
975
976
977
978
979
980 private void analyzeFullyQualifiedClassNames(List<ClassNameInformation> classNames,
981 Map<String, Integer> vendor, Map<String, Integer> product) {
982 for (ClassNameInformation entry : classNames) {
983 final List<String> list = entry.getPackageStructure();
984 addEntry(vendor, list.get(0));
985
986 if (list.size() == 2) {
987 addEntry(product, list.get(1));
988 }
989 if (list.size() == 3) {
990 addEntry(vendor, list.get(1));
991 addEntry(product, list.get(1));
992 addEntry(product, list.get(2));
993 }
994 if (list.size() >= 4) {
995 addEntry(vendor, list.get(1));
996 addEntry(vendor, list.get(2));
997 addEntry(product, list.get(1));
998 addEntry(product, list.get(2));
999 addEntry(product, list.get(3));
1000 }
1001 }
1002 }
1003
1004
1005
1006
1007
1008
1009
1010
1011 private void addEntry(Map<String, Integer> collection, String key) {
1012 if (collection.containsKey(key)) {
1013 collection.put(key, collection.get(key) + 1);
1014 } else {
1015 collection.put(key, 1);
1016 }
1017 }
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028 private static void addMatchingValues(List<ClassNameInformation> classes, String value, EvidenceCollection evidence) {
1029 if (value == null || value.isEmpty() || classes == null || classes.isEmpty()) {
1030 return;
1031 }
1032 final String text = value.toLowerCase();
1033 for (ClassNameInformation cni : classes) {
1034 for (String key : cni.getPackageStructure()) {
1035 final Pattern p = Pattern.compile("\b" + key + "\b");
1036 if (p.matcher(text).find()) {
1037
1038 evidence.addEvidence("jar", "package name", key, Confidence.HIGHEST);
1039 }
1040 }
1041 }
1042 }
1043
1044
1045
1046
1047
1048
1049
1050
1051 private boolean isPackage(String key, String value) {
1052
1053 return !key.matches(".*(version|title|vendor|name|license|description).*")
1054 && value.matches("^([a-zA-Z_][a-zA-Z0-9_\\$]*(\\.[a-zA-Z_][a-zA-Z0-9_\\$]*)*)?$");
1055
1056 }
1057
1058
1059
1060
1061
1062
1063
1064 public static void extractLicense(Model pom, Dependency dependency) {
1065
1066 if (pom.getLicenses() != null) {
1067 String license = null;
1068 for (License lic : pom.getLicenses()) {
1069 String tmp = null;
1070 if (lic.getName() != null) {
1071 tmp = lic.getName();
1072 }
1073 if (lic.getUrl() != null) {
1074 if (tmp == null) {
1075 tmp = lic.getUrl();
1076 } else {
1077 tmp += ": " + lic.getUrl();
1078 }
1079 }
1080 if (tmp == null) {
1081 continue;
1082 }
1083 if (HTML_DETECTION_PATTERN.matcher(tmp).find()) {
1084 tmp = Jsoup.parse(tmp).text();
1085 }
1086 if (license == null) {
1087 license = tmp;
1088 } else {
1089 license += "\n" + tmp;
1090 }
1091 }
1092 if (license != null) {
1093 dependency.setLicense(license);
1094
1095 }
1096 }
1097 }
1098
1099
1100
1101
1102 protected static class ClassNameInformation {
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124 ClassNameInformation(String className) {
1125 name = className;
1126 if (name.contains("/")) {
1127 final String[] tmp = className.toLowerCase().split("/");
1128 int start = 0;
1129 int end = 3;
1130 if ("com".equals(tmp[0]) || "org".equals(tmp[0])) {
1131 start = 1;
1132 end = 4;
1133 }
1134 if (tmp.length <= end) {
1135 end = tmp.length - 1;
1136 }
1137 for (int i = start; i <= end; i++) {
1138 packageStructure.add(tmp[i]);
1139 }
1140 } else {
1141 packageStructure.add(name);
1142 }
1143 }
1144
1145
1146
1147 private String name;
1148
1149
1150
1151
1152
1153
1154 public String getName() {
1155 return name;
1156 }
1157
1158
1159
1160
1161
1162
1163 public void setName(String name) {
1164 this.name = name;
1165 }
1166
1167
1168
1169 private final ArrayList<String> packageStructure = new ArrayList<String>();
1170
1171
1172
1173
1174
1175
1176 public ArrayList<String> getPackageStructure() {
1177 return packageStructure;
1178 }
1179 }
1180
1181
1182
1183
1184
1185
1186
1187 private File getNextTempDirectory() throws AnalysisException {
1188 dirCount += 1;
1189 final File directory = new File(tempFileLocation, String.valueOf(dirCount));
1190
1191 if (directory.exists()) {
1192 return getNextTempDirectory();
1193 }
1194 if (!directory.mkdirs()) {
1195 final String msg = String.format("Unable to create temp directory '%s'.", directory.getAbsolutePath());
1196 throw new AnalysisException(msg);
1197 }
1198 return directory;
1199 }
1200 }