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 com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import com.github.packageurl.PackageURLBuilder;
23 import com.google.common.base.Strings;
24 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25 import java.io.File;
26 import java.io.FileFilter;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.Reader;
33 import java.io.UnsupportedEncodingException;
34 import java.nio.charset.StandardCharsets;
35 import java.nio.file.Paths;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Enumeration;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Map.Entry;
44 import java.util.Properties;
45 import java.util.Set;
46 import java.util.StringTokenizer;
47 import java.util.concurrent.atomic.AtomicInteger;
48 import java.util.jar.Attributes;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarFile;
51 import java.util.jar.Manifest;
52 import java.util.regex.Pattern;
53 import java.util.zip.ZipEntry;
54
55 import org.apache.commons.io.FilenameUtils;
56 import org.apache.commons.lang3.StringUtils;
57 import org.apache.commons.io.IOUtils;
58 import org.jsoup.Jsoup;
59 import org.owasp.dependencycheck.Engine;
60 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
61 import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
62 import org.owasp.dependencycheck.dependency.Confidence;
63 import org.owasp.dependencycheck.dependency.Dependency;
64 import org.owasp.dependencycheck.dependency.EvidenceType;
65 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
66 import org.owasp.dependencycheck.dependency.naming.Identifier;
67 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
68 import org.owasp.dependencycheck.exception.InitializationException;
69 import org.owasp.dependencycheck.utils.FileFilterBuilder;
70 import org.owasp.dependencycheck.utils.FileUtils;
71 import org.owasp.dependencycheck.utils.Settings;
72 import org.owasp.dependencycheck.xml.pom.Developer;
73 import org.owasp.dependencycheck.xml.pom.License;
74 import org.owasp.dependencycheck.xml.pom.Model;
75 import org.owasp.dependencycheck.xml.pom.PomUtils;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78
79
80
81
82
83
84
85 public class JarAnalyzer extends AbstractFileTypeAnalyzer {
86
87
88
89
90
91
92 public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.JAVA;
93
94
95
96 private static final Logger LOGGER = LoggerFactory.getLogger(JarAnalyzer.class);
97
98
99
100
101 private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
102
103
104
105 private static final String NEWLINE = System.getProperty("line.separator");
106
107
108
109
110 private static final Set<String> IGNORE_VALUES = newHashSet(
111 "Sun Java System Application Server");
112
113
114
115 private static final Set<String> IGNORE_KEYS = newHashSet(
116 "built-by",
117 "created-by",
118 "builtby",
119 "built-with",
120 "builtwith",
121 "createdby",
122 "build-jdk",
123 "buildjdk",
124 "ant-version",
125 "antversion",
126 "dynamicimportpackage",
127 "dynamicimport-package",
128 "dynamic-importpackage",
129 "dynamic-import-package",
130 "import-package",
131 "ignore-package",
132 "export-package",
133 "importpackage",
134 "import-template",
135 "importtemplate",
136 "java-vendor",
137 "export-template",
138 "exporttemplate",
139 "ignorepackage",
140 "exportpackage",
141 "sealed",
142 "manifest-version",
143 "archiver-version",
144 "manifestversion",
145 "archiverversion",
146 "classpath",
147 "class-path",
148 "tool",
149 "bundle-manifestversion",
150 "bundlemanifestversion",
151 "bundle-vendor",
152 "include-resource",
153 "embed-dependency",
154 "embedded-artifacts",
155 "ipojo-components",
156 "ipojo-extension",
157 "plugin-dependencies",
158 "today",
159 "tstamp",
160 "dstamp",
161 "eclipse-sourcereferences",
162 "kotlin-version",
163 "require-capability",
164 "require-bundle");
165
166
167
168
169 @SuppressWarnings("deprecation")
170 private static final String IMPLEMENTATION_VENDOR_ID = Attributes.Name.IMPLEMENTATION_VENDOR_ID
171 .toString();
172
173
174
175 private static final String BUNDLE_VERSION = "Bundle-Version";
176
177
178
179 private static final String BUNDLE_DESCRIPTION = "Bundle-Description";
180
181
182
183 private static final String BUNDLE_NAME = "Bundle-Name";
184
185
186
187 private static final Pattern HTML_DETECTION_PATTERN = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);
188
189
190
191 private static final String ANALYZER_NAME = "Jar Analyzer";
192
193
194
195 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
196
197
198
199 private static final List<String> EXCLUDE_JARS = Arrays.asList("-doc.jar", "-src.jar", "-javadoc.jar", "-sources.jar");
200
201
202
203 private static final String[] EXTENSIONS = {"jar", "war", "aar"};
204
205
206
207 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
208
209
210
211
212 private static final byte[] ZIP_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x03, 0x04};
213
214
215
216
217 private static final byte[] ZIP_EMPTY_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x05, 0x06};
218
219
220
221
222 private static final byte[] ZIP_SPANNED_FIRST_BYTES = new byte[]{0x50, 0x4B, 0x07, 0x08};
223
224
225
226
227
228 private File tempFileLocation = null;
229
230
231
232
233
234 private static final String VALID_NAME = "^[A-Za-z0-9_\\-.]+$";
235
236
237
238
239
240
241
242 @Override
243 protected FileFilter getFileFilter() {
244 return FILTER;
245 }
246
247
248
249
250
251
252 @Override
253 public String getName() {
254 return ANALYZER_NAME;
255 }
256
257
258
259
260
261
262 @Override
263 public AnalysisPhase getAnalysisPhase() {
264 return ANALYSIS_PHASE;
265 }
266
267 @Override
268 public boolean accept(File pathname) {
269 final boolean accepted = super.accept(pathname);
270 return accepted && !isExcludedJar(pathname);
271 }
272
273
274
275
276
277
278
279
280
281 private boolean isExcludedJar(File path) {
282 final String fileName = path.getName().toLowerCase();
283 return EXCLUDE_JARS.stream().anyMatch(fileName::endsWith);
284 }
285
286
287
288
289
290
291
292
293 @Override
294 protected String getAnalyzerEnabledSettingKey() {
295 return Settings.KEYS.ANALYZER_JAR_ENABLED;
296 }
297
298
299
300
301
302
303
304
305
306
307 @Override
308 public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
309 final List<ClassNameInformation> classNames = collectClassNames(dependency);
310 final String fileName = dependency.getFileName().toLowerCase();
311 if ((classNames.isEmpty()
312 && (fileName.endsWith("-sources.jar")
313 || fileName.endsWith("-javadoc.jar")
314 || fileName.endsWith("-src.jar")
315 || fileName.endsWith("-doc.jar")
316 || isMacOSMetaDataFile(dependency, engine)))
317 || !isZipFile(dependency)) {
318 engine.removeDependency(dependency);
319 return;
320 }
321 Exception exception = null;
322 boolean hasManifest = false;
323 try {
324 hasManifest = parseManifest(dependency, classNames);
325 } catch (IOException ex) {
326 LOGGER.debug("Invalid Manifest", ex);
327 exception = ex;
328 }
329 boolean hasPOM = false;
330 try {
331 hasPOM = analyzePOM(dependency, classNames, engine);
332 } catch (AnalysisException ex) {
333 LOGGER.debug("Error parsing pom.xml", ex);
334 exception = ex;
335 }
336 final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
337 analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
338 dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
339
340 if (exception != null) {
341 throw new AnalysisException(String.format("An error occurred extracting evidence from "
342 + "%s, analysis may be incomplete; please see the log for more details.",
343 dependency.getDisplayFileName()), exception);
344 }
345 }
346
347
348
349
350
351
352
353
354
355
356
357
358 @SuppressFBWarnings(justification = "If actual file path is not null the path will have elements and getFileName will not be called on a null",
359 value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"})
360 private boolean isMacOSMetaDataFile(final Dependency dependency, final Engine engine) {
361 if (dependency.getActualFilePath() != null) {
362 final String fileName = Paths.get(dependency.getActualFilePath()).getFileName().toString();
363 return fileName.startsWith("._") && hasDependencyWithFilename(engine.getDependencies(), fileName.substring(2));
364 }
365 return false;
366 }
367
368
369
370
371
372
373
374
375
376
377
378 @SuppressFBWarnings(justification = "If actual file path is not null the path will have elements and getFileName will not be called on a null",
379 value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"})
380 private boolean hasDependencyWithFilename(final Dependency[] dependencies, final String fileName) {
381 for (final Dependency dependency : dependencies) {
382 if (dependency.getActualFilePath() != null
383 && Paths.get(dependency.getActualFilePath()).getFileName().toString().equalsIgnoreCase(fileName)) {
384 return true;
385 }
386 }
387 return false;
388 }
389
390
391
392
393
394
395
396
397
398
399
400 @SuppressFBWarnings(justification = "try with resources will clean up the output stream", value = {"OBL_UNSATISFIED_OBLIGATION"})
401 private boolean isZipFile(final Dependency dependency) {
402 final byte[] buffer = new byte[4];
403 try (FileInputStream fileInputStream = new FileInputStream(dependency.getActualFilePath())) {
404 if (fileInputStream.read(buffer) > 0
405 && (Arrays.equals(buffer, ZIP_FIRST_BYTES)
406 || Arrays.equals(buffer, ZIP_EMPTY_FIRST_BYTES)
407 || Arrays.equals(buffer, ZIP_SPANNED_FIRST_BYTES))) {
408 return true;
409 }
410 } catch (Exception e) {
411 LOGGER.warn("Unable to check if '{}' is a zip file", dependency.getActualFilePath());
412 LOGGER.trace("", e);
413 }
414 return false;
415 }
416
417
418
419
420
421
422
423
424
425
426
427
428
429 protected boolean analyzePOM(Dependency dependency, List<ClassNameInformation> classes, Engine engine) throws AnalysisException {
430
431
432 boolean evidenceAdded = false;
433 try (JarFile jar = new JarFile(dependency.getActualFilePath(), false)) {
434
435 final String repoPomName = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
436 final File repoPom = new File(repoPomName);
437 if (repoPom.isFile()) {
438 final Model pom = PomUtils.readPom(repoPom);
439 evidenceAdded |= setPomEvidence(dependency, pom, classes, true);
440 }
441
442 final List<String> pomEntries = retrievePomListing(jar);
443
444 for (String path : pomEntries) {
445 LOGGER.debug("Reading pom entry: {}", path);
446 try {
447
448 final Properties pomProperties = retrievePomProperties(path, jar);
449 final File pomFile = extractPom(path, jar);
450 final Model pom = PomUtils.readPom(pomFile);
451 pom.setGAVFromPomDotProperties(pomProperties);
452 pom.processProperties(pomProperties);
453
454 final String artifactId = new File(path).getParentFile().getName();
455 if (dependency.getActualFile().getName().startsWith(artifactId)) {
456 evidenceAdded |= setPomEvidence(dependency, pom, classes, true);
457 } else {
458 final String displayPath = String.format("%s%s%s",
459 dependency.getFilePath(),
460 File.separator,
461 path);
462 final String displayName = String.format("%s%s%s",
463 dependency.getFileName(),
464 File.separator,
465 path);
466 final Dependency newDependency = new Dependency();
467 newDependency.setActualFilePath(pomFile.getAbsolutePath());
468 newDependency.setFileName(displayName);
469 newDependency.setFilePath(displayPath);
470 newDependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
471 String groupId = pom.getGroupId();
472 String version = pom.getVersion();
473 if (groupId == null) {
474 groupId = pom.getParentGroupId();
475 }
476 if (version == null) {
477 version = pom.getParentVersion();
478 }
479 if (groupId == null) {
480 newDependency.setName(pom.getArtifactId());
481 newDependency.setPackagePath(String.format("%s:%s", pom.getArtifactId(), version));
482 } else {
483 newDependency.setName(String.format("%s:%s", groupId, pom.getArtifactId()));
484 newDependency.setPackagePath(String.format("%s:%s:%s", groupId, pom.getArtifactId(), version));
485 }
486 newDependency.setDisplayFileName(String.format("%s (shaded: %s)",
487 dependency.getDisplayFileName(), newDependency.getPackagePath()));
488 newDependency.setVersion(version);
489 setPomEvidence(newDependency, pom, null, true);
490 if (dependency.getProjectReferences().size() > 0) {
491 newDependency.addAllProjectReferences(dependency.getProjectReferences());
492 }
493 engine.addDependency(newDependency);
494 }
495 } catch (AnalysisException ex) {
496 LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
497 LOGGER.trace("", ex);
498 }
499 }
500 } catch (IOException ex) {
501 LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
502 LOGGER.trace("", ex);
503 }
504 return evidenceAdded;
505 }
506
507
508
509
510
511
512
513
514
515 private Properties retrievePomProperties(String path, final JarFile jar) {
516 final Properties pomProperties = new Properties();
517 final String propPath = path.substring(0, path.length() - 7) + "pom.properties";
518 final ZipEntry propEntry = jar.getEntry(propPath);
519 if (propEntry != null) {
520 try (Reader reader = new InputStreamReader(jar.getInputStream(propEntry), StandardCharsets.UTF_8)) {
521 pomProperties.load(reader);
522 LOGGER.debug("Read pom.properties: {}", propPath);
523 } catch (UnsupportedEncodingException ex) {
524 LOGGER.trace("UTF-8 is not supported", ex);
525 } catch (IOException ex) {
526 LOGGER.trace("Unable to read the POM properties", ex);
527 }
528 }
529 return pomProperties;
530 }
531
532
533
534
535
536
537
538
539
540 private List<String> retrievePomListing(final JarFile jar) throws IOException {
541 final List<String> pomEntries = new ArrayList<>();
542 final Enumeration<JarEntry> entries = jar.entries();
543 while (entries.hasMoreElements()) {
544 final JarEntry entry = entries.nextElement();
545 final String entryName = new File(entry.getName()).getName().toLowerCase();
546 if (!entry.isDirectory() && "pom.xml".equals(entryName)
547 && entry.getName().toUpperCase().startsWith("META-INF")) {
548 pomEntries.add(entry.getName());
549 }
550 }
551 return pomEntries;
552 }
553
554
555
556
557
558
559
560
561
562
563 private File extractPom(String path, JarFile jar) throws AnalysisException {
564 final File tmpDir = getNextTempDirectory();
565 final File file = new File(tmpDir, "pom.xml");
566 final ZipEntry entry = jar.getEntry(path);
567 if (entry == null) {
568 throw new AnalysisException(String.format("Pom (%s) does not exist in %s", path, jar.getName()));
569 }
570 try (InputStream input = jar.getInputStream(entry);
571 FileOutputStream fos = new FileOutputStream(file)) {
572 IOUtils.copy(input, fos);
573 } catch (IOException ex) {
574 LOGGER.warn("An error occurred reading '{}' from '{}'.", path, jar.getName());
575 LOGGER.error("", ex);
576 }
577 return file;
578 }
579
580
581
582
583
584
585
586
587
588
589
590
591 public static boolean setPomEvidence(Dependency dependency, Model pom,
592 List<ClassNameInformation> classes, boolean isMainPom) {
593 if (pom == null) {
594 return false;
595 }
596 boolean foundSomething = false;
597 boolean addAsIdentifier = true;
598 String groupid = intepolationFailCheck(pom.getGroupId());
599 String parentGroupId = intepolationFailCheck(pom.getParentGroupId());
600 String artifactid = intepolationFailCheck(pom.getArtifactId());
601 String parentArtifactId = intepolationFailCheck(pom.getParentArtifactId());
602 String version = intepolationFailCheck(pom.getVersion());
603 String parentVersion = intepolationFailCheck(pom.getParentVersion());
604
605 if (("org.sonatype.oss".equals(parentGroupId) && "oss-parent".equals(parentArtifactId))
606 || ("org.springframework.boot".equals(parentGroupId) && "spring-boot-starter-parent".equals(parentArtifactId))) {
607 parentGroupId = null;
608 parentArtifactId = null;
609 parentVersion = null;
610 }
611
612 if ((groupid == null || groupid.isEmpty()) && parentGroupId != null && !parentGroupId.isEmpty()) {
613 groupid = parentGroupId;
614 }
615
616 final String originalGroupID = groupid;
617
618 if ((artifactid == null || artifactid.isEmpty()) && parentArtifactId != null && !parentArtifactId.isEmpty()) {
619 artifactid = parentArtifactId;
620 }
621
622 final String originalArtifactID = artifactid;
623 if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) {
624 artifactid = artifactid.substring(4);
625 }
626
627 if ((version == null || version.isEmpty()) && parentVersion != null && !parentVersion.isEmpty()) {
628 version = parentVersion;
629 }
630
631 if (isMainPom && dependency.getName() == null && originalArtifactID != null && !originalArtifactID.isEmpty()) {
632 if (originalGroupID != null && !originalGroupID.isEmpty()) {
633 dependency.setName(String.format("%s:%s", originalGroupID, originalArtifactID));
634 } else {
635 dependency.setName(originalArtifactID);
636 }
637 }
638 if (isMainPom && dependency.getVersion() == null && version != null && !version.isEmpty()) {
639 dependency.setVersion(version);
640 }
641
642 if (groupid != null && !groupid.isEmpty()) {
643 foundSomething = true;
644 dependency.addEvidence(EvidenceType.VENDOR, "pom", "groupid", groupid, Confidence.HIGHEST);
645
646
647
648 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "groupid", groupid, Confidence.HIGHEST);
649 addMatchingValues(classes, groupid, dependency, EvidenceType.VENDOR);
650 addMatchingValues(classes, groupid, dependency, EvidenceType.PRODUCT);
651 if (parentGroupId != null && !parentGroupId.isEmpty() && !parentGroupId.equals(groupid)) {
652 dependency.addEvidence(EvidenceType.VENDOR, "pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
653
654
655 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "parent-groupid", parentGroupId, Confidence.MEDIUM);
656 addMatchingValues(classes, parentGroupId, dependency, EvidenceType.VENDOR);
657 addMatchingValues(classes, parentGroupId, dependency, EvidenceType.PRODUCT);
658 }
659 } else {
660 addAsIdentifier = false;
661 }
662
663 if (artifactid != null && !artifactid.isEmpty()) {
664 foundSomething = true;
665 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "artifactid", artifactid, Confidence.HIGHEST);
666 dependency.addEvidence(EvidenceType.VENDOR, "pom", "artifactid", artifactid, Confidence.LOW);
667 addMatchingValues(classes, artifactid, dependency, EvidenceType.VENDOR);
668 addMatchingValues(classes, artifactid, dependency, EvidenceType.PRODUCT);
669 if (parentArtifactId != null && !parentArtifactId.isEmpty() && !parentArtifactId.equals(artifactid)) {
670 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "parent-artifactid", parentArtifactId, Confidence.MEDIUM);
671 dependency.addEvidence(EvidenceType.VENDOR, "pom", "parent-artifactid", parentArtifactId, Confidence.LOW);
672 addMatchingValues(classes, parentArtifactId, dependency, EvidenceType.VENDOR);
673 addMatchingValues(classes, parentArtifactId, dependency, EvidenceType.PRODUCT);
674 }
675 } else {
676 addAsIdentifier = false;
677 }
678
679 if (version != null && !version.isEmpty()) {
680 foundSomething = true;
681 dependency.addEvidence(EvidenceType.VERSION, "pom", "version", version, Confidence.HIGHEST);
682 if (parentVersion != null && !parentVersion.isEmpty() && !parentVersion.equals(version)) {
683 dependency.addEvidence(EvidenceType.VERSION, "pom", "parent-version", version, Confidence.LOW);
684 }
685 } else {
686 addAsIdentifier = false;
687 }
688
689 if (addAsIdentifier && isMainPom) {
690 Identifier id = null;
691 try {
692 if (originalArtifactID != null && originalArtifactID.matches(VALID_NAME)
693 && originalGroupID != null && originalGroupID.matches(VALID_NAME)) {
694 final PackageURL purl = PackageURLBuilder.aPackageURL().withType("maven").withNamespace(originalGroupID)
695 .withName(originalArtifactID).withVersion(version).build();
696 id = new PurlIdentifier(purl, Confidence.HIGH);
697 } else {
698 LOGGER.debug("Invalid maven identifier identified: " + originalGroupID + ":" + originalArtifactID);
699 }
700 } catch (MalformedPackageURLException ex) {
701 final String gav = String.format("%s:%s:%s", originalGroupID, originalArtifactID, version);
702 LOGGER.debug("Error building package url for " + gav + "; using generic identifier instead.", ex);
703 id = new GenericIdentifier("maven:" + gav, Confidence.HIGH);
704 }
705 if (id != null) {
706 dependency.addSoftwareIdentifier(id);
707 }
708 }
709
710
711 final String org = pom.getOrganization();
712 if (org != null && !org.isEmpty()) {
713 dependency.addEvidence(EvidenceType.VENDOR, "pom", "organization name", org, Confidence.HIGH);
714 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "organization name", org, Confidence.LOW);
715 addMatchingValues(classes, org, dependency, EvidenceType.VENDOR);
716 addMatchingValues(classes, org, dependency, EvidenceType.PRODUCT);
717 }
718
719 String orgUrl = pom.getOrganizationUrl();
720 if (orgUrl != null && !orgUrl.isEmpty()) {
721 if (orgUrl.startsWith("https://github.com/") || orgUrl.startsWith("https://gitlab.com/")) {
722 orgUrl = orgUrl.substring(19);
723 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", orgUrl, Confidence.HIGH);
724 } else {
725 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "organization url", orgUrl, Confidence.LOW);
726 }
727 dependency.addEvidence(EvidenceType.VENDOR, "pom", "organization url", orgUrl, Confidence.MEDIUM);
728 }
729
730 final String pomName = pom.getName();
731 if (pomName != null && !pomName.isEmpty() && !"${project.groupId}:${project.artifactId}".equals(pomName)) {
732 foundSomething = true;
733 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "name", pomName, Confidence.HIGH);
734 dependency.addEvidence(EvidenceType.VENDOR, "pom", "name", pomName, Confidence.HIGH);
735 addMatchingValues(classes, pomName, dependency, EvidenceType.VENDOR);
736 addMatchingValues(classes, pomName, dependency, EvidenceType.PRODUCT);
737 }
738
739
740 final String description = pom.getDescription();
741 if (description != null && !description.isEmpty()
742 && !description.startsWith("POM was created by")
743 && !description.startsWith("Sonatype helps open source projects")
744 && !description.endsWith("project for Spring Boot")) {
745 foundSomething = true;
746 final String trimmedDescription = addDescription(dependency, description, "pom", "description");
747 addMatchingValues(classes, trimmedDescription, dependency, EvidenceType.VENDOR);
748 addMatchingValues(classes, trimmedDescription, dependency, EvidenceType.PRODUCT);
749 }
750
751 String projectURL = pom.getProjectURL();
752 if (projectURL != null && !projectURL.trim().isEmpty()) {
753 if (projectURL.startsWith("https://github.com/") || projectURL.startsWith("https://gitlab.com/")) {
754 projectURL = projectURL.substring(19);
755 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", projectURL, Confidence.HIGH);
756 } else {
757 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "url", projectURL, Confidence.MEDIUM);
758 }
759 dependency.addEvidence(EvidenceType.VENDOR, "pom", "url", projectURL, Confidence.HIGHEST);
760
761 }
762
763 if (pom.getDevelopers() != null && !pom.getDevelopers().isEmpty()) {
764 for (Developer dev : pom.getDevelopers()) {
765 if (!Strings.isNullOrEmpty(dev.getId())) {
766 dependency.addEvidence(EvidenceType.VENDOR, "pom", "developer id", dev.getId(), Confidence.MEDIUM);
767 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "developer id", dev.getId(), Confidence.LOW);
768 }
769 if (!Strings.isNullOrEmpty(dev.getName())) {
770 dependency.addEvidence(EvidenceType.VENDOR, "pom", "developer name", dev.getName(), Confidence.MEDIUM);
771 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "developer name", dev.getName(), Confidence.LOW);
772 }
773 if (!Strings.isNullOrEmpty(dev.getEmail())) {
774 dependency.addEvidence(EvidenceType.VENDOR, "pom", "developer email", dev.getEmail(), Confidence.LOW);
775 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "developer email", dev.getEmail(), Confidence.LOW);
776 }
777 if (!Strings.isNullOrEmpty(dev.getOrganizationUrl())) {
778 dependency.addEvidence(EvidenceType.VENDOR, "pom", "developer org URL", dev.getOrganizationUrl(), Confidence.MEDIUM);
779 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "developer org URL", dev.getOrganizationUrl(), Confidence.LOW);
780 }
781 final String devOrg = dev.getOrganization();
782 if (!Strings.isNullOrEmpty(devOrg)) {
783 dependency.addEvidence(EvidenceType.VENDOR, "pom", "developer org", devOrg, Confidence.MEDIUM);
784 dependency.addEvidence(EvidenceType.PRODUCT, "pom", "developer org", devOrg, Confidence.LOW);
785 addMatchingValues(classes, devOrg, dependency, EvidenceType.VENDOR);
786 addMatchingValues(classes, devOrg, dependency, EvidenceType.PRODUCT);
787 }
788 }
789 }
790
791 extractLicense(pom, dependency);
792 return foundSomething;
793 }
794
795
796
797
798
799
800
801
802
803
804
805
806 protected void analyzePackageNames(List<ClassNameInformation> classNames,
807 Dependency dependency, boolean addPackagesAsEvidence) {
808 final Map<String, Integer> vendorIdentifiers = new HashMap<>();
809 final Map<String, Integer> productIdentifiers = new HashMap<>();
810 analyzeFullyQualifiedClassNames(classNames, vendorIdentifiers, productIdentifiers);
811
812 final int classCount = classNames.size();
813
814 vendorIdentifiers.forEach((key, value) -> {
815 final float ratio = value / (float) classCount;
816 if (ratio > 0.5) {
817
818 dependency.addVendorWeighting(key);
819 if (addPackagesAsEvidence && key.length() > 1) {
820 dependency.addEvidence(EvidenceType.VENDOR, "jar", "package name", key, Confidence.LOW);
821 }
822 }
823 });
824 productIdentifiers.forEach((key, value) -> {
825 final float ratio = value / (float) classCount;
826 if (ratio > 0.5) {
827
828 dependency.addProductWeighting(key);
829 if (addPackagesAsEvidence && key.length() > 1) {
830 dependency.addEvidence(EvidenceType.PRODUCT, "jar", "package name", key, Confidence.LOW);
831 }
832 }
833 });
834 }
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853 protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation)
854 throws IOException {
855 boolean foundSomething = false;
856 try (JarFile jar = new JarFile(dependency.getActualFilePath(), false)) {
857 final Manifest manifest = jar.getManifest();
858 if (manifest == null) {
859 if (!dependency.getFileName().toLowerCase().endsWith("-sources.jar")
860 && !dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
861 && !dependency.getFileName().toLowerCase().endsWith("-src.jar")
862 && !dependency.getFileName().toLowerCase().endsWith("-doc.jar")) {
863 LOGGER.debug("Jar file '{}' does not contain a manifest.", dependency.getFileName());
864 }
865 return false;
866 }
867 String source = "Manifest";
868 String specificationVersion = null;
869 boolean hasImplementationVersion = false;
870 Attributes atts = manifest.getMainAttributes();
871 for (Entry<Object, Object> entry : atts.entrySet()) {
872 String key = entry.getKey().toString();
873 String value = atts.getValue(key);
874 if (HTML_DETECTION_PATTERN.matcher(value).find()) {
875 value = Jsoup.parse(value).text();
876 }
877 if (value.startsWith("git@github.com:") || value.startsWith("git@gitlab.com:")) {
878 value = value.substring(15);
879 }
880 if (IGNORE_VALUES.contains(value)) {
881 continue;
882 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
883 foundSomething = true;
884 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.HIGH);
885 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
886 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
887 hasImplementationVersion = true;
888 foundSomething = true;
889 dependency.addEvidence(EvidenceType.VERSION, source, key, value, Confidence.HIGH);
890 } else if ("specification-version".equalsIgnoreCase(key)) {
891 specificationVersion = value;
892 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
893 foundSomething = true;
894 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.HIGH);
895 addMatchingValues(classInformation, value, dependency, EvidenceType.VENDOR);
896 } else if (key.equalsIgnoreCase(IMPLEMENTATION_VENDOR_ID)) {
897 foundSomething = true;
898 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.MEDIUM);
899 addMatchingValues(classInformation, value, dependency, EvidenceType.VENDOR);
900 } else if (key.equalsIgnoreCase(BUNDLE_DESCRIPTION)) {
901 if (!value.startsWith("Sonatype helps open source projects")) {
902 foundSomething = true;
903 addDescription(dependency, value, "manifest", key);
904 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
905 }
906 } else if (key.equalsIgnoreCase(BUNDLE_NAME)) {
907 foundSomething = true;
908 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.MEDIUM);
909 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
910
911
912 } else if (key.equalsIgnoreCase(BUNDLE_VERSION)) {
913 foundSomething = true;
914 dependency.addEvidence(EvidenceType.VERSION, source, key, value, Confidence.HIGH);
915 } else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) {
916
917 continue;
918
919 } else if ("implementation-url".equalsIgnoreCase(key)
920 && value != null
921 && value.startsWith("https://projects.spring.io/spring-boot/#/spring-boot-starter-parent/parent/")) {
922 continue;
923 } else {
924 key = key.toLowerCase();
925 if (!IGNORE_KEYS.contains(key)
926 && !key.endsWith("jdk")
927 && !key.contains("lastmodified")
928 && !key.endsWith("package")
929 && !key.endsWith("classpath")
930 && !key.endsWith("class-path")
931 && !key.endsWith("-scm")
932 && !key.startsWith("scm-")
933 && !value.trim().startsWith("scm:")
934 && !isImportPackage(key, value)
935 && !isPackage(key, value)) {
936 foundSomething = true;
937 if (key.contains("version")) {
938 if (!key.contains("specification")) {
939 dependency.addEvidence(EvidenceType.VERSION, source, key, value, Confidence.MEDIUM);
940 }
941 } else if ("build-id".equals(key)) {
942 int pos = value.indexOf('(');
943 if (pos > 0) {
944 value = value.substring(0, pos - 1);
945 }
946 pos = value.indexOf('[');
947 if (pos > 0) {
948 value = value.substring(0, pos - 1);
949 }
950 dependency.addEvidence(EvidenceType.VERSION, source, key, value, Confidence.MEDIUM);
951 } else if (key.contains("title")) {
952 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.MEDIUM);
953 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
954 } else if (key.contains("vendor")) {
955 if (key.contains("specification")) {
956 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.LOW);
957 } else {
958 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.MEDIUM);
959 addMatchingValues(classInformation, value, dependency, EvidenceType.VENDOR);
960 }
961 } else if (key.contains("name")) {
962 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.MEDIUM);
963 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.MEDIUM);
964 addMatchingValues(classInformation, value, dependency, EvidenceType.VENDOR);
965 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
966 } else if (key.contains("license")) {
967 addLicense(dependency, value);
968 } else if (key.contains("description")) {
969 if (!value.startsWith("Sonatype helps open source projects")) {
970 final String trimmedDescription = addDescription(dependency, value, "manifest", key);
971 addMatchingValues(classInformation, trimmedDescription, dependency, EvidenceType.VENDOR);
972 addMatchingValues(classInformation, trimmedDescription, dependency, EvidenceType.PRODUCT);
973 }
974 } else {
975 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.LOW);
976 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.LOW);
977 addMatchingValues(classInformation, value, dependency, EvidenceType.VERSION);
978 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
979 if (value.matches(".*\\d.*")) {
980 final StringTokenizer tokenizer = new StringTokenizer(value, " ");
981 while (tokenizer.hasMoreElements()) {
982 final String s = tokenizer.nextToken();
983 if (s.matches("^[0-9.]+$")) {
984 dependency.addEvidence(EvidenceType.VERSION, source, key, s, Confidence.LOW);
985 }
986 }
987 }
988 }
989 }
990 }
991 }
992 for (Map.Entry<String, Attributes> item : manifest.getEntries().entrySet()) {
993 final String name = item.getKey();
994 source = "manifest: " + name;
995 atts = item.getValue();
996 for (Entry<Object, Object> entry : atts.entrySet()) {
997 final String key = entry.getKey().toString();
998 final String value = atts.getValue(key);
999 if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_TITLE.toString())) {
1000 foundSomething = true;
1001 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.MEDIUM);
1002 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
1003 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VERSION.toString())) {
1004 foundSomething = true;
1005 dependency.addEvidence(EvidenceType.VERSION, source, key, value, Confidence.MEDIUM);
1006 } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
1007 foundSomething = true;
1008 dependency.addEvidence(EvidenceType.VENDOR, source, key, value, Confidence.MEDIUM);
1009 addMatchingValues(classInformation, value, dependency, EvidenceType.VENDOR);
1010 } else if (key.equalsIgnoreCase(Attributes.Name.SPECIFICATION_TITLE.toString())) {
1011 foundSomething = true;
1012 dependency.addEvidence(EvidenceType.PRODUCT, source, key, value, Confidence.MEDIUM);
1013 addMatchingValues(classInformation, value, dependency, EvidenceType.PRODUCT);
1014 }
1015 }
1016 }
1017 if (specificationVersion != null && !hasImplementationVersion) {
1018 foundSomething = true;
1019 dependency.addEvidence(EvidenceType.VERSION, source, "specification-version", specificationVersion, Confidence.HIGH);
1020 }
1021 }
1022 return foundSomething;
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040 public static String addDescription(Dependency dependency, String description, String source, String key) {
1041 if (dependency.getDescription() == null) {
1042 dependency.setDescription(description);
1043 }
1044 String desc;
1045 if (HTML_DETECTION_PATTERN.matcher(description).find()) {
1046 desc = Jsoup.parse(description).text();
1047 } else {
1048 desc = description;
1049 }
1050 dependency.setDescription(desc);
1051 if (desc.length() > 100) {
1052 desc = desc.replaceAll("\\s\\s+", " ");
1053 final int posSuchAs = desc.toLowerCase().indexOf("such as ", 100);
1054 final int posLike = desc.toLowerCase().indexOf("like ", 100);
1055 final int posWillUse = desc.toLowerCase().indexOf("will use ", 100);
1056 final int posUses = desc.toLowerCase().indexOf(" uses ", 100);
1057
1058 int pos = -1;
1059 pos = Math.max(pos, posSuchAs);
1060 if (pos >= 0 && posLike >= 0) {
1061 pos = Math.min(pos, posLike);
1062 } else {
1063 pos = Math.max(pos, posLike);
1064 }
1065 if (pos >= 0 && posWillUse >= 0) {
1066 pos = Math.min(pos, posWillUse);
1067 } else {
1068 pos = Math.max(pos, posWillUse);
1069 }
1070 if (pos >= 0 && posUses >= 0) {
1071 pos = Math.min(pos, posUses);
1072 } else {
1073 pos = Math.max(pos, posUses);
1074 }
1075 if (pos > 0) {
1076 desc = desc.substring(0, pos) + "...";
1077 }
1078
1079
1080
1081
1082
1083
1084
1085 }
1086 return desc;
1087 }
1088
1089
1090
1091
1092
1093
1094
1095 private void addLicense(Dependency d, String license) {
1096 if (d.getLicense() == null) {
1097 d.setLicense(license);
1098 } else if (!d.getLicense().contains(license)) {
1099 d.setLicense(d.getLicense() + NEWLINE + license);
1100 }
1101 }
1102
1103
1104
1105
1106
1107
1108
1109
1110 @Override
1111 public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
1112 try {
1113 final File baseDir = getSettings().getTempDirectory();
1114 tempFileLocation = File.createTempFile("check", "tmp", baseDir);
1115 if (!tempFileLocation.delete()) {
1116 final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
1117 setEnabled(false);
1118 throw new InitializationException(msg);
1119 }
1120 if (!tempFileLocation.mkdirs()) {
1121 final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
1122 setEnabled(false);
1123 throw new InitializationException(msg);
1124 }
1125 } catch (IOException ex) {
1126 setEnabled(false);
1127 throw new InitializationException("Unable to create a temporary file", ex);
1128 }
1129 }
1130
1131
1132
1133
1134 @Override
1135 public void closeAnalyzer() {
1136 if (tempFileLocation != null && tempFileLocation.exists()) {
1137 LOGGER.debug("Attempting to delete temporary files from `{}`", tempFileLocation.toString());
1138 final boolean success = FileUtils.delete(tempFileLocation);
1139 if (!success && tempFileLocation.exists()) {
1140 final String[] l = tempFileLocation.list();
1141 if (l != null && l.length > 0) {
1142 LOGGER.warn("Failed to delete the JAR Analyzder's temporary files from `{}`, "
1143 + "see the log for more details", tempFileLocation.getAbsolutePath());
1144 }
1145 }
1146 }
1147 }
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 private boolean isImportPackage(String key, String value) {
1159 final Pattern packageRx = Pattern.compile("^(\\s*[a-zA-Z0-9_#\\$\\*\\.]+\\s*[,;])+(\\s*[a-zA-Z0-9_#\\$\\*\\.]+\\s*)?$");
1160 final boolean matches = packageRx.matcher(value).matches();
1161 return matches && (key.contains("import") || key.contains("include") || value.length() > 10);
1162 }
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172 protected List<ClassNameInformation> collectClassNames(Dependency dependency) {
1173 final List<ClassNameInformation> classNames = new ArrayList<>();
1174 try (JarFile jar = new JarFile(dependency.getActualFilePath(), false)) {
1175 final Enumeration<JarEntry> entries = jar.entries();
1176 while (entries.hasMoreElements()) {
1177 final JarEntry entry = entries.nextElement();
1178 final String name = entry.getName().toLowerCase();
1179
1180 if (name.endsWith(".class") && !name.matches("^javax?\\..*$")) {
1181 final ClassNameInformation className = new ClassNameInformation(name.substring(0, name.length() - 6));
1182 classNames.add(className);
1183 }
1184 }
1185 } catch (IOException ex) {
1186 LOGGER.warn("Unable to open jar file '{}'.", dependency.getFileName());
1187 LOGGER.debug("", ex);
1188 }
1189 return classNames;
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204 private void analyzeFullyQualifiedClassNames(List<ClassNameInformation> classNames,
1205 Map<String, Integer> vendor, Map<String, Integer> product) {
1206 for (ClassNameInformation entry : classNames) {
1207 final List<String> list = entry.getPackageStructure();
1208 addEntry(vendor, list.get(0));
1209
1210 if (list.size() == 2) {
1211 addEntry(product, list.get(1));
1212 } else if (list.size() == 3) {
1213 addEntry(vendor, list.get(1));
1214 addEntry(product, list.get(1));
1215 addEntry(product, list.get(2));
1216 } else if (list.size() >= 4) {
1217 addEntry(vendor, list.get(1));
1218 addEntry(vendor, list.get(2));
1219 addEntry(product, list.get(1));
1220 addEntry(product, list.get(2));
1221 addEntry(product, list.get(3));
1222 }
1223 }
1224 }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234 private void addEntry(Map<String, Integer> collection, String key) {
1235 if (collection.containsKey(key)) {
1236 collection.put(key, collection.get(key) + 1);
1237 } else {
1238 collection.put(key, 1);
1239 }
1240 }
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253 protected static void addMatchingValues(List<ClassNameInformation> classes, String value, Dependency dep, EvidenceType type) {
1254 if (value == null || value.isEmpty() || classes == null || classes.isEmpty()) {
1255 return;
1256 }
1257 final HashSet<String> tested = new HashSet<>();
1258
1259 for (ClassNameInformation cni : classes) {
1260
1261 for (String key : cni.getPackageStructure()) {
1262
1263 if (!tested.contains(key)) {
1264 tested.add(key);
1265 final int pos = StringUtils.indexOfIgnoreCase(value, key);
1266 if ((pos == 0 && (key.length() == value.length() || (key.length() < value.length()
1267 && !Character.isLetterOrDigit(value.charAt(key.length())))))
1268 || (pos > 0 && !Character.isLetterOrDigit(value.charAt(pos - 1))
1269 && (pos + key.length() == value.length() || (key.length() < value.length()
1270 && !Character.isLetterOrDigit(value.charAt(pos + key.length())))))) {
1271 dep.addEvidence(type, "jar", "package name", key, Confidence.HIGHEST);
1272 }
1273 }
1274 }
1275 }
1276 }
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286 private boolean isPackage(String key, String value) {
1287
1288 return !key.matches(".*(version|title|vendor|name|license|description).*")
1289 && value.matches("^[a-zA-Z_][a-zA-Z0-9_\\$]*\\.([a-zA-Z_][a-zA-Z0-9_\\$]*\\.)*([a-zA-Z_][a-zA-Z0-9_\\$]*)$");
1290
1291 }
1292
1293
1294
1295
1296
1297
1298
1299 private static String intepolationFailCheck(String value) {
1300 if (value != null && value.contains("${")) {
1301 return null;
1302 }
1303 return value;
1304 }
1305
1306
1307
1308
1309
1310
1311
1312
1313 public static void extractLicense(Model pom, Dependency dependency) {
1314
1315 if (pom.getLicenses() != null) {
1316 StringBuilder license = null;
1317 for (License lic : pom.getLicenses()) {
1318 String tmp = null;
1319 if (lic.getName() != null) {
1320 tmp = lic.getName();
1321 }
1322 if (lic.getUrl() != null) {
1323 if (tmp == null) {
1324 tmp = lic.getUrl();
1325 } else {
1326 tmp += ": " + lic.getUrl();
1327 }
1328 }
1329 if (tmp == null) {
1330 continue;
1331 }
1332 if (HTML_DETECTION_PATTERN.matcher(tmp).find()) {
1333 tmp = Jsoup.parse(tmp).text();
1334 }
1335 if (license == null) {
1336 license = new StringBuilder(tmp);
1337 } else {
1338 license.append("\n").append(tmp);
1339 }
1340 }
1341 if (license != null) {
1342 dependency.setLicense(license.toString());
1343
1344 }
1345 }
1346 }
1347
1348
1349
1350
1351 protected static class ClassNameInformation {
1352
1353
1354
1355
1356 private String name;
1357
1358
1359
1360
1361 private final ArrayList<String> packageStructure = new ArrayList<>();
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385 ClassNameInformation(String className) {
1386 name = className;
1387 if (name.contains("/")) {
1388 final String[] tmp = StringUtils.split(className.toLowerCase(), '/');
1389 int start = 0;
1390 int end = 3;
1391 if ("com".equals(tmp[0]) || "org".equals(tmp[0])) {
1392 start = 1;
1393 end = 4;
1394 }
1395 if (tmp.length <= end) {
1396 end = tmp.length - 1;
1397 }
1398 packageStructure.addAll(Arrays.asList(tmp).subList(start, end + 1));
1399 } else {
1400 packageStructure.add(name);
1401 }
1402 }
1403
1404
1405
1406
1407
1408
1409 public String getName() {
1410 return name;
1411 }
1412
1413
1414
1415
1416
1417
1418 public void setName(String name) {
1419 this.name = name;
1420 }
1421
1422
1423
1424
1425
1426
1427 public ArrayList<String> getPackageStructure() {
1428 return packageStructure;
1429 }
1430 }
1431
1432
1433
1434
1435
1436
1437
1438 private File getNextTempDirectory() throws AnalysisException {
1439 final int dirCount = DIR_COUNT.incrementAndGet();
1440 final File directory = new File(tempFileLocation, String.valueOf(dirCount));
1441
1442 if (directory.exists()) {
1443 return getNextTempDirectory();
1444 }
1445 if (!directory.mkdirs()) {
1446 final String msg = String.format("Unable to create temp directory '%s'.", directory.getAbsolutePath());
1447 throw new AnalysisException(msg);
1448 }
1449 return directory;
1450 }
1451 }