diff --git a/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED index d9811e349a..de7c28c948 100644 --- a/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED @@ -1,3 +1,4 @@ CTL_ResetWindowsAction=Reset Windows -ResetWindowAction.confirm.text=The program will close and restart to perform the resetting of window locations.\n\nAre you sure you want to reset all window locations? -ResetWindowAction.confirm.title=Reset Windows +ResetWindowAction.caseCloseFailure.text=Unable to close the current case, the software will restart and the windows locations will reset the next time the software is closed. +ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, the software will restart and the windows locations will reset but the current case will not be opened upon restart. +ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. Are you sure you want to restart the software to reset all window locations? diff --git a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java index 36a9b915df..c5298bc64e 100644 --- a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java +++ b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java @@ -20,8 +20,8 @@ package org.sleuthkit.autopsy.apputils; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.logging.Level; -import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.apache.commons.io.FileUtils; import org.openide.LifecycleManager; @@ -32,9 +32,10 @@ import org.openide.awt.ActionRegistration; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; -import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** @@ -51,39 +52,66 @@ public final class ResetWindowsAction extends CallableSystemAction { private static final String DISPLAY_NAME = Bundle.CTL_ResetWindowsAction(); private static final long serialVersionUID = 1L; private final static Logger logger = Logger.getLogger(ResetWindowsAction.class.getName()); + private final static String WINDOWS2LOCAL = "Windows2Local"; + private final static String CASE_TO_REOPEN_FILE = "caseToOpen.txt"; @Override public boolean isEnabled() { - return !Case.isCaseOpen(); + return true; } - @NbBundle.Messages({"ResetWindowAction.confirm.title=Reset Windows", - "ResetWindowAction.confirm.text=The program will close and restart to perform the resetting of window locations.\n\nAre you sure you want to reset all window locations?"}) + @NbBundle.Messages({"ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. " + + "If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. " + + "Are you sure you want to restart the software to reset all window locations?", + "ResetWindowAction.caseCloseFailure.text=Unable to close the current case, " + + "the software will restart and the windows locations will reset the next time the software is closed.", + "ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, " + + "the software will restart and the windows locations will reset but the current case will not be opened upon restart."}) @Override public void performAction() { SwingUtilities.invokeLater(() -> { - int response = JOptionPane.showConfirmDialog( - WindowManager.getDefault().getMainWindow(), - Bundle.ResetWindowAction_confirm_text(), - Bundle.ResetWindowAction_confirm_title(), - JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE); - if (response == JOptionPane.YES_OPTION) { + boolean response = MessageNotifyUtil.Message.confirm(Bundle.ResetWindowAction_confirm_text()); + if (response) { + //adding the shutdown hook, closing the current case, and marking for restart can be re-ordered if slightly different behavior is desired Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { - FileUtils.deleteDirectory(new File(PlatformUtil.getUserConfigDirectory() + File.separator + "Windows2Local")); + FileUtils.deleteDirectory(new File(PlatformUtil.getUserConfigDirectory() + File.separator + WINDOWS2LOCAL)); } catch (IOException ex) { - logger.log(Level.WARNING, "Unable to delete config directory, window locations will not be reset.", ex); + //While we would like the user to be aware of this in the unlikely event that the directory can not be deleted + //Because our deletion is being attempted in a shutdown hook I don't know that we can pop up UI elements during the shutdown proces + logger.log(Level.SEVERE, "Unable to delete config directory, window locations will not be reset. To manually reset the windows please delete the following directory while the software is closed. " + PlatformUtil.getUserConfigDirectory() + File.separator + "Windows2Local", ex); } } }); - LifecycleManager.getDefault().markForRestart(); - LifecycleManager.getDefault().exit(); + try { + if (Case.isCaseOpen()) { + String caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath().toString(); + File caseToOpenFile = new File(ResetWindowsAction.getCaseToReopenFilePath()); + Charset encoding = null; //prevents writeStringToFile from having ambiguous arguments + FileUtils.writeStringToFile(caseToOpenFile, caseMetadataFilePath, encoding); + Case.closeCurrentCase(); + } + // The method markForRestart can not be undone once it is called. + LifecycleManager.getDefault().markForRestart(); + //we need to call exit last + LifecycleManager.getDefault().exit(); + } catch (CaseActionException ex) { + logger.log(Level.WARNING, Bundle.ResetWindowAction_caseCloseFailure_text(), ex); + MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseCloseFailure_text(), MessageNotifyUtil.MessageType.ERROR); + } catch (IOException ex) { + logger.log(Level.WARNING, Bundle.ResetWindowAction_caseSaveMetadata_text(), ex); + MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseSaveMetadata_text(), MessageNotifyUtil.MessageType.ERROR); + } } }); } + + public static String getCaseToReopenFilePath(){ + return PlatformUtil.getUserConfigDirectory() + File.separator + CASE_TO_REOPEN_FILE; + } /** * Set this action to be enabled/disabled @@ -91,6 +119,7 @@ public final class ResetWindowsAction extends CallableSystemAction { * @param value whether to enable this action or not */ @Override + public void setEnabled(boolean value) { super.setEnabled(value); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index f731913af5..1a5bb3d251 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -346,6 +346,12 @@ RecentCases.getName.text=Clear Recent Cases RecentItems.openRecentCase.msgDlg.text=Case {0} no longer exists. SelectDataSourceProcessorPanel.name.text=Select Data Source Type StartupWindow.title.text=Welcome +# {0} - autFilePath +StartupWindowProvider.openCase.cantOpen=Unable to open previously open case with metadata file: {0} +# {0} - reOpenFilePath +StartupWindowProvider.openCase.deleteOpenFailure=Unable to open or delete file containing path {0} to previously open case. The previous case will not be opened. +# {0} - autFilePath +StartupWindowProvider.openCase.noFile=Unable to open previously open case because metadata file not found at: {0} UnpackagePortableCaseDialog.title.text=Unpackage Portable Case UnpackagePortableCaseDialog.UnpackagePortableCaseDialog.extensions=Portable case package (.zip, .zip.001) UnpackagePortableCaseDialog.validatePaths.badExtension=File extension must be .zip or .zip.001 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 88cdb293fc..2cc9a4bbf8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1934,7 +1934,7 @@ public class Case { * * @return A CaseMetaData object. */ - CaseMetadata getMetadata() { + public CaseMetadata getMetadata() { return metadata; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 5f7ffe9ea7..96f9899dae 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -218,7 +218,7 @@ public final class CaseMetadata { * * @return The path to the metadata file */ - Path getFilePath() { + public Path getFilePath() { return metadataFilePath; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 07a88ee07f..9b174f28f7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -284,10 +284,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour 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; + // There was an error with ingest, but the data source has already been added + // so proceed with the defaultIngestStream. Code in openIngestStream + // should have caused a dialog to popup with the errors. + ingestStream = new DefaultIngestStream(); } doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java index 9b62bdb0e0..821beea6b3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java @@ -18,12 +18,18 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.Collection; import java.util.Iterator; import java.util.logging.Level; -import org.netbeans.spi.sendopts.OptionProcessor; +import javax.swing.SwingUtilities; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.apputils.ResetWindowsAction; import org.sleuthkit.autopsy.commandlineingest.CommandLineIngestManager; import org.sleuthkit.autopsy.commandlineingest.CommandLineOpenCaseManager; import org.sleuthkit.autopsy.commandlineingest.CommandLineOptionProcessor; @@ -61,15 +67,22 @@ public class StartupWindowProvider implements StartupWindowInterface { return instance; } + @NbBundle.Messages({ + "# {0} - autFilePath", + "StartupWindowProvider.openCase.noFile=Unable to open previously open case because metadata file not found at: {0}", + "# {0} - reOpenFilePath", + "StartupWindowProvider.openCase.deleteOpenFailure=Unable to open or delete file containing path {0} to previously open case. The previous case will not be opened.", + "# {0} - autFilePath", + "StartupWindowProvider.openCase.cantOpen=Unable to open previously open case with metadata file: {0}"}) private void init() { if (startupWindowToUse == null) { // first check whether we are running from command line if (isRunningFromCommandLine()) { - + String defaultArg = getDefaultArgument(); - if(defaultArg != null) { - new CommandLineOpenCaseManager(defaultArg).start(); - return; + if (defaultArg != null) { + new CommandLineOpenCaseManager(defaultArg).start(); + return; } else { // Autopsy is running from command line logger.log(Level.INFO, "Running from command line"); //NON-NLS @@ -83,36 +96,41 @@ public class StartupWindowProvider implements StartupWindowInterface { if (RuntimeProperties.runningWithGUI()) { checkSolr(); } - //discover the registered windows Collection startupWindows = Lookup.getDefault().lookupAll(StartupWindowInterface.class); int windowsCount = startupWindows.size(); - if (windowsCount == 1) { - startupWindowToUse = startupWindows.iterator().next(); - logger.log(Level.INFO, "Will use the default startup window: " + startupWindowToUse.toString()); //NON-NLS - } else if (windowsCount == 2) { - //pick the non default one - Iterator it = startupWindows.iterator(); - while (it.hasNext()) { - StartupWindowInterface window = it.next(); - if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) { - startupWindowToUse = window; - logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS - break; + switch (windowsCount) { + case 1: + startupWindowToUse = startupWindows.iterator().next(); + logger.log(Level.INFO, "Will use the default startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + case 2: { + //pick the non default one + Iterator it = startupWindows.iterator(); + while (it.hasNext()) { + StartupWindowInterface window = it.next(); + if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) { + startupWindowToUse = window; + logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + } } + break; } - } else { - // select first non-Autopsy start up window - Iterator it = startupWindows.iterator(); - while (it.hasNext()) { - StartupWindowInterface window = it.next(); - if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) { - startupWindowToUse = window; - logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS - break; + default: { + // select first non-Autopsy start up window + Iterator it = startupWindows.iterator(); + while (it.hasNext()) { + StartupWindowInterface window = it.next(); + if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) { + startupWindowToUse = window; + logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + } } + break; } } @@ -121,6 +139,45 @@ public class StartupWindowProvider implements StartupWindowInterface { startupWindowToUse = new org.sleuthkit.autopsy.casemodule.StartupWindow(); } } + File openPreviousCaseFile = new File(ResetWindowsAction.getCaseToReopenFilePath()); + + if (openPreviousCaseFile.exists()) { + //do actual opening on another thread + new Thread(() -> { + String caseFilePath = ""; + String unableToOpenMessage = null; + try { + //avoid readFileToString having ambiguous arguments + Charset encoding = null; + caseFilePath = FileUtils.readFileToString(openPreviousCaseFile, encoding); + if (new File(caseFilePath).exists()) { + FileUtils.forceDelete(openPreviousCaseFile); + //close the startup window as we attempt to open the case + close(); + Case.openAsCurrentCase(caseFilePath); + + } else { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_noFile(caseFilePath); + logger.log(Level.WARNING, unableToOpenMessage); + } + } catch (IOException ex) { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_deleteOpenFailure(ResetWindowsAction.getCaseToReopenFilePath()); + logger.log(Level.WARNING, unableToOpenMessage, ex); + } catch (CaseActionException ex) { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_cantOpen(caseFilePath); + logger.log(Level.WARNING, unableToOpenMessage, ex); + } + + if (RuntimeProperties.runningWithGUI() && !StringUtils.isBlank(unableToOpenMessage)) { + final String message = unableToOpenMessage; + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.warn(message); + //the case was not opened restore the startup window + open(); + }); + } + }).start(); + } } private void checkSolr() { @@ -147,9 +204,9 @@ public class StartupWindowProvider implements StartupWindowInterface { * @return True if running from command line, false otherwise */ private boolean isRunningFromCommandLine() { - + CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class); - if(processor != null) { + if (processor != null) { return processor.isRunFromCommandLine(); } return false; @@ -157,12 +214,12 @@ public class StartupWindowProvider implements StartupWindowInterface { /** * Get the default argument from the CommandLineOptionProcessor. - * - * @return If set, the default argument otherwise null. + * + * @return If set, the default argument otherwise null. */ - private String getDefaultArgument() { + private String getDefaultArgument() { CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class); - if(processor != null) { + if (processor != null) { return processor.getDefaultArgument(); } return null; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index e5f9ac5fae..5ac6a2cb14 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -301,7 +301,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { /** - * Values for configuration located in the /etc/*.conf file. + * Values for configuration located in the /etc/\*.conf file. */ private static class ConfValues { private final String XmxVal; @@ -335,7 +335,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } /** - * Retrieve the /etc/*.conf file values pertinent to settings. + * Retrieve the /etc/\*.conf file values pertinent to settings. * @return The conf file values. * @throws IOException */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 6d7a1dc7ff..d2a7f32bf7 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -376,6 +376,10 @@ TagNode.propertySheet.origNameDisplayName=Original Name TagsNode.displayName.text=Tags TagsNode.createSheet.name.name=Name TagsNode.createSheet.name.displayName=Name +UnsupportedContentNode.createSheet.name.desc=no description +UnsupportedContentNode.createSheet.name.displayName=Name +UnsupportedContentNode.createSheet.name.name=Name +UnsupportedContentNode.displayName=Unsupported Content ViewsNode.name.text=Views ViewsNode.createSheet.name.name=Name ViewsNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java index af7eb3a249..3f6706952a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel.OsAccounts.OsAccountNode; + /** * Visitor Pattern interface that goes over Content nodes in the data source * area of the tree. @@ -50,6 +52,9 @@ interface ContentNodeVisitor { T visit(BlackboardArtifactNode bban); + T visit(UnsupportedContentNode ucn); + + T visit(OsAccountNode bban); /** * Visitor with an implementable default behavior for all types. Override @@ -122,5 +127,15 @@ interface ContentNodeVisitor { public T visit(BlackboardArtifactNode bban) { return defaultVisit(bban); } + + @Override + public T visit(UnsupportedContentNode ucn) { + return defaultVisit(ucn); + } + + @Override + public T visit(OsAccountNode bban) { + return defaultVisit(bban); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java index b5d808153d..7601f4a88c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java @@ -32,6 +32,7 @@ import org.sleuthkit.datamodel.Pool; import org.sleuthkit.datamodel.SlackFile; import org.sleuthkit.datamodel.SleuthkitItemVisitor; import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.UnsupportedContent; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; @@ -99,6 +100,11 @@ public class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default visit(BlackboardArtifact art) { return new BlackboardArtifactNode(art); } + + @Override + public AbstractContentNode visit(UnsupportedContent uc) { + return new UnsupportedContentNode(uc); + } @Override protected AbstractContentNode defaultVisit(SleuthkitVisitableItem di) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index fa87c8df2e..61bc401ee5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -202,6 +202,11 @@ public interface DisplayableItemNodeVisitor { T visit(HostNode node); T visit(DataSourcesByTypeNode node); + + /* + * Unsupported node + */ + T visit(UnsupportedContentNode ucn); /** * Visitor with an implementable default behavior for all types. Override @@ -574,5 +579,10 @@ public interface DisplayableItemNodeVisitor { public T visit(PersonGroupingNode node) { return defaultVisit(node); } + + @Override + public T visit(UnsupportedContentNode node) { + return defaultVisit(node); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java index 9b7802bdbc..170251bd13 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -29,18 +30,26 @@ import java.util.List; import java.util.Optional; import java.util.logging.Level; import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; +import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; -import org.openide.util.lookup.Lookups; +import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.events.OsAccountChangedEvent; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountRealm; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; @@ -52,6 +61,7 @@ public final class OsAccounts implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(OsAccounts.class.getName()); private static final String ICON_PATH = "org/sleuthkit/autopsy/images/os-account.png"; private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + private static final String REALM_DATA_AVAILABLE_EVENT = "REALM_DATA_AVAILABLE_EVENT"; private SleuthkitCase skCase; private final long filteringDSObjId; @@ -114,9 +124,9 @@ public final class OsAccounts implements AutopsyVisitableItem { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); - if(eventType.equals(Case.Events.OS_ACCOUNT_ADDED.toString()) + if (eventType.equals(Case.Events.OS_ACCOUNT_ADDED.toString()) || eventType.equals(Case.Events.OS_ACCOUNT_REMOVED.toString())) { - refresh(true); + refresh(true); } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { // case was closed. Remove listeners so that we don't get called with a stale case handle if (evt.getNewValue() == null) { @@ -126,22 +136,22 @@ public final class OsAccounts implements AutopsyVisitableItem { } } }; - + @Override protected void addNotify() { Case.addEventTypeSubscriber(EnumSet.of(Case.Events.OS_ACCOUNT_ADDED, Case.Events.OS_ACCOUNT_REMOVED), listener); Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener); } - + @Override protected void removeNotify() { Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_ADDED), listener); Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener); } - + @Override protected boolean createKeys(List list) { - if(skCase != null) { + if (skCase != null) { try { if (filteringDSObjId == 0) { list.addAll(skCase.getOsAccountManager().getOsAccounts()); @@ -166,35 +176,52 @@ public final class OsAccounts implements AutopsyVisitableItem { /** * An OsAccount leaf Node. */ - public static final class OsAccountNode extends DisplayableItemNode { + public static final class OsAccountNode extends AbstractContentNode { private OsAccount account; - + private final PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - if(((OsAccountChangedEvent)evt).getOsAccount().getId() == account.getId()) { - // Update the account node to the new one - account = ((OsAccountChangedEvent)evt).getOsAccount(); - updateSheet(); + if (evt.getPropertyName().equals(Case.Events.OS_ACCOUNT_CHANGED.name())) { + if (((OsAccountChangedEvent) evt).getOsAccount().getId() == account.getId()) { + // Update the account node to the new one + account = ((OsAccountChangedEvent) evt).getOsAccount(); + updateSheet(); + } + } else if (evt.getPropertyName().equals(REALM_DATA_AVAILABLE_EVENT)) { + OsAccountRealm realm = (OsAccountRealm) evt.getNewValue(); + + // Currently only 0 or 1 names are supported, this will need + // to be modified if that changes. + List realmNames = realm.getRealmNames(); + if (!realmNames.isEmpty()) { + updateSheet(new NodeProperty<>( + Bundle.OsAccounts_accountRealmNameProperty_name(), + Bundle.OsAccounts_accountRealmNameProperty_displayName(), + Bundle.OsAccounts_accountRealmNameProperty_desc(), + "")); + } } } }; + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); + /** * Constructs a new OsAccountNode. * * @param account Node object. */ OsAccountNode(OsAccount account) { - super(Children.LEAF, Lookups.fixed(account)); + super(account); this.account = account; setName(account.getName()); setDisplayName(account.getName()); setIconBaseWithExtension(ICON_PATH); - - Case.addEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_CHANGED), listener); + + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_CHANGED), weakListener); } @Override @@ -211,7 +238,16 @@ public final class OsAccounts implements AutopsyVisitableItem { public String getItemType() { return getClass().getName(); } - + + /** + * Returns the OsAccount associated with this node. + * + * @return + */ + OsAccount getOsAccount() { + return account; + } + @Messages({ "OsAccounts_accountNameProperty_name=Name", "OsAccounts_accountNameProperty_displayName=Name", @@ -226,13 +262,13 @@ public final class OsAccounts implements AutopsyVisitableItem { "OsAccounts_loginNameProperty_displayName=Login Name", "OsAccounts_loginNameProperty_desc=Os Account login name" }) - + /** - * Refreshes this node's property sheet. - */ - void updateSheet() { - this.setSheet(createSheet()); - } + * Refreshes this node's property sheet. + */ + void updateSheet() { + this.setSheet(createSheet()); + } @Override protected Sheet createSheet() { @@ -255,9 +291,8 @@ public final class OsAccounts implements AutopsyVisitableItem { Bundle.OsAccounts_loginNameProperty_displayName(), Bundle.OsAccounts_loginNameProperty_desc(), optional.isPresent() ? optional.get() : "")); - // TODO - load realm on background thread + // Fill with empty string, fetch on background task. String realmName = ""; - //String realmName = account.getRealm().getRealmNames().isEmpty() ? "" : account.getRealm().getRealmNames().get(0); propertiesSet.put(new NodeProperty<>( Bundle.OsAccounts_accountRealmNameProperty_name(), Bundle.OsAccounts_accountRealmNameProperty_displayName(), @@ -274,16 +309,91 @@ public final class OsAccounts implements AutopsyVisitableItem { Bundle.OsAccounts_createdTimeProperty_desc(), timeDisplayStr)); + backgroundTasksPool.submit(new GetOsAccountRealmTask(new WeakReference<>(this), weakListener)); + return sheet; } - + @Override public Action[] getActions(boolean popup) { List actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(popup))); actionsList.addAll(DataModelActionsFactory.getActions(account)); - + return actionsList.toArray(new Action[actionsList.size()]); } + + @Override + protected List getAllTagsFromDatabase() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return null; + } + + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) { + return null; + } + + @Override + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); + } + + /** + * Task for grabbing the osAccount realm. + */ + static class GetOsAccountRealmTask implements Runnable { + + private final WeakReference weakNodeRef; + private final PropertyChangeListener listener; + + /** + * Construct a new task. + * + * @param weakContentRef + * @param listener + */ + GetOsAccountRealmTask(WeakReference weakContentRef, PropertyChangeListener listener) { + this.weakNodeRef = weakContentRef; + this.listener = listener; + } + + @Override + public void run() { + OsAccountNode node = weakNodeRef.get(); + if (node == null) { + return; + } + + try { + long realmId = node.getOsAccount().getRealmId(); + OsAccountRealm realm = Case.getCurrentCase().getSleuthkitCase().getOsAccountRealmManager().getRealmByRealmId(realmId); + + if (listener != null && realm != null) { + listener.propertyChange(new PropertyChangeEvent( + AutopsyEvent.SourceType.LOCAL.toString(), + REALM_DATA_AVAILABLE_EVENT, + null, realm)); + } + + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + } + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java new file mode 100644 index 0000000000..4d8a3473e6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java @@ -0,0 +1,186 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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.datamodel; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import org.apache.commons.lang3.tuple.Pair; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; +import org.sleuthkit.datamodel.UnsupportedContent; +import org.sleuthkit.datamodel.Tag; + +/** + * This class is used to represent the "Node" for an unsupported content object. + */ +public class UnsupportedContentNode extends AbstractContentNode { + + /** + * + * @param unsupportedContent underlying Content instance + */ + @NbBundle.Messages({ + "UnsupportedContentNode.displayName=Unsupported Content", + }) + public UnsupportedContentNode(UnsupportedContent unsupportedContent) { + super(unsupportedContent); + + // set name, display name, and icon + this.setDisplayName(Bundle.UnsupportedContentNode_displayName()); + + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS + } + + /** + * Right click action for UnsupportedContentNode node + * + * @param popup + * + * @return + */ + @Override + public Action[] getActions(boolean popup) { + List actionsList = new ArrayList<>(); + + for (Action a : super.getActions(true)) { + actionsList.add(a); + } + + return actionsList.toArray(new Action[actionsList.size()]); + + } + + @NbBundle.Messages({ + "UnsupportedContentNode.createSheet.name.name=Name", + "UnsupportedContentNode.createSheet.name.displayName=Name", + "UnsupportedContentNode.createSheet.name.desc=no description", + }) + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(Bundle.UnsupportedContentNode_createSheet_name_name(), + Bundle.UnsupportedContentNode_createSheet_name_displayName(), + Bundle.UnsupportedContentNode_createSheet_name_desc(), + this.getDisplayName())); + + return sheet; + } + + @Override + public T accept(ContentNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + + /** + * Returns correlation attribute instance for the underlying content of the + * node. + * + * Null implementation of an abstract method. + * + * @return correlation attribute instance for the underlying content of the + * node. + */ + @Override + protected CorrelationAttributeInstance getCorrelationAttributeInstance() { + return null; + } + + /** + * Returns Score property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags. + * + * @return Score property for the underlying content of the node. + */ + @Override + protected Pair getScorePropertyAndDescription(List tags) { + return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR); + } + + /** + * Returns comment property for the node. + * + * Null implementation of an abstract method. + * + * @param tags list of tags + * @param attribute correlation attribute instance + * + * @return Comment property for the underlying content of the node. + */ + @Override + protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + return DataResultViewerTable.HasCommentStatus.NO_COMMENT; + } + + /** + * Returns occurrences/count property for the node. + * + * Null implementation of an abstract method. + * + * @param attributeType the type of the attribute to count + * @param attributeValue the value of the attribute to coun + * @param defaultDescription a description to use when none is determined by + * the getCountPropertyAndDescription method + * + * @return count property for the underlying content of the node. + */ + @Override + protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) { + return Pair.of(-1L, NO_DESCR); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index c648387016..0ab883cbd4 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -59,6 +59,7 @@ SelectionContext.views=Views ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory. ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree. ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree. +ViewContextAction.errorMessage.unsupportedParent=Unable to navigate to content not supported in this release. VolumeDetailsPanel.volumeIDLabel.text=Volume ID: VolumeDetailsPanel.volumeIDValue.text=... VolumeDetailsPanel.startValue.text=... diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index a412bb5970..bf99de6183 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -304,7 +304,6 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); } // action to go to the source file of the artifact - // action to go to the source file of the artifact Content fileContent = ban.getLookup().lookup(AbstractFile.class); if (fileContent == null) { Content content = ban.getLookup().lookup(Content.class); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index ca9aa43198..6fb134ceec 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -63,6 +63,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.UnsupportedContent; import org.sleuthkit.datamodel.VolumeSystem; /** @@ -161,7 +162,8 @@ public class ViewContextAction extends AbstractAction { @Messages({ "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.", "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.", - "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree." + "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree.", + "ViewContextAction.errorMessage.unsupportedParent=Unable to navigate to content not supported in this release." }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { @@ -181,6 +183,13 @@ public class ViewContextAction extends AbstractAction { logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS return; } + + if ((parentContent != null) + && (parentContent instanceof UnsupportedContent)) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_unsupportedParent()); + logger.log(Level.WARNING, String.format("Could not navigate to unsupported content with id: %d", parentContent.getId())); //NON-NLS + return; + } /* * Get the "Data Sources" node from the tree view. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java index b4a9a0182a..cd50214b84 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java @@ -60,7 +60,7 @@ final class ObjectDetectedFilterPanel extends AbstractDiscoveryFilterPanel { objectsList.clearList(); List setNames = DiscoveryUiUtils.getSetNames(BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION); for (String name : setNames) { - objectsList.addElement(name, null, null); + objectsList.addElement(name, null, name); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error loading object detected set names", ex); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java index cf02c8e11b..a50f266c57 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java @@ -31,6 +31,7 @@ import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.LocalDirectory; import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.UnsupportedContent; import org.sleuthkit.datamodel.VirtualDirectory; /** @@ -113,4 +114,9 @@ final class GetRootDirectoryVisitor extends GetFilesContentVisitor { public Collection visit(OsAccount art) { return getAllFromChildren(art); } + + @Override + public Collection visit(UnsupportedContent uc) { + return getAllFromChildren(uc); + } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java index 1ddc1ed401..7d41888121 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java @@ -175,8 +175,8 @@ final class ChromeCacheExtractor { fileManager = currentCase.getServices().getFileManager(); // Create an output folder to save any derived files - absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName); - relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString(); + absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName, context.getJobId()); + relOutputFolderName = Paths.get(RAImageIngestModule.getRelModuleOutputPath(currentCase, moduleName, context.getJobId())).normalize().toString(); File dir = new File(absOutputFolderName); if (dir.exists() == false) { @@ -206,7 +206,7 @@ final class ChromeCacheExtractor { outDir.mkdirs(); } - String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath; + String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cachePath; File tempDir = new File(cacheTempPath); if (tempDir.exists() == false) { tempDir.mkdirs(); @@ -222,7 +222,7 @@ final class ChromeCacheExtractor { private void cleanup () { for (Entry entry : this.fileCopyCache.entrySet()) { - Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() ); + Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()), entry.getKey() ); try { entry.getValue().getFileCopy().getChannel().close(); entry.getValue().getFileCopy().close(); @@ -652,7 +652,7 @@ final class ChromeCacheExtractor { // write the file to disk so that we can have a memory-mapped ByteBuffer AbstractFile cacheFile = abstractFileOptional.get(); RandomAccessFile randomAccessFile = null; - String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cacheFolderName + cacheFile.getName(); //NON-NLS + String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cacheFolderName + cacheFile.getName(); //NON-NLS try { File newFile = new File(tempFilePathname); ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java index 3570f71622..c163ab16ad 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java @@ -130,42 +130,43 @@ class Chromium extends Extract { this.dataSource = dataSource; this.context = context; dataFound = false; + long ingestJobId = context.getJobId(); for (Map.Entry browser : BROWSERS_MAP.entrySet()) { String browserName = browser.getKey(); String browserLocation = browser.getValue(); progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_History", browserName)); - this.getHistory(browser.getKey(), browser.getValue()); + this.getHistory(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Bookmarks", browserName)); - this.getBookmark(browser.getKey(), browser.getValue()); + this.getBookmark(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Cookies", browserName)); - this.getCookie(browser.getKey(), browser.getValue()); + this.getCookie(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Logins", browserName)); - this.getLogins(browser.getKey(), browser.getValue()); + this.getLogins(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_AutoFill", browserName)); - this.getAutofill(browser.getKey(), browser.getValue()); + this.getAutofill(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Downloads", browserName)); - this.getDownload(browser.getKey(), browser.getValue()); + this.getDownload(browser.getKey(), browser.getValue(), ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } @@ -179,8 +180,11 @@ class Chromium extends Extract { /** * Query for history databases and add artifacts + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getHistory(String browser, String browserLocation) { + private void getHistory(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List historyFiles; String historyFileName = HISTORY_FILE_NAME; @@ -215,7 +219,7 @@ class Chromium extends Extract { Collection bbartifacts = new ArrayList<>(); int j = 0; while (j < allocatedHistoryFiles.size()) { - String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS final AbstractFile historyFile = allocatedHistoryFiles.get(j++); if ((historyFile.getSize() == 0) || (historyFile.getName().toLowerCase().contains("-slack")) || (historyFile.getName().toLowerCase().contains("cache")) || (historyFile.getName().toLowerCase().contains("media")) @@ -281,8 +285,11 @@ class Chromium extends Extract { /** * Search for bookmark files and make artifacts. + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getBookmark(String browser, String browserLocation) { + private void getBookmark(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List bookmarkFiles; String bookmarkFileName = BOOKMARK_FILE_NAME; @@ -315,7 +322,7 @@ class Chromium extends Extract { || (bookmarkFile.getName().toLowerCase().contains("bak")) || (bookmarkFile.getParentPath().toLowerCase().contains("backup"))) { continue; } - String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -423,8 +430,11 @@ class Chromium extends Extract { /** * Queries for cookie files and adds artifacts + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getCookie(String browser, String browserLocation) { + private void getCookie(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List cookiesFiles; @@ -456,7 +466,7 @@ class Chromium extends Extract { if ((cookiesFile.getSize() == 0) || (cookiesFile.getName().toLowerCase().contains("-slack"))) { continue; } - String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -519,8 +529,11 @@ class Chromium extends Extract { /** * Queries for download files and adds artifacts + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getDownload(String browser, String browserLocation) { + private void getDownload(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List downloadFiles; String historyFileName = HISTORY_FILE_NAME; @@ -551,7 +564,7 @@ class Chromium extends Extract { continue; } - String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -633,8 +646,11 @@ class Chromium extends Extract { /** * Gets user logins from Login Data sqlite database + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getLogins(String browser, String browserLocation) { + private void getLogins(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List loginDataFiles; @@ -665,7 +681,7 @@ class Chromium extends Extract { if ((loginDataFile.getSize() == 0) || (loginDataFile.getName().toLowerCase().contains("-slack"))) { continue; } - String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -736,8 +752,11 @@ class Chromium extends Extract { /** * Gets and parses Autofill data from 'Web Data' database, and creates * TSK_WEB_FORM_AUTOFILL, TSK_WEB_FORM_ADDRESS artifacts + * @param browser + * @param browserLocation + * @param ingestJobId The ingest job id. */ - private void getAutofill(String browser, String browserLocation) { + private void getAutofill(String browser, String browserLocation, long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List webDataFiles; @@ -768,7 +787,7 @@ class Chromium extends Extract { if ((webDataFile.getSize() == 0) || (webDataFile.getName().toLowerCase().contains("-slack"))) { continue; } - String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS + String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java index 0d64661f6c..9519a37bb1 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java @@ -128,18 +128,33 @@ public class DefaultDomainCategorizer implements DomainCategorizer { private Map mapping = null; @Override - public void initialize() throws DomainCategorizerException { - if (this.mapping == null) { - try { - this.mapping = loadMapping(); - } catch (IOException ex) { - throw new DomainCategorizerException("Unable to load domain type csv for domain category analysis", ex); - } + public synchronized void initialize() throws DomainCategorizerException { + if (isInitialized()) { + return; + } + + try { + this.mapping = loadMapping(); + } catch (IOException ex) { + throw new DomainCategorizerException("Unable to load domain type csv for domain category analysis", ex); } } + /** + * Returns true if this categorizer is properly initialized. + * + * @return True if this categorizer is properly initialized. + */ + private synchronized boolean isInitialized() { + return this.mapping != null; + } + @Override - public DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + public synchronized DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + if (!isInitialized()) { + initialize(); + } + // use host; use domain as fallback if no host provided String hostToUse = StringUtils.isBlank(host) ? domain : host; @@ -162,7 +177,7 @@ public class DefaultDomainCategorizer implements DomainCategorizer { } @Override - public void close() throws Exception { + public synchronized void close() throws Exception { // clear out the mapping to release resources mapping = null; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java index c2abe0223d..5945712018 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java @@ -520,12 +520,13 @@ abstract class Extract { * * @param context * @param file + * @param IngestJobId The ingest job id. * @return Newly created copy of the AbstractFile * @throws IOException */ - protected File createTemporaryFile(IngestJobContext context, AbstractFile file) throws IOException{ + protected File createTemporaryFile(IngestJobContext context, AbstractFile file, long ingestJobId) throws IOException{ Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath( - getCurrentCase(), getName()), file.getName() + file.getId() + file.getNameExtension()); + getCurrentCase(), getName(), ingestJobId), file.getName() + file.getId() + file.getNameExtension()); java.io.File tempFile = tempFilePath.toFile(); try { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java index 3187955c10..68f280a5fa 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java @@ -64,7 +64,6 @@ import org.sleuthkit.datamodel.TskCoreException; final class ExtractEdge extends Extract { private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName()); - private final Path moduleTempResultPath; private Content dataSource; private IngestJobContext context; private HashMap> containersTable; @@ -125,9 +124,8 @@ final class ExtractEdge extends Extract { /** * Extract the bookmarks, cookies, downloads and history from Microsoft Edge */ - ExtractEdge() throws NoCurrentCaseException { + ExtractEdge() { super(Bundle.ExtractEdge_Module_Name()); - moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME); } @Override @@ -137,6 +135,9 @@ final class ExtractEdge extends Extract { @Override void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { + String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), EDGE, context.getJobId()); + String moduleTempResultDir = Paths.get(moduleTempDir, EDGE_RESULT_FOLDER_NAME).toString(); + this.dataSource = dataSource; this.context = context; this.setFoundData(false); @@ -186,7 +187,7 @@ final class ExtractEdge extends Extract { } try { - this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar); + this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar, moduleTempDir, moduleTempResultDir); } catch (IOException | TskCoreException ex) { LOG.log(Level.SEVERE, "Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); // NON-NLS this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail()); @@ -194,7 +195,7 @@ final class ExtractEdge extends Extract { progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks()); try { - this.processSpartanDbFile(esedumper, spartanFiles); + this.processSpartanDbFile(esedumper, spartanFiles, moduleTempDir, moduleTempResultDir); } catch (IOException | TskCoreException ex) { LOG.log(Level.SEVERE, "Error processing 'spartan.edb' files for Microsoft Edge", ex); // NON-NLS this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail()); @@ -207,10 +208,13 @@ final class ExtractEdge extends Extract { * * @param eseDumperPath Path to ESEDatabaseView.exe * @param webCacheFiles List of case WebCacheV01.dat files + * @param moduleTempDir The temp directory for this module. + * @param moduleTempResultDir The temp results directory for this module. * @throws IOException * @throws TskCoreException */ - void processWebCacheDbFile(String eseDumperPath, List webCacheFiles, DataSourceIngestModuleProgress progressBar) throws IOException, TskCoreException { + void processWebCacheDbFile(String eseDumperPath, List webCacheFiles, DataSourceIngestModuleProgress progressBar, + String moduleTempDir, String moduleTempResultDir) throws IOException, TskCoreException { for (AbstractFile webCacheFile : webCacheFiles) { @@ -223,7 +227,7 @@ final class ExtractEdge extends Extract { //Run the dumper String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX + Integer.toString((int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; //NON-NLS - File tempWebCacheFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName); + File tempWebCacheFile = new File(moduleTempDir, tempWebCacheFileName); try { ContentUtils.writeToFile(webCacheFile, tempWebCacheFile, @@ -232,7 +236,7 @@ final class ExtractEdge extends Extract { throw new IOException("Error writingToFile: " + webCacheFile, ex); //NON-NLS } - File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) webCacheFile.getId())); + File resultsDir = new File(moduleTempDir, Integer.toString((int) webCacheFile.getId())); resultsDir.mkdirs(); try { executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(), @@ -267,10 +271,13 @@ final class ExtractEdge extends Extract { * * @param eseDumperPath Path to ESEDatabaseViewer * @param spartanFiles List of the case spartan.edb files + * @param moduleTempDir The temp directory for this module. + * @param moduleTempResultDir The temp results directory for this module. * @throws IOException * @throws TskCoreException */ - void processSpartanDbFile(String eseDumperPath, List spartanFiles) throws IOException, TskCoreException { + void processSpartanDbFile(String eseDumperPath, List spartanFiles, + String moduleTempDir, String moduleTempResultDir) throws IOException, TskCoreException { for (AbstractFile spartanFile : spartanFiles) { @@ -281,7 +288,7 @@ final class ExtractEdge extends Extract { //Run the dumper String tempSpartanFileName = EDGE_WEBCACHE_PREFIX + Integer.toString((int) spartanFile.getId()) + EDGE_WEBCACHE_EXT; - File tempSpartanFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName); + File tempSpartanFile = new File(moduleTempDir, tempSpartanFileName); try { ContentUtils.writeToFile(spartanFile, tempSpartanFile, @@ -290,7 +297,7 @@ final class ExtractEdge extends Extract { throw new IOException("Error writingToFile: " + spartanFile, ex); //NON-NLS } - File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) spartanFile.getId())); + File resultsDir = new File(moduleTempResultDir, Integer.toString((int) spartanFile.getId())); resultsDir.mkdirs(); try { executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(), diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java index 58f4672118..29754fcdcb 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java @@ -31,6 +31,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -43,7 +44,6 @@ import java.util.stream.Collectors; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -67,7 +67,6 @@ import org.sleuthkit.datamodel.TskCoreException; class ExtractIE extends Extract { private static final Logger logger = Logger.getLogger(ExtractIE.class.getName()); - private final String moduleTempResultsDir; private String PASCO_LIB_PATH; private final String JAVA_PATH; private static final String RESOURCE_URL_PREFIX = "res://"; @@ -84,14 +83,16 @@ class ExtractIE extends Extract { "Progress_Message_IE_AutoFill=IE Auto Fill", "Progress_Message_IE_Logins=IE Logins",}) - ExtractIE() throws NoCurrentCaseException { + ExtractIE() { super(NbBundle.getMessage(ExtractIE.class, "ExtractIE.moduleName.text")); - moduleTempResultsDir = RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), "IE") + File.separator + "results"; //NON-NLS JAVA_PATH = PlatformUtil.getJavaPath(); } @Override public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { + String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), "IE", context.getJobId()); + String moduleTempResultsDir = Paths.get(moduleTempDir, "results").toString(); + this.dataSource = dataSource; this.context = context; dataFound = false; @@ -111,7 +112,7 @@ class ExtractIE extends Extract { } progressBar.progress(Bundle.Progress_Message_IE_History()); - this.getHistory(); + this.getHistory(moduleTempDir, moduleTempResultsDir); } /** @@ -297,8 +298,10 @@ class ExtractIE extends Extract { /** * Locates index.dat files, runs Pasco on them, and creates artifacts. + * @param moduleTempDir The path to the module temp directory. + * @param moduleTempResultsDir The path to the module temp results directory. */ - private void getHistory() { + private void getHistory(String moduleTempDir, String moduleTempResultsDir) { logger.log(Level.INFO, "Pasco results path: {0}", moduleTempResultsDir); //NON-NLS boolean foundHistory = false; @@ -350,7 +353,7 @@ class ExtractIE extends Extract { //BlackboardArtifact bbart = fsc.newArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY); indexFileName = "index" + Integer.toString((int) indexFile.getId()) + ".dat"; //NON-NLS //indexFileName = "index" + Long.toString(bbart.getArtifactID()) + ".dat"; - temps = RAImageIngestModule.getRATempPath(currentCase, "IE") + File.separator + indexFileName; //NON-NLS + temps = moduleTempDir + File.separator + indexFileName; //NON-NLS File datFile = new File(temps); if (context.dataSourceIngestIsCancelled()) { break; @@ -366,7 +369,7 @@ class ExtractIE extends Extract { } String filename = "pasco2Result." + indexFile.getId() + ".txt"; //NON-NLS - boolean bPascProcSuccess = executePasco(temps, filename); + boolean bPascProcSuccess = executePasco(temps, filename, moduleTempResultsDir); if (context.dataSourceIngestIsCancelled()) { return; } @@ -375,7 +378,7 @@ class ExtractIE extends Extract { //Now fetch the results, parse them and the delete the files. if (bPascProcSuccess) { // Don't add TSK_OS_ACCOUNT artifacts to the ModuleDataEvent - bbartifacts.addAll(parsePascoOutput(indexFile, filename).stream() + bbartifacts.addAll(parsePascoOutput(indexFile, filename, moduleTempResultsDir).stream() .filter(bbart -> bbart.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) .collect(Collectors.toList())); if (context.dataSourceIngestIsCancelled()) { @@ -402,6 +405,7 @@ class ExtractIE extends Extract { * * @param indexFilePath Path to local index.dat file to analyze * @param outputFileName Name of file to save output to + * @param moduleTempResultsDir the path to the module temp directory. * * @return false on error */ @@ -409,7 +413,7 @@ class ExtractIE extends Extract { "# {0} - sub module name", "ExtractIE_executePasco_errMsg_errorRunningPasco={0}: Error analyzing Internet Explorer web history", }) - private boolean executePasco(String indexFilePath, String outputFileName) { + private boolean executePasco(String indexFilePath, String outputFileName, String moduleTempResultsDir) { boolean success = true; try { final String outputFileFullPath = moduleTempResultsDir + File.separator + outputFileName; @@ -451,10 +455,11 @@ class ExtractIE extends Extract { * @param origFile Original index.dat file that was analyzed to * get this output * @param pascoOutputFileName name of pasco output file + * @param moduleTempResultsDir the path to the module temp directory. * * @return A collection of created artifacts */ - private Collection parsePascoOutput(AbstractFile origFile, String pascoOutputFileName) { + private Collection parsePascoOutput(AbstractFile origFile, String pascoOutputFileName, String moduleTempResultsDir) { Collection bbartifacts = new ArrayList<>(); String fnAbs = moduleTempResultsDir + File.separator + pascoOutputFileName; diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java index 32312c2ee1..3144c831b2 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java @@ -91,6 +91,7 @@ final class ExtractPrefetch extends Extract { void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { this.context = context; + long ingestJobId = context.getJobId(); String modOutPath = Case.getCurrentCase().getModuleDirectory() + File.separator + PREFETCH_DIR_NAME; File dir = new File(modOutPath); @@ -102,7 +103,7 @@ final class ExtractPrefetch extends Extract { } } - extractPrefetchFiles(dataSource); + extractPrefetchFiles(dataSource, ingestJobId); final String prefetchDumper = getPathForPrefetchDumper(); if (prefetchDumper == null) { @@ -116,7 +117,7 @@ final class ExtractPrefetch extends Extract { String modOutFile = modOutPath + File.separator + dataSource.getName() + "-" + PREFETCH_PARSER_DB_FILE; try { - String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME); + String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME, ingestJobId); parsePrefetchFiles(prefetchDumper, tempDirPath, modOutFile, modOutPath); createAppExecArtifacts(modOutFile, dataSource); } catch (IOException ex) { @@ -131,7 +132,7 @@ final class ExtractPrefetch extends Extract { * * @param dataSource - datasource to search for prefetch files */ - void extractPrefetchFiles(Content dataSource) { + void extractPrefetchFiles(Content dataSource, long ingestJobId) { List pFiles; FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); @@ -154,7 +155,7 @@ final class ExtractPrefetch extends Extract { String ext = FilenameUtils.getExtension(origFileName); String baseName = FilenameUtils.getBaseName(origFileName); String fileName = String.format("%s_%d.%s", baseName, pFile.getId(), ext); - String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME); + String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME, ingestJobId); String prefetchFile = Paths.get(baseRaTempPath, fileName).toString(); try { ContentUtils.writeToFile(pFile, new File(prefetchFile)); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java index 1ced615661..5d727f5b7d 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java @@ -136,7 +136,7 @@ final class ExtractRecycleBin extends Extract { return; // No need to continue } - String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin"); //NON-NLS + String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin", context.getJobId()); //NON-NLS // cycle through the $I files and process each. for (AbstractFile iFile : iFiles) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index a1048d6df6..95f0fee429 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -294,14 +294,15 @@ class ExtractRegistry extends Extract { /** * Identifies registry files in the database by mtimeItem, runs regripper on * them, and parses the output. + * @param ingestJobId The ingest job id. */ - private void analyzeRegistryFiles() { + private void analyzeRegistryFiles(long ingestJobId) { List allRegistryFiles = findRegistryFiles(); // open the log file FileWriter logFile = null; try { - logFile = new FileWriter(RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + "regripper-info.txt"); //NON-NLS + logFile = new FileWriter(RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + "regripper-info.txt"); //NON-NLS } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } @@ -313,8 +314,8 @@ class ExtractRegistry extends Extract { String regFileName = regFile.getName(); long regFileId = regFile.getId(); - String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + regFileName; - String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS + String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg", ingestJobId) + File.separator + regFileName; + String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS File regFileNameLocalFile = new File(regFileNameLocal); try { ContentUtils.writeToFile(regFile, regFileNameLocalFile, context::dataSourceIngestIsCancelled); @@ -366,7 +367,7 @@ class ExtractRegistry extends Extract { // create a report for the full output if (!regOutputFiles.fullPlugins.isEmpty()) { //parse the full regripper output from SAM hive files - if (regFileNameLocal.toLowerCase().contains("sam") && parseSamPluginOutput(regOutputFiles.fullPlugins, regFile) == false) { + if (regFileNameLocal.toLowerCase().contains("sam") && parseSamPluginOutput(regOutputFiles.fullPlugins, regFile, ingestJobId) == false) { this.addErrorMessage( NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults", this.getName(), regFileName)); @@ -1049,11 +1050,12 @@ class ExtractRegistry extends Extract { * * @param regFilePath the path to the registry file being parsed * @param regAbstractFile the file to associate newly created artifacts with + * @param ingestJobId The ingest job id. * * @return true if successful, false if parsing failed at some point */ - private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile) { - parseSystemHostDomain(); + private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile, long ingestJobId) { + parseSystemHostDomain(ingestJobId); File regfile = new File(regFilePath); List newArtifacts = new ArrayList<>(); @@ -1139,14 +1141,15 @@ class ExtractRegistry extends Extract { /** * Finds the Host and Domain information from the registry. + * @param ingestJobId The ingest job id. */ - private void parseSystemHostDomain() { + private void parseSystemHostDomain(long ingestJobId) { List regFiles = findRegistryFiles(); for (AbstractFile systemHive: regFiles) { if (systemHive.getName().toLowerCase().equals("system")) { - String systemFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + systemHive.getName(); + String systemFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg", ingestJobId) + File.separator + systemHive.getName(); File systemFileNameLocalFile = new File(systemFileNameLocal); if (!systemFileNameLocalFile.exists()) { @@ -1989,7 +1992,7 @@ class ExtractRegistry extends Extract { this.context = context; progressBar.progress(Bundle.Progress_Message_Analyze_Registry()); - analyzeRegistryFiles(); + analyzeRegistryFiles(context.getJobId()); } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java index 15575ab61d..2bf0351542 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java @@ -289,7 +289,7 @@ final class ExtractSafari extends Extract { return; } - File tempHistoryFile = createTemporaryFile(context, historyFile); + File tempHistoryFile = createTemporaryFile(context, historyFile, context.getJobId()); try { ContentUtils.writeToFile(historyFile, tempHistoryFile, context::dataSourceIngestIsCancelled); @@ -324,7 +324,7 @@ final class ExtractSafari extends Extract { return; } - File tempFile = createTemporaryFile(context, file); + File tempFile = createTemporaryFile(context, file, context.getJobId()); try { if(!context.dataSourceIngestIsCancelled()) { @@ -354,7 +354,7 @@ final class ExtractSafari extends Extract { return; } - File tempFile = createTemporaryFile(context, file); + File tempFile = createTemporaryFile(context, file, context.getJobId()); try { if(!context.dataSourceIngestIsCancelled()) { @@ -385,7 +385,7 @@ final class ExtractSafari extends Extract { File tempFile = null; try { - tempFile = createTemporaryFile(context, file); + tempFile = createTemporaryFile(context, file, context.getJobId()); if(!context.dataSourceIngestIsCancelled()) { postArtifacts(getCookieArtifacts(file, tempFile, context)); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java index 53b65951f9..50c2c6afd4 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java @@ -100,7 +100,7 @@ final class ExtractSru extends Extract { dir.mkdirs(); } - String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "sru"); //NON-NLS + String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "sru", context.getJobId()); //NON-NLS String softwareHiveFileName = getSoftwareHiveFile(dataSource, tempDirPath); if (softwareHiveFileName == null) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index c4a472a350..6dd30ed4cc 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -108,47 +108,52 @@ class Firefox extends Extract { this.dataSource = dataSource; this.context = context; dataFound = false; + long ingestJobId = context.getJobId(); progressBar.progress(Bundle.Progress_Message_Firefox_History()); - this.getHistory(); + this.getHistory(context.getJobId()); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(Bundle.Progress_Message_Firefox_Bookmarks()); - this.getBookmark(); + this.getBookmark(ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(Bundle.Progress_Message_Firefox_Downloads()); - this.getDownload(); + this.getDownload(ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(Bundle.Progress_Message_Firefox_Cookies()); - this.getCookie(); + this.getCookie(ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(Bundle.Progress_Message_Firefox_FormHistory()); - this.getFormsHistory(); + this.getFormsHistory(ingestJobId); if (context.dataSourceIngestIsCancelled()) { return; } progressBar.progress(Bundle.Progress_Message_Firefox_AutoFill()); - this.getAutofillProfiles(); + this.getAutofillProfiles(ingestJobId); } - private void getHistory() { + /** + * Get Firefox history. + * @param ingestJobId The ingest job id. + */ + private void getHistory(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List historyFiles; try { @@ -180,7 +185,7 @@ class Firefox extends Extract { } String fileName = historyFile.getName(); - String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(historyFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -254,8 +259,9 @@ class Firefox extends Extract { /** * Queries for bookmark files and adds artifacts + * @param ingestJobId The ingest job id. */ - private void getBookmark() { + private void getBookmark(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List bookmarkFiles; @@ -281,7 +287,7 @@ class Firefox extends Extract { continue; } String fileName = bookmarkFile.getName(); - String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -351,8 +357,9 @@ class Firefox extends Extract { /** * Queries for cookies file and adds artifacts + * @param ingestJobId The ingest job id. */ - private void getCookie() { + private void getCookie(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List cookiesFiles; try { @@ -381,7 +388,7 @@ class Firefox extends Extract { continue; } String fileName = cookiesFile.getName(); - String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -468,18 +475,20 @@ class Firefox extends Extract { /** * Queries for downloads files and adds artifacts + * @param ingestJobId The ingest job id. */ - private void getDownload() { - getDownloadPreVersion24(); - getDownloadVersion24(); + private void getDownload(long ingestJobId) { + getDownloadPreVersion24(ingestJobId); + getDownloadVersion24(ingestJobId); } /** * Finds downloads artifacts from Firefox data from versions before 24.0. * * Downloads were stored in a separate downloads database. + * @param ingestJobId The ingest job id. */ - private void getDownloadPreVersion24() { + private void getDownloadPreVersion24(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List downloadsFiles; @@ -505,7 +514,7 @@ class Firefox extends Extract { continue; } String fileName = downloadsFile.getName(); - String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS int errors = 0; try { ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled); @@ -611,8 +620,9 @@ class Firefox extends Extract { * Gets download artifacts from Firefox data from version 24. * * Downloads are stored in the places database. + * @param ingestJobId The ingest job id. */ - private void getDownloadVersion24() { + private void getDownloadVersion24(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List downloadsFiles; try { @@ -637,7 +647,7 @@ class Firefox extends Extract { continue; } String fileName = downloadsFile.getName(); - String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + "-downloads" + j + ".db"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + "-downloads" + j + ".db"; //NON-NLS int errors = 0; try { ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled); @@ -742,8 +752,9 @@ class Firefox extends Extract { /** * Gets data from formshistory.sqlite database. * Parses and creates artifacts. + * @param ingestJobId The ingest job id. */ - private void getFormsHistory() { + private void getFormsHistory(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List formHistoryFiles; @@ -777,7 +788,7 @@ class Firefox extends Extract { } String fileName = formHistoryFile.getName(); - String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS + String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS try { ContentUtils.writeToFile(formHistoryFile, new File(tempFilePath), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { @@ -864,9 +875,9 @@ class Firefox extends Extract { /** * Gets data from autofill-profiles.json file. * Parses file and makes artifacts. - * + * @param ingestJobId The ingest job id. */ - private void getAutofillProfiles() { + private void getAutofillProfiles(long ingestJobId) { FileManager fileManager = currentCase.getServices().getFileManager(); List autofillProfilesFiles; try { @@ -891,7 +902,7 @@ class Firefox extends Extract { if (profileFile.getSize() == 0) { continue; } - String temps = RAImageIngestModule.getRATempPath(currentCase, "Firefox") + File.separator + profileFile.getName() + j + ".json"; //NON-NLS + String temps = RAImageIngestModule.getRATempPath(currentCase, "Firefox", ingestJobId) + File.separator + profileFile.getName() + j + ".json"; //NON-NLS try { ContentUtils.writeToFile(profileFile, new File(temps), context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java index b92ebffa98..a18ced587e 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java @@ -23,15 +23,16 @@ package org.sleuthkit.autopsy.recentactivity; import java.io.File; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestServices; @@ -41,7 +42,6 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -49,6 +49,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; */ public final class RAImageIngestModule implements DataSourceIngestModule { + private static final String RECENT_ACTIVITY_FOLDER = "RecentActivity"; private static final Logger logger = Logger.getLogger(RAImageIngestModule.class.getName()); private final List extractors = new ArrayList<>(); private final List browserExtractors = new ArrayList<>(); @@ -64,18 +65,11 @@ public final class RAImageIngestModule implements DataSourceIngestModule { @Override public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; - + tskCase = Case.getCurrentCase().getSleuthkitCase(); - Extract iexplore; - Extract edge; - try { - iexplore = new ExtractIE(); - edge = new ExtractEdge(); - } catch (NoCurrentCaseException ex) { - throw new IngestModuleException(ex.getMessage(), ex); - } - + Extract iexplore = new ExtractIE(); + Extract edge = new ExtractEdge(); Extract registry = new ExtractRegistry(); Extract recentDocuments = new RecentDocumentsByLnk(); Extract chrome = new Chromium(); @@ -104,10 +98,10 @@ public final class RAImageIngestModule implements DataSourceIngestModule { extractors.add(webAccountType); // this needs to run after the web browser modules extractors.add(zoneInfo); // this needs to run after the web browser modules extractors.add(recycleBin); // this needs to run after ExtractRegistry and ExtractOS - extractors.add(sru); + extractors.add(sru); extractors.add(prefetch); extractors.add(messageDomainType); - + browserExtractors.add(chrome); browserExtractors.add(firefox); browserExtractors.add(iexplore); @@ -141,8 +135,8 @@ public final class RAImageIngestModule implements DataSourceIngestModule { try { extracter.process(dataSource, context, progressBar, accountCache); - if(extracter instanceof ExtractRegistry) { - accountCache.initialize(tskCase, ((DataSource)dataSource).getHost()); + if (extracter instanceof ExtractRegistry) { + accountCache.initialize(tskCase, ((DataSource) dataSource).getHost()); } } catch (Exception ex) { logger.log(Level.SEVERE, "Exception occurred in " + extracter.getName(), ex); //NON-NLS @@ -221,23 +215,39 @@ public final class RAImageIngestModule implements DataSourceIngestModule { return ProcessResult.OK; } + /** + * Makes a path of the format + * [basePath]/[RECENT_ACTIVITY_FOLDER]/[module]_[ingest job id] if it does not + * already exist and returns the created folder. + * + * @param basePath The base path (a case-related folder like temp or + * output). + * @param module The module name to include in the folder name. + * @param ingestJobId The id of the ingest job. + * @return The path to the folder. + */ + private static String getAndMakeRAPath(String basePath, String module, long ingestJobId) { + String moduleFolder = String.format("%s_%d", module, ingestJobId); + Path tmpPath = Paths.get(basePath, RECENT_ACTIVITY_FOLDER, moduleFolder); + File dir = tmpPath.toFile(); + if (dir.exists() == false) { + dir.mkdirs(); + } + return tmpPath.toString(); + } + /** * Get the temp path for a specific sub-module in recent activity. Will * create the dir if it doesn't exist. * * @param a_case Case that directory is for - * @param mod Module name that will be used for a sub folder in the temp - * folder to prevent name collisions + * @param mod Module name that will be used for a sub folder in the temp + * folder to prevent name collisions * * @return Path to directory */ - static String getRATempPath(Case a_case, String mod) { - String tmpDir = a_case.getTempDirectory() + File.separator + "RecentActivity" + File.separator + mod; //NON-NLS - File dir = new File(tmpDir); - if (dir.exists() == false) { - dir.mkdirs(); - } - return tmpDir; + static String getRATempPath(Case a_case, String mod, long ingestJobId) { + return getAndMakeRAPath(a_case.getTempDirectory(), mod, ingestJobId); } /** @@ -245,28 +255,24 @@ public final class RAImageIngestModule implements DataSourceIngestModule { * create the dir if it doesn't exist. * * @param a_case Case that directory is for - * @param mod Module name that will be used for a sub folder in the temp - * folder to prevent name collisions + * @param mod Module name that will be used for a sub folder in the temp + * folder to prevent name collisions * * @return Path to directory */ - static String getRAOutputPath(Case a_case, String mod) { - String tmpDir = a_case.getModuleDirectory() + File.separator + "RecentActivity" + File.separator + mod; //NON-NLS - File dir = new File(tmpDir); - if (dir.exists() == false) { - dir.mkdirs(); - } - return tmpDir; + static String getRAOutputPath(Case a_case, String mod, long ingestJobId) { + return getAndMakeRAPath(a_case.getModuleDirectory(), mod, ingestJobId); } - + /** * Get relative path for module output folder. * * @throws NoCurrentCaseException if there is no open case. * @return the relative path of the module output folder */ - static String getRelModuleOutputPath() throws NoCurrentCaseException { - return Paths.get(Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath(), - "RecentActivity").normalize().toString() ; //NON-NLS + static String getRelModuleOutputPath(Case autCase, String mod, long ingestJobId) { + return Paths.get(getAndMakeRAPath(autCase.getModuleOutputDirectoryRelativePath(), mod, ingestJobId)) + .normalize() + .toString(); } }