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