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