1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck;
19
20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21 import org.apache.commons.jcs3.JCS;
22 import org.jspecify.annotations.NonNull;
23 import org.jspecify.annotations.Nullable;
24 import org.owasp.dependencycheck.analyzer.AnalysisPhase;
25 import org.owasp.dependencycheck.analyzer.Analyzer;
26 import org.owasp.dependencycheck.analyzer.AnalyzerService;
27 import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
28 import org.owasp.dependencycheck.data.nvdcve.DatabaseManager;
29 import org.owasp.dependencycheck.data.nvdcve.CveDB;
30 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
31 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
32 import org.owasp.dependencycheck.data.update.CachedWebDataSource;
33 import org.owasp.dependencycheck.data.update.UpdateService;
34 import org.owasp.dependencycheck.data.update.exception.UpdateException;
35 import org.owasp.dependencycheck.dependency.Dependency;
36 import org.owasp.dependencycheck.exception.ExceptionCollection;
37 import org.owasp.dependencycheck.exception.InitializationException;
38 import org.owasp.dependencycheck.exception.NoDataException;
39 import org.owasp.dependencycheck.exception.ReportException;
40 import org.owasp.dependencycheck.exception.WriteLockException;
41 import org.owasp.dependencycheck.reporting.ReportGenerator;
42 import org.owasp.dependencycheck.utils.FileUtils;
43 import org.owasp.dependencycheck.utils.Settings;
44 import org.owasp.dependencycheck.utils.WriteLock;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import javax.annotation.concurrent.NotThreadSafe;
49 import java.io.File;
50 import java.io.FileFilter;
51 import java.io.IOException;
52 import java.nio.file.Files;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collection;
56 import java.util.Collections;
57 import java.util.EnumMap;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Objects;
64 import java.util.Set;
65 import java.util.concurrent.CancellationException;
66 import java.util.concurrent.ExecutionException;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Future;
70 import java.util.concurrent.TimeUnit;
71
72 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINAL;
73 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS;
74 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS_PHASE2;
75 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.IDENTIFIER_ANALYSIS;
76 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION;
77 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION2;
78 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INITIAL;
79 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_FINDING_ANALYSIS;
80 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
81 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION1;
82 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION2;
83 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION3;
84 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_FINDING_ANALYSIS;
85 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_IDENTIFIER_ANALYSIS;
86 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_INFORMATION_COLLECTION;
87 import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
88 import org.owasp.dependencycheck.dependency.naming.Identifier;
89
90
91
92
93
94
95
96
97
98 @NotThreadSafe
99 public class Engine implements FileFilter, AutoCloseable {
100
101
102
103
104 private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
105
106
107
108 private final List<Dependency> dependencies = Collections.synchronizedList(new ArrayList<>());
109
110
111
112 private final Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<>(AnalysisPhase.class);
113
114
115
116 private final Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<>();
117
118
119
120
121 private final Mode mode;
122
123
124
125
126 private final ClassLoader serviceClassLoader;
127
128
129
130 private final Settings settings;
131
132
133
134 private final Map<String, Object> objects = new HashMap<>();
135
136
137
138 private Dependency[] dependenciesExternalView = null;
139
140
141
142 private CveDB database = null;
143
144
145
146
147
148 public Engine(@NonNull final Settings settings) {
149 this(Mode.STANDALONE, settings);
150 }
151
152
153
154
155
156
157
158 public Engine(@NonNull final Mode mode, @NonNull final Settings settings) {
159 this(Thread.currentThread().getContextClassLoader(), mode, settings);
160 }
161
162
163
164
165
166
167
168 public Engine(@NonNull final ClassLoader serviceClassLoader, @NonNull final Settings settings) {
169 this(serviceClassLoader, Mode.STANDALONE, settings);
170 }
171
172
173
174
175
176
177
178
179 public Engine(@NonNull final ClassLoader serviceClassLoader, @NonNull final Mode mode, @NonNull final Settings settings) {
180 this.settings = settings;
181 this.serviceClassLoader = serviceClassLoader;
182 this.mode = mode;
183 initializeEngine();
184 }
185
186
187
188
189
190
191
192
193 protected final void initializeEngine() {
194 loadAnalyzers();
195 }
196
197
198
199
200 @Override
201 public void close() {
202 if (mode.isDatabaseRequired()) {
203 if (database != null) {
204 database.close();
205 database = null;
206 }
207 }
208 JCS.shutdown();
209 }
210
211
212
213
214
215 private void loadAnalyzers() {
216 if (!analyzers.isEmpty()) {
217 return;
218 }
219 mode.getPhases().forEach((phase) -> analyzers.put(phase, new ArrayList<>()));
220 final AnalyzerService service = new AnalyzerService(serviceClassLoader, settings);
221 final List<Analyzer> iterator = service.getAnalyzers(mode.getPhases());
222 iterator.forEach((a) -> {
223 a.initialize(this.settings);
224 analyzers.get(a.getAnalysisPhase()).add(a);
225 if (a instanceof FileTypeAnalyzer) {
226 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
227 }
228 });
229 }
230
231
232
233
234
235
236
237 public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
238 return analyzers.get(phase);
239 }
240
241
242
243
244
245
246
247
248 public synchronized void addDependency(Dependency dependency) {
249 if (dependency.isVirtual()) {
250 for (Dependency existing : dependencies) {
251 if (existing.isVirtual()
252 && existing.getSha256sum() != null
253 && existing.getSha256sum().equals(dependency.getSha256sum())
254 && existing.getDisplayFileName() != null
255 && existing.getDisplayFileName().equals(dependency.getDisplayFileName())
256 && identifiersMatch(existing.getSoftwareIdentifiers(), dependency.getSoftwareIdentifiers())) {
257 DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
258 return;
259 }
260 }
261 }
262 dependencies.add(dependency);
263 dependenciesExternalView = null;
264 }
265
266
267
268
269 public synchronized void sortDependencies() {
270
271
272
273 }
274
275
276
277
278
279
280 public synchronized void removeDependency(@NonNull final Dependency dependency) {
281 dependencies.remove(dependency);
282 dependenciesExternalView = null;
283 }
284
285
286
287
288
289
290 @SuppressFBWarnings(justification = "This is the intended external view of the dependencies", value = {"EI_EXPOSE_REP"})
291 public synchronized Dependency[] getDependencies() {
292 if (dependenciesExternalView == null) {
293 dependenciesExternalView = dependencies.toArray(new Dependency[0]);
294 }
295 return dependenciesExternalView;
296 }
297
298
299
300
301
302
303 public synchronized void setDependencies(@NonNull final List<Dependency> dependencies) {
304 this.dependencies.clear();
305 this.dependencies.addAll(dependencies);
306 dependenciesExternalView = null;
307 }
308
309
310
311
312
313
314
315
316
317
318 public List<Dependency> scan(@NonNull final String[] paths) {
319 return scan(paths, null);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333 public List<Dependency> scan(@NonNull final String[] paths, @Nullable final String projectReference) {
334 final List<Dependency> deps = new ArrayList<>();
335 for (String path : paths) {
336 final List<Dependency> d = scan(path, projectReference);
337 if (d != null) {
338 deps.addAll(d);
339 }
340 }
341 return deps;
342 }
343
344
345
346
347
348
349
350
351
352 public List<Dependency> scan(@NonNull final String path) {
353 return scan(path, null);
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367 public List<Dependency> scan(@NonNull final String path, String projectReference) {
368 final File file = new File(path);
369 return scan(file, projectReference);
370 }
371
372
373
374
375
376
377
378
379
380
381 public List<Dependency> scan(File[] files) {
382 return scan(files, null);
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396 public List<Dependency> scan(File[] files, String projectReference) {
397 final List<Dependency> deps = new ArrayList<>();
398 for (File file : files) {
399 final List<Dependency> d = scan(file, projectReference);
400 if (d != null) {
401 deps.addAll(d);
402 }
403 }
404 return deps;
405 }
406
407
408
409
410
411
412
413
414
415
416 public List<Dependency> scan(Collection<File> files) {
417 return scan(files, null);
418 }
419
420
421
422
423
424
425
426
427
428
429
430
431 public List<Dependency> scan(Collection<File> files, String projectReference) {
432 final List<Dependency> deps = new ArrayList<>();
433 files.stream().map((file) -> scan(file, projectReference))
434 .filter(Objects::nonNull)
435 .forEach(deps::addAll);
436 return deps;
437 }
438
439
440
441
442
443
444
445
446
447
448 public List<Dependency> scan(File file) {
449 return scan(file, null);
450 }
451
452
453
454
455
456
457
458
459
460
461
462
463 @Nullable
464 public List<Dependency> scan(@NonNull final File file, String projectReference) {
465 if (file.exists()) {
466 if (file.isDirectory()) {
467 return scanDirectory(file, projectReference);
468 } else {
469 final Dependency d = scanFile(file, projectReference);
470 if (d != null) {
471 final List<Dependency> deps = new ArrayList<>();
472 deps.add(d);
473 return deps;
474 }
475 }
476 }
477 return null;
478 }
479
480
481
482
483
484
485
486
487 protected List<Dependency> scanDirectory(File dir) {
488 return scanDirectory(dir, null);
489 }
490
491
492
493
494
495
496
497
498
499
500
501 protected List<Dependency> scanDirectory(@NonNull final File dir, @Nullable final String projectReference) {
502 final File[] files = dir.listFiles();
503 final List<Dependency> deps = new ArrayList<>();
504 if (files != null) {
505 for (File f : files) {
506 if (f.isDirectory()) {
507 final List<Dependency> d = scanDirectory(f, projectReference);
508 if (d != null) {
509 deps.addAll(d);
510 }
511 } else {
512 final Dependency d = scanFile(f, projectReference);
513 if (d != null) {
514 deps.add(d);
515 }
516 }
517 }
518 }
519 return deps;
520 }
521
522
523
524
525
526
527
528
529 protected Dependency scanFile(@NonNull final File file) {
530 return scanFile(file, null);
531 }
532
533
534
535
536
537
538
539
540
541
542
543
544 protected synchronized Dependency scanFile(@NonNull final File file, @Nullable final String projectReference) {
545 Dependency dependency = null;
546 if (file.isFile()) {
547 if (accept(file)) {
548 dependency = new Dependency(file);
549 if (projectReference != null) {
550 dependency.addProjectReference(projectReference);
551 }
552 final String sha1 = dependency.getSha1sum();
553 boolean found = false;
554
555 if (sha1 != null) {
556 for (Dependency existing : dependencies) {
557 if (sha1.equals(existing.getSha1sum())) {
558 if (existing.getDisplayFileName().contains(": ")
559 || dependency.getDisplayFileName().contains(": ")
560 || dependency.getActualFilePath().contains("dctemp")) {
561 continue;
562 }
563 found = true;
564 if (projectReference != null) {
565 existing.addProjectReference(projectReference);
566 }
567 if (existing.getActualFilePath() != null && dependency.getActualFilePath() != null
568 && !existing.getActualFilePath().equals(dependency.getActualFilePath())) {
569
570 if (DependencyBundlingAnalyzer.firstPathIsShortest(existing.getFilePath(), dependency.getFilePath())) {
571 DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
572
573
574 return existing;
575 } else {
576
577
578 found = false;
579 }
580
581 } else {
582
583 return existing;
584 }
585 break;
586 }
587 }
588 }
589 if (!found) {
590 dependencies.add(dependency);
591 dependenciesExternalView = null;
592 }
593 }
594 } else {
595 LOGGER.debug("Path passed to scanFile(File) is not a file that can be scanned by dependency-check: {}. Skipping the file.", file);
596 }
597 return dependency;
598 }
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616 public void analyzeDependencies() throws ExceptionCollection {
617 final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
618
619 initializeAndUpdateDatabase(exceptions);
620
621
622 try {
623 ensureDataExists();
624 } catch (NoDataException ex) {
625 throwFatalExceptionCollection("Unable to continue dependency-check analysis.", ex, exceptions);
626 }
627 LOGGER.info("\n\nDependency-Check is an open source tool performing a best effort analysis of 3rd party dependencies; false positives and "
628 + "false negatives may exist in the analysis performed by the tool. Use of the tool and the reporting provided constitutes "
629 + "acceptance for use in an AS IS condition, and there are NO warranties, implied or otherwise, with regard to the analysis "
630 + "or its use. Any use of the tool and the reporting provided is at the user's risk. In no event shall the copyright holder "
631 + "or OWASP be held liable for any damages whatsoever arising out of or in connection with the use of this tool, the analysis "
632 + "performed, or the resulting report.\n\n\n"
633 + " About ODC: https://dependency-check.github.io/DependencyCheck/general/internals.html\n"
634 + " False Positives: https://dependency-check.github.io/DependencyCheck/general/suppression.html\n"
635 + "\n");
636 LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
637 LOGGER.info("Analysis Started");
638 final long analysisStart = System.currentTimeMillis();
639
640
641 for (AnalysisPhase phase : mode.getPhases()) {
642 final List<Analyzer> analyzerList = analyzers.get(phase);
643
644 for (final Analyzer analyzer : analyzerList) {
645 final long analyzerStart = System.currentTimeMillis();
646 try {
647 initializeAnalyzer(analyzer);
648 } catch (InitializationException ex) {
649 exceptions.add(ex);
650 if (ex.isFatal()) {
651 continue;
652 }
653 }
654
655 if (analyzer.isEnabled()) {
656 executeAnalysisTasks(analyzer, exceptions);
657
658 final long analyzerDurationMillis = System.currentTimeMillis() - analyzerStart;
659 final long analyzerDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(analyzerDurationMillis);
660 LOGGER.info("Finished {} ({} seconds)", analyzer.getName(), analyzerDurationSeconds);
661 } else {
662 LOGGER.debug("Skipping {} (not enabled)", analyzer.getName());
663 }
664 }
665 }
666 mode.getPhases().stream()
667 .map(analyzers::get)
668 .forEach((analyzerList) -> analyzerList.forEach(this::closeAnalyzer));
669
670 LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
671 final long analysisDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - analysisStart);
672 LOGGER.info("Analysis Complete ({} seconds)", analysisDurationSeconds);
673 if (exceptions.size() > 0) {
674 throw new ExceptionCollection(exceptions);
675 }
676 }
677
678
679
680
681
682
683
684 private void initializeAndUpdateDatabase(@NonNull final List<Throwable> exceptions) throws ExceptionCollection {
685 if (!mode.isDatabaseRequired()) {
686 return;
687 }
688 final boolean autoUpdate;
689 autoUpdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
690 if (autoUpdate) {
691 try {
692 doUpdates(true);
693 } catch (UpdateException ex) {
694 exceptions.add(ex);
695 LOGGER.warn("Unable to update 1 or more Cached Web DataSource, using local "
696 + "data instead. Results may not include recent vulnerabilities.");
697 LOGGER.debug("Update Error", ex);
698 } catch (DatabaseException ex) {
699 throwFatalDatabaseException(ex, exceptions);
700 }
701 } else {
702 try {
703 if (DatabaseManager.isH2Connection(settings) && !DatabaseManager.h2DataFileExists(settings)) {
704 throw new ExceptionCollection(new NoDataException("Autoupdate is disabled and the database does not exist"), true);
705 } else {
706 openDatabase(true, true);
707 }
708 } catch (IOException ex) {
709 throw new ExceptionCollection(new DatabaseException("Autoupdate is disabled and unable to connect to the database"), true);
710 } catch (DatabaseException ex) {
711 throwFatalDatabaseException(ex, exceptions);
712 }
713 }
714 }
715
716
717
718
719
720
721
722
723
724 private void throwFatalDatabaseException(DatabaseException ex, final List<Throwable> exceptions) throws ExceptionCollection {
725 final String msg;
726 if (ex.getMessage().contains("Unable to connect") && DatabaseManager.isH2Connection(settings)) {
727 msg = "Unable to connect to the database - if this error persists it may be "
728 + "due to a corrupt database. Consider running `purge` to delete the existing database";
729 } else {
730 msg = "Unable to connect to the dependency-check database";
731 }
732 exceptions.add(new DatabaseException(msg, ex));
733 throw new ExceptionCollection(exceptions, true);
734 }
735
736
737
738
739
740
741
742
743
744 protected void executeAnalysisTasks(@NonNull final Analyzer analyzer, List<Throwable> exceptions) throws ExceptionCollection {
745 LOGGER.debug("Starting {}", analyzer.getName());
746 final List<AnalysisTask> analysisTasks = getAnalysisTasks(analyzer, exceptions);
747 final ExecutorService executorService = getExecutorService(analyzer);
748
749 try {
750 final int timeout = settings.getInt(Settings.KEYS.ANALYSIS_TIMEOUT, 180);
751 final List<Future<Void>> results = executorService.invokeAll(analysisTasks, timeout, TimeUnit.MINUTES);
752
753
754 for (Future<Void> result : results) {
755 try {
756 result.get();
757 } catch (ExecutionException e) {
758 throwFatalExceptionCollection("Analysis task failed with a fatal exception.", e, exceptions);
759 } catch (CancellationException e) {
760 throwFatalExceptionCollection("Analysis task was cancelled.", e, exceptions);
761 }
762 }
763 } catch (InterruptedException e) {
764 Thread.currentThread().interrupt();
765 throwFatalExceptionCollection("Analysis has been interrupted.", e, exceptions);
766 } finally {
767 executorService.shutdown();
768 }
769 }
770
771
772
773
774
775
776
777
778 protected synchronized List<AnalysisTask> getAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) {
779 final List<AnalysisTask> result = new ArrayList<>();
780 dependencies.stream().map((dependency) -> new AnalysisTask(analyzer, dependency, this, exceptions)).forEach(result::add);
781 return result;
782 }
783
784
785
786
787
788
789
790 protected ExecutorService getExecutorService(Analyzer analyzer) {
791 if (analyzer.supportsParallelProcessing()) {
792 final int maximumNumberOfThreads = Runtime.getRuntime().availableProcessors();
793 LOGGER.debug("Parallel processing with up to {} threads: {}.", maximumNumberOfThreads, analyzer.getName());
794 return Executors.newFixedThreadPool(maximumNumberOfThreads);
795 } else {
796 LOGGER.debug("Parallel processing is not supported: {}.", analyzer.getName());
797 return Executors.newSingleThreadExecutor();
798 }
799 }
800
801
802
803
804
805
806
807
808 protected void initializeAnalyzer(@NonNull final Analyzer analyzer) throws InitializationException {
809 try {
810 LOGGER.debug("Initializing {}", analyzer.getName());
811 analyzer.prepare(this);
812 } catch (InitializationException ex) {
813 LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
814 LOGGER.debug("", ex);
815 if (ex.isFatal()) {
816 try {
817 analyzer.close();
818 } catch (Throwable ex1) {
819 LOGGER.trace("", ex1);
820 }
821 }
822 throw ex;
823 } catch (Throwable ex) {
824 LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName());
825 LOGGER.debug("", ex);
826 try {
827 analyzer.close();
828 } catch (Throwable ex1) {
829 LOGGER.trace("", ex1);
830 }
831 throw new InitializationException("Unexpected Exception", ex);
832 }
833 }
834
835
836
837
838
839
840 protected void closeAnalyzer(@NonNull final Analyzer analyzer) {
841 LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
842 try {
843 analyzer.close();
844 } catch (Throwable ex) {
845 LOGGER.trace("", ex);
846 }
847 }
848
849
850
851
852
853
854
855
856
857
858 public boolean doUpdates() throws UpdateException, DatabaseException {
859 return doUpdates(false);
860 }
861
862
863
864
865
866
867
868
869
870
871
872
873 public boolean doUpdates(boolean remainOpen) throws UpdateException, DatabaseException {
874 if (mode.isDatabaseRequired()) {
875 try (WriteLock dblock = new WriteLock(getSettings(), DatabaseManager.isH2Connection(getSettings()))) {
876
877 openDatabase(false, false);
878 LOGGER.info("Checking for updates");
879 final long updateStart = System.currentTimeMillis();
880 final UpdateService service = new UpdateService(serviceClassLoader);
881 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
882 boolean dbUpdatesMade = false;
883 UpdateException updateException = null;
884 while (iterator.hasNext()) {
885 try {
886 final CachedWebDataSource source = iterator.next();
887 dbUpdatesMade |= source.update(this);
888 } catch (UpdateException ex) {
889 updateException = ex;
890 LOGGER.error(ex.getMessage(), ex);
891 }
892 }
893 if (dbUpdatesMade) {
894 database.defrag();
895 }
896 database.close();
897 database = null;
898 if (updateException != null) {
899 throw updateException;
900 }
901 LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
902 if (remainOpen) {
903
904 openDatabase(true, false);
905 }
906
907 return dbUpdatesMade;
908 } catch (WriteLockException ex) {
909 throw new UpdateException("Unable to obtain an exclusive lock on the H2 database to perform updates", ex);
910 }
911 } else {
912 LOGGER.info("Skipping update check in evidence collection mode.");
913 return false;
914 }
915 }
916
917
918
919
920
921
922
923 public boolean purge() {
924 boolean result = true;
925 final UpdateService service = new UpdateService(serviceClassLoader);
926 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
927 while (iterator.hasNext()) {
928 result &= iterator.next().purge(this);
929 }
930 try {
931 final File cache = new File(settings.getDataDirectory(), "cache");
932 if (cache.exists()) {
933 if (FileUtils.delete(cache)) {
934 LOGGER.info("Cache directory purged");
935 }
936 }
937 } catch (IOException ex) {
938 throw new RuntimeException(ex);
939 }
940 try {
941 final File cache = new File(settings.getDataDirectory(), "oss_cache");
942 if (cache.exists()) {
943 if (FileUtils.delete(cache)) {
944 LOGGER.info("OSS Cache directory purged");
945 }
946 }
947 } catch (IOException ex) {
948 throw new RuntimeException(ex);
949 }
950
951 return result;
952 }
953
954
955
956
957
958
959
960
961
962
963
964 public void openDatabase() throws DatabaseException {
965 openDatabase(false, true);
966 }
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982 @SuppressWarnings("try")
983 public void openDatabase(boolean readOnly, boolean lockRequired) throws DatabaseException {
984 if (mode.isDatabaseRequired() && database == null) {
985 try (WriteLock dblock = new WriteLock(getSettings(), lockRequired && DatabaseManager.isH2Connection(settings))) {
986 if (readOnly
987 && DatabaseManager.isH2Connection(settings)
988 && settings.getString(Settings.KEYS.DB_CONNECTION_STRING).contains("file:%s")) {
989 final File db = DatabaseManager.getH2DataFile(settings);
990 if (db.isFile()) {
991 final File temp = settings.getTempDirectory();
992 final File tempDB = new File(temp, db.getName());
993 LOGGER.debug("copying database {} to {}", db.toPath(), temp.toPath());
994 Files.copy(db.toPath(), tempDB.toPath());
995 settings.setString(Settings.KEYS.H2_DATA_DIRECTORY, temp.getPath());
996 final String connStr = settings.getString(Settings.KEYS.DB_CONNECTION_STRING);
997 if (!connStr.contains("ACCESS_MODE_DATA")) {
998 settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connStr + "ACCESS_MODE_DATA=r");
999 }
1000 settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
1001 database = new CveDB(settings);
1002 } else {
1003 throw new DatabaseException("Unable to open database - configured database file does not exist: " + db);
1004 }
1005 } else {
1006 database = new CveDB(settings);
1007 }
1008 } catch (IOException ex) {
1009 throw new DatabaseException("Unable to open database in read only mode", ex);
1010 } catch (WriteLockException ex) {
1011 throw new DatabaseException("Failed to obtain lock - unable to open database", ex);
1012 }
1013 database.open();
1014 }
1015 }
1016
1017
1018
1019
1020
1021
1022 public CveDB getDatabase() {
1023 return this.database;
1024 }
1025
1026
1027
1028
1029
1030
1031
1032 @NonNull
1033 public List<Analyzer> getAnalyzers() {
1034 final List<Analyzer> analyzerList = new ArrayList<>();
1035
1036 mode.getPhases().stream()
1037 .map(analyzers::get)
1038 .forEachOrdered(analyzerList::addAll);
1039 return analyzerList;
1040 }
1041
1042
1043
1044
1045
1046
1047
1048
1049 @Override
1050 public boolean accept(@Nullable final File file) {
1051 if (file == null) {
1052 return false;
1053 }
1054
1055
1056 return this.fileTypeAnalyzers.stream().map((a) -> a.accept(file)).reduce(false, (accumulator, result) -> accumulator || result);
1057 }
1058
1059
1060
1061
1062
1063
1064 public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
1065 return this.fileTypeAnalyzers;
1066 }
1067
1068
1069
1070
1071
1072
1073 public Settings getSettings() {
1074 return settings;
1075 }
1076
1077
1078
1079
1080
1081
1082
1083 public Object getObject(String key) {
1084 return objects.get(key);
1085 }
1086
1087
1088
1089
1090
1091
1092
1093 public void putObject(String key, Object object) {
1094 objects.put(key, object);
1095 }
1096
1097
1098
1099
1100
1101
1102
1103
1104 public boolean hasObject(String key) {
1105 return objects.containsKey(key);
1106 }
1107
1108
1109
1110
1111
1112
1113 public void removeObject(String key) {
1114 objects.remove(key);
1115 }
1116
1117
1118
1119
1120
1121
1122 public Mode getMode() {
1123 return mode;
1124 }
1125
1126
1127
1128
1129
1130
1131
1132 protected void addFileTypeAnalyzer(@NonNull final FileTypeAnalyzer fta) {
1133 this.fileTypeAnalyzers.add(fta);
1134 }
1135
1136
1137
1138
1139
1140
1141
1142 private void ensureDataExists() throws NoDataException {
1143 if (mode.isDatabaseRequired() && (database == null || !database.dataExists())) {
1144 throw new NoDataException("No documents exist");
1145 }
1146 }
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157 private void throwFatalExceptionCollection(String message, @NonNull final Throwable throwable,
1158 @NonNull final List<Throwable> exceptions) throws ExceptionCollection {
1159 LOGGER.error(message);
1160 LOGGER.debug("", throwable);
1161 exceptions.add(throwable);
1162 throw new ExceptionCollection(exceptions, true);
1163 }
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176 @Deprecated
1177 public void writeReports(String applicationName, File outputDir, String format) throws ReportException {
1178 writeReports(applicationName, null, null, null, outputDir, format, null);
1179 }
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193 public void writeReports(String applicationName, File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1194 writeReports(applicationName, null, null, null, outputDir, format, exceptions);
1195 }
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212 @Deprecated
1213 public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1214 @Nullable final String artifactId, @Nullable final String version,
1215 @NonNull final File outputDir, String format) throws ReportException {
1216 writeReports(applicationName, groupId, artifactId, version, outputDir, format, null);
1217 }
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234 public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1235 @Nullable final String artifactId, @Nullable final String version,
1236 @NonNull final File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1237 if (mode == Mode.EVIDENCE_COLLECTION) {
1238 throw new UnsupportedOperationException("Cannot generate report in evidence collection mode.");
1239 }
1240 final DatabaseProperties prop = database.getDatabaseProperties();
1241
1242 final ReportGenerator r = new ReportGenerator(applicationName, groupId, artifactId, version,
1243 dependencies, getAnalyzers(), prop, settings, exceptions);
1244 try {
1245 r.write(outputDir.getAbsolutePath(), format);
1246 } catch (ReportException ex) {
1247 final String msg = String.format("Error generating the report for %s", applicationName);
1248 LOGGER.debug(msg, ex);
1249 throw new ReportException(msg, ex);
1250 }
1251 }
1252
1253
1254 private boolean identifiersMatch(Set<Identifier> left, Set<Identifier> right) {
1255 if (left != null && right != null && left.size() > 0 && left.size() == right.size()) {
1256 int count = 0;
1257 for (Identifier l : left) {
1258 for (Identifier r : right) {
1259 if (l.getValue().equals(r.getValue())) {
1260 count += 1;
1261 break;
1262 }
1263 }
1264 }
1265 return count == left.size();
1266 }
1267 return false;
1268 }
1269
1270
1271
1272
1273 public enum Mode {
1274
1275
1276
1277
1278 EVIDENCE_COLLECTION(
1279 false,
1280 INITIAL,
1281 PRE_INFORMATION_COLLECTION,
1282 INFORMATION_COLLECTION,
1283 INFORMATION_COLLECTION2,
1284 POST_INFORMATION_COLLECTION1,
1285 POST_INFORMATION_COLLECTION2,
1286 POST_INFORMATION_COLLECTION3
1287 ),
1288
1289
1290
1291
1292
1293
1294 EVIDENCE_PROCESSING(
1295 true,
1296 PRE_IDENTIFIER_ANALYSIS,
1297 IDENTIFIER_ANALYSIS,
1298 POST_IDENTIFIER_ANALYSIS,
1299 PRE_FINDING_ANALYSIS,
1300 FINDING_ANALYSIS,
1301 POST_FINDING_ANALYSIS,
1302 FINDING_ANALYSIS_PHASE2,
1303 FINAL
1304 ),
1305
1306
1307
1308
1309 STANDALONE(true, AnalysisPhase.values());
1310
1311
1312
1313
1314 private final boolean databaseRequired;
1315
1316
1317
1318 private final List<AnalysisPhase> phases;
1319
1320
1321
1322
1323
1324
1325
1326 Mode(boolean databaseRequired, AnalysisPhase... phases) {
1327 this.databaseRequired = databaseRequired;
1328 this.phases = Collections.unmodifiableList(Arrays.asList(phases));
1329 }
1330
1331
1332
1333
1334
1335
1336 private boolean isDatabaseRequired() {
1337 return databaseRequired;
1338 }
1339
1340
1341
1342
1343
1344
1345 public List<AnalysisPhase> getPhases() {
1346 return phases;
1347 }
1348 }
1349 }