diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index ec9f070e93..d4097eb69f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -365,6 +365,7 @@ public class EamArtifactUtil { case CARVED: case DERIVED: case LOCAL: + case LAYOUT_FILE: return true; case FS: return file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 5cd40a900d..3efcade0f5 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -39,7 +39,7 @@ MediaFileViewer.title=Media MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio) MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory. MediaViewImagePanel.errorLabel.text=Could not load file into Media View. -MediaViewImagePanel.externalViewerButton.text=Open in External Viewer +MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E MediaViewVideoPanel.pauseButton.text=\u25ba MediaViewVideoPanel.progressLabel.text=00:00 MediaViewVideoPanel.infoLabel.text=info diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index b98c350d68..b96e61e462 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -60,7 +60,7 @@ import org.sleuthkit.datamodel.AbstractFile; * Image viewer part of the Media View layered pane. Uses JavaFX to display the * image. */ -@NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer", +@NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E", "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.", "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."}) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index 29e289d67e..35a3007ece 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; /** * A DataResultTopComponent object is a NetBeans top component that provides @@ -100,7 +101,7 @@ public final class DataResultTopComponent extends TopComponent implements DataRe DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); initInstance(description, node, childNodeCount, resultViewTopComponent); return resultViewTopComponent; - } + } /** * Creates a result view top component that provides multiple views of the @@ -147,7 +148,7 @@ public final class DataResultTopComponent extends TopComponent implements DataRe DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), DataContentTopComponent.findInstance()); return resultViewTopComponent; } - + /** * Initializes a partially initialized result view top component. * @@ -248,6 +249,8 @@ public final class DataResultTopComponent extends TopComponent implements DataRe setName(title); getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS + getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS + getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java index 5af1d5c085..69e3787e99 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java @@ -22,6 +22,8 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -31,7 +33,6 @@ import org.openide.nodes.Node; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; @@ -87,6 +88,16 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable dataSources = tskCase.getDataSources(); + + Collections.sort(dataSources, new Comparator() { + @Override + public int compare(DataSource dataS1, DataSource dataS2) { + String dataS1Name = dataS1.getName().toLowerCase(); + String dataS2Name = dataS2.getName().toLowerCase(); + return dataS1Name.compareTo(dataS2Name); + } + }); + List keys = new ArrayList<>(); dataSources.forEach((datasource) -> { keys.add(new DataSourceGrouping(datasource)); @@ -140,4 +151,4 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable 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. @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -82,7 +83,13 @@ public class DataModelActionsFactory { final FileNode fileNode = new FileNode(file); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, fileNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, fileNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, fileNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -90,24 +97,20 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } - + public static List getActions(SlackFile slackFile, boolean isArtifactSource) { List actionsList = new ArrayList<>(); actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), slackFile)); @@ -121,20 +124,18 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -145,7 +146,13 @@ public class DataModelActionsFactory { LayoutFileNode layoutFileNode = new LayoutFileNode(file); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, layoutFileNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, layoutFileNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, layoutFileNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance());// actionsList.add(null); // creates a menu separator @@ -153,20 +160,16 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -177,7 +180,13 @@ public class DataModelActionsFactory { DirectoryNode directoryNode = new DirectoryNode(directory); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, directoryNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -185,20 +194,16 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -209,7 +214,13 @@ public class DataModelActionsFactory { VirtualDirectoryNode directoryNode = new VirtualDirectoryNode(directory); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, directoryNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -217,31 +228,33 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } - + public static List getActions(LocalDirectory directory, boolean isArtifactSource) { List actionsList = new ArrayList<>(); actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), directory)); LocalDirectoryNode directoryNode = new LocalDirectoryNode(directory); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, directoryNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -249,20 +262,16 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -273,7 +282,13 @@ public class DataModelActionsFactory { final LocalFileNode localFileNode = new LocalFileNode(file); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, localFileNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, localFileNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, localFileNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -281,20 +296,16 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -305,7 +316,13 @@ public class DataModelActionsFactory { final LocalFileNode localFileNode = new LocalFileNode(file); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, localFileNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, localFileNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, localFileNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -313,20 +330,16 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } @@ -340,7 +353,6 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - if (isArtifactSource) { final Collection selectedArtifactsList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); @@ -348,19 +360,23 @@ public class DataModelActionsFactory { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } public static List getActions(ContentTag contentTag, boolean isArtifactSource) { - List actionsList = new ArrayList<>(); actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), contentTag.getContent())); final ContentTagNode tagNode = new ContentTagNode(contentTag); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -368,35 +384,35 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.add(DeleteContentTagAction.getInstance()); actionsList.add(ReplaceContentTagAction.getInstance()); - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } - - + public static List getActions(BlackboardArtifactTag artifactTag, boolean isArtifactSource) { List actionsList = new ArrayList<>(); actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), artifactTag.getContent())); final BlackboardArtifactTagNode tagNode = new BlackboardArtifactTagNode(artifactTag); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); - actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator @@ -404,27 +420,22 @@ public class DataModelActionsFactory { if (isArtifactSource) { actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - - final Collection selectedFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } - if(isArtifactSource) { - final Collection selectedArtifactsList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if(selectedArtifactsList.size() == 1) { + if (isArtifactSource) { + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if (selectedArtifactsList.size() == 1) { actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } } - actionsList.add(DeleteBlackboardArtifactTagAction.getInstance()); actionsList.add(ReplaceBlackboardArtifactTagAction.getInstance()); - actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; } - + public static List getActions(Content content, boolean isArtifactSource) { if (content instanceof File) { return getActions((File) content, isArtifactSource); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index d4e0ebf261..5b3f2fa32a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -23,6 +23,7 @@ import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.logging.Level; @@ -123,6 +124,17 @@ public class DataSourcesNode extends DisplayableItemNode { Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); currentKeys = new ArrayList<>(Arrays.asList(content)); } + + Collections.sort(currentKeys, new Comparator() { + @Override + public int compare(Content content1, Content content2) { + String content1Name = content1.getName().toLowerCase(); + String content2Name = content2.getName().toLowerCase(); + return content1Name.compareTo(content2Name); + } + + }); + setKeys(currentKeys); } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS @@ -165,4 +177,4 @@ public class DataSourcesNode extends DisplayableItemNode { NAME)); return sheet; } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index eb853ef80b..4e81d4b428 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -424,6 +424,8 @@ public class DeletedContent implements AutopsyVisitableItem { + " AND type = " + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType() //NON-NLS + " )" + " OR type = " + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.getFileType() //NON-NLS + + " OR (dir_flags = " + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() + + " AND type = " + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.getFileType() + " )" + " )"; //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() //+ " AND type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS.getFileType() diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 13e7ef34a5..1695b253b0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -49,9 +50,9 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; * children. */ public class FileNode extends AbstractFsContentNode { - + private static final Logger logger = Logger.getLogger(FileNode.class.getName()); - + /** * Gets the path to the icon file that should be used to visually represent * an AbstractFile, using the file name extension to select the icon. @@ -148,7 +149,7 @@ public class FileNode extends AbstractFsContentNode { @NbBundle.Messages({ "FileNode.getActions.viewFileInDir.text=View File in Directory", "FileNode.getActions.viewInNewWin.text=View in New Window", - "FileNode.getActions.openInExtViewer.text=Open in External Viewer", + "FileNode.getActions.openInExtViewer.text=Open in External Viewer Ctrl+E", "FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"}) public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); @@ -160,7 +161,14 @@ public class FileNode extends AbstractFsContentNode { } actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this)); - actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction( + Bundle.FileNode_getActions_openInExtViewer_text(), this)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent())); actionsList.add(null); // Creates an item separator @@ -168,12 +176,11 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(null); // Creates an item separator actionsList.add(AddContentTagAction.getInstance()); - final Collection selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); if (1 == selectedFilesList.size()) { actionsList.add(DeleteFileContentTagAction.getInstance()); } actionsList.addAll(ContextMenuExtensionPoint.getActions()); - if (FileTypeExtensions.getArchiveExtensions().contains("." + this.content.getNameExtension().toLowerCase())) { + if (FileTypeExtensions.getArchiveExtensions().contains("." + this.content.getNameExtension().toLowerCase())) { try { if (this.content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0) { actionsList.add(new ExtractArchiveWithPasswordAction(this.getContent())); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index ad4ea9de99..54bb2e7285 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -44,7 +44,6 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; import org.sleuthkit.autopsy.coreutils.Logger; @@ -100,6 +99,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + "))" diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 555d61d77d..bfd8f7bd91 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Map; import javax.swing.Action; import org.openide.util.NbBundle; import org.openide.util.Utilities; @@ -31,6 +30,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.AbstractFile; @@ -64,6 +64,12 @@ public class LayoutFileNode extends AbstractAbstractFileNode { if (lf.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED)) { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); //NON-NLS + } else if (lf.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE)) { + if (lf.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS + } else { + this.setIconBaseWithExtension(FileNode.getIconForFileType(lf)); + } } else { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS } @@ -89,15 +95,19 @@ public class LayoutFileNode extends AbstractAbstractFileNode { actionsList.addAll(Arrays.asList(super.getActions(true))); actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.viewInNewWin.text"), this)); - actionsList.add(new ExternalViewerAction( - NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.openInExtViewer.text"), this)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction( + NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.openInExtViewer.text"), this)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); - final Collection selectedFilesList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 5c83708fec..6d64aeb621 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -61,6 +62,7 @@ public class LocalFileNode extends AbstractAbstractFileNode { } + @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(true))); @@ -69,16 +71,20 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "LocalFileNode.getActions.viewInNewWin.text"), this)); - actionsList.add(new ExternalViewerAction( - NbBundle.getMessage(this.getClass(), "LocalFileNode.getActions.openInExtViewer.text"), this)); + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction( + NbBundle.getMessage(this.getClass(), "LocalFileNode.getActions.openInExtViewer.text"), this)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); - final Collection selectedFilesList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 0bdb1b5d9c..32c9acd3c1 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -52,7 +52,7 @@ ChangeViewAction.menuItem.view.string=String DataResultFilterNode.action.viewFileInDir.text=View File in Directory DataResultFilterNode.action.viewSrcFileInDir.text=View Source File in Directory DataResultFilterNode.action.viewInNewWin.text=View in New Window -DataResultFilterNode.action.openInExtViewer.text=Open in External Viewer +DataResultFilterNode.action.openInExtViewer.text=Open in External Viewer Ctrl+E DataResultFilterNode.action.searchFilesSameMd5.text=Search for files with the same MD5 hash DataResultFilterNode.action.viewInDir.text=View in Directory DirectoryTreeFilterNode.action.collapseAll.text=Collapse All diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 0a0e8a7be2..1fe95f35b6 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -5,12 +5,16 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contai DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source? DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module. DirectoryTreeTopComponent.resultsView.title=Listing +ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened. ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch. ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists. ExternalViewerAction.actionPerformed.failure.open.url=Cannot open URL ExternalViewerAction.actionPerformed.failure.permission.message=Permission to open the file was denied. ExternalViewerAction.actionPerformed.failure.support.message=This platform (operating system) does not support opening a file in an editor this way. -ExternalViewerAction.actionPerformed.failure.title=Open File Failure +# {0} - file name +ExternalViewerAction.actionPerformed.failure.title=Open File Failure {0} +ExternalViewerAction.actionPerformed.urlFailure.title=Open URL Failure +ExternalViewerShortcutAction.title.text=Open in External Viewer Ctrl+E ExtractAction.noOpenCase.errMsg=No open case available. ExtractUnallocAction.imageError=Error extracting unallocated space from image ExtractUnallocAction.noFiles=No unallocated files found on volume @@ -80,7 +84,7 @@ ChangeViewAction.menuItem.view.string=String DataResultFilterNode.action.viewFileInDir.text=View File in Directory DataResultFilterNode.action.viewSrcFileInDir.text=View Source File in Directory DataResultFilterNode.action.viewInNewWin.text=View in New Window -DataResultFilterNode.action.openInExtViewer.text=Open in External Viewer +DataResultFilterNode.action.openInExtViewer.text=Open in External Viewer Ctrl+E DataResultFilterNode.action.searchFilesSameMd5.text=Search for files with the same MD5 hash DataResultFilterNode.action.viewInDir.text=View in Directory DirectoryTreeFilterNode.action.collapseAll.text=Collapse All diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index e097f75a92..c375c7f02b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -432,19 +432,23 @@ public class DataResultFilterNode extends FilterNode { actionsList.addAll(DataModelActionsFactory.getActions(c, false)); } if (n != null) { + final Collection selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInNewWin.text"), n)); - actionsList.add(new ExternalViewerAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.openInExtViewer.text"), n)); + if (selectedFilesList.size() == 1) { + actionsList.add(new ExternalViewerAction( + NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.openInExtViewer.text"), n)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); actionsList.add(AddBlackboardArtifactTagAction.getInstance()); - final Collection selectedFilesList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); if (selectedFilesList.size() == 1) { actionsList.add(DeleteFileContentTagAction.getInstance()); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java index 3451b073a3..cc50b2cd2a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2018 Basis Technology Corp. + * + * Copyright 2011-2019 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. @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.SlackFileNode; +import org.sleuthkit.datamodel.AbstractFile; /** * Extracts a File object to a temporary file in the case directory, and then @@ -44,24 +45,20 @@ import org.sleuthkit.autopsy.datamodel.SlackFileNode; public class ExternalViewerAction extends AbstractAction { private final static Logger logger = Logger.getLogger(ExternalViewerAction.class.getName()); - private final org.sleuthkit.datamodel.AbstractFile fileObject; + private final AbstractFile fileObject; private String fileObjectExt; final static String[] EXECUTABLE_EXT = {".exe", ".dll", ".com", ".bat", ".msi", ".reg", ".scr", ".cmd"}; //NON-NLS + private boolean isExecutable; - /** - * - * @param title Name of the action - * @param fileNode File to display - */ - public ExternalViewerAction(String title, Node fileNode) { + ExternalViewerAction(String title, AbstractFile file, boolean isSlackFile) { super(title); - this.fileObject = fileNode.getLookup().lookup(org.sleuthkit.datamodel.AbstractFile.class); + this.fileObject = file; long size = fileObject.getSize(); String fileName = fileObject.getName(); int extPos = fileName.lastIndexOf('.'); - boolean isExecutable = false; + isExecutable = false; if (extPos != -1) { String extension = fileName.substring(extPos, fileName.length()).toLowerCase(); fileObjectExt = extension; @@ -79,13 +76,35 @@ public class ExternalViewerAction extends AbstractAction { // find an application for files without an extension // or if file is executable (for security reasons) // Also skip slack files since their extension is the original extension + "-slack" - if (!(size > 0) || extPos == -1 || isExecutable || (fileNode instanceof SlackFileNode)) { + if (!(size > 0) || extPos == -1 || isExecutable || isSlackFile) { this.setEnabled(false); } } + /** + * + * @param title Name of the action + * @param fileNode File to display + */ + public ExternalViewerAction(String title, Node fileNode) { + this(title, fileNode.getLookup().lookup(org.sleuthkit.datamodel.AbstractFile.class), fileNode instanceof SlackFileNode); + } + @Override + @Messages({ + "# {0} - file name", + "ExternalViewerAction.actionPerformed.failure.title=Open File Failure {0}", + "ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened." + }) public void actionPerformed(ActionEvent e) { + if (isExecutable) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.ExternalViewerAction_actionPerformed_failure_exe_message(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(this.fileObject.getName()), + JOptionPane.ERROR_MESSAGE); + return; + } + // Get the temp folder path of the case Case openCase; try { @@ -124,7 +143,6 @@ public class ExternalViewerAction extends AbstractAction { * @param file the file object */ @Messages({ - "ExternalViewerAction.actionPerformed.failure.title=Open File Failure", "ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch.", "ExternalViewerAction.actionPerformed.failure.support.message=This platform (operating system) does not support opening a file in an editor this way.", "ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists.", @@ -146,14 +164,14 @@ public class ExternalViewerAction extends AbstractAction { runtime.exec(execArray); } catch (IOException ex) { logger.log(Level.WARNING, "Could not open the specified viewer for the given file: " + file.getName(), ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_IO_message(), Bundle.ExternalViewerAction_actionPerformed_failure_title(), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_IO_message(), Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), JOptionPane.ERROR_MESSAGE); } } else { try { String localpath = file.getPath(); if (localpath.toLowerCase().contains("http")) { - String url_path = file.getPath().replaceAll("\\\\","/"); - Desktop.getDesktop().browse(new URI(url_path.replaceFirst("/","//"))); + String url_path = file.getPath().replaceAll("\\\\", "/"); + Desktop.getDesktop().browse(new URI(url_path.replaceFirst("/", "//"))); } else { Desktop.getDesktop().open(file); } @@ -162,75 +180,77 @@ public class ExternalViewerAction extends AbstractAction { logger.log(Level.WARNING, "Could not find a viewer for the given file: " + file.getName(), ex); //NON-NLS JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_IO_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), JOptionPane.ERROR_MESSAGE); } catch (UnsupportedOperationException ex) { logger.log(Level.WARNING, "Platform cannot open " + file.getName() + " in the defined editor.", ex); //NON-NLS JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_support_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), JOptionPane.ERROR_MESSAGE); } catch (IllegalArgumentException ex) { logger.log(Level.WARNING, "Could not find the given file: " + file.getName(), ex); //NON-NLS JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_missingFile_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), JOptionPane.ERROR_MESSAGE); } catch (SecurityException ex) { logger.log(Level.WARNING, "Could not get permission to open the given file: " + file.getName(), ex); //NON-NLS JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.ExternalViewerAction_actionPerformed_failure_permission_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), JOptionPane.ERROR_MESSAGE); } catch (URISyntaxException ex) { - logger.log(Level.WARNING, "Could not open URL provided: " + file.getPath(), ex); - JOptionPane.showMessageDialog(null, - Bundle.ExternalViewerAction_actionPerformed_failure_open_url(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), - JOptionPane.ERROR_MESSAGE); + logger.log(Level.WARNING, "Could not open URL provided: " + file.getPath(), ex); + JOptionPane.showMessageDialog(null, + Bundle.ExternalViewerAction_actionPerformed_failure_open_url(), + Bundle.ExternalViewerAction_actionPerformed_failure_title(file.getName()), + JOptionPane.ERROR_MESSAGE); } } } /** * Opens a URL using the default desktop browser - * - * @param path URL to open + * + * @param path URL to open */ + @Messages({ + "ExternalViewerAction.actionPerformed.urlFailure.title=Open URL Failure"}) public static void openURL(String path) { - String url_path = path.replaceAll("\\\\","/"); + String url_path = path.replaceAll("\\\\", "/"); try { - Desktop.getDesktop().browse(new URI(url_path.replaceFirst("/","//"))); + Desktop.getDesktop().browse(new URI(url_path.replaceFirst("/", "//"))); } catch (IOException ex) { logger.log(Level.WARNING, "Could not find a viewer for the given URL: " + url_path, ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_IO_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_urlFailure_title(), JOptionPane.ERROR_MESSAGE); } catch (UnsupportedOperationException ex) { logger.log(Level.WARNING, "Platform cannot open " + url_path + " in the defined editor.", ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_support_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_urlFailure_title(), JOptionPane.ERROR_MESSAGE); } catch (IllegalArgumentException ex) { logger.log(Level.WARNING, "Could not find the given URL: " + url_path, ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_missingFile_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_urlFailure_title(), JOptionPane.ERROR_MESSAGE); } catch (SecurityException ex) { logger.log(Level.WARNING, "Could not get permission to open the given URL: " + url_path, ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_permission_message(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), + Bundle.ExternalViewerAction_actionPerformed_urlFailure_title(), JOptionPane.ERROR_MESSAGE); } catch (URISyntaxException ex) { - logger.log(Level.WARNING, "Could not open URL provided: " + url_path, ex); - JOptionPane.showMessageDialog(null, - Bundle.ExternalViewerAction_actionPerformed_failure_open_url(), - Bundle.ExternalViewerAction_actionPerformed_failure_title(), - JOptionPane.ERROR_MESSAGE); + logger.log(Level.WARNING, "Could not open URL provided: " + url_path, ex); + JOptionPane.showMessageDialog(null, + Bundle.ExternalViewerAction_actionPerformed_failure_open_url(), + Bundle.ExternalViewerAction_actionPerformed_urlFailure_title(), + JOptionPane.ERROR_MESSAGE); } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerShortcutAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerShortcutAction.java new file mode 100644 index 0000000000..3066db5679 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerShortcutAction.java @@ -0,0 +1,68 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.directorytree; + +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.Collection; +import java.util.HashSet; +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; +import org.openide.util.NbBundle.Messages; +import org.openide.util.Utilities; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Extracts a File object to a temporary file in the case directory, and then + * tries to open it in the user's system with the default or user specified + * associated application. + */ +@Messages({"ExternalViewerShortcutAction.title.text=Open in External Viewer Ctrl+E"}) +public class ExternalViewerShortcutAction extends AbstractAction { + + public static final KeyStroke EXTERNAL_VIEWER_SHORTCUT = KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_MASK); + + private ExternalViewerShortcutAction() { + super(Bundle.ExternalViewerShortcutAction_title_text()); + } + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ExternalViewerShortcutAction instance; + + public static synchronized ExternalViewerShortcutAction getInstance() { + if (null == instance) { + instance = new ExternalViewerShortcutAction(); + } + return instance; + } + + @Override + public void actionPerformed(ActionEvent e) { + final Collection selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if (!selectedFiles.isEmpty()) { + for (AbstractFile file : selectedFiles) { + ExternalViewerAction action = new ExternalViewerAction(Bundle.ExternalViewerShortcutAction_title_text(), file, false); + action.actionPerformed(e); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index 2f6c6ed559..8ff938e60b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,6 +65,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; import org.sleuthkit.autopsy.timeline.explorernodes.EventNode; @@ -103,7 +104,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private final TimeLineController controller; - /** Lookup that will be exposed through the (Global Actions Context) */ + /** + * Lookup that will be exposed through the (Global Actions Context) + */ private final ModifiableProxyLookup proxyLookup = new ModifiableProxyLookup(); private final PropertyChangeListener focusPropertyListener = new PropertyChangeListener() { @@ -261,7 +264,8 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS - + getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS + getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS this.controller = controller; //create linked result and content views diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index d5e4440ee6..f7e8b9ba92 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Modality; import javax.annotation.concurrent.GuardedBy; +import javax.swing.JComponent; import javax.swing.SwingUtilities; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.lang3.ObjectUtils.notEqual; @@ -64,6 +65,7 @@ import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.gui.DataSourceCell; @@ -238,6 +240,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl public ImageGalleryTopComponent() { setName(Bundle.CTL_ImageGalleryTopComponent()); initComponents(); + getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS + getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java index a698c22191..e5b8749bf0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenExternalViewerAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,9 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.awt.event.ActionEvent; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; import javax.swing.SwingUtilities; import org.controlsfx.control.action.Action; import org.openide.util.NbBundle; @@ -33,10 +36,11 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; * appropriate text and graphic */ @NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer", - "OpenExternalViewerAction.displayName=External Viewer"}) + "OpenExternalViewerAction.displayName=External Viewer"}) public class OpenExternalViewerAction extends Action { private static final Image EXTERNAL = new Image(OpenExternalViewerAction.class.getResource("/org/sleuthkit/autopsy/imagegallery/images/external.png").toExternalForm()); //NON-NLS + public static final KeyCombination EXTERNAL_VIEWER_SHORTCUT = new KeyCodeCombination(KeyCode.E, KeyCombination.CONTROL_DOWN); private static final ActionEvent ACTION_EVENT = new ActionEvent(OpenExternalViewerAction.class, ActionEvent.ACTION_PERFORMED, ""); //Swing ActionEvent //NOI18N public OpenExternalViewerAction(DrawableFile file) { @@ -49,9 +53,12 @@ public class OpenExternalViewerAction extends Action { ExternalViewerAction externalViewerAction = new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file.getAbstractFile())); setLongText(Bundle.MediaViewImagePanel_externalViewerButton_text()); - setEventHandler(actionEvent -> //fx ActionEvent + setEventHandler(actionEvent + -> //fx ActionEvent SwingUtilities.invokeLater(() -> externalViewerAction.actionPerformed(ACTION_EVENT)) ); setGraphic(new ImageView(EXTERNAL)); + setAccelerator(EXTERNAL_VIEWER_SHORTCUT); } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index e6b9479f50..79cbff1a76 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,6 @@ import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javax.swing.Action; import javax.swing.SwingUtilities; -import org.controlsfx.control.action.ActionUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.Utilities; @@ -60,6 +59,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; @@ -214,11 +214,13 @@ public abstract class DrawableTileBase extends DrawableUIBase { .actionPerformed(null); })); menuItems.add(contentViewer); - - OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file); - MenuItem externalViewer = ActionUtils.createMenuItem(openExternalViewerAction); - externalViewer.textProperty().unbind(); - externalViewer.textProperty().bind(openExternalViewerAction.longTextProperty()); + MenuItem externalViewer = new MenuItem("Open in External Viewer"); + externalViewer.setOnAction(actionEvent + -> SwingUtilities.invokeLater(() -> { + ExternalViewerShortcutAction.getInstance() + .actionPerformed(null); + })); + externalViewer.setAccelerator(OpenExternalViewerAction.EXTERNAL_VIEWER_SHORTCUT); menuItems.add(externalViewer); Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); @@ -346,7 +348,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS final ContentTag addedTag = evt.getAddedTag(); if (fileID == addedTag.getContent().getId() - && addedTag.getName().equals(followUpTagName)) { + && addedTag.getName().equals(followUpTagName)) { Platform.runLater(() -> { followUpImageView.setImage(followUpIcon); followUpToggle.setSelected(true); @@ -362,7 +364,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); if (fileID == deletedTagInfo.getContentID() - && deletedTagInfo.getName().equals(followUpTagName)) { + && deletedTagInfo.getName().equals(followUpTagName)) { updateFollowUpIcon(); } }); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 67e6496584..715ec31dac 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -1,4 +1,5 @@ OpenIDE-Module-Display-Category=Ingest Module + OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search @@ -87,7 +88,7 @@ KeywordSearchEditListPanel.exportButtonActionPerformed.kwListExportedMsg=Keyword KeywordSearchEditListPanel.kwColName=Keyword KeywordSearchEditListPanel.addKeyword.message=Add a new word to the keyword search list: KeywordSearchEditListPanel.addKeyword.title=New Keyword -KeywordSearchFilterNode.getFileActions.openExternViewActLbl=Open in External Viewer +KeywordSearchFilterNode.getFileActions.openExternViewActLbl=Open in External Viewer Ctrl+E KeywordSearchFilterNode.getFileActions.searchSameMd5=Search for files with the same MD5 hash KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index 034e4355d3..f64794953f 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -122,7 +122,7 @@ KeywordSearchEditListPanel.exportButtonActionPerformed.kwListExportedMsg=Keyword KeywordSearchEditListPanel.kwColName=Keyword KeywordSearchEditListPanel.addKeyword.message=Add a new word to the keyword search list: KeywordSearchEditListPanel.addKeyword.title=New Keyword -KeywordSearchFilterNode.getFileActions.openExternViewActLbl=Open in External Viewer +KeywordSearchFilterNode.getFileActions.openExternViewActLbl=Open in External Viewer Ctrl+E KeywordSearchFilterNode.getFileActions.searchSameMd5=Search for files with the same MD5 hash KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list. diff --git a/ManifestTool/ManifestGenerationAlgorithms.au3 b/ManifestTool/ManifestGenerationAlgorithms.au3 new file mode 100644 index 0000000000..8cab9ec663 --- /dev/null +++ b/ManifestTool/ManifestGenerationAlgorithms.au3 @@ -0,0 +1,287 @@ +;============================================================================== +; Autopsy Forensic Browser +; +; Copyright 2019 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. +;============================================================================== +#include +#include +#include +#include +#include +#include +#include +#include + +;Get the list of names of algorithms +Global $algorithms[3] ;increase size of array when adding new algorithms +$algorithms[0] = "Single Data Source" +$algorithms[1] = "Folder of Logical Files" +$algorithms[2] = "One Data Source Per Folder" + +; $algorithms[2] = "All Files In One Folder" +Global $progressArea = Null +Global $manifestFileNameEnd = "Manifest" +Global $manifestExtension = ".xml" + + +;Return an array containing the names of all algorithms +Func GetAlgorithmNames() + Return $algorithms +EndFunc + +;Return the name of the first algorithm as a default algorithm +Func GetDefaultAlgorithmName() + Return $algorithms[0] +EndFunc + +;Run the function that corresponds to the specified Algorithm name +;Use Null for $progressArea if not called from a GUI with a $progressArea +Func RunAlgorithm($selectedAlgorithm, $settings, ByRef $progressAreaRef) + $progressArea = $progressAreaRef + UpdateProgressArea("Analyzing: " & $settings[0]) + if ($selectedAlgorithm == $algorithms[2]) Then + OneDataSourcePerFolder($settings) + ElseIf ($selectedAlgorithm == $algorithms[0]) Then + SingleDataSource($settings) + ElseIf ($selectedAlgorithm == $algorithms[1]) Then + SingleDataSource($settings) +; ElseIf ($selectedAlgorithm == $algorithms[2]) Then +; AllFilesInOneFolder($settings) + EndIf + UpdateProgressArea("-------------------------------------------------------------------------------------------") ;blank line for some +EndFunc + +;Create a manifest file in the specified $caseDir named $manifestDir _Manifest.xml +;if the $manifestFile is specified the datasource included will be the file instead of the entire folder +Func GenerateCaseNameAndWriteManifestFile($caseDir, $subDirName, $manifestFile) + Local $manifestName = "" + Local $caseName = "" + Local $dataSourcePath = "" + ;If the manifestDirectory is not Null use it for the file name + if ($subDirName <> Null) Then + $manifestName = $subDirName + $dataSourcePath = $manifestName + if ($manifestFile <> Null) Then + $dataSourcePath = $dataSourcePath & "\" & $manifestFile + EndIf + ;If the manifestDirectory was Null then use the file name + ElseIf ($manifestFile <> Null) Then + $manifestName = $manifestFile + $dataSourcePath = $manifestName + Else + UpdateProgressArea("ERROR: Invalid arguements provided, unable to create manifest file") + Return + EndIf + + Local $splitCaseDir = StringSplit($caseDir, "\", $STR_ENTIRESPLIT) + $caseName = $splitCaseDir[$splitCaseDir[0]] + + Local $manfiestFilePath = $caseDir & "\" & $manifestName & "_" & $manifestFileNameEnd & $manifestExtension + WriteManifestFile($manfiestFilePath, $manifestName, $caseName, $dataSourcePath) +EndFunc + +;Write the specified manifest file. +Func WriteManifestFile($manifestFilePath, $manifestName, $caseName, $dataSourcePath) + _FileCreate($manifestFilePath) + Local $fileHandle = FileOpen($manifestFilePath, $FO_APPEND) + If $fileHandle == -1 Then + UpdateProgressArea("ERROR: " & $manifestName & " Unable to create manifest file") + Return + EndIf + FileWrite($fileHandle,'' & @CRLF) + FileWrite($fileHandle,'' & @CRLF) + FileWrite($fileHandle,'' & $caseName &'' & @CRLF) + ;Device ID is not a required field + FileWrite($fileHandle,'' & $dataSourcePath & '' & @CRLF) + FileWrite($fileHandle,'' & @CRLF) + FileClose($fileHandle) + UpdateProgressArea($manifestName & " manifest created") +EndFunc + +;get the extension of a file +Func GetFileExtension($fileName) + Local $fileExtension + _PathSplit ($fileName, "", "", "", $fileExtension) + Return $fileExtension +EndFunc + +;Return 0 for false if no manifest files exist in the caseDir, or 1 for true if manifest files do exist +Func ManifestFilesAlreadyExist($fileList) + Local $fileName + Local $fileExtension + For $i = 1 To $fileList[0] Step 1 + _PathSplit ($fileList[$i], "", "", $fileName, $fileExtension) + If StringCompare($fileExtension, $manifestExtension, $STR_NOCASESENSE) == 0 Then + Local $splitFileName = StringSplit($fileName, "_", $STR_ENTIRESPLIT) + if $splitFileName[0] > 1 Then ;It split into more than one chunk so the last chunk should match our _Manifest + If StringCompare($splitFileName[$splitFileName[0]], $manifestFileNameEnd, $STR_NOCASESENSE) == 0 Then + UpdateProgressArea("Folder already contains manifest file: " & $fileList[$i]) + Return 1 + EndIf + EndIf + EndIf + Next + Return 0 +EndFunc + +;Check if a manifest file already exists for a specific datasource in the case Dir +;Return 1 if a manifest exists +;Return 0 if no manifest exists +Func ManifestAlreadyExists($manifestFilePath) + If FileExists($manifestFilePath) == 1 Then + Return 1 + Else + Return 0 + EndIf +EndFunc + + +;Algorithm for the "One Data Source Per Folder" +;Creates manifest files +Func OneDataSourcePerFolder($settings) + Local $validDirectory = 1 + Local $caseDir = $settings[0] + ;_FileListToArray returns the count of files/folders as the first value then the contents + Local $fileList = _FileListToArray($caseDir, Default, $FLTA_FILES, False) + Local $caseDirSplit = StringSplit($caseDir, "\", $STR_ENTIRESPLIT) + Local $caseDirName + if ($caseDirSplit[0] > 1) Then + ;if case folder is longer than one directory display just the directory name in progress messages + $caseDirName = $caseDirSplit[$caseDirSplit[0]] + Else + ;if there is only one directory use the entire case dir path + EndIf + If (@error == 1) Then + $validDirectory = 0 + UpdateProgressArea("ERROR: " & $caseDirName & " not found") + MsgBox($MB_OK, "Directory Not Found", "Selected directory " & $caseDirName & " was not found.") + ElseIf (@error > 0) Then + ;An acceptable condition as no files means no manifest files + EndIf + + Local $dirList = _FileListToArray($caseDir, Default, $FLTA_FOLDERS, True) + If (@error ==4) Then + UpdateProgressArea($caseDirName & " no folders found") + MsgBox($MB_OK, "Selected Directory Empty", "Selected directory " & $caseDirName & " did not contain any subfolders to use as data sources for manifest files.") + $validDirectory = 0 + EndIf + + If $validDirectory = 1 Then + Local $validExtensions[4] = [".e01", ".l01", ".001", ".ad1"] ;valid extensions for the One Data Source Per Folder algorithm + Local $subDirectoryFileList + Local $validSubDirectory + For $fileNumber = 1 TO $dirList[0] Step 1 + Local $manifestFile = Null + Local $manifestDir = $dirList[$fileNumber] + Local $splitManifestDir = StringSplit($manifestDir, "\", $STR_ENTIRESPLIT) + Local $manifestDirName = $splitManifestDir[$splitManifestDir[0]] + $subDirectoryFileList = _FileListToArray($dirList[$fileNumber], Default, Default, False) + $validSubDirectory = 1 + If (@error == 1) Then + $validSubDirectory = 0 + UpdateProgressArea("ERROR: " & $dirList[$fileNumber] & " not found") + ElseIf (@error ==4) Then + UpdateProgressArea($manifestDirName & " empty, no manifest created") + $validSubDirectory = 0 + EndIf + If $validSubDirectory == 1 Then + For $i = 1 TO $subDirectoryFileList[0] Step 1 + Local $currentFilesExtension = GetFileExtension($subDirectoryFileList[$i]) + For $extension IN $validExtensions + ;should only be one file or directory in this folder since we checked the number of contents previously + If StringCompare($extension, $currentFilesExtension, $STR_NOCASESENSE) == 0 Then + $manifestFile = $subDirectoryFileList[$i] + ExitLoop 2 ;match was found no reason to check remaining extensions or files in a One Data Source Per Folder algorithm + EndIf + Next + Next + Local $manifestFilePath = $caseDir & "\" & $manifestDirName & "_" & $manifestFileNameEnd & $manifestExtension + If (ManifestAlreadyExists($manifestFilePath) <> 1) Then + ;should only be one file and it should end with a valid extension add as image file, or the whole directory is added as a logical file set + GenerateCaseNameAndWriteManifestFile($caseDir, $manifestDirName, $manifestFile) + Else + UpdateProgressArea($manifestDirName & " manifest exists, skipping") + EndIf + EndIf + Next + UpdateProgressArea($caseDirName & " manifest generation complete") + EndIf +EndFunc + +;Create a manifest file for a single data source in the same directory that contains the data source (also used for Folder of Logical Files) +Func SingleDataSource($settings) + Local $dataSourcePath = $settings[0] + Local $caseDir = "" + Local $caseDrive = "" + Local $dsName = "" + Local $dsExtension = "" + _PathSplit ($dataSourcePath, $caseDrive, $caseDir, $dsName, $dsExtension) + $caseDir = $caseDrive & $caseDir + Local $caseName = $settings[1] + Local $manfiestFilePath = $caseDir & "\" & $dsName & "_" & $manifestFileNameEnd & $manifestExtension + If (ManifestAlreadyExists($manfiestFilePath) <> 1) Then + ;should only be one file and it should end with a valid extension add as image file, or the whole directory is added as a logical file set + WriteManifestFile($manfiestFilePath, $dsName, $caseName, $dsName & $dsExtension) + Else + UpdateProgressArea($dsName & " manifest exists, skipping") + EndIf + +EndFunc + +;Algorithm for the All Files in One Folder +;Creates manifest files for all files and directories in a single directory +Func AllFilesInOneFolder($settings) + Local $validDirectory = 1 + Local $caseDir = $settings[0] + ;_FileListToArray returns the count of files/folders as the first value then the contents + Local $fileList = _FileListToArray($caseDir, Default, $FLTA_FILES, False) + If (@error == 1) Then + $validDirectory = 0 + UpdateProgressArea("Selected directory " & $caseDir & " was not found") + MsgBox($MB_OK, "Directory Not Found", "Selected directory " & $caseDir & " was not found") + ElseIf (@error > 0) Then + Local $dirList = _FileListToArray($caseDir, Default, $FLTA_FOLDERS, True) + If (@error ==4) Then + UpdateProgressArea("Selected directory " & $caseDir & " was empty and contained nothing to generate manifest files for") + MsgBox($MB_OK, "Selected Directory Empty", "Selected directory " & $caseDir & " was empty and contained nothing to generate manifest files for") + $validDirectory = 0 + EndIf + ;An acceptable condition as no files means no manifest files + ElseIf ManifestFilesAlreadyExist($fileList) == 1 Then + UpdateProgressArea("Selected directory " & $caseDir & " already contains manifest files, they must be deleted before generating new ones") + MsgBox($MB_OK, "Manifest Files Exist", "Selected directory " & $caseDir & " already contains manifest files, they must be deleted before generating new ones") + $validDirectory = 0 + EndIf + Local $contentsList = _FileListToArray ($caseDir, Default, Default, False) + If $validDirectory = 1 Then + For $fileNumber = 1 TO $contentsList[0] Step 1 + Local $manifestDir = Null + Local $manifestFile = $contentsList[$fileNumber] + GenerateCaseNameAndWriteManifestFile($caseDir, $manifestDir, $manifestFile) + Next + UpdateProgressArea($caseDir & " manifest generation complete") + EndIf +EndFunc + +;If the progress area is Null it will not be updated +Func UpdateProgressArea($textToAdd) + if ($progressArea <> Null) Then + Local $currentProgressAreaText = GUICtrlRead($progressArea) + $currentProgressAreaText = $currentProgressAreaText & @CRLF & "--" & $textToAdd + GUICtrlSetData($progressArea, $currentProgressAreaText) + _GUICtrlEdit_Scroll($progressArea, $SB_SCROLLCARET) + EndIf +EndFunc \ No newline at end of file diff --git a/ManifestTool/ManifestTool.au3 b/ManifestTool/ManifestTool.au3 new file mode 100644 index 0000000000..015a2d8b58 --- /dev/null +++ b/ManifestTool/ManifestTool.au3 @@ -0,0 +1,316 @@ +;============================================================================== +; Autopsy Forensic Browser +; +; Copyright 2019 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. +;============================================================================== +#include +#include +#include +#include +#include +#include + + +Opt("GUIOnEventMode", 1) ; Change to OnEvent mode +;============================================== +; +;Draw GUI and declare variables +; +;============================================== +local $windowHeight = 500 +local $windowWidth = 400 +local $windowTitle = "Autopsy AutoIngest Manifest File Generator" +Global $hMainGUI = GUICreate($windowTitle, $windowWidth, $windowHeight) ;To make GUI resize add following args -1, -1, $WS_OVERLAPPEDWINDOW) +;GUICtrlSetResizing ($hMainGUI, $GUI_DOCKBORDERS) +GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEButton") + +Global $propertiesFile = "ManifestTool.settings" +Global $workingDir = @WorkingDir + +local $topMargin = 12 +local $leftMargin = 12 +local $labelOffset = 1 +local $buttonOffset = -3 +local $progressAreaInset = 8 +local $distanceFromTop = $topMargin +local $distanceFromLeft = $leftMargin +Global $defaultDirectory = @MyDocumentsDir & "\" +local $labelWidth = 58 +local $fieldWidth = 235 +local $buttonWidth = 60 +local $fieldHeight = 20 +local $progressAreaWidth = $windowWidth - 2*($progressAreaInset+$leftMargin) +local $gapBetweenWidth = 10 +local $gapBetweenHeight = 10 + +;Draw the GUI Code +GUICtrlCreateLabel("Algorithm", $distanceFromLeft, $distanceFromTop+$labelOffset) +$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth + +Global $algorithmComboBox = GUICtrlCreateCombo(GetDefaultAlgorithmName(), $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight, $CBS_DROPDOWNLIST) +GUICtrlSetOnEvent($algorithmComboBox, "Redraw") +Global $allAlgorithmNames = GetAlgorithmNames() +for $algorithmName IN $allAlgorithmNames +; Add additional items to the combobox. + GUICtrlSetData($algorithmComboBox, $algorithmName) +Next + + +$distanceFromLeft = $leftMargin +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight + +Global $rootFolderLabel = GUICtrlCreateLabel("Root Folder", $distanceFromLeft, $distanceFromTop+$labelOffset) +$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth +Global $rootFolderField = GUICtrlCreateInput("", $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight) +$distanceFromLeft = $distanceFromLeft +$fieldWidth+$gapBetweenWidth +Global $browseButton = GUICtrlCreateButton("Browse", $distanceFromLeft, $distanceFromTop+$buttonOffset, $buttonWidth) +$distanceFromLeft = $leftMargin +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight + +Global $caseNameLabel = GUICtrlCreateLabel("Case Name", $distanceFromLeft, $distanceFromTop+$labelOffset) +$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth +Global $caseNameField = GUICtrlCreateInput("", $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight) +$distanceFromLeft = $leftMargin +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight + +$distanceFromTop = $distanceFromTop + $gapBetweenHeight ;add an extra gap before run button +Global $runButton = GUICtrlCreateButton("Run", $distanceFromLeft, $distanceFromTop+$buttonOffset, $buttonWidth) +GUICtrlSetOnEvent($runButton, "AlgorithmRunAction") +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight + +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight ;add extra gap before progress area +local $ProgressLabel = GUICtrlCreateLabel("Progress", $distanceFromLeft, $distanceFromTop+$labelOffset) +$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight + +$distanceFromLeft = $distanceFromLeft + $progressAreaInset +$progressAreaHeight = $windowHeight -$distanceFromTop - $gapBetweenHeight - $gapBetweenHeight - $fieldHeight ;calculate height of progress area to use remaining space minus space for exit button +Global $progressField = GUICtrlCreateEdit("", $distanceFromLeft, $distanceFromTop, $progressAreaWidth, $progressAreaHeight, BitOr($ES_READONLY,$WS_VSCROLL, $ES_MULTILINE)) + +$distanceFromLeft = $distanceFromLeft + $progressAreaWidth - $buttonWidth +$distanceFromTop = $distanceFromTop + $progressAreaHeight + $gapBetweenHeight +Local $exitButton = GUICtrlCreateButton("Exit", $distanceFromLeft, $distanceFromTop+$buttonOffset, $buttonWidth) +GUICtrlSetOnEvent($exitButton, "CLOSEButton") + + +GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEButton") +GUISwitch($hMainGUI) +GUISetState(@SW_SHOW) +ChangeToDefaultGUI() + +ReadPropertiesFile() + +Local $oldCaseName = GUICtrlRead($caseNameField) +local $oldRootFolder = GUICtrlRead($rootFolderField) +While 1 + Sleep(100) ; Sleep to reduce CPU usage + ValidateFields($oldCaseName, $oldRootFolder) ;validate here so that we check the current value of any input areas without requiring a change in focus + $oldCaseName = GUICtrlRead($caseNameField) + $oldRootFolder = GUICtrlRead($rootFolderField) +WEnd + + +;============================================== +; +;Functions +; +;============================================== + +; Read the saved properties file, if none exist make one with the current settings +Func ReadPropertiesFile() +If FileExists($propertiesFile) <> 1 Then + FileChangeDir($workingDir) + _FileCreate($propertiesFile) + WritePropertiesFile() +Endif + Local $propertiesFileHandle = FileOpen($propertiesFile, $FO_READ) + Local $savedSelection = FileReadLine($propertiesFileHandle, 1) + Local $indexOfSelection = _ArraySearch($allAlgorithmNames, $savedSelection) + if ($indexOfSelection >= 0) Then + GUICtrlSetData($algorithmComboBox, $savedSelection, $savedSelection) + EndIf + Local $savedDirectory = FileReadLine($propertiesFileHandle, 2) + if (FileExists($savedDirectory)) Then + $defaultDirectory = $savedDirectory + EndIf + FileClose($propertiesFileHandle) + Redraw() +EndFunc + +; Write the current settings to the properties file +Func WritePropertiesFile() + FileChangeDir($workingDir) + Local $propertiesFileHandle = FileOpen($propertiesFile, $FO_OVERWRITE) + If $propertiesFileHandle == -1 Then ;can't access the properties file so exit + Return + EndIf + FileWrite($propertiesFileHandle, GUICtrlRead($algorithmComboBox) & @CRLF) + FileWrite($propertiesFileHandle, $defaultDirectory & @CRLF) + FileClose($propertiesFileHandle) +EndFunc + + + +;Make only the settings and labels relevent to the selected Algorithm visible using $GUI_SHOW and $GUI_HIDE +Func Redraw() + ; Note: At this point @GUI_CtrlId would equal algorithmComboBox + Local $selectedAlgName = GUICtrlRead($algorithmComboBox) + ;Move controls based on what is hidden or shown using ControlGetPos() and GUICtrlSetPos() + If $selectedAlgName == $allAlgorithmNames[2] Then ;"One Data Source Per Folder" + ChangeToDefaultGUI() + ElseIf $selectedAlgName == $allAlgorithmNames[0] Then ;"Single Data Source" + ChangeToSingleDataSourceGUI() + ElseIf $selectedAlgName == $allAlgorithmNames[1] Then ;"Folder of Logical Files" + ChangeToFolderOfLogicalFilesGUI() + EndIf +EndFunc ;==>AlgorithmComboBox + +;Change the controls displayed in the GUI to the ones needed for the Single Data Source algorithm +Func ChangeToSingleDataSourceGUI() + ClearFields() + GUICtrlSetData($rootFolderLabel, "Data Source") + GUICtrlSetState($caseNameField, $GUI_SHOW) + GUICtrlSetState($caseNameLabel, $GUI_SHOW) + GUICtrlSetOnEvent($browseButton, "BrowseForDataSourceFile") + GUICtrlSetState($runButton, $GUI_DISABLE) +EndFunc + +;Change the controls displayed in the GUI to the ones needed for the Folder of Logical Files algorithm +Func ChangeToFolderOfLogicalFilesGUI() + ClearFields() + GUICtrlSetData($rootFolderLabel, "Data Source") + GUICtrlSetData($rootFolderLabel, "Data Source") + GUICtrlSetState($caseNameField, $GUI_SHOW) + GUICtrlSetState($caseNameLabel, $GUI_SHOW) + GUICtrlSetOnEvent($browseButton, "Browse") + GUICtrlSetState($runButton, $GUI_DISABLE) +EndFunc + +;Change the controls displayed in the GUI to the ones needed for One +Func ChangeToDefaultGUI() + ClearFields() + GUICtrlSetData($rootFolderLabel, "Root Folder") + GUICtrlSetState($rootFolderField, $GUI_SHOW) + GUICtrlSetState($rootFolderLabel, $GUI_SHOW) + GUICtrlSetState($caseNameField, $GUI_HIDE) + GUICtrlSetState($caseNameLabel, $GUI_HIDE) + GUICtrlSetOnEvent($browseButton, "Browse") + ;rename to RootDirectory to root directory + ;hide case name field + GUICtrlSetState($runButton, $GUI_DISABLE) +EndFunc + +;ensure that all fields for the selected algorithm are valid +Func ValidateFields($oldCaseName, $oldRootFolder) + Local $dataSourcePath = GUICtrlRead($rootFolderField) + Local $caseName = GUICtrlRead($caseNameField) + if ($dataSourcePath <> $oldRootFolder Or $caseName <> $oldCaseName) Then + Local $selectedAlgName = GUICtrlRead($algorithmComboBox) + If $selectedAlgName == $allAlgorithmNames[2] Then ;"One Data Source Per Folder" + ValidateDefaultFields($dataSourcePath) + ElseIf $selectedAlgName == $allAlgorithmNames[0] Then ;"Single Data Source" + ValidateSingleDataSourceFields($dataSourcePath, $caseName) + ElseIf $selectedAlgName == $allAlgorithmNames[1] Then ;"Folder of Logical Files" + ValidateSingleDataSourceFields($dataSourcePath, $caseName) + EndIf + EndIf +EndFunc + +;ensure that the settings for the default algorithm are valid before enabling it +Func ValidateDefaultFields($rootFolderPath) + if ($rootFolderPath <> "" And FileExists($rootFolderPath)) Then + GUICtrlSetState($runButton, $GUI_ENABLE) + Else + GUICtrlSetState($runButton, $GUI_DISABLE) + EndIf +EndFunc + +;ensure that the settings for the Single Data Source and Folder of Logical Files algorithms are valid +Func ValidateSingleDataSourceFields($dataSourcePath, $caseName) + if ($dataSourcePath <> "" And FileExists($dataSourcePath) And $caseName <> "") Then + GUICtrlSetState($runButton, $GUI_ENABLE) + Else + GUICtrlSetState($runButton, $GUI_DISABLE) + EndIf +EndFunc + +;clear all input fields, and reset them to an empty string +Func ClearFields() + GUICtrlSetData($rootFolderField, "") + GUICtrlSetData($caseNameField, "") +EndFunc + +;Open a directory chooser +Func Browse() + ; Note: At this point @GUI_CtrlId would equal $browseButton + GUICtrlSetState($browseButton, $GUI_DISABLE) + Local $selectedDirectory = FileSelectFolder("Select Folder", $defaultDirectory) + Local $caseDir = "" + Local $caseDrive = "" + If (FileExists($selectedDirectory)) Then + _PathSplit($selectedDirectory, $caseDrive, $caseDir, "", "") + $defaultDirectory = $caseDrive & $caseDir + GUICtrlSetData($rootFolderField, $selectedDirectory) + EndIf + GUICtrlSetState($caseNameField, $GUI_FOCUS) + GUICtrlSetState($browseButton, $GUI_ENABLE) +EndFunc ;==>BrowseButton + +; Open a file chooser +Func BrowseForDataSourceFile() + ; Note: At this point @GUI_CtrlId would equal $browseButton + GUICtrlSetState($browseButton, $GUI_DISABLE) + Local $selectedDataSource = FileOpenDialog("Select Data Source", $defaultDirectory, "All Supported Types (*.img; *.dd; *.001; *.aa; *.raw; *.bin; *.E01; *.vmdk; *.vhd) |Raw Images (*.img; *.dd; *.001; *.aa; *.raw; *.bin) |Encase Images (*.E01) |Virtual Machines (*.vmdk; *.vhd) |Logical Evidence File (*.L01) |All Files (*.*)", $FD_FILEMUSTEXIST) + Local $caseDir = "" + Local $caseDrive = "" + If (FileExists($selectedDataSource)) Then + _PathSplit ($selectedDataSource, $caseDrive, $caseDir, "", "") + $defaultDirectory = $caseDrive & $caseDir + GUICtrlSetData($rootFolderField, $selectedDataSource) + EndIf + GUICtrlSetState($caseNameField, $GUI_FOCUS) + GUICtrlSetState($browseButton, $GUI_ENABLE) +EndFunc + +;Perform the action associated with the run button which should be defined in ManifestGenerationAlgorithms.au3 +Func AlgorithmRunAction() + ; Note: At this point @GUI_CtrlId would equal $runButton + GUICtrlSetState($runButton, $GUI_DISABLE) + RunAlgorithm(GUICtrlRead($algorithmComboBox), GetSettings(), $progressField) + GUICtrlSetState($runButton, $GUI_ENABLE) +EndFunc ;==>RunButton + +;Get an array of settings as they are set on this panel +Func GetSettings() + Local $settings[2] + $settings[0] = GUICtrlRead($rootFolderField) + $settings[1] = GUICtrlRead($caseNameField) + Return $settings +EndFunc + +;Close the tool +Func CLOSEButton() + ; Note: at this point @GUI_CtrlId would equal $GUI_EVENT_CLOSE, + ; @GUI_WinHandle will be either $hMainGUI or $hDummyGUI + GUICtrlSetState($exitButton, $GUI_DISABLE) + If @GUI_WinHandle = $hMainGUI Then + Local $msgBoxAnswer = MsgBox(1, "Close Tool Confirmation", "Press OK to confirm closing the tool") + if $msgBoxAnswer == 1 Then + WritePropertiesFile() + Exit + EndIf + EndIf + GUICtrlSetState($exitButton, $GUI_ENABLE) +EndFunc ;==>CLOSEButton \ No newline at end of file diff --git a/ManifestTool/ManifestTool.exe b/ManifestTool/ManifestTool.exe new file mode 100644 index 0000000000..c71401456b Binary files /dev/null and b/ManifestTool/ManifestTool.exe differ diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 937b659039..521b871e47 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -2,7 +2,6 @@ cannotBuildXmlParser=Unable to build XML parser: cannotLoadSEUQA=Unable to load Search Engine URL Query Analyzer settings file, SEUQAMappings.xml: cannotParseXml=Unable to parse XML file: ChromeCacheExtractor.moduleName=ChromeCacheExtractor -# {0} - OS name DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.parentModuleName=Recent Activity Extract.indexError.message=Failed to index artifact for keyword search. @@ -48,8 +47,16 @@ ExtractSafari_Error_Getting_History=An error occurred while processing Safari hi ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files ExtractSafari_Module_Name=Safari +ExtractZone_Internet=Internet Zone +ExtractZone_Local_Intranet=Local Intranet Zone +ExtractZone_Local_Machine=Local Machine Zone +ExtractZone_process_errMsg=An error occured processing ':Zone.Indentifier' files. +ExtractZone_process_errMsg_find=A failure occured while searching for :Zone.Indentifier files. +ExtractZone_progress_Msg=Extracting :Zone.Identifer files +ExtractZone_Restricted=Restricted Sites Zone +ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\n\The module currently supports Windows only disk images.\n\The plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web acitivity (sites visited, stored cookies, bookmarked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module Chrome.moduleName=Chrome @@ -173,12 +180,11 @@ RecentDocumentsByLnk.parentModuleName.noSpace=RecentActivity RecentDocumentsByLnk.parentModuleName=Recent Activity RegRipperFullNotFound=Full version RegRipper executable not found. RegRipperNotFound=Autopsy RegRipper executable not found. -# {0} - file name SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE -SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\n\count: {2}\nSplit Tokens: \n{3} +SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\ncount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity UsbDeviceIdMapper.parseAndLookup.text=Product: {0} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java index 503cd639ba..23a6f3ab22 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java @@ -37,6 +37,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -514,12 +515,13 @@ class Chrome extends Extract { tempList = this.dbConnect(temps, DOWNLOAD_QUERY_V30); } - logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS + logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS for (HashMap result : tempList) { Collection bbattributes = new ArrayList<>(); + String fullPath = result.get("full_path").toString(); //NON-NLS bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, - RecentActivityExtracterModuleFactory.getModuleName(), (result.get("full_path").toString()))); //NON-NLS - long pathID = Util.findID(dataSource, (result.get("full_path").toString())); //NON-NLS + RecentActivityExtracterModuleFactory.getModuleName(), fullPath)); + long pathID = Util.findID(dataSource, fullPath); if (pathID != -1) { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID, NbBundle.getMessage(this.getClass(), @@ -546,6 +548,19 @@ class Chrome extends Extract { if (bbart != null) { bbartifacts.add(bbart); } + + // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it.. + try { + for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) { + BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE); + downloadSourceArt.addAttributes(createDownloadSourceAttributes(result.get("url").toString())); + + bbartifacts.add(downloadSourceArt); + break; + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", fullPath), ex); //NON-NLS + } } dbFile.delete(); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java index 3ffbad514b..2938bb8dd5 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java @@ -379,7 +379,7 @@ abstract class Extract { * @param accessTime Time the download occurred * @param domain Domain of the URL * @param programName Name of the module creating the attribute - * @return A collection of attributed of a downloaded file + * @return A collection of attributes of a downloaded file */ protected Collection createDownloadAttributes(String path, Long pathID, String url, Long accessTime, String domain, String programName) { Collection bbattributes = new ArrayList<>(); @@ -414,6 +414,22 @@ abstract class Extract { return bbattributes; } + /** + * Creates a list of the attributes for source of a downloaded file + * + * @param url source URL of the downloaded file + * @return A collection of attributes for source of a downloaded file + */ + protected Collection createDownloadSourceAttributes(String url) { + Collection bbattributes = new ArrayList<>(); + + bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, + RecentActivityExtracterModuleFactory.getModuleName(), + (url != null) ? url : "")); //NON-NLS + + return bbattributes; + } + /** * Create temporary file for the given AbstractFile. The new file will be * created in the temp directory for the module with a unique file name. diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java index 3f98bcdf91..41de0f59d8 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java @@ -36,6 +36,7 @@ import java.util.Iterator; import java.util.List; import java.util.logging.Level; import javax.xml.parsers.ParserConfigurationException; +import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.Logger; @@ -48,6 +49,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.xml.sax.SAXException; @@ -503,7 +505,7 @@ final class ExtractSafari extends Extract { for(NSObject obj: objectArray){ if(obj instanceof NSDictionary){ - bbartifacts.add(parseDownloadDictionary(dataSource, origFile, (NSDictionary)obj)); + bbartifacts.addAll(parseDownloadDictionary(dataSource, origFile, (NSDictionary)obj)); } } break; @@ -612,12 +614,15 @@ final class ExtractSafari extends Extract { * @return a Blackboard Artifact for the download. * @throws TskCoreException */ - private BlackboardArtifact parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException { + private Collection parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException { + Collection bbartifacts = new ArrayList<>(); String url = null; String path = null; Long time = null; Long pathID = null; - + + FileManager fileManager = getCurrentCase().getServices().getFileManager(); + NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL); if (nsstring != null) { url = nsstring.toString(); @@ -636,7 +641,16 @@ final class ExtractSafari extends Extract { BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD); bbart.addAttributes(this.createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName())); - - return bbart; + bbartifacts.add(bbart); + + // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it. + for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(path), FilenameUtils.getPath(path))) { + BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE); + downloadSourceArt.addAttributes(createDownloadSourceAttributes(url)); + bbartifacts.add(downloadSourceArt); + break; + } + + return bbartifacts; } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java new file mode 100755 index 0000000000..56c6748712 --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java @@ -0,0 +1,388 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * + * 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.recentactivity; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extract the :Zone.Indentifier alternate data stream files. A file with + * a :Zone.Indentifier extention contains information about the similarly + * named (with out zone identifer extension) downloaded file. + */ +final class ExtractZoneIdentifier extends Extract { + + private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName()); + + private static final String ZONE_IDENTIFIER_FILE = "%:Zone.Identifier"; //NON-NLS + private static final String ZONE_IDENTIFIER = ":Zone.Identifier"; //NON-NLS + + @Messages({ + "ExtractZone_process_errMsg_find=A failure occured while searching for :Zone.Indentifier files.", + "ExtractZone_process_errMsg=An error occured processing ':Zone.Indentifier' files.", + "ExtractZone_progress_Msg=Extracting :Zone.Identifer files" + }) + + @Override + void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { + + progressBar.progress(Bundle.ExtractZone_progress_Msg()); + + List zoneFiles = null; + try { + zoneFiles = currentCase.getServices().getFileManager().findFiles(dataSource, ZONE_IDENTIFIER_FILE); + } catch (TskCoreException ex) { + addErrorMessage(Bundle.ExtractZone_process_errMsg_find()); + LOG.log(Level.SEVERE, "Unable to find zone identifier files, exception thrown. ", ex); // NON-NLS + } + + if (zoneFiles == null || zoneFiles.isEmpty()) { + return; + } + + Set knownPathIDs = null; + try { + knownPathIDs = getPathIDsForType(TSK_WEB_DOWNLOAD); + } catch (TskCoreException ex) { + addErrorMessage(Bundle.ExtractZone_process_errMsg()); + LOG.log(Level.SEVERE, "Failed to build PathIDs List for TSK_WEB_DOWNLOAD", ex); // NON-NLS + } + + if (knownPathIDs == null) { + return; + } + + Collection sourceArtifacts = new ArrayList<>(); + Collection downloadArtifacts = new ArrayList<>(); + + for (AbstractFile zoneFile : zoneFiles) { + try { + processZoneFile(context, dataSource, zoneFile, sourceArtifacts, downloadArtifacts, knownPathIDs); + } catch (TskCoreException ex) { + addErrorMessage(Bundle.ExtractZone_process_errMsg()); + String message = String.format("Failed to process zone identifier file %s", zoneFile.getName()); //NON-NLS + LOG.log(Level.WARNING, message, ex); + } + } + + IngestServices services = IngestServices.getInstance(); + + if (!sourceArtifacts.isEmpty()) { + services.fireModuleDataEvent(new ModuleDataEvent( + RecentActivityExtracterModuleFactory.getModuleName(), + TSK_DOWNLOAD_SOURCE, sourceArtifacts)); + } + + if (!downloadArtifacts.isEmpty()) { + services.fireModuleDataEvent(new ModuleDataEvent( + RecentActivityExtracterModuleFactory.getModuleName(), + TSK_WEB_DOWNLOAD, downloadArtifacts)); + } + } + + /** + * Process a single Zone Identifier file. + * + * @param context IngetJobContext + * @param dataSource Content + * @param zoneFile Zone Indentifier file + * @param sourceArtifacts List for TSK_DOWNLOAD_SOURCE artifacts + * @param downloadArtifacts List for TSK_WEB_DOWNLOAD aritfacts + * + * @throws TskCoreException + */ + private void processZoneFile(IngestJobContext context, Content dataSource, + AbstractFile zoneFile, Collection sourceArtifacts, + Collection downloadArtifacts, + Set knownPathIDs) throws TskCoreException { + + ZoneIdentifierInfo zoneInfo = null; + + try { + zoneInfo = new ZoneIdentifierInfo(zoneFile); + } catch (IOException ex) { + String message = String.format("Unable to parse temporary File for %s", zoneFile.getName()); //NON-NLS + LOG.log(Level.WARNING, message, ex); + } + + if (zoneInfo == null) { + return; + } + + AbstractFile downloadFile = getDownloadFile(dataSource, zoneFile); + + if (downloadFile != null) { + // Only create a new TSK_WEB_DOWNLOAD artifact if one does not exist for downloadFile + if (!knownPathIDs.contains(downloadFile.getDataSourceObjectId())) { + // The zone identifier file is the parent of this artifact + // because it is the file we parsed to get the data + BlackboardArtifact downloadBba = createDownloadArtifact(zoneFile, zoneInfo); + if (downloadBba != null) { + downloadArtifacts.add(downloadBba); + } + } + + // check if download has a child TSK_DOWNLOAD_SOURCE artifact, if not create one + if (downloadFile.getArtifactsCount(TSK_DOWNLOAD_SOURCE) == 0) { + BlackboardArtifact sourceBba = createDownloadSourceArtifact(downloadFile, zoneInfo); + if (sourceBba != null) { + sourceArtifacts.add(sourceBba); + } + } + } + } + + /** + * Find the file that the Zone.Identifer file was created alongside. + * + * @param dataSource Content + * @param zoneFile The zone identifier case file + * + * @return The downloaded file or null if a file was not found + * + * @throws TskCoreException + */ + private AbstractFile getDownloadFile(Content dataSource, AbstractFile zoneFile) throws TskCoreException { + AbstractFile downloadFile = null; + + org.sleuthkit.autopsy.casemodule.services.FileManager fileManager + = currentCase.getServices().getFileManager(); + + String downloadFileName = zoneFile.getName().replace(ZONE_IDENTIFIER, ""); //NON-NLS + + List fileList = fileManager.findFiles(dataSource, downloadFileName, zoneFile.getParentPath()); + + if (fileList.size() == 1) { + downloadFile = fileList.get(0); + + // Check that the download file and the zone file came from the same dir + if (!downloadFile.getParentPath().equals(zoneFile.getParentPath())) { + downloadFile = null; + } else if (zoneFile.getMetaAddr() != downloadFile.getMetaAddr()) { + downloadFile = null; + } + } + + return downloadFile; + } + + /** + * Create a Download Source Artifact for the given ZoneIdentifierInfo + * object. + * + * @param downloadFile AbstractFile representing the file downloaded, not + * the zone indentifier file. + * @param zoneInfo Zone Indentifer file wrapper object + * + * @return TSK_DOWNLOAD_SOURCE object for given parameters + */ + private BlackboardArtifact createDownloadSourceArtifact(AbstractFile downloadFile, ZoneIdentifierInfo zoneInfo) { + + Collection bbattributes = new ArrayList<>(); + + bbattributes.addAll(Arrays.asList( + new BlackboardAttribute(TSK_URL, + RecentActivityExtracterModuleFactory.getModuleName(), + StringUtils.defaultString(zoneInfo.getURL(), "")), + + new BlackboardAttribute(TSK_DOMAIN, + RecentActivityExtracterModuleFactory.getModuleName(), + (zoneInfo.getURL() != null) ? NetworkUtils.extractDomain(zoneInfo.getURL()) : ""), + + new BlackboardAttribute(TSK_LOCATION, + RecentActivityExtracterModuleFactory.getModuleName(), + StringUtils.defaultString(zoneInfo.getZoneIdAsString(), "")))); //NON-NLS + + return addArtifact(TSK_DOWNLOAD_SOURCE, downloadFile, bbattributes); + } + + /** + * Create a TSK_WEB_DOWNLOAD Artifact for the given zone indentifier file. + * + * @param zoneFile Zone identifier file + * @param zoneInfo ZoneIdentifierInfo file wrapper object + * + * @return BlackboardArifact for the given parameters + */ + private BlackboardArtifact createDownloadArtifact(AbstractFile zoneFile, ZoneIdentifierInfo zoneInfo) { + + Collection bbattributes = createDownloadAttributes( + null, null, + zoneInfo.getURL(), null, + (zoneInfo.getURL() != null ? NetworkUtils.extractDomain(zoneInfo.getURL()) : ""), + null); + return addArtifact(TSK_WEB_DOWNLOAD, zoneFile, bbattributes); + } + + /** + * Creates a list of PathIDs for the given Artifact type. + * + * @param type BlackboardArtifact.ARTIFACT_TYPE + * + * @return A list of PathIDs + * + * @throws TskCoreException + */ + private Set getPathIDsForType(BlackboardArtifact.ARTIFACT_TYPE type) throws TskCoreException { + Set idList = new HashSet<>(); + for (BlackboardArtifact artifact : currentCase.getSleuthkitCase().getBlackboardArtifacts(type)) { + BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(TSK_PATH_ID)); + + if (pathIDAttribute != null) { + long contentID = pathIDAttribute.getValueLong(); + if (contentID != -1) { + idList.add(contentID); + } + } + } + return idList; + } + + @Messages({ + "ExtractZone_Local_Machine=Local Machine Zone", + "ExtractZone_Local_Intranet=Local Intranet Zone", + "ExtractZone_Trusted=Trusted Sites Zone", + "ExtractZone_Internet=Internet Zone", + "ExtractZone_Restricted=Restricted Sites Zone" + }) + + /** + * Wrapper class for information in the :ZoneIdentifier file. The + * Zone.Identifier file has a simple format of key=value. There + * are four known keys: ZoneId, ReferrerUrl, HostUrl, and + * LastWriterPackageFamilyName. Not all browsers will put all values in the + * file, in fact most will only supply the ZoneId. Only Edge supplies the + * LastWriterPackageFamilyName. + */ + private final static class ZoneIdentifierInfo { + + private static final String ZONE_ID = "ZoneId"; //NON-NLS + private static final String REFERRER_URL = "ReferrerUrl"; //NON-NLS + private static final String HOST_URL = "HostUrl"; //NON-NLS + private static final String FAMILY_NAME = "LastWriterPackageFamilyName"; //NON-NLS + + private final Properties properties = new Properties(null); + + /** + * Opens the zone file, reading for the key\value pairs and puts them + * into a HashMap. + * + * @param zoneFile The ZoneIdentifier file + * + * @throws FileNotFoundException + * @throws IOException + */ + ZoneIdentifierInfo(AbstractFile zoneFile) throws IOException { + properties.load(new ReadContentInputStream(zoneFile)); + } + + /** + * Get the integer zone id + * + * @return interger zone id or -1 if unknown + */ + private int getZoneId() { + int zoneValue = -1; + String value = properties.getProperty(ZONE_ID); + if (value != null) { + zoneValue = Integer.parseInt(value); + } + + return zoneValue; + } + + /** + * Get the string description of the zone id. + * + * @return String description or null if a zone id was not found + */ + private String getZoneIdAsString() { + switch (getZoneId()) { + case 0: + return Bundle.ExtractZone_Local_Machine(); + case 1: + return Bundle.ExtractZone_Local_Intranet(); + case 2: + return Bundle.ExtractZone_Trusted(); + case 3: + return Bundle.ExtractZone_Internet(); + case 4: + return Bundle.ExtractZone_Restricted(); + default: + return null; + } + } + + /** + * Get the URL from which the file was downloaded. + * + * @return String url or null if a host url was not found + */ + private String getURL() { + return properties.getProperty(HOST_URL); + } + + /** + * Get the referrer url. + * + * @return String url or null if a host url was not found + */ + private String getReferrer() { + return properties.getProperty(REFERRER_URL); + } + + /** + * Gets the string value for the key LastWriterPackageFamilyName. + * + * @return String value or null if the value was not found + */ + private String getFamilyName() { + return properties.getProperty(FAMILY_NAME); + } + } + +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 129d86d10d..8430beb9b1 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -42,6 +42,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; +import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -499,14 +500,14 @@ class Firefox extends Extract { (Long.valueOf(result.get("startTime").toString())))); //NON-NLS String target = result.get("target").toString(); //NON-NLS - + String downloadedFilePath = ""; if (target != null) { try { - String decodedTarget = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS + downloadedFilePath = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, RecentActivityExtracterModuleFactory.getModuleName(), - decodedTarget)); - long pathID = Util.findID(dataSource, decodedTarget); + downloadedFilePath)); + long pathID = Util.findID(dataSource, downloadedFilePath); if (pathID != -1) { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID, RecentActivityExtracterModuleFactory.getModuleName(), @@ -532,6 +533,19 @@ class Firefox extends Extract { if (bbart != null) { bbartifacts.add(bbart); } + + // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it. + try { + for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { + BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE); + downloadSourceArt.addAttributes(createDownloadSourceAttributes(source)); + bbartifacts.add(downloadSourceArt); + break; + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", + downloadedFilePath), ex); //NON-NLS + } } if (errors > 0) { this.addErrorMessage( @@ -619,13 +633,14 @@ class Firefox extends Extract { //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", "Last Visited", (Long.valueOf(result.get("startTime").toString())))); String target = result.get("target").toString(); //NON-NLS + String downloadedFilePath = ""; if (target != null) { try { - String decodedTarget = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS + downloadedFilePath = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, RecentActivityExtracterModuleFactory.getModuleName(), - decodedTarget)); - long pathID = Util.findID(dataSource, decodedTarget); + downloadedFilePath)); + long pathID = Util.findID(dataSource, downloadedFilePath); if (pathID != -1) { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID, RecentActivityExtracterModuleFactory.getModuleName(), @@ -652,6 +667,19 @@ class Firefox extends Extract { if (bbart != null) { bbartifacts.add(bbart); } + + // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it. + try { + for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { + BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE); + downloadSourceArt.addAttributes(createDownloadSourceAttributes(url)); + bbartifacts.add(downloadSourceArt); + break; + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", + downloadedFilePath), ex); //NON-NLS + } } if (errors > 0) { this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getDlV24.errMsg.errParsingArtifacts", diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java index b81a512ecb..db815e9274 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java @@ -76,6 +76,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule { Extract osExtract = new ExtractOs(); Extract dataSourceAnalyzer = new DataSourceUsageAnalyzer(); Extract safari = new ExtractSafari(); + Extract zoneInfo = new ExtractZoneIdentifier(); extractors.add(chrome); extractors.add(firefox); @@ -87,6 +88,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule { extractors.add(registry); // this should run after quicker modules like the browser modules and needs to run before the DataSourceUsageAnalyzer extractors.add(osExtract); // this needs to run before the DataSourceUsageAnalyzer extractors.add(dataSourceAnalyzer); //this needs to run after ExtractRegistry and ExtractOs + extractors.add(zoneInfo); // this needs to run after the web browser modules browserExtractors.add(chrome); browserExtractors.add(firefox); diff --git a/build.xml b/build.xml index 7e7e87d388..dff466f39d 100644 --- a/build.xml +++ b/build.xml @@ -102,6 +102,7 @@ + diff --git a/docs/doxygen-user/images/AutoIngest/manifest_tool_root_folder.png b/docs/doxygen-user/images/AutoIngest/manifest_tool_root_folder.png new file mode 100644 index 0000000000..ac58fa06e5 Binary files /dev/null and b/docs/doxygen-user/images/AutoIngest/manifest_tool_root_folder.png differ diff --git a/docs/doxygen-user/images/AutoIngest/manifest_tool_ui.png b/docs/doxygen-user/images/AutoIngest/manifest_tool_ui.png new file mode 100644 index 0000000000..5c5c9458d1 Binary files /dev/null and b/docs/doxygen-user/images/AutoIngest/manifest_tool_ui.png differ diff --git a/docs/doxygen-user/images/portable_case_empty_image.png b/docs/doxygen-user/images/portable_case_empty_image.png new file mode 100644 index 0000000000..e6766db08b Binary files /dev/null and b/docs/doxygen-user/images/portable_case_empty_image.png differ diff --git a/docs/doxygen-user/images/portable_case_folder.png b/docs/doxygen-user/images/portable_case_folder.png new file mode 100644 index 0000000000..a02bd4e087 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_folder.png differ diff --git a/docs/doxygen-user/images/portable_case_original_version.png b/docs/doxygen-user/images/portable_case_original_version.png new file mode 100644 index 0000000000..74b906c671 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_original_version.png differ diff --git a/docs/doxygen-user/images/portable_case_portable_version.png b/docs/doxygen-user/images/portable_case_portable_version.png new file mode 100644 index 0000000000..1172e06708 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_portable_version.png differ diff --git a/docs/doxygen-user/images/portable_case_report_panel.png b/docs/doxygen-user/images/portable_case_report_panel.png new file mode 100644 index 0000000000..91b9c0c722 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_report_panel.png differ diff --git a/docs/doxygen-user/images/portable_case_tags.png b/docs/doxygen-user/images/portable_case_tags.png new file mode 100644 index 0000000000..d5b7064d27 Binary files /dev/null and b/docs/doxygen-user/images/portable_case_tags.png differ diff --git a/docs/doxygen-user/images/reports_case.png b/docs/doxygen-user/images/reports_case.png index 59f98b38f6..8e128f51e7 100644 Binary files a/docs/doxygen-user/images/reports_case.png and b/docs/doxygen-user/images/reports_case.png differ diff --git a/docs/doxygen-user/images/reports_html_header.png b/docs/doxygen-user/images/reports_html_header.png index 6d4d88276f..205e7fa2f0 100644 Binary files a/docs/doxygen-user/images/reports_html_header.png and b/docs/doxygen-user/images/reports_html_header.png differ diff --git a/docs/doxygen-user/images/reports_select.png b/docs/doxygen-user/images/reports_select.png index c14fb223b1..fe4f1f10bd 100644 Binary files a/docs/doxygen-user/images/reports_select.png and b/docs/doxygen-user/images/reports_select.png differ diff --git a/docs/doxygen-user/manifest_tool.dox b/docs/doxygen-user/manifest_tool.dox new file mode 100644 index 0000000000..638a325a74 --- /dev/null +++ b/docs/doxygen-user/manifest_tool.dox @@ -0,0 +1,62 @@ +/*! \page manifest_tool_page Manifest Tool + +\section manifest_tool_overview Overview + +Manifest Tool is an executable designed to assist in the automated creation of manifest files which are necessary to run Auto Ingest on a data source. There is no installation necessary. To use the tool double click on Manifest Tool executable, when it opens select the option with the algorithm you wish to run from the combo box, and fill in all the available settings before clicking the Run button. A log with the success or failure of each manifest file it attempts to create will appear in the progress area. + +\section manifest_tool_output Output + +The output of the Manifest Tool will be XML files ending in _Manifest.xml. + +\subsection manifest_tool_one_ds_per_folder One Data Source Per Folder + +The One Data Source Per Folder algorithm is designed for a specific use case when the case folder contains multiple subfolders, with each generally containing one data data source of a short list of types. Please see \ref manifest_tool_algorithm_specifics for details on this algorithm. + +To use this algorithm, use the Browse button to select a root folder as the case directory. Then select the Run button to generate manifest files for each of the data sources detected. A manifest file will be generated for each subfolder of the selected root folder, the manifest files will be placed inside the selected root folder. + +\subsection manifest_tool_single_ds Single Data Source + +The Single Data Source algorithm is for creating a manifest file for a single image or logical file with a user specified case name. + +To use this algorithm, use the Browse button to select a file to use as your data source, and enter a case name in the case name field. Then select the Run button to generate a manifest file. The manifest file will be created in the same folder as your selected data source. + +\subsection manifest_tool_logical_file_folder Folder of Logical Files + +The Folder of Logical Files algorithm is for creating a single manifest file for an entire folder of files which will all be ingested as logical files. + +To use this algorithm, use the Browse button to select a folder to add as a folder of logical files, and enter a case name in the case name field. Then select the Run button to generate a manifest file. The manifest file will be created in the parent folder of your selected folder of logical files. + +\section manifest_tol_example Example + +Given a root folder that looks like this: + +\image html AutoIngest/manifest_tool_root_folder.png + +A user having selected the One Data Source Per Folder algorithm will get output that looks like the following, where a manifest now exists for each non-empty subfolder. The root folder's name will be used as the case name in the manifest files (in this example the case name will be TestCaseFolder.) + +\image html AutoIngest/manifest_tool_ui.png + +The contents of an XML file will have the following format: + +\verbatim + + +TestCaseFolder +interestingL01\interesting_files2.L01 + +\endverbatim + +\section manifest_tool_algorithm_specifics One Data Source Per Folder Algorithm Specifics +
    +
  • The only configuration setting the user needs to choose is a root folder. +
  • The name of the specified root folder will become the case name used in the manifest files. +
  • Each non-empty subfolder in the root folder will have a manifest file created for it. +
  • All manifest files will be created in the root folder. +
  • Files directly in the root folder will be ignored and remain unprocessed. +
  • Subfolders which contain an .E01, .L01, .001, or .AD1 file, will have the first file of this type used as the data source in the manifest file. +
  • Subfolders which have more than one .E01, .L01, .001, or .AD1 file will have the additional files ignored and they will remain unprocessed. +
  • Subfolders without an .E01, .L01, .001, or .AD1 file will have the entire subfolder added as the data source. +
  • If the root folder already contains a specific _Manifest.xml file then it will not be replaced or modified. +
+ +*/ \ No newline at end of file diff --git a/docs/doxygen-user/portable_case.dox b/docs/doxygen-user/portable_case.dox new file mode 100644 index 0000000000..692b70bcb8 --- /dev/null +++ b/docs/doxygen-user/portable_case.dox @@ -0,0 +1,48 @@ +/*! \page portable_case_page Portable Cases + +\section portable_case_overview Overview + +A portable case is a partial copy of a normal Autopsy case that can be opened from anywhere. The general use case is as follows: +
    +
  1. Alice is analyzing one or more data sources using Autopsy. She tags files and results that are of particular interest. +
  2. Alice wants to share her findings with Bob but is unable to send him the original data sources. +
  3. Alice creates a portable case which will contain only her tagged files and results, plus any files associated with those results, and sends it to Bob. +
  4. Bob can open the portable case in Autopsy and view all files and results Alice tagged, and run any of the normal Autopsy features. +
+ +For example, Alice's original case could look like this: + +\image html portable_case_original_version.png + +The portable version could like this: + +\image html portable_case_portable_version.png + +Alice only tagged eight files and results, so most of the original content is no longer in the case. Some of the data sources had no tagged items so they're not included at all. The file structure of any tagged files is preserved - you can see that the tagged image in the screenshot is still in the same location, but the non-tagged files are gone. Note that although the original images (such as "image1.vhd") appear in the tree, their contents are not included in the portable case. + +\section portable_case_creation Creating a Portable Case + +First you'll want to make sure that all the files and results you want included in the portable case are tagged - see the \ref tagging_page page for more details. +You can see what tags you've added in the \ref tree_viewer_page. + +\image html portable_case_tags.png + +Portable cases are created through the \ref reporting_page feature. The Generate Report dialog will display a list of all tags that are in use in the current case and you can choose which ones you would like to include. At the bottom you can select the output folder for the new case. By default it will be placed in the "Reports" folder in the current case. + +\image html portable_case_report_panel.png + +Here you can see the new portable case. It will be named with the original case name plus "(Portable)". The portable case is initially missing many of the normal Autopsy folders - these will be created the first time a user opens it. The portable case folder can be zipped and sent to a different user. + +\image html portable_case_folder.png + +\section portable_case_usage Using a Portable Case + +Portable cases generally behave like any other Autopsy case. You can run ingest, do keyword searches, use the timeline viewer, etc. One point to note is that while the original data source names appear in the case, the data sources themselves were not copied into the portable case. + +\image html portable_case_empty_image.png + +This may cause warning or error messages when using ingest modules that run on the full image, such as the \ref data_source_integrity_page. You will also not be able to view the data sources in the content viewer. + +You can also add additonal data sources to the portable case if you wish. The case will no longer be portable, but if desired you could generate a new portable case that will include tagged files and results from the new data sources as well as the original case. + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/reporting.dox b/docs/doxygen-user/reporting.dox index a48a1ed63c..63c1917036 100644 --- a/docs/doxygen-user/reporting.dox +++ b/docs/doxygen-user/reporting.dox @@ -82,6 +82,10 @@ This report module generates a KML file from any GPS data in the case. This file \image html reports_kml.png +\subsection report_portable_case Portable Case + +This report module generates a new Autopsy case from any tagged files and results. See the \ref portable_case_page page for additional information. + \subsection report_stix STIX The STIX module allows you to generate a report and Interesting File artifacts by running a STIX file (or files) against the data sources in the case.