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