diff --git a/BUILDING.txt b/BUILDING.txt
index 0d3e3ed9bd..a77d6c8add 100644
--- a/BUILDING.txt
+++ b/BUILDING.txt
@@ -37,16 +37,16 @@ to the root 64-bit JRE directory.
2) Get Sleuth Kit Setup
2a) Download and build a Release version of Sleuth Kit (TSK) 4.0. See
win32\BUILDING.txt in the TSK package for more information. You need to
- build the tsk_jni project. Select the Release_PostgreSQL Win32 or x64 target,
+ build the tsk_jni project. Select the Release Win32 or x64 target,
depending upon your target build. You can use a released version or download
the latest from github:
- git://github.com/sleuthkit/sleuthkit.git
-2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in
+2b) Build the TSK JAR file by typing 'ant dist' in
bindings/java in the
TSK source code folder from a command line. Note it is case
sensitive. You can also add the code to a NetBeans project and build
- it from there, selecting the dist-PostgreSQL target.
+ it from there, selecting the dist target.
2c) Set TSK_HOME environment variable to the root directory of TSK
@@ -54,6 +54,9 @@ to the root 64-bit JRE directory.
from the TSK root directory to install the libraries and such in
the needed places (i.e. '/usr/local').
+2e) Build the TSK CaseUco jar file by running 'ant' in
+ the case-uco/java folder of the TSK source folder. You can also add the
+ code to a NetBeans project and build using the regular 'build' action.
3) For Windows builds, GStreamer must be setup. GStreamer is used to view video
files. You can either download it and install it, or you can copy it from the
@@ -100,7 +103,7 @@ the build process.
- The Sleuth Kit Java datamodel JAR file has native JNI libraries
that are copied into it. These JNI libraries have dependencies on
-libewf, zlib, libpq, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows
+libewf, zlib, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows
platforms, the JNI library also has a dependency on libtsk (on Windows,
it is compiled into libtsk_jni).
diff --git a/Core/build.xml b/Core/build.xml
index a1290ef2e8..52f12a4b0b 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -54,6 +54,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -105,7 +115,9 @@
+ tofile="${ext.dir}/SparseBitSet-1.1.jar"/>
+
diff --git a/Core/manifest.mf b/Core/manifest.mf
index 00681f3e7f..0f9b4579f6 100644
--- a/Core/manifest.mf
+++ b/Core/manifest.mf
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.core/10
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
-OpenIDE-Module-Implementation-Version: 31
+OpenIDE-Module-Implementation-Version: 32
OpenIDE-Module-Requires: org.openide.windows.WindowManager
AutoUpdate-Show-In-Client: true
AutoUpdate-Essential-Module: true
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 3bf03f2103..8dbd7f8d92 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -83,6 +83,7 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sis-metadata-0.8.jar=release\\modules\\ext\\sis-metadata-0.8.jar
file.reference.sis-netcdf-0.8.jar=release\\modules\\ext\\sis-netcdf-0.8.jar
file.reference.sis-utility-0.8.jar=release\\modules\\ext\\sis-utility-0.8.jar
+file.reference.sleuthkit-caseuco-4.10.0.jar=release/modules/ext/sleuthkit-caseuco-4.10.0.jar
file.reference.slf4j-api-1.7.25.jar=release\\modules\\ext\\slf4j-api-1.7.25.jar
file.reference.sqlite-jdbc-3.25.2.jar=release/modules/ext/sqlite-jdbc-3.25.2.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
@@ -90,7 +91,7 @@ file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0
file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar
file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar
file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar
-file.reference.sleuthkit-4.9.0.jar=release/modules/ext/sleuthkit-4.9.0.jar
+file.reference.sleuthkit-4.10.0.jar=release/modules/ext/sleuthkit-4.10.0.jar
file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar
file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar
file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar
@@ -138,5 +139,5 @@ nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
nbm.needs.restart=true
source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar
-spec.version.base=10.19
+spec.version.base=10.20
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index e06cc6ca29..61e6a86b04 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -472,8 +472,8 @@
release/modules/ext/commons-pool2-2.4.2.jar
- ext/sleuthkit-4.9.0.jar
- release/modules/ext/sleuthkit-4.9.0.jar
+ ext/sleuthkit-4.10.0.jar
+ release/modules/ext/sleuthkit-4.10.0.jarext/jxmapviewer2-2.4.jar
@@ -779,6 +779,10 @@
ext/curator-client-2.8.0.jarrelease/modules/ext/curator-client-2.8.0.jar
+
+ ext/sleuthkit-caseuco-4.10.0.jar
+ release/modules/ext/sleuthkit-caseuco-4.10.0.jar
+ ext/fontbox-2.0.13.jarrelease\modules\ext\fontbox-2.0.13.jar
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
index 53cf076c2e..9c059205f5 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2019 Basis Technology Corp.
+ * Copyright 2013-2020 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -139,7 +139,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
if (!tagNamesMap.isEmpty()) {
for (Map.Entry entry : tagNamesMap.entrySet()) {
TagName tagName = entry.getValue();
- TagSet tagSet = tagName.getTagSet();
+ TagSet tagSet = tagsManager.getTagSet(tagName);
// Show custom tags before predefined tags in the menu
if (tagSet != null) {
diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
index b7ff3a73a0..b6c882cd5e 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java
@@ -29,6 +29,9 @@ import org.openide.util.Utilities;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
@@ -72,6 +75,12 @@ public class DeleteContentTagAction extends AbstractAction {
new Thread(() -> {
for (ContentTag tag : selectedTags) {
try {
+ // Check if there is an image tag before deleting the content tag.
+ ContentViewerTag imageTag = ContentViewerTagManager.getTag(tag, ImageTagRegion.class);
+ if(imageTag != null) {
+ ContentViewerTagManager.deleteTag(imageTag);
+ }
+
Case.getCurrentCaseThrows().getServices().getTagsManager().deleteContentTag(tag);
} catch (TskCoreException | NoCurrentCaseException ex) {
Logger.getLogger(DeleteContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
index cb719e2f0a..4141e3d29d 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java
@@ -39,6 +39,9 @@ import org.openide.util.actions.Presenter;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.tags.TagUtils;
import org.sleuthkit.datamodel.AbstractFile;
@@ -123,6 +126,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
try {
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS
+
+ // Check if there is an image tag before deleting the content tag.
+ ContentViewerTag imageTag = ContentViewerTagManager.getTag(contentTag, ImageTagRegion.class);
+ if(imageTag != null) {
+ ContentViewerTagManager.deleteTag(imageTag);
+ }
+
tagsManager.deleteContentTag(contentTag);
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
index ba586c7b22..a0c3b1c5c0 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2018 Basis Technology Corp.
+ * Copyright 2013-2020 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,6 @@ import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.logging.Level;
import java.util.List;
import java.util.Map;
@@ -36,7 +35,6 @@ import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JList;
import javax.swing.KeyStroke;
-import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -150,7 +148,7 @@ public class GetTagNameAndCommentDialog extends JDialog {
}
);
- try {
+ try {
TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager();
List standardTagNames = TagsManager.getStandardTagNames();
Map tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap());
@@ -161,7 +159,7 @@ public class GetTagNameAndCommentDialog extends JDialog {
tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> {
TagSet tagSet = null;
try {
- tagSet = tagName.getTagSet();
+ tagSet = tagsManager.getTagSet(tagName);
} catch (TskCoreException ex) {
Logger.getLogger(GetTagNameAndCommentDialog.class
.getName()).log(Level.SEVERE, "Failed to get tag set", ex); //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java
index 51b898eb84..1c69bc133c 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java
@@ -29,6 +29,9 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
@@ -83,9 +86,19 @@ public final class ReplaceContentTagAction extends ReplaceTagAction
try {
logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS
+ // Check if there is an image tag before deleting the content tag.
+ ContentViewerTag imageTag = ContentViewerTagManager.getTag(oldTag, ImageTagRegion.class);
+ if(imageTag != null) {
+ ContentViewerTagManager.deleteTag(imageTag);
+ }
+
tagsManager.deleteContentTag(oldTag);
- tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment);
-
+ ContentTag newTag = tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment);
+
+ // Resave the image tag if present.
+ if(imageTag != null) {
+ ContentViewerTagManager.saveTag(newTag, imageTag.getDetails());
+ }
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS
Platform.runLater(()
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java
index 91929db577..34ed836e3e 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2018 Basis Technology Corp.
+ * Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -131,7 +131,7 @@ abstract class ReplaceTagAction extends AbstractAction implements
if (!tagNamesMap.isEmpty()) {
for (Map.Entry entry : tagNamesMap.entrySet()) {
TagName tagName = entry.getValue();
- TagSet tagSet = tagName.getTagSet();
+ TagSet tagSet = tagsManager.getTagSet(tagName);
// Show custom tags before predefined tags in the menu
if (tagSet != null) {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
index a1443bde31..53320d9313 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java
@@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagewriter.ImageWriterService;
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
+import org.sleuthkit.datamodel.AddDataSourceCallbacks;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitJNI;
@@ -42,17 +43,10 @@ import org.sleuthkit.datamodel.TskDataException;
class AddImageTask implements Runnable {
private final Logger logger = Logger.getLogger(AddImageTask.class.getName());
- private final String deviceId;
- private final String imagePath;
- private final int sectorSize;
- private final String timeZone;
- private final ImageWriterSettings imageWriterSettings;
- private final boolean ignoreFatOrphanFiles;
- private final String md5;
- private final String sha1;
- private final String sha256;
+ private final ImageDetails imageDetails;
private final DataSourceProcessorProgressMonitor progressMonitor;
- private final DataSourceProcessorCallback callback;
+ private final AddDataSourceCallbacks addDataSourceCallbacks;
+ private final AddImageTaskCallback addImageTaskCallback;
private boolean criticalErrorOccurred;
/*
@@ -73,40 +67,18 @@ class AddImageTask implements Runnable {
/**
* Constructs a runnable task that adds an image to the case database.
- *
- * @param deviceId An ASCII-printable identifier for the device
- * associated with the data source that is
- * intended to be unique across multiple cases
- * (e.g., a UUID).
- * @param imagePath Path to the image file.
- * @param sectorSize The sector size (use '0' for autodetect).
- * @param timeZone The time zone to use when processing dates
- * and times for the image, obtained from
- * java.util.TimeZone.getID.
- * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
- * FAT filesystem.
- * @param md5 The MD5 hash of the image, may be null.
- * @param sha1 The SHA-1 hash of the image, may be null.
- * @param sha256 The SHA-256 hash of the image, may be null.
- * @param imageWriterPath Path that a copy of the image should be
- * written to. Use empty string to disable image
- * writing
+ *
+ * @param imageDetails Holds all data about the image.
* @param progressMonitor Progress monitor to report progress during
* processing.
- * @param callback Callback to call when processing is done.
+ * @param addDataSourceCallbacks Callback for sending data to the ingest pipeline if an ingest stream is being used.
+ * @param addImageTaskCallback Callback for dealing with add image task completion.
*/
- AddImageTask(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings,
- DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
- this.deviceId = deviceId;
- this.imagePath = imagePath;
- this.sectorSize = sectorSize;
- this.timeZone = timeZone;
- this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
- this.md5 = md5;
- this.sha1 = sha1;
- this.sha256 = sha256;
- this.imageWriterSettings = imageWriterSettings;
- this.callback = callback;
+ AddImageTask(ImageDetails imageDetails, DataSourceProcessorProgressMonitor progressMonitor, AddDataSourceCallbacks addDataSourceCallbacks,
+ AddImageTaskCallback addImageTaskCallback) {
+ this.imageDetails = imageDetails;
+ this.addDataSourceCallbacks = addDataSourceCallbacks;
+ this.addImageTaskCallback = addImageTaskCallback;
this.progressMonitor = progressMonitor;
tskAddImageProcessLock = new Object();
}
@@ -120,21 +92,21 @@ class AddImageTask implements Runnable {
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
- logger.log(Level.SEVERE, String.format("Failed to add image data source at %s, no current case", imagePath), ex);
+ logger.log(Level.SEVERE, String.format("Failed to start AddImageTask for %s, no current case", imageDetails.getImagePath()), ex);
return;
}
progressMonitor.setIndeterminate(true);
progressMonitor.setProgress(0);
String imageWriterPath = "";
- if (imageWriterSettings != null) {
- imageWriterPath = imageWriterSettings.getPath();
+ if (imageDetails.imageWriterSettings != null) {
+ imageWriterPath = imageDetails.imageWriterSettings.getPath();
}
List errorMessages = new ArrayList<>();
List newDataSources = new ArrayList<>();
try {
synchronized (tskAddImageProcessLock) {
if (!tskAddImageProcessStopped) {
- tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles, imageWriterPath);
+ tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(imageDetails.timeZone, true, imageDetails.ignoreFatOrphanFiles, imageWriterPath);
} else {
return;
}
@@ -143,7 +115,7 @@ class AddImageTask implements Runnable {
progressUpdateThread.start();
runAddImageProcess(errorMessages);
progressUpdateThread.interrupt();
- commitOrRevertAddImageProcess(currentCase, errorMessages, newDataSources);
+ finishAddImageProcess(errorMessages, newDataSources);
progressMonitor.setProgress(100);
} finally {
DataSourceProcessorCallback.DataSourceProcessorResult result;
@@ -154,7 +126,7 @@ class AddImageTask implements Runnable {
} else {
result = DataSourceProcessorResult.NO_ERRORS;
}
- callback.done(result, errorMessages, newDataSources);
+ addImageTaskCallback.onCompleted(result, errorMessages, newDataSources);
}
}
@@ -177,7 +149,7 @@ class AddImageTask implements Runnable {
tskAddImageProcess.stop();
} catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error cancelling adding image %s to the case database", imagePath), ex); //NON-NLS
+ logger.log(Level.SEVERE, String.format("Error cancelling adding image %s to the case database", imageDetails.getImagePath()), ex); //NON-NLS
}
}
}
@@ -191,23 +163,22 @@ class AddImageTask implements Runnable {
*/
private void runAddImageProcess(List errorMessages) {
try {
- tskAddImageProcess.run(deviceId, new String[]{imagePath}, sectorSize);
+ tskAddImageProcess.run(imageDetails.deviceId, imageDetails.image, imageDetails.sectorSize, this.addDataSourceCallbacks);
} catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Critical error occurred adding image %s", imagePath), ex); //NON-NLS
+ logger.log(Level.SEVERE, String.format("Critical error occurred adding image %s", imageDetails.getImagePath()), ex); //NON-NLS
criticalErrorOccurred = true;
errorMessages.add(ex.getMessage());
} catch (TskDataException ex) {
- logger.log(Level.WARNING, String.format("Non-critical error occurred adding image %s", imagePath), ex); //NON-NLS
+ logger.log(Level.WARNING, String.format("Non-critical error occurred adding image %s", imageDetails.getImagePath()), ex); //NON-NLS
errorMessages.add(ex.getMessage());
}
}
/**
- * Commits or reverts the results of the TSK add image process. If the
- * process was stopped before it completed or there was a critical error the
- * results are reverted, otherwise they are committed.
+ * Handle the results of the TSK add image process.
+ * The image will be in the database even if a critical error occurred or
+ * the user canceled.
*
- * @param currentCase The current case.
* @param errorMessages Error messages, if any, are added to this list for
* eventual return via the callback.
* @param newDataSources If the new image is successfully committed, it is
@@ -216,84 +187,66 @@ class AddImageTask implements Runnable {
*
* @return
*/
- private void commitOrRevertAddImageProcess(Case currentCase, List errorMessages, List newDataSources) {
+ private void finishAddImageProcess(List errorMessages, List newDataSources) {
synchronized (tskAddImageProcessLock) {
- if (tskAddImageProcessStopped || criticalErrorOccurred) {
+ Image newImage = imageDetails.image;
+ String verificationError = newImage.verifyImageSize();
+ if (!verificationError.isEmpty()) {
+ errorMessages.add(verificationError);
+ }
+ if (imageDetails.imageWriterSettings != null) {
+ ImageWriterService.createImageWriter(newImage.getId(), imageDetails.imageWriterSettings);
+ }
+ newDataSources.add(newImage);
+
+ // If the add image process was cancelled don't do any further processing here
+ if (tskAddImageProcessStopped) {
+ return;
+ }
+
+ if (!StringUtils.isBlank(imageDetails.md5)) {
try {
- tskAddImageProcess.revert();
+ newImage.setMD5(imageDetails.md5);
} catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error reverting after adding image %s to the case database", imagePath), ex); //NON-NLS
+ logger.log(Level.SEVERE, String.format("Failed to add MD5 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
errorMessages.add(ex.getMessage());
criticalErrorOccurred = true;
+ } catch (TskDataException ignored) {
+ /*
+ * The only reasonable way for this to happen at
+ * present is through C/C++ processing of an EWF
+ * image, which is not an error.
+ */
}
- } else {
+ }
+ if (!StringUtils.isBlank(imageDetails.sha1)) {
try {
- long imageId = tskAddImageProcess.commit();
- if (imageId != 0) {
- Image newImage = currentCase.getSleuthkitCase().getImageById(imageId);
- String verificationError = newImage.verifyImageSize();
- if (!verificationError.isEmpty()) {
- errorMessages.add(verificationError);
- }
- if (imageWriterSettings != null) {
- ImageWriterService.createImageWriter(imageId, imageWriterSettings);
- }
- newDataSources.add(newImage);
- if (!StringUtils.isBlank(md5)) {
- try {
- newImage.setMD5(md5);
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Failed to add MD5 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
- errorMessages.add(ex.getMessage());
- criticalErrorOccurred = true;
- } catch (TskDataException ignored) {
- /*
- * The only reasonable way for this to happen at
- * present is through C/C++ processing of an EWF
- * image, which is not an error.
- */
- }
- }
- if (!StringUtils.isBlank(sha1)) {
- try {
- newImage.setSha1(sha1);
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Failed to add SHA1 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
- errorMessages.add(ex.getMessage());
- criticalErrorOccurred = true;
- } catch (TskDataException ignored) {
- /*
- * The only reasonable way for this to happen at
- * present is through C/C++ processing of an EWF
- * image, which is not an error.
- */
- }
- }
- if (!StringUtils.isBlank(sha256)) {
- try {
- newImage.setSha256(sha256);
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Failed to add SHA256 for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
- errorMessages.add(ex.getMessage());
- criticalErrorOccurred = true;
- } catch (TskDataException ignored) {
- /*
- * The only reasonable way for this to happen at
- * present is through C/C++ processing of an EWF
- * image, which is not an error.
- */
- }
- }
- } else {
- String errorMessage = String.format("Error commiting after adding image %s to the case database, no object id returned", imagePath); //NON-NLS
- logger.log(Level.SEVERE, errorMessage);
- errorMessages.add(errorMessage);
- criticalErrorOccurred = true;
- }
+ newImage.setSha1(imageDetails.sha1);
} catch (TskCoreException ex) {
- logger.log(Level.SEVERE, String.format("Error committing adding image %s to the case database", imagePath), ex); //NON-NLS
+ logger.log(Level.SEVERE, String.format("Failed to add SHA1 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
errorMessages.add(ex.getMessage());
criticalErrorOccurred = true;
+ } catch (TskDataException ignored) {
+ /*
+ * The only reasonable way for this to happen at
+ * present is through C/C++ processing of an EWF
+ * image, which is not an error.
+ */
+ }
+ }
+ if (!StringUtils.isBlank(imageDetails.sha256)) {
+ try {
+ newImage.setSha256(imageDetails.sha256);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Failed to add SHA256 for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
+ errorMessages.add(ex.getMessage());
+ criticalErrorOccurred = true;
+ } catch (TskDataException ignored) {
+ /*
+ * The only reasonable way for this to happen at
+ * present is through C/C++ processing of an EWF
+ * image, which is not an error.
+ */
}
}
}
@@ -352,4 +305,37 @@ class AddImageTask implements Runnable {
}
}
+ /**
+ * Utility class to hold image data.
+ */
+ static class ImageDetails {
+ String deviceId;
+ Image image;
+ int sectorSize;
+ String timeZone;
+ boolean ignoreFatOrphanFiles;
+ String md5;
+ String sha1;
+ String sha256;
+ ImageWriterSettings imageWriterSettings;
+
+ ImageDetails(String deviceId, Image image, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings) {
+ this.deviceId = deviceId;
+ this.image = image;
+ this.sectorSize = sectorSize;
+ this.timeZone = timeZone;
+ this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
+ this.md5 = md5;
+ this.sha1 = sha1;
+ this.sha256 = sha256;
+ this.imageWriterSettings = imageWriterSettings;
+ }
+
+ String getImagePath() {
+ if (image.getPaths().length > 0) {
+ return image.getPaths()[0];
+ }
+ return "Unknown data source path";
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTaskCallback.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTaskCallback.java
new file mode 100644
index 0000000000..ec76166419
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTaskCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.util.List;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
+import org.sleuthkit.datamodel.Content;
+
+/**
+ * Called on completion of the add image task.
+ */
+interface AddImageTaskCallback {
+
+ /**
+ * Called when the add image task is completed.
+ *
+ * @param result The result from the data source processor.
+ * @param errList The list of errors.
+ * @param newDataSources The list of new data sources.
+ */
+ void onCompleted(DataSourceProcessorResult result, List errList, List newDataSources);
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
index 15eb0c3c67..2b880093e9 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java
@@ -302,7 +302,9 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel {
private void startIngest() {
if (!newContents.isEmpty() && readyToIngest && !ingested) {
ingested = true;
- IngestManager.getInstance().queueIngestJob(newContents, ingestJobSettings);
+ if (dsProcessor != null && ! dsProcessor.supportsIngestStream()) {
+ IngestManager.getInstance().queueIngestJob(newContents, ingestJobSettings);
+ }
setStateFinished();
}
}
@@ -360,8 +362,12 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel {
setStateStarted();
- // Kick off the DSProcessor
- dsProcessor.run(getDSPProgressMonitorImpl(), cbObj);
+ // Kick off the DSProcessor
+ if (dsProcessor.supportsIngestStream()) {
+ dsProcessor.runWithIngestStream(ingestJobSettings, getDSPProgressMonitorImpl(), cbObj);
+ } else {
+ dsProcessor.run(getDSPProgressMonitorImpl(), cbObj);
+ }
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java
index 9dad21446e..045bf775b2 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java
@@ -41,6 +41,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.util.List;
+import org.sleuthkit.autopsy.ingest.IngestStream;
+import org.sleuthkit.autopsy.ingest.IngestStreamClosedException;
+
+/**
+ * This is a default ingest stream to use with the data source processors when
+ * an IngestStream is not supplied. Adding files/data sources are no-ops.
+ */
+class DefaultIngestStream implements IngestStream {
+
+ private boolean isClosed = false;
+ private boolean isStopped = false;
+
+ @Override
+ public void addFiles(List fileObjectIds) throws IngestStreamClosedException {
+ // Do nothing
+ }
+
+ @Override
+ public synchronized boolean isClosed() {
+ return isClosed;
+ }
+
+ @Override
+ public synchronized void close() {
+ isClosed = true;
+ }
+
+ @Override
+ public synchronized void stop() {
+ isClosed = true;
+ isStopped = true;
+ }
+
+ @Override
+ public synchronized boolean wasStopped() {
+ return isStopped;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
index 596272f09f..9dd9a39fd4 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
@@ -24,6 +24,7 @@ import javax.swing.JPanel;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
+import java.util.logging.Level;
import java.util.UUID;
import javax.swing.filechooser.FileFilter;
import org.openide.util.NbBundle;
@@ -33,7 +34,14 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.DataSourceUtils;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
+import org.sleuthkit.autopsy.ingest.IngestJobSettings;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.IngestStream;
+import org.sleuthkit.datamodel.Image;
+import org.sleuthkit.datamodel.SleuthkitJNI;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* A image file data source processor that implements the DataSourceProcessor
@@ -49,6 +57,7 @@ import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor {
private final static String DATA_SOURCE_TYPE = NbBundle.getMessage(ImageDSProcessor.class, "ImageDSProcessor.dsType.text");
+ private final Logger logger = Logger.getLogger(ImageDSProcessor.class.getName());
private static final List allExt = new ArrayList<>();
private static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC);
private static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC);
@@ -58,6 +67,8 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
private static final List filtersList = new ArrayList<>();
private final ImageFilePanel configPanel;
private AddImageTask addImageTask;
+ private IngestStream ingestStream = null;
+ private Image image = null;
/*
* TODO: Remove the setDataSourceOptionsCalled flag and the settings fields
* when the deprecated method setDataSourceOptions is removed.
@@ -170,6 +181,77 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
*/
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
+ ingestStream = new DefaultIngestStream();
+ readConfigSettings();
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback);
+ }
+
+ /**
+ * Adds a data source to the case database using a background task in a
+ * separate thread and the settings provided by the selection and
+ * configuration panel. Files found during ingest will be sent directly to the
+ * IngestStream provided. Returns as soon as the background task is started.
+ * The background task uses a callback object to signal task completion and
+ * return results.
+ *
+ * This method should not be called unless isPanelValid returns true, and
+ * should only be called for DSPs that support ingest streams.
+ *
+ * @param settings The ingest job settings.
+ * @param progress Progress monitor that will be used by the
+ * background task to report progress.
+ * @param callBack Callback that will be used by the background task
+ * to return results.
+ */
+ @Override
+ public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
+ DataSourceProcessorCallback callBack) {
+
+ // Read the settings from the wizard
+ readConfigSettings();
+
+ // Set up the data source before creating the ingest stream
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ // Now initialize the ingest stream
+ try {
+ ingestStream = IngestManager.getInstance().openIngestStream(image, settings);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error starting ingest modules", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+
+ doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack);
+ }
+
+ /**
+ * Store the options from the config panel.
+ */
+ private void readConfigSettings() {
if (!setDataSourceOptionsCalled) {
configPanel.storeSettings();
deviceId = UUID.randomUUID().toString();
@@ -190,8 +272,17 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
sha256 = null;
}
}
- run(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback);
}
+
+ /**
+ * Check if this DSP supports ingest streams.
+ *
+ * @return True if this DSP supports an ingest stream, false otherwise.
+ */
+ @Override
+ public boolean supportsIngestStream() {
+ return true;
+ }
/**
* Adds a data source to the case database using a background task in a
@@ -215,7 +306,19 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
* @param callback Callback to call when processing is done.
*/
public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
- run(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callback);
+ ingestStream = new DefaultIngestStream();
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ doAddImageProcess(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callback);
}
/**
@@ -224,6 +327,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
* selection and configuration panel. Returns as soon as the background task
* is started and uses the callback object to signal task completion and
* return results.
+ *
+ * The image should be loaded in the database and stored in "image"
+ * before calling this method. Additionally, an ingest stream should be initialized
+ * and stored in "ingestStream".
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
@@ -243,8 +350,31 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
* during processing.
* @param callback Callback to call when processing is done.
*/
- private void run(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
- addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback);
+ private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
+
+ // If the data source or ingest stream haven't been initialized, stop processing
+ if (ingestStream == null) {
+ String message = "Ingest stream was not initialized before running the add image process on " + imagePath;
+ logger.log(Level.SEVERE, message);
+ final List errors = new ArrayList<>();
+ errors.add(message);
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+ if (image == null) {
+ String message = "Image was not added to database before running the add image process on " + imagePath;
+ logger.log(Level.SEVERE, message);
+ final List errors = new ArrayList<>();
+ errors.add(message);
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null);
+ addImageTask = new AddImageTask(imageDetails,
+ progressMonitor,
+ new StreamingAddDataSourceCallbacks(ingestStream),
+ new StreamingAddImageTaskCallback(ingestStream, callback));
new Thread(addImageTask).start();
}
@@ -260,6 +390,9 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
if (null != addImageTask) {
addImageTask.cancelTask();
}
+ if (ingestStream != null) {
+ ingestStream.stop();
+ }
}
/**
@@ -316,7 +449,20 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.ignoreFatOrphanFiles = false;
setDataSourceOptionsCalled = true;
- run(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
+
+ ingestStream = new DefaultIngestStream();
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
index b1e6d83482..6381acd32d 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
@@ -31,7 +31,9 @@ import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages;
+import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.IngestModuleInfo;
@@ -47,6 +49,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName());
private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED);
+ private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE);
+
private List ingestJobs;
private final List ingestJobsForSelectedDataSource = new ArrayList<>();
private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel();
@@ -79,6 +83,16 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
refresh();
}
});
+
+ Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> {
+ if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) {
+ return;
+ }
+
+ if (CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName())) {
+ refresh();
+ }
+ });
}
/**
@@ -110,9 +124,15 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
*/
private void refresh() {
try {
- SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
- this.ingestJobs = skCase.getIngestJobs();
- setDataSource(selectedDataSource);
+ if (Case.isCaseOpen()) {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ this.ingestJobs = skCase.getIngestJobs();
+ setDataSource(selectedDataSource);
+ } else {
+ this.ingestJobs = new ArrayList<>();
+ setDataSource(null);
+ }
+
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
index 60c0aed94b..0551d15466 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java
@@ -18,15 +18,22 @@
*/
package org.sleuthkit.autopsy.casemodule;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.List;
import java.util.UUID;
+import java.util.logging.Level;
import javax.swing.JPanel;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
+import org.sleuthkit.datamodel.Image;
+import org.sleuthkit.datamodel.SleuthkitJNI;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* A local drive data source processor that implements the DataSourceProcessor
@@ -37,6 +44,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
@ServiceProvider(service = DataSourceProcessor.class)
public class LocalDiskDSProcessor implements DataSourceProcessor {
+ private final Logger logger = Logger.getLogger(LocalDiskDSProcessor.class.getName());
private static final String DATA_SOURCE_TYPE = NbBundle.getMessage(LocalDiskDSProcessor.class, "LocalDiskDSProcessor.dsType.text");
private final LocalDiskPanel configPanel;
private AddImageTask addDiskTask;
@@ -139,7 +147,25 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
imageWriterSettings = null;
}
}
- addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, progressMonitor, callback);
+
+ Image image;
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{drivePath}, sectorSize,
+ timeZone, null, null, null, deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ addDiskTask = new AddImageTask(
+ new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
+ progressMonitor,
+ new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
+ new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
new Thread(addDiskTask).start();
}
@@ -191,7 +217,23 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
* @param callback Callback to call when processing is done.
*/
private void run(String deviceId, String drivePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
- addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, progressMonitor, callback);
+ Image image;
+ try {
+ image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
+ new String[]{drivePath}, sectorSize,
+ timeZone, null, null, null, deviceId);
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
+ final List errors = new ArrayList<>();
+ errors.add(ex.getMessage());
+ callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
+ return;
+ }
+
+ addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
+ progressMonitor,
+ new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
+ new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
new Thread(addDiskTask).start();
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddDataSourceCallbacks.java b/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddDataSourceCallbacks.java
new file mode 100644
index 0000000000..fb26c7a195
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddDataSourceCallbacks.java
@@ -0,0 +1,66 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.util.List;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.ingest.IngestStream;
+import org.sleuthkit.autopsy.ingest.IngestStreamClosedException;
+import org.sleuthkit.datamodel.AddDataSourceCallbacks;
+
+/**
+ * A set of callbacks to be called during the process of adding a data source to
+ * the case database. This implementation of the interface is suitable for
+ * streaming ingest use cases.
+ */
+class StreamingAddDataSourceCallbacks implements AddDataSourceCallbacks {
+
+ private final Logger logger = Logger.getLogger(StreamingAddDataSourceCallbacks.class.getName());
+ private final IngestStream ingestStream;
+
+ /**
+ * Constructs a set of callbacks to be called during the process of adding a
+ * data source to the case database. This implementation of the interface is
+ * suitable for streaming ingest use cases.
+ *
+ * @param stream The IngestStream to send data to
+ */
+ StreamingAddDataSourceCallbacks(IngestStream stream) {
+ ingestStream = stream;
+ }
+
+ @Override
+ public void onFilesAdded(List fileObjectIds) {
+ if (ingestStream.wasStopped()) {
+ return;
+ }
+
+ try {
+ ingestStream.addFiles(fileObjectIds);
+ } catch (IngestStreamClosedException ex) {
+ if (!ingestStream.wasStopped()) {
+ // If the ingest stream is closed but not stopped log the error.
+ // This state should only happen once the data source is completely
+ // added which means it's a severe error that files are still being added.
+ logger.log(Level.SEVERE, "Error adding files to ingest stream - ingest stream is closed");
+ }
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddImageTaskCallback.java b/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddImageTaskCallback.java
new file mode 100644
index 0000000000..d03f85097a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/StreamingAddImageTaskCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule;
+
+import java.util.List;
+import org.sleuthkit.autopsy.ingest.IngestStream;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
+import org.sleuthkit.datamodel.Content;
+
+/**
+ * A callback to be called on completion of an add image task. This
+ * implementation of the interface is suitable for streaming ingest use cases.
+ * It closes the ingest stream and then calls the data source processor done
+ * callback.
+ */
+class StreamingAddImageTaskCallback implements AddImageTaskCallback {
+
+ private final IngestStream ingestStream;
+ private final DataSourceProcessorCallback dspCallback;
+
+ /**
+ * Constructs a callback to be called on completion of an add image task.
+ * This implementation of the interface is suitable for streaming ingest use
+ * cases. It closes the ingest stream and then calls the data source
+ * processor done callback.
+ *
+ * @param ingestStream The ingest stream that data is being sent to.
+ * @param dspCallback The callback for non-ingest stream related
+ * processing.
+ */
+ StreamingAddImageTaskCallback(IngestStream ingestStream, DataSourceProcessorCallback dspCallback) {
+ this.ingestStream = ingestStream;
+ this.dspCallback = dspCallback;
+ }
+
+ /**
+ * Called when the add image task is completed.
+ *
+ * @param result The result from the data source processor.
+ * @param errList The list of errors.
+ * @param newDataSources The list of new data sources.
+ */
+ @Override
+ public void onCompleted(DataSourceProcessorResult result, List errList, List newDataSources) {
+ ingestStream.close();
+ dspCallback.done(result, errList, newDataSources);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
old mode 100755
new mode 100644
similarity index 96%
rename from Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
rename to Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
index fe998b0e57..eb7affe9ee
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
@@ -1,4 +1,6 @@
CTL_DataSourceSummaryAction=Data Source Summary
+DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count
+DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle_ja.properties
similarity index 100%
rename from Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle_ja.properties
rename to Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle_ja.properties
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
index c2d8263056..dcbf9bb82d 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
@@ -18,7 +18,8 @@
*/
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
-import java.util.ArrayList;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@@ -43,6 +44,297 @@ final class DataSourceInfoUtilities {
private static final Logger logger = Logger.getLogger(DataSourceInfoUtilities.class.getName());
+ /**
+ * Gets a count of files for a particular datasource where it is not a
+ * virtual directory and has a name.
+ *
+ * @param currentDataSource The datasource.
+ * @param additionalWhere Additional sql where clauses.
+ * @param onError The message to log on error.
+ *
+ * @return The count of files or null on error.
+ */
+ private static Long getCountOfFiles(DataSource currentDataSource, String additionalWhere, String onError) {
+ if (currentDataSource != null) {
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ return skCase.countFilesWhere(
+ "dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ + " AND name<>''"
+ + " AND data_source_obj_id=" + currentDataSource.getId()
+ + " AND " + additionalWhere);
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, onError, ex);
+ //unable to get count of files for the specified types cell will be displayed as empty
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get count of files in a data source.
+ *
+ * @param currentDataSource The data source.
+ *
+ * @return The count.
+ */
+ static Long getCountOfFiles(DataSource currentDataSource) {
+ return getCountOfFiles(currentDataSource,
+ "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(),
+ "Unable to get count of files, providing empty results");
+ }
+
+ /**
+ * Get count of unallocated files in a data source.
+ *
+ * @param currentDataSource The data source.
+ *
+ * @return The count.
+ */
+ static Long getCountOfUnallocatedFiles(DataSource currentDataSource) {
+ return getCountOfFiles(currentDataSource,
+ "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(),
+ "Unable to get counts of unallocated files for datasource, providing empty results");
+ }
+
+ /**
+ * Get count of directories in a data source.
+ *
+ * @param currentDataSource The data source.
+ *
+ * @return The count.
+ */
+ static Long getCountOfDirectories(DataSource currentDataSource) {
+ return getCountOfFiles(currentDataSource,
+ "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ + " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue(),
+ "Unable to get count of directories for datasource, providing empty results");
+ }
+
+ /**
+ * Get count of slack files in a data source.
+ *
+ * @param currentDataSource The data source.
+ *
+ * @return The count.
+ */
+ static Long getCountOfSlackFiles(DataSource currentDataSource) {
+ return getCountOfFiles(currentDataSource,
+ "type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(),
+ "Unable to get count of slack files for datasources, providing empty results");
+ }
+
+ /**
+ * An interface for handling a result set and returning a value.
+ */
+ private interface ResultSetHandler {
+
+ T process(ResultSet resultset) throws SQLException;
+ }
+
+ /**
+ * Retrieves a result based on the provided query.
+ *
+ * @param query The query.
+ * @param processor The result set handler.
+ * @param errorMessage The error message to display if there is an error
+ * retrieving the resultset.
+ *
+ * @return The ResultSetHandler value or null if no ResultSet could be
+ * obtained.
+ */
+ private static T getBaseQueryResult(String query, ResultSetHandler processor, String errorMessage) {
+ try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
+ ResultSet resultSet = dbQuery.getResultSet();
+ try {
+ return processor.process(resultSet);
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, errorMessage, ex);
+ }
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, errorMessage, ex);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the size of unallocated files in a particular datasource.
+ *
+ * @param currentDataSource The data source.
+ *
+ * @return The size or null if the query could not be executed.
+ */
+ static Long getSizeOfUnallocatedFiles(DataSource currentDataSource) {
+ if (currentDataSource == null) {
+ return null;
+ }
+
+ final String valueParam = "value";
+ final String countParam = "count";
+ String query = "SELECT SUM(size) AS " + valueParam + ", COUNT(*) AS " + countParam
+ + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
+ + " AND name<>''"
+ + " AND data_source_obj_id=" + currentDataSource.getId();
+
+ ResultSetHandler handler = (resultSet) -> {
+ if (resultSet.next()) {
+ // ensure that there is an unallocated count result that is attached to this data source
+ long resultCount = resultSet.getLong(valueParam);
+ return (resultCount > 0) ? resultSet.getLong(valueParam) : null;
+ } else {
+ return null;
+ }
+ };
+ String errorMessage = "Unable to get size of unallocated files; returning null.";
+
+ return getBaseQueryResult(query, handler, errorMessage);
+ }
+
+ /**
+ * Retrieves counts for each artifact type in a data source.
+ *
+ * @param selectedDataSource The data source.
+ *
+ * @return A mapping of artifact type name to the counts or null if there
+ * was an error executing the query.
+ */
+ static Map getCountsOfArtifactsByType(DataSource selectedDataSource) {
+ if (selectedDataSource == null) {
+ return Collections.emptyMap();
+ }
+
+ final String nameParam = "name";
+ final String valueParam = "value";
+ String query
+ = "SELECT bbt.display_name AS " + nameParam + ", COUNT(*) AS " + valueParam
+ + " FROM blackboard_artifacts bba "
+ + " INNER JOIN blackboard_artifact_types bbt ON bba.artifact_type_id = bbt.artifact_type_id"
+ + " WHERE bba.data_source_obj_id =" + selectedDataSource.getId()
+ + " GROUP BY bbt.display_name";
+
+ ResultSetHandler