diff --git a/Core/manifest.mf b/Core/manifest.mf index 31bfec73de..7aa34c46dc 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -1,10 +1,10 @@ -Manifest-Version: 1.0 -OpenIDE-Module: org.sleuthkit.autopsy.core/9 -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties -OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 9 -OpenIDE-Module-Requires: org.openide.windows.WindowManager, org.netbeans.api.javahelp.Help -AutoUpdate-Show-In-Client: true -AutoUpdate-Essential-Module: true -OpenIDE-Module-Install: org/sleuthkit/autopsy/core/Installer.class - +Manifest-Version: 1.0 +OpenIDE-Module: org.sleuthkit.autopsy.core/9 +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties +OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml +OpenIDE-Module-Implementation-Version: 9 +OpenIDE-Module-Requires: org.openide.windows.WindowManager, org.netbeans.api.javahelp.Help +AutoUpdate-Show-In-Client: true +AutoUpdate-Essential-Module: true +OpenIDE-Module-Install: org/sleuthkit/autopsy/core/Installer.class + diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 9b49c8e266..88dd528aeb 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -191,6 +191,7 @@ + org.sleuthkit.autopsy.actions org.sleuthkit.autopsy.casemodule org.sleuthkit.autopsy.casemodule.services org.sleuthkit.autopsy.core diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java new file mode 100755 index 0000000000..4e3efcad87 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -0,0 +1,69 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.util.Collection; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this Action allow users to apply tags to blackboard artifacts. + */ +public class AddBlackboardArtifactTagAction extends AddTagAction { + // 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 AddBlackboardArtifactTagAction instance; + + public static synchronized AddBlackboardArtifactTagAction getInstance() { + if (null == instance) { + instance = new AddBlackboardArtifactTagAction(); + } + return instance; + } + + private AddBlackboardArtifactTagAction() { + super(""); + } + + @Override + protected String getActionDisplayName() { + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).size() > 1 ? "Tag Results" : "Tag Result"; + } + + @Override + protected void addTag(TagName tagName, String comment) { + Collection selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); + for (BlackboardArtifact artifact : selectedArtifacts) { + try { + Case.getCurrentCase().getServices().getTagsManager().addBlackboardArtifactTag(artifact, tagName, comment); + } + catch (TskCoreException ex) { + Logger.getLogger(AddBlackboardArtifactTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); + JOptionPane.showMessageDialog(null, "Unable to tag " + artifact.getDisplayName() + ".", "Tagging Error", JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java new file mode 100755 index 0000000000..8760ed364f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.util.Collection; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this Action allow users to apply tags to content. + */ +public class AddContentTagAction extends AddTagAction { + // 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 AddContentTagAction instance; + + public static synchronized AddContentTagAction getInstance() { + if (null == instance) { + instance = new AddContentTagAction(); + } + return instance; + } + + private AddContentTagAction() { + super(""); + } + + @Override + protected String getActionDisplayName() { + return Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size() > 1 ? "Tag Files" : "Tag File"; + } + + @Override + protected void addTag(TagName tagName, String comment) { + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + for (AbstractFile file : selectedFiles) { + try { + // Handle the special cases of current (".") and parent ("..") directory entries. + if (file.getName().equals(".")) { + Content parentFile = file.getParent(); + if (parentFile instanceof AbstractFile) { + file = (AbstractFile)parentFile; + } + else { + JOptionPane.showMessageDialog(null, "Unable to tag " + parentFile.getName() + ", not a regular file.", "Cannot Apply Tag", JOptionPane.WARNING_MESSAGE); + continue; + } + } + else if (file.getName().equals("..")) { + Content parentFile = file.getParent(); + if (parentFile instanceof AbstractFile) { + parentFile = (AbstractFile)((AbstractFile)parentFile).getParent(); + if (parentFile instanceof AbstractFile) { + file = (AbstractFile)parentFile; + } + else { + JOptionPane.showMessageDialog(null, "Unable to tag " + parentFile.getName() + ", not a regular file.", "Cannot Apply Tag", JOptionPane.WARNING_MESSAGE); + continue; + } + } + else { + JOptionPane.showMessageDialog(null, "Unable to tag " + parentFile.getName() + ", not a regular file.", "Cannot Apply Tag", JOptionPane.WARNING_MESSAGE); + continue; + } + } + + Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); + } + catch (TskCoreException ex) { + Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error tagging result", ex); + JOptionPane.showMessageDialog(null, "Unable to tag " + file.getName() + ".", "Tagging Error", JOptionPane.ERROR_MESSAGE); + } + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java new file mode 100755 index 0000000000..65f6a5e589 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -0,0 +1,148 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * An abstract base class for Actions that allow users to tag SleuthKit data + * model objects. + */ +abstract class AddTagAction extends TagAction implements Presenter.Popup { + private static final String NO_COMMENT = ""; + + AddTagAction(String menuText) { + super(menuText); + } + + @Override + public JMenuItem getPopupPresenter() { + return new TagMenu(); + } + + @Override + protected void doAction(ActionEvent event) { + } + + /** + * Template method to allow derived classes to provide a string for for a + * menu item label. + */ + abstract protected String getActionDisplayName(); + + /** + * Template method to allow derived classes to add the indicated tag and + * comment to one or more a SleuthKit data model objects. + */ + abstract protected void addTag(TagName tagName, String comment); + + /** + * Instances of this class implement a context menu user interface for + * creating or selecting a tag name for a tag and specifying an optional tag + * comment. + */ + // @@@ This user interface has some significant usability issues and needs + // to be reworked. + private class TagMenu extends JMenu { + TagMenu() { + super(getActionDisplayName()); + + // Get the current set of tag names. + TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); + List tagNames = null; + try { + tagNames = tagsManager.getAllTagNames(); + } + catch (TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + + // Create a "Quick Tag" sub-menu. + JMenu quickTagMenu = new JMenu("Quick Tag"); + add(quickTagMenu); + + // Each tag name in the current set of tags gets its own menu item in + // the "Quick Tags" sub-menu. Selecting one of these menu items adds + // a tag with the associated tag name. + if (null != tagNames && !tagNames.isEmpty()) { + for (final TagName tagName : tagNames) { + JMenuItem tagNameItem = new JMenuItem(tagName.getDisplayName()); + tagNameItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addTag(tagName, NO_COMMENT); + refreshDirectoryTree(); + } + }); + quickTagMenu.add(tagNameItem); + } + } + else { + JMenuItem empty = new JMenuItem("No tags"); + empty.setEnabled(false); + quickTagMenu.add(empty); + } + + quickTagMenu.addSeparator(); + + // The "Quick Tag" menu also gets an "Choose Tag..." menu item. + // Selecting this item initiates a dialog that can be used to create + // or select a tag name and adds a tag with the resulting name. + JMenuItem newTagMenuItem = new JMenuItem("New Tag..."); + newTagMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + TagName tagName = GetTagNameDialog.doDialog(); + if (tagName != null) { + addTag(tagName, NO_COMMENT); + refreshDirectoryTree(); + } + } + }); + quickTagMenu.add(newTagMenuItem); + + // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates + // a dialog that can be used to create or select a tag name with an + // optional comment and adds a tag with the resulting name. + JMenuItem tagAndCommentItem = new JMenuItem("Tag and Comment..."); + tagAndCommentItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(); + if (null != tagNameAndComment) { + addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment()); + refreshDirectoryTree(); + } + } + }); + add(tagAndCommentItem); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties new file mode 100755 index 0000000000..ad0c347ecb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -0,0 +1,16 @@ +GetTagNameDialog.tagNameField.text= +GetTagNameDialog.cancelButton.text=Cancel +GetTagNameDialog.okButton.text=OK +GetTagNameDialog.preexistingLabel.text=Pre-existing Tags: +GetTagNameDialog.newTagPanel.border.title=New Tag +GetTagNameDialog.tagNameLabel.text=Tag Name: +GetTagNameAndCommentDialog.newTagButton.text=New Tag +GetTagNameAndCommentDialog.okButton.text=OK +GetTagNameAndCommentDialog.commentText.toolTipText=Enter an optional tag comment or leave blank +GetTagNameAndCommentDialog.commentText.text= +GetTagNameAndCommentDialog.commentLabel.text=Comment: +# To change this template, choose Tools | Templates +# and open the template in the editor. +GetTagNameAndCommentDialog.cancelButton.text=Cancel +GetTagNameAndCommentDialog.tagCombo.toolTipText=Select tag to use +GetTagNameAndCommentDialog.tagLabel.text=Tag: diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java new file mode 100755 index 0000000000..3899b09c82 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java @@ -0,0 +1,67 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this Action allow users to delete tags applied to blackboard artifacts. + */ +public class DeleteBlackboardArtifactTagAction extends TagAction { + private static final String MENU_TEXT = "Delete Tag(s)"; + + // 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 DeleteBlackboardArtifactTagAction instance; + + public static synchronized DeleteBlackboardArtifactTagAction getInstance() { + if (null == instance) { + instance = new DeleteBlackboardArtifactTagAction(); + } + return instance; + } + + private DeleteBlackboardArtifactTagAction() { + super(MENU_TEXT); + } + + @Override + protected void doAction(ActionEvent event) { + Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); + for (BlackboardArtifactTag tag : selectedTags) { + try { + Case.getCurrentCase().getServices().getTagsManager().deleteBlackboardArtifactTag(tag); + } + catch (TskCoreException ex) { + Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); + JOptionPane.showMessageDialog(null, "Unable to delete tag " + tag.getName() + ".", "Tag Deletion Error", JOptionPane.ERROR_MESSAGE); + } + } + } +} + diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java new file mode 100755 index 0000000000..6f4bfd42a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this Action allow users to delete tags applied to content. + */ +public class DeleteContentTagAction extends TagAction { + private static final String MENU_TEXT = "Delete Tag(s)"; + + // 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 DeleteContentTagAction instance; + + public static synchronized DeleteContentTagAction getInstance() { + if (null == instance) { + instance = new DeleteContentTagAction(); + } + return instance; + } + + private DeleteContentTagAction() { + super(MENU_TEXT); + } + + @Override + protected void doAction(ActionEvent e) { + Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class); + for (ContentTag tag : selectedTags) { + try { + Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(tag); + } + catch (TskCoreException ex) { + Logger.getLogger(AddContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); + JOptionPane.showMessageDialog(null, "Unable to delete tag " + tag.getName() + ".", "Tag Deletion Error", JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form similarity index 81% rename from Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.form rename to Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 1bacfb8942..cbbdaebb26 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -78,7 +78,7 @@ - + @@ -91,7 +91,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -114,31 +114,31 @@ - + - + - + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java similarity index 70% rename from Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java rename to Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index 4d00770205..e953694d90 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -16,11 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.directorytree; +package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -import java.util.TreeSet; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; @@ -29,28 +31,28 @@ import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.KeyStroke; import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; -/** - * Tag dialog for tagging files and results. User enters an optional comment. - */ -public class TagAndCommentDialog extends JDialog { +public class GetTagNameAndCommentDialog extends JDialog { + private static final String NO_TAG_NAMES_MESSAGE = "No Tags"; + private final HashMap tagNames = new HashMap<>(); + private TagNameAndComment tagNameAndComment = null; - private static final String NO_TAG_MESSAGE = "No Tags"; - private String tagName = ""; - private String comment = ""; - - public static class CommentedTag { - private String name; + public static class TagNameAndComment { + private TagName tagName; private String comment; - CommentedTag(String name, String comment) { - this.name = name; + private TagNameAndComment(TagName tagName, String comment) { + this.tagName = tagName; this.comment = comment; } - public String getName() { - return name; + public TagName getTagName() { + return tagName; } public String getComment() { @@ -58,25 +60,16 @@ public class TagAndCommentDialog extends JDialog { } } - public static CommentedTag doDialog() { - TagAndCommentDialog dialog = new TagAndCommentDialog(); - if (!dialog.tagName.isEmpty()) { - return new CommentedTag(dialog.tagName, dialog.comment); - } - else { - return null; - } + public static TagNameAndComment doDialog() { + GetTagNameAndCommentDialog dialog = new GetTagNameAndCommentDialog(); + return dialog.tagNameAndComment; } - /** - * Creates new form TagDialog - */ - private TagAndCommentDialog() { - super((JFrame)WindowManager.getDefault().getMainWindow(), "Tag and Comment", true); - + private GetTagNameAndCommentDialog() { + super((JFrame)WindowManager.getDefault().getMainWindow(), "Create Tag", true); initComponents(); - // Close the dialog when Esc is pressed + // Set up the dialog to close when Esc is pressed. String cancelName = "cancel"; InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); @@ -87,24 +80,30 @@ public class TagAndCommentDialog extends JDialog { dispose(); } }); - - // get the current list of tag names - TreeSet tags = Tags.getAllTagNames(); - - // if there are no tags, add the NO_TAG_MESSAGE - if (tags.isEmpty()) { - tags.add(NO_TAG_MESSAGE); + + // Populate the combo box with the available tag names and save the + // tag name DTOs to be enable to return the one the user selects. + TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); + List currentTagNames = null; + try { + currentTagNames = tagsManager.getAllTagNames(); } - - // add the tags to the combo box - for (String tag : tags) { - tagCombo.addItem(tag); + catch (TskCoreException ex) { + Logger.getLogger(GetTagNameAndCommentDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + if (null != currentTagNames && currentTagNames.isEmpty()) { + tagCombo.addItem(NO_TAG_NAMES_MESSAGE); + } + else { + for (TagName tagName : currentTagNames) { + tagNames.put(tagName.getDisplayName(), tagName); + tagCombo.addItem(tagName.getDisplayName()); + } } - //center it - this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - - setVisible(true); // blocks + // Center and show the dialog box. + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); } /** @@ -130,30 +129,30 @@ public class TagAndCommentDialog extends JDialog { } }); - org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.okButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.okButton.text")); // NOI18N okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.cancelButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.cancelButton.text")); // NOI18N cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); - tagCombo.setToolTipText(org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.tagCombo.toolTipText")); // NOI18N + tagCombo.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.tagCombo.toolTipText")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(tagLabel, org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.tagLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(tagLabel, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.tagLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.commentLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentLabel.text")); // NOI18N - commentText.setText(org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.commentText.text")); // NOI18N - commentText.setToolTipText(org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.commentText.toolTipText")); // NOI18N + commentText.setText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.text")); // NOI18N + commentText.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.toolTipText")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(newTagButton, org.openide.util.NbBundle.getMessage(TagAndCommentDialog.class, "TagAndCommentDialog.newTagButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newTagButton, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.newTagButton.text")); // NOI18N newTagButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { newTagButtonActionPerformed(evt); @@ -212,27 +211,26 @@ public class TagAndCommentDialog extends JDialog { }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - tagName = (String)tagCombo.getSelectedItem(); - comment = commentText.getText(); + tagNameAndComment = new TagNameAndComment(tagNames.get((String)tagCombo.getSelectedItem()), commentText.getText()); dispose(); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + tagNameAndComment = null; dispose(); }//GEN-LAST:event_cancelButtonActionPerformed - /** - * Closes the dialog - */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog + tagNameAndComment = null; dispose(); }//GEN-LAST:event_closeDialog private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed - String newTagName = CreateTagDialog.getNewTagNameDialog(null); + TagName newTagName = GetTagNameDialog.doDialog(); if (newTagName != null) { - tagCombo.addItem(newTagName); - tagCombo.setSelectedItem(newTagName); + tagNames.put(newTagName.getDisplayName(), newTagName); + tagCombo.addItem(newTagName.getDisplayName()); + tagCombo.setSelectedItem(newTagName.getDisplayName()); } }//GEN-LAST:event_newTagButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.form similarity index 87% rename from Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.form rename to Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.form index 1136325546..a281ea606e 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.form @@ -72,7 +72,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -124,7 +124,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -168,14 +168,14 @@ - + - + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java similarity index 64% rename from Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.java rename to Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index 8fb571cf06..fb0d50ddc4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/CreateTagDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -16,72 +16,130 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.directorytree; +package org.sleuthkit.autopsy.actions; -import java.awt.Dimension; -import java.awt.Toolkit; +import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JOptionPane; +import javax.swing.KeyStroke; import javax.swing.table.AbstractTableModel; import org.openide.util.ImageUtilities; -import org.sleuthkit.autopsy.datamodel.Tags; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; -public class CreateTagDialog extends JDialog { +public class GetTagNameDialog extends JDialog { private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; - private static String newTagName; + private final HashMap tagNames = new HashMap<>(); + private TagName tagName = null; - /** - * Creates new form CreateTagDialog - */ - private CreateTagDialog(JFrame parent) { - super(parent, true); - init(); - } + public static TagName doDialog() { + GetTagNameDialog dialog = new GetTagNameDialog(); + return dialog.tagName; + } - public static String getNewTagNameDialog(JFrame parent) { - new CreateTagDialog(parent); - return newTagName; - } - - private void init() { - - setTitle("Create a new tag"); - + private GetTagNameDialog() { + super((JFrame)WindowManager.getDefault().getMainWindow(), "Create Tag", true); + setIconImage(ImageUtilities.loadImage(TAG_ICON_PATH)); initComponents(); - tagsTable.setModel(new TagsTableModel()); + // Set up the dialog to close when Esc is pressed. + String cancelName = "cancel"; + InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); + ActionMap actionMap = getRootPane().getActionMap(); + actionMap.put(cancelName, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + + // Get the current set of tag names and hash them for a speedy lookup in + // case the user chooses an existing tag name from the tag names table. + TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); + List currentTagNames = null; + try { + currentTagNames = tagsManager.getAllTagNames(); + } + catch (TskCoreException ex) { + Logger.getLogger(GetTagNameDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + if (null != currentTagNames) { + for (TagName name : currentTagNames) { + this.tagNames.put(name.getDisplayName(), name); + } + } + else { + currentTagNames = new ArrayList<>(); + } + + // Populate the tag names table. + tagsTable.setModel(new TagsTableModel(currentTagNames)); tagsTable.setTableHeader(null); - - //completely disable selections tagsTable.setCellSelectionEnabled(false); tagsTable.setFocusable(false); tagsTable.setRowHeight(tagsTable.getRowHeight() + 5); - - setIconImage(ImageUtilities.loadImage(TAG_ICON_PATH)); - - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - // set the popUp window / JFrame - int w = this.getSize().width; - int h = this.getSize().height; - - // set the location of the popUp Window on the center of the screen - setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2); - setVisible(true); //blocks + + // Center and show the dialog box. + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); } private boolean containsIllegalCharacters(String content) { - if ((content.contains("\\") || content.contains(":") || content.contains("*") - || content.contains("?") || content.contains("\"") || content.contains("<") - || content.contains(">") || content.contains("|"))) { - return true; - } - return false; + return (content.contains("\\")|| + content.contains(":") || + content.contains("*") || + content.contains("?") || + content.contains("\"")|| + content.contains("<") || + content.contains(">") || + content.contains("|")); } + private class TagsTableModel extends AbstractTableModel { + private final ArrayList tagNames = new ArrayList<>(); + + TagsTableModel(List tagNames) { + for (TagName tagName : tagNames) { + this.tagNames.add(tagName); + } + } + + @Override + public int getRowCount() { + return tagNames.size(); + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public String getValueAt(int rowIndex, int columnIndex) { + return tagNames.get(rowIndex).getDisplayName(); + } + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -107,14 +165,14 @@ public class CreateTagDialog extends JDialog { } }); - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.cancelButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.cancelButton.text")); // NOI18N cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); - org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.okButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.okButton.text")); // NOI18N okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); @@ -137,13 +195,13 @@ public class CreateTagDialog extends JDialog { tagsTable.setTableHeader(null); jScrollPane1.setViewportView(tagsTable); - org.openide.awt.Mnemonics.setLocalizedText(preexistingLabel, org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.preexistingLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(preexistingLabel, org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.preexistingLabel.text")); // NOI18N - newTagPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.newTagPanel.border.title"))); // NOI18N + newTagPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.newTagPanel.border.title"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(tagNameLabel, org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.tagNameLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(tagNameLabel, org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.tagNameLabel.text")); // NOI18N - tagNameField.setText(org.openide.util.NbBundle.getMessage(CreateTagDialog.class, "CreateTagDialog.tagNameField.text")); // NOI18N + tagNameField.setText(org.openide.util.NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.tagNameField.text")); // NOI18N tagNameField.addKeyListener(new java.awt.event.KeyAdapter() { public void keyReleased(java.awt.event.KeyEvent evt) { tagNameFieldKeyReleased(evt); @@ -211,20 +269,39 @@ public class CreateTagDialog extends JDialog { }// //GEN-END:initComponents private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - newTagName = null; + tagName = null; dispose(); }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - String tagName = tagNameField.getText(); - if (tagName.isEmpty()) { + String tagDisplayName = tagNameField.getText(); + if (tagDisplayName.isEmpty()) { JOptionPane.showMessageDialog(null, "Must supply a tag name to continue.", "Tag Name", JOptionPane.ERROR_MESSAGE); - } else if (containsIllegalCharacters(tagName)) { - JOptionPane.showMessageDialog(null, "The tag name contains illegal characters.\nCannot contain any of the following symbols: \\ : * ? \" < > |", - "Illegal Characters", JOptionPane.ERROR_MESSAGE); - } else { - newTagName = tagName; - dispose(); + } + else if (containsIllegalCharacters(tagDisplayName)) { + JOptionPane.showMessageDialog(null, "The tag name contains illegal characters.\nCannot contain any of the following symbols: \\ : * ? \" < > |", "Illegal Characters", JOptionPane.ERROR_MESSAGE); + } + else { + tagName = tagNames.get(tagDisplayName); + if (tagName == null) { + try { + tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); + dispose(); + } + catch (TskCoreException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); + JOptionPane.showMessageDialog(null, "Unable to add the " + tagDisplayName + " tag name to the case.", "Tagging Error", JOptionPane.ERROR_MESSAGE); + tagName = null; + } + catch (TagsManager.TagNameAlreadyExistsException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); + JOptionPane.showMessageDialog(null, "A " + tagDisplayName + " tag name has already been defined.", "Duplicate Tag Error", JOptionPane.ERROR_MESSAGE); + tagName = null; + } + } + else { + dispose(); + } } }//GEN-LAST:event_okButtonActionPerformed @@ -251,32 +328,5 @@ public class CreateTagDialog extends JDialog { private javax.swing.JTable tagsTable; // End of variables declaration//GEN-END:variables - private class TagsTableModel extends AbstractTableModel { - List tagNames; - - TagsTableModel() { - tagNames = new ArrayList<>(Tags.getAllTagNames()); - } - - @Override - public int getRowCount() { - return tagNames.size(); - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - return false; - } - - @Override - public int getColumnCount() { - return 1; - } - - @Override - public String getValueAt(int rowIndex, int columnIndex) { - return tagNames.get(rowIndex); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/actions/TagAction.java b/Core/src/org/sleuthkit/autopsy/actions/TagAction.java new file mode 100755 index 0000000000..0d6e74efd3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/TagAction.java @@ -0,0 +1,62 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.actions; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Abstract base class for Actions involving tags. + */ +public abstract class TagAction extends AbstractAction { + public TagAction(String menuText) { + super(menuText); + } + + @Override + public void actionPerformed(ActionEvent event) { + doAction(event); + refreshDirectoryTree(); + } + + /** + * Derived classes must implement this Template Method for actionPerformed(). + * @param event ActionEvent object passed to actionPerformed() + */ + abstract protected void doAction(ActionEvent event); + + /** + * Derived classes should call this method any time a tag is created, updated + * or deleted outside of an actionPerformed() call. + */ + protected void refreshDirectoryTree() { + // The way the "directory tree" currently works, a new tags sub-tree + // needs to be made to reflect the results of invoking tag Actions. The + // way to do this is to call DirectoryTreeTopComponent.refreshTree(), + // which calls RootContentChildren.refreshKeys(BlackboardArtifact.ARTIFACT_TYPE... types) + // for the RootContentChildren object that is the child factory for the + // ResultsNode that is the root of the tags sub-tree. There is a switch + // statement in RootContentChildren.refreshKeys() that maps both + // BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE and BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT + // to making a call to refreshKey(TagsNodeKey). + DirectoryTreeTopComponent.findInstance().refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java new file mode 100644 index 0000000000..23025a82c3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -0,0 +1,358 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.casemodule; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.TskException; + +/* + * A background task (swingworker) that adds the given image to + * database using the Sleuthkit JNI interface. + * + * It updates the given ProgressMonitor as it works through adding the image, + * and et the end, calls the specified Callback. + */ +public class AddImageTask implements Runnable { + + private Logger logger = Logger.getLogger(AddImageTask.class.getName()); + + private Case currentCase; + // true if the process was requested to stop + private volatile boolean cancelled = false; + //true if revert has been invoked. + private boolean reverted = false; + private boolean hasCritError = false; + + private volatile boolean addImageDone = false; + + private List errorList = new ArrayList(); + + private DSPProgressMonitor progressMonitor; + private DSPCallback callbackObj; + + private final List newContents = Collections.synchronizedList(new ArrayList()); + + private SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess; + private Thread dirFetcher; + + private String imagePath; + private String dataSourcetype; + String timeZone; + boolean noFatOrphans; + + + /* + * A Swingworker that updates the progressMonitor with the name of the + * directory currently being processed by the AddImageTask + */ + private class CurrentDirectoryFetcher implements Runnable { + + DSPProgressMonitor progressMonitor; + SleuthkitJNI.CaseDbHandle.AddImageProcess process; + + CurrentDirectoryFetcher(DSPProgressMonitor aProgressMonitor, SleuthkitJNI.CaseDbHandle.AddImageProcess proc) { + this.progressMonitor = aProgressMonitor; + this.process = proc; + } + + /** + * @return the currently processing directory + */ + @Override + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + String currDir = process.currentDirectory(); + if (currDir != null) { + if (!currDir.isEmpty() ) { + progressMonitor.setProgressText("Adding: " + currDir); + } + } + Thread.sleep(2 * 1000); + } + return; + } catch (InterruptedException ie) { + return; + } + } + } + + + protected AddImageTask(String imgPath, String tz, boolean noOrphans, DSPProgressMonitor aProgressMonitor, DSPCallback cbObj ) { + + currentCase = Case.getCurrentCase(); + + + this.imagePath = imgPath; + this.timeZone = tz; + this.noFatOrphans = noOrphans; + + this.callbackObj = cbObj; + this.progressMonitor = aProgressMonitor; + } + + /** + * Starts the addImage process, but does not commit the results. + * + * @return + * + * @throws Exception + */ + @Override + public void run() { + + + errorList.clear(); + + //lock DB for writes in this thread + SleuthkitCase.dbWriteLock(); + + addImageProcess = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans); + dirFetcher = new Thread( new CurrentDirectoryFetcher(progressMonitor, addImageProcess)); + + try { + progressMonitor.setIndeterminate(true); + progressMonitor.setProgress(0); + + dirFetcher.start(); + + addImageProcess.run(new String[]{this.imagePath}); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Core errors occurred while running add image. ", ex); + //critical core/system error and process needs to be interrupted + hasCritError = true; + errorList.add(ex.getMessage()); + } catch (TskDataException ex) { + logger.log(Level.WARNING, "Data errors occurred while running add image. ", ex); + errorList.add(ex.getMessage()); + } + finally { + + } + + // handle addImage done + postProcess(); + + // unclock the DB + SleuthkitCase.dbWriteUnlock(); + + return; + } + + /** + * Commit the newly added image to DB + * + * + * @throws Exception if commit or adding the image to the case failed + */ + private void commitImage() throws Exception { + + long imageId = 0; + try { + imageId = addImageProcess.commit(); + } catch (TskException e) { + logger.log(Level.WARNING, "Errors occured while committing the image", e); + errorList.add(e.getMessage()); + } finally { + + if (imageId != 0) { + // get the newly added Image so we can return to caller + Image newImage = currentCase.getSleuthkitCase().getImageById(imageId); + + //while we have the image, verify the size of its contents + String verificationErrors = newImage.verifyImageSize(); + if (verificationErrors.equals("") == false) { + //data error (non-critical) + errorList.add(verificationErrors); + } + + // Add the image to the list of new content + newContents.add(newImage); + } + + logger.log(Level.INFO, "Image committed, imageId: " + imageId); + logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); + } + } + + /** + * Post processing after the addImageProcess is done. + * + */ + private void postProcess() { + + + // cancel the directory fetcher + dirFetcher.interrupt(); + + addImageDone = true; + // attempt actions that might fail and force the process to stop + + if (cancelled || hasCritError) { + logger.log(Level.WARNING, "Critical errors or interruption in add image process. Image will not be comitted."); + revert(); + } + + if (!errorList.isEmpty()) { + logger.log(Level.INFO, "There were errors that occured in add image process"); + } + + + // When everything happens without an error: + if (!(cancelled || hasCritError)) { + + try { + // Tell the progress monitor we're done + progressMonitor.setProgress(100); + + if (newContents.isEmpty()) { + if (addImageProcess != null) { // and if we're done configuring ingest + // commit anything + try { + commitImage(); + } catch (Exception ex) { + errorList.add(ex.getMessage()); + // Log error/display warning + logger.log(Level.SEVERE, "Error adding image to case.", ex); + } + } else { + logger.log(Level.SEVERE, "Missing image process object"); + } + } + + else { //already commited? + logger.log(Level.INFO, "Assuming image already committed, will not commit."); + } + + } catch (Exception ex) { + //handle unchecked exceptions post image add + errorList.add(ex.getMessage()); + + logger.log(Level.WARNING, "Unexpected errors occurred while running post add image cleanup. ", ex); + logger.log(Level.SEVERE, "Error adding image to case", ex); + } finally { + + + } + } + + // invoke the callBack, unless the caller cancelled + if (!cancelled) { + doCallBack(); + } + + return; + } + + + + /* + * Call the callback with results, new content, and errors, if any + */ + private void doCallBack() + { + DSPCallback.DSP_Result result; + + if (hasCritError) { + result = DSPCallback.DSP_Result.CRITICAL_ERRORS; + } + else if (!errorList.isEmpty()) { + result = DSPCallback.DSP_Result.NONCRITICAL_ERRORS; + } + else { + result = DSPCallback.DSP_Result.NO_ERRORS; + } + + // invoke the callcak, passing it the result, list of new contents, and list of errors + callbackObj.done(result, errorList, newContents); + } + + /* + * cancel the image addition, if possible + */ + public void cancelTask() { + + cancelled = true; + + if (!addImageDone) { + try { + interrupt(); + } + catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to interrup the add image task..."); + } + } + else { + try { + revert(); + } + catch(Exception ex) { + logger.log(Level.SEVERE, "Failed to revert the add image task..."); + } + } + } + /* + * Interrurp the add image process if it is still running + */ + private void interrupt() throws Exception { + + try { + logger.log(Level.INFO, "interrupt() add image process"); + addImageProcess.stop(); //it might take time to truly stop processing and writing to db + } catch (TskException ex) { + throw new Exception("Error stopping add-image process.", ex); + } + } + + /* + * Revert - if image has already been added but not committed yet + */ + void revert() { + + if (!reverted) { + try { + logger.log(Level.INFO, "Revert after add image process"); + try { + addImageProcess.revert(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error reverting add image process", ex); + } + } finally { + + } + reverted = true; + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 7b0faf13f8..476bbdd1df 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.casemodule; + import java.awt.Color; +import java.awt.EventQueue; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -27,6 +29,7 @@ import javax.swing.event.ChangeListener; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.Lookup; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; /** * The final panel of the add image wizard. It displays a progress bar and @@ -50,6 +53,49 @@ class AddImageWizardAddingProgressPanel implements WizardDescriptor.FinishablePa private AddImageWizardAddingProgressVisual component; private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 + private DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl(); + + public DSPProgressMonitorImpl getDSPProgressMonitorImpl() { + return dspProgressMonitorImpl; + } + + private class DSPProgressMonitorImpl implements DSPProgressMonitor { + @Override + public void setIndeterminate(final boolean indeterminate) { + // update the progress bar asynchronously + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + getComponent().getProgressBar().setIndeterminate(indeterminate); + } + }); + } + + @Override + public void setProgress(final int progress) { + // update the progress bar asynchronously + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + getComponent().getProgressBar().setValue(progress); + } + }); + } + + @Override + public void setProgressText(final String text) { + // update the progress UI asynchronously + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + getComponent().setProgressMsgText(text); + } + }); + } + + + + } /** * Get the visual component for the panel. In this template, the component * is kept separate. This can be more efficient: if the wizard is created diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.form b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.form index 53febaf0bf..e5e9937501 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.form @@ -40,7 +40,7 @@ - + @@ -136,16 +136,19 @@ - - - - + + + + + + + + + - - - + @@ -153,12 +156,10 @@ - - - + - - + + @@ -185,7 +186,7 @@ - + @@ -196,11 +197,11 @@ - + - + @@ -213,16 +214,6 @@ - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java index a95ad671f2..a37b8cefe3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java @@ -71,7 +71,7 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { donePanel.setVisible(false); viewLogButton.setVisible(false); //match visual background of panel - this.TextArea_CurrentDirectory.setBackground(this.getBackground()); + this.progressTextArea.setBackground(this.getBackground()); } @@ -95,10 +95,10 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { /** * Updates the currently processing directory * - * @param dir the text to update with + * @param msg the text to update with */ - public void setCurrentDirText(String dir) { - this.TextArea_CurrentDirectory.setText(dir); + public void setProgressMsgText(String msg) { + this.progressTextArea.setText(msg); } /** @@ -114,8 +114,10 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { //progressBar.setValue(100); //always invoked when process completed if (hasCriticalErrors) { - statusLabel.setText("*Failed to add image (critical errors encountered). Click below to view the log."); + statusLabel.setForeground(Color.RED); + statusLabel.setText("*Failed to add data source (critical errors encountered). Click below to view the log."); } else { + statusLabel.setForeground(Color.BLACK); statusLabel.setText("*Data Source added (non-critical errors encountered). Click below to view the log."); } @@ -140,8 +142,7 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { inProgressPanel = new javax.swing.JPanel(); progressBar = new javax.swing.JProgressBar(); progressLabel = new javax.swing.JLabel(); - TextArea_CurrentDirectory = new javax.swing.JTextArea(); - subTitle2Label = new javax.swing.JLabel(); + progressTextArea = new javax.swing.JTextArea(); subTitle1Label = new javax.swing.JLabel(); javax.swing.GroupLayout loadingPanelLayout = new javax.swing.GroupLayout(loadingPanel); @@ -193,16 +194,14 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(AddImageWizardAddingProgressVisual.class, "AddImageWizardAddingProgressVisual.progressLabel.text")); // NOI18N progressLabel.setPreferredSize(null); - TextArea_CurrentDirectory.setEditable(false); - TextArea_CurrentDirectory.setBackground(new java.awt.Color(240, 240, 240)); - TextArea_CurrentDirectory.setLineWrap(true); - TextArea_CurrentDirectory.setRows(5); - TextArea_CurrentDirectory.setWrapStyleWord(true); - TextArea_CurrentDirectory.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), org.openide.util.NbBundle.getMessage(AddImageWizardAddingProgressVisual.class, "AddImageWizardAddingProgressVisual.TextArea_CurrentDirectory.border.title"))); // NOI18N - TextArea_CurrentDirectory.setCursor(new java.awt.Cursor(java.awt.Cursor.TEXT_CURSOR)); - TextArea_CurrentDirectory.setFocusable(false); - - org.openide.awt.Mnemonics.setLocalizedText(subTitle2Label, org.openide.util.NbBundle.getMessage(AddImageWizardAddingProgressVisual.class, "AddImageWizardAddingProgressVisual.subTitle2Label.text")); // NOI18N + progressTextArea.setEditable(false); + progressTextArea.setBackground(new java.awt.Color(240, 240, 240)); + progressTextArea.setLineWrap(true); + progressTextArea.setRows(5); + progressTextArea.setWrapStyleWord(true); + progressTextArea.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), org.openide.util.NbBundle.getMessage(AddImageWizardAddingProgressVisual.class, "AddImageWizardAddingProgressVisual.progressTextArea.border.title"))); // NOI18N + progressTextArea.setCursor(new java.awt.Cursor(java.awt.Cursor.TEXT_CURSOR)); + progressTextArea.setFocusable(false); org.openide.awt.Mnemonics.setLocalizedText(subTitle1Label, org.openide.util.NbBundle.getMessage(AddImageWizardAddingProgressVisual.class, "AddImageWizardAddingProgressVisual.subTitle1Label.text")); // NOI18N @@ -210,26 +209,26 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { inProgressPanel.setLayout(inProgressPanelLayout); inProgressPanelLayout.setHorizontalGroup( inProgressPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, inProgressPanelLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 475, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) .addGroup(inProgressPanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(inProgressPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(subTitle2Label) - .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(subTitle1Label, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(TextArea_CurrentDirectory) - .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(0, 8, Short.MAX_VALUE)) + .addGroup(inProgressPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(progressTextArea) + .addComponent(progressLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(subTitle1Label, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) ); inProgressPanelLayout.setVerticalGroup( inProgressPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(inProgressPanelLayout.createSequentialGroup() .addComponent(subTitle1Label) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(subTitle2Label, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(19, 19, 19) .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(TextArea_CurrentDirectory, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(progressTextArea, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) @@ -245,7 +244,7 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { .addComponent(titleLabel) .addComponent(inProgressPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(donePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 69, Short.MAX_VALUE)) + .addGap(0, 67, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -268,15 +267,14 @@ public class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { dialog.setVisible(true); }//GEN-LAST:event_viewLogButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - protected javax.swing.JTextArea TextArea_CurrentDirectory; protected javax.swing.JPanel donePanel; protected javax.swing.JPanel inProgressPanel; private javax.swing.JPanel loadingPanel; private javax.swing.JProgressBar progressBar; protected javax.swing.JLabel progressLabel; + protected javax.swing.JTextArea progressTextArea; protected javax.swing.JLabel statusLabel; protected javax.swing.JLabel subTitle1Label; - protected javax.swing.JLabel subTitle2Label; protected javax.swing.JLabel titleLabel; protected javax.swing.JButton viewLogButton; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java index 39a00baacc..d662439f3b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourcePanel.java @@ -43,6 +43,7 @@ class AddImageWizardChooseDataSourcePanel implements WizardDescriptor.Panel - - - - - - - - - - @@ -65,16 +55,7 @@ - - - - - - - - - - + @@ -89,41 +70,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -160,7 +106,7 @@ - + @@ -192,7 +138,7 @@ - + @@ -204,7 +150,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourceVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourceVisual.java index 40323635da..885b5cced0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourceVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardChooseDataSourceVisual.java @@ -18,49 +18,44 @@ */ package org.sleuthkit.autopsy.casemodule; + import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.Component; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; +import java.util.HashMap; import java.util.List; -import java.util.SimpleTimeZone; -import java.util.TimeZone; -import javax.swing.ComboBoxModel; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; import javax.swing.JPanel; +import javax.swing.JList; +import javax.swing.JSeparator; import javax.swing.event.DocumentEvent; -import javax.swing.event.ListDataListener; -import org.sleuthkit.autopsy.casemodule.ContentTypePanel.ContentType; +import javax.swing.ListCellRenderer; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; /** - * visual component for the first panel of add image wizard. Allows user to pick - * data source and timezone. + * visual component for the first panel of add image wizard. + * Allows the user to choose the data source type and then select the data source * */ final class AddImageWizardChooseDataSourceVisual extends JPanel { - enum EVENT { - - UPDATE_UI, FOCUS_NEXT - }; - static final List rawExt = Arrays.asList(new String[]{".img", ".dd", ".001", ".aa", ".raw"}); - static final String rawDesc = "Raw Images (*.img, *.dd, *.001, *.aa, *.raw)"; - static GeneralFilter rawFilter = new GeneralFilter(rawExt, rawDesc); - static final List encaseExt = Arrays.asList(new String[]{".e01"}); - static final String encaseDesc = "Encase Images (*.e01)"; - static GeneralFilter encaseFilter = new GeneralFilter(encaseExt, encaseDesc); - static final List allExt = new ArrayList(); - - static { - allExt.addAll(rawExt); - allExt.addAll(encaseExt); - } - static final String allDesc = "All Supported Types"; - static GeneralFilter allFilter = new GeneralFilter(allExt, allDesc); + static final Logger logger = Logger.getLogger(AddImageWizardChooseDataSourceVisual.class.getName()); + private AddImageWizardChooseDataSourcePanel wizPanel; - private ContentTypeModel model; - private ContentTypePanel currentPanel; + + private JPanel currentPanel; + private Map datasourceProcessorsMap = new HashMap(); + + + List coreDSPTypes = new ArrayList(); /** * Creates new form AddImageVisualPanel1 @@ -70,24 +65,83 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { AddImageWizardChooseDataSourceVisual(AddImageWizardChooseDataSourcePanel wizPanel) { initComponents(); this.wizPanel = wizPanel; - createTimeZoneList(); + customInit(); } private void customInit() { - model = new ContentTypeModel(); - typeComboBox.setModel(model); - typeComboBox.setSelectedIndex(0); + typePanel.setLayout(new BorderLayout()); - updateCurrentPanel(ImageFilePanel.getDefault()); + + discoverDataSourceProcessors(); + + // set up the DSP type combobox + typeComboBox.removeAllItems(); + + Set dspTypes = datasourceProcessorsMap.keySet(); + + // make a list of core DSPs + // ensure that the core DSPs are at the top and in a fixed order + coreDSPTypes.add(ImageDSProcessor.dsType); + coreDSPTypes.add(LocalDiskDSProcessor.dsType); + coreDSPTypes.add(LocalFilesDSProcessor.dsType); + + for(String dspType:coreDSPTypes){ + typeComboBox.addItem(dspType); + } + + // now add any addtional DSPs that haven't already been added + for(String dspType:dspTypes){ + if (!coreDSPTypes.contains(dspType)) { + typeComboBox.addItem(dspType); + } + } + + // set a custom renderer that draws a separator at the end of the core DSPs in the combobox + typeComboBox.setRenderer(new ComboboxSeparatorRenderer(typeComboBox.getRenderer()){ + @Override + protected boolean addSeparatorAfter(JList list, Object value, int index){ + return (index == coreDSPTypes.size() - 1); + } + }); + + //add actionlistner to listen for change + ActionListener cbActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dspSelectionChanged(); + } + }; + typeComboBox.addActionListener(cbActionListener); + typeComboBox.setSelectedIndex(0); } + private void discoverDataSourceProcessors() { + + for (DataSourceProcessor dsProcessor: Lookup.getDefault().lookupAll(DataSourceProcessor.class)) { + + if (!datasourceProcessorsMap.containsKey(dsProcessor.getType()) ) { + dsProcessor.reset(); + datasourceProcessorsMap.put(dsProcessor.getType(), dsProcessor); + } + else { + logger.log(Level.SEVERE, "discoverDataSourceProcessors(): A DataSourceProcessor already exists for type = " + dsProcessor.getType() ); + } + } + } + + private void dspSelectionChanged() { + // update the current panel to selection + currentPanel = getCurrentDSProcessor().getPanel(); + updateCurrentPanel(currentPanel); + } + /** * Changes the current panel to the given panel. * * @param panel instance of ImageTypePanel to change to */ - private void updateCurrentPanel(ContentTypePanel panel) { + private void updateCurrentPanel(JPanel panel) { currentPanel = panel; typePanel.removeAll(); typePanel.add((JPanel) currentPanel, BorderLayout.CENTER); @@ -96,28 +150,32 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { currentPanel.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString())) { + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString())) { updateUI(null); } - if (evt.getPropertyName().equals(AddImageWizardChooseDataSourceVisual.EVENT.FOCUS_NEXT.toString())) { + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString())) { wizPanel.moveFocusToNext(); } } }); - currentPanel.select(); - if (currentPanel.getContentType().equals(ContentType.LOCAL)) { - //disable image specific options - noFatOrphansCheckbox.setEnabled(false); - descLabel.setEnabled(false); - timeZoneComboBox.setEnabled(false); - } else { - noFatOrphansCheckbox.setEnabled(true); - descLabel.setEnabled(true); - timeZoneComboBox.setEnabled(true); - } + updateUI(null); } + /** + * Returns the currently selected DS Processor + * @return DataSourceProcessor the DataSourceProcessor corresponding to the data source type selected in the combobox + */ + public DataSourceProcessor getCurrentDSProcessor() { + // get the type of the currently selected panel and then look up + // the correspodning DS Handler in the map + String dsType = (String) typeComboBox.getSelectedItem(); + DataSourceProcessor dsProcessor = datasourceProcessorsMap.get(dsType); + + return dsProcessor; + + } + /** * Returns the name of the this panel. This name will be shown on the left * panel of the "Add Image" wizard panel. @@ -129,94 +187,6 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { return "Enter Data Source Information"; } - /** - * Gets the data sources path from the Image Path Text Field. - * - * @return data source path, can be comma separated for multiples - */ - public String getContentPaths() { - return currentPanel.getContentPaths(); - } - - /** - * Gets the data sources type selected - * - * @return data source selected - */ - public ContentType getContentType() { - return currentPanel.getContentType(); - } - - /** - * Reset the data sources panel selected - */ - public void reset() { - currentPanel.reset(); - } - - /** - * Sets the image path of the current panel. - * - * @param s the image path to set - */ - public void setContentPath(String s) { - currentPanel.setContentPath(s); - } - - /** - * - * @return true if no fat orphans processing is selected - */ - boolean getNoFatOrphans() { - return noFatOrphansCheckbox.isSelected(); - } - - /** - * Gets the time zone that selected on the drop down list. - * - * @return timeZone the time zone that selected - */ - public String getSelectedTimezone() { - String tz = timeZoneComboBox.getSelectedItem().toString(); - return tz.substring(tz.indexOf(")") + 2).trim(); - } - - // add the timeZone list to the timeZoneComboBox - /** - * Creates the drop down list for the time zones and then makes the local - * machine time zones to be selected. - */ - public void createTimeZoneList() { - // load and add all timezone - String[] ids = SimpleTimeZone.getAvailableIDs(); - for (String id : ids) { - TimeZone zone = TimeZone.getTimeZone(id); - int offset = zone.getRawOffset() / 1000; - int hour = offset / 3600; - int minutes = (offset % 3600) / 60; - String item = String.format("(GMT%+d:%02d) %s", hour, minutes, id); - - /* - * DateFormat dfm = new SimpleDateFormat("z"); - * dfm.setTimeZone(zone); boolean hasDaylight = - * zone.useDaylightTime(); String first = dfm.format(new Date(2010, - * 1, 1)); String second = dfm.format(new Date(2011, 6, 6)); int mid - * = hour * -1; String result = first + Integer.toString(mid); - * if(hasDaylight){ result = result + second; } - * timeZoneComboBox.addItem(item + " (" + result + ")"); - */ - timeZoneComboBox.addItem(item); - } - // get the current timezone - TimeZone thisTimeZone = Calendar.getInstance().getTimeZone(); - int thisOffset = thisTimeZone.getRawOffset() / 1000; - int thisHour = thisOffset / 3600; - int thisMinutes = (thisOffset % 3600) / 60; - String formatted = String.format("(GMT%+d:%02d) %s", thisHour, thisMinutes, thisTimeZone.getID()); - - // set the selected timezone - timeZoneComboBox.setSelectedItem(formatted); - } /** * This method is called from within the constructor to initialize the form. @@ -229,14 +199,10 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { buttonGroup1 = new javax.swing.ButtonGroup(); jLabel2 = new javax.swing.JLabel(); nextLabel = new javax.swing.JLabel(); - timeZoneLabel = new javax.swing.JLabel(); - timeZoneComboBox = new javax.swing.JComboBox(); - noFatOrphansCheckbox = new javax.swing.JCheckBox(); - descLabel = new javax.swing.JLabel(); inputPanel = new javax.swing.JPanel(); typeTabel = new javax.swing.JLabel(); typePanel = new javax.swing.JPanel(); - typeComboBox = new javax.swing.JComboBox(); + typeComboBox = new javax.swing.JComboBox(); imgInfoLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.jLabel2.text")); // NOI18N @@ -245,15 +211,6 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(nextLabel, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.nextLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.timeZoneLabel.text")); // NOI18N - - timeZoneComboBox.setMaximumRowCount(30); - - org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.noFatOrphansCheckbox.text")); // NOI18N - noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.noFatOrphansCheckbox.toolTipText")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.descLabel.text")); // NOI18N - inputPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); org.openide.awt.Mnemonics.setLocalizedText(typeTabel, org.openide.util.NbBundle.getMessage(AddImageWizardChooseDataSourceVisual.class, "AddImageWizardChooseDataSourceVisual.typeTabel.text")); // NOI18N @@ -269,7 +226,7 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { ); typePanelLayout.setVerticalGroup( typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 77, Short.MAX_VALUE) + .addGap(0, 173, Short.MAX_VALUE) ); javax.swing.GroupLayout inputPanelLayout = new javax.swing.GroupLayout(inputPanel); @@ -295,7 +252,7 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { .addComponent(typeTabel) .addComponent(typeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 77, Short.MAX_VALUE) + .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE) .addContainerGap()) ); @@ -313,14 +270,6 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(nextLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel) - .addGap(18, 18, 18) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 252, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(noFatOrphansCheckbox) - .addGroup(layout.createSequentialGroup() - .addGap(21, 21, 21) - .addComponent(descLabel)) .addComponent(imgInfoLabel)) .addGap(0, 54, Short.MAX_VALUE))) .addContainerGap()) @@ -332,30 +281,18 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { .addComponent(imgInfoLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(inputPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(timeZoneLabel) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(noFatOrphansCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(descLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 64, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 45, Short.MAX_VALUE) .addComponent(nextLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; - private javax.swing.JLabel descLabel; private javax.swing.JLabel imgInfoLabel; private javax.swing.JPanel inputPanel; private javax.swing.JLabel jLabel2; private javax.swing.JLabel nextLabel; - private javax.swing.JCheckBox noFatOrphansCheckbox; - private javax.swing.JComboBox timeZoneComboBox; - private javax.swing.JLabel timeZoneLabel; - private javax.swing.JComboBox typeComboBox; + private javax.swing.JComboBox typeComboBox; private javax.swing.JPanel typePanel; private javax.swing.JLabel typeTabel; // End of variables declaration//GEN-END:variables @@ -369,44 +306,31 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel { * @param e the document event */ public void updateUI(DocumentEvent e) { - this.wizPanel.enableNextButton(currentPanel.enableNext()); + // Enable the Next button if the current DSP panel is valid + this.wizPanel.enableNextButton(getCurrentDSProcessor().validatePanel()); } - /** - * ComboBoxModel to control typeComboBox and supply ImageTypePanels. - */ - private class ContentTypeModel implements ComboBoxModel { + + public abstract class ComboboxSeparatorRenderer implements ListCellRenderer{ + private ListCellRenderer delegate; + private JPanel separatorPanel = new JPanel(new BorderLayout()); + private JSeparator separator = new JSeparator(); - private ContentTypePanel selected; - private ContentTypePanel[] types = ContentTypePanel.getPanels(); - - @Override - public void setSelectedItem(Object anItem) { - selected = (ContentTypePanel) anItem; - updateCurrentPanel(selected); + public ComboboxSeparatorRenderer(ListCellRenderer delegate){ + this.delegate = delegate; } - @Override - public Object getSelectedItem() { - return selected; + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){ + Component comp = delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if(index!=-1 && addSeparatorAfter(list, value, index)){ + separatorPanel.removeAll(); + separatorPanel.add(comp, BorderLayout.CENTER); + separatorPanel.add(separator, BorderLayout.SOUTH); + return separatorPanel; + }else + return comp; } - @Override - public int getSize() { - return types.length; - } - - @Override - public ContentTypePanel getElementAt(int index) { - return types[index]; - } - - @Override - public void addListDataListener(ListDataListener l) { - } - - @Override - public void removeListDataListener(ListDataListener l) { - } + protected abstract boolean addSeparatorAfter(JList list, Object value, int index); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java index 3f5b77e281..a83d4549dc 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java @@ -18,41 +18,25 @@ */ package org.sleuthkit.autopsy.casemodule; + import org.sleuthkit.autopsy.ingest.IngestConfigurator; import java.awt.Color; import java.awt.Component; -import java.awt.EventQueue; import java.awt.Window; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.logging.Level; import javax.swing.JButton; import javax.swing.JOptionPane; -import javax.swing.JProgressBar; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import javax.swing.event.ChangeListener; import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.Lookup; -import org.sleuthkit.autopsy.casemodule.ContentTypePanel.ContentType; -import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.FileSystem; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskDataException; -import org.sleuthkit.datamodel.TskException; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.datamodel.VolumeSystem; - +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; /** * second panel of add image wizard, allows user to configure ingest modules. * @@ -68,28 +52,27 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel newContents = Collections.synchronizedList(new ArrayList()); private boolean ingested = false; private boolean readyToIngest = false; - // the paths of the image files to be added - private String dataSourcePath; - private ContentType dataSourceType; - // the time zone where the image is added - private String timeZone; - //whether to not process FAT filesystem orphans - private boolean noFatOrphans; + // task that will clean up the created database file if the wizard is cancelled before it finishes - private AddImageAction.CleanupTask cleanupImage; // initialized to null in readSettings() - private CurrentDirectoryFetcher fetcher; - private AddImageProcess process; - private AddImageAction action; - private AddImageTask addImageTask; - private AddLocalFilesTask addLocalFilesTask; + private AddImageAction.CleanupTask cleanupTask; + + private AddImageAction addImageAction; + private AddImageWizardAddingProgressPanel progressPanel; + private AddImageWizardChooseDataSourcePanel dataSourcePanel; + + private DataSourceProcessor dsProcessor; + - AddImageWizardIngestConfigPanel(AddImageAction action, AddImageWizardAddingProgressPanel proPanel) { - this.action = action; + AddImageWizardIngestConfigPanel(AddImageWizardChooseDataSourcePanel dsPanel, AddImageAction action, AddImageWizardAddingProgressPanel proPanel) { + this.addImageAction = action; this.progressPanel = proPanel; + this.dataSourcePanel = dsPanel; + ingestConfig = Lookup.getDefault().lookup(IngestConfigurator.class); List messages = ingestConfig.setContext(AddImageWizardIngestConfigPanel.class.getCanonicalName()); if (messages.isEmpty() == false) { @@ -183,24 +166,14 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel { - - AddImageTask task; - JProgressBar prog; - AddImageWizardAddingProgressVisual wiz; - AddImageProcess proc; - - CurrentDirectoryFetcher(JProgressBar prog, AddImageWizardAddingProgressVisual wiz, AddImageProcess proc) { - this.wiz = wiz; - this.proc = proc; - this.prog = prog; - } - - /** - * @return the currently processing directory - */ - @Override - protected Integer doInBackground() { - try { - while (prog.getValue() < 100 || prog.isIndeterminate()) { //TODO Rely on state variable in AddImgTask class - - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - wiz.setCurrentDirText(proc.currentDirectory()); - } - }); - - Thread.sleep(2 * 1000); - } - return 1; - } catch (InterruptedException ie) { - return -1; - } - } - } - - /** - * Thread that will add logical files to database, and then kick-off ingest - * modules. Note: the add logical files task cannot currently be reverted as - * the add image task can. This is a separate task from AddImgTask because - * it is much simpler and does not require locks, since the underlying file - * manager methods acquire the locks for each transaction when adding - * logical files. - */ - private class AddLocalFilesTask extends SwingWorker { - - private JProgressBar progressBar; - private Case currentCase; - // true if the process was requested to stop - private boolean interrupted = false; - private boolean hasCritError = false; - private String errorString = null; - private WizardDescriptor settings; - private Logger logger = Logger.getLogger(AddLocalFilesTask.class.getName()); - - protected AddLocalFilesTask(WizardDescriptor settings) { - this.progressBar = progressPanel.getComponent().getProgressBar(); - currentCase = Case.getCurrentCase(); - this.settings = settings; - } - - /** - * Starts the addImage process, but does not commit the results. - * - * @return - * - * @throws Exception - */ - @Override - protected Integer doInBackground() { - this.setProgress(0); - // Add a cleanup task to interupt the backgroud process if the - // wizard exits while the background process is running. - AddImageAction.CleanupTask cancelledWhileRunning = action.new CleanupTask() { - @Override - void cleanup() throws Exception { - logger.log(Level.INFO, "Add logical files process interrupted."); - //nothing to be cleanedup - } - }; - - cancelledWhileRunning.enable(); - final LocalFilesAddProgressUpdater progUpdater = new LocalFilesAddProgressUpdater(this.progressBar, progressPanel.getComponent()); - try { - final FileManager fileManager = currentCase.getServices().getFileManager(); - progressPanel.setStateStarted(); - String[] paths = dataSourcePath.split(LocalFilesPanel.FILES_SEP); - List absLocalPaths = new ArrayList(); - for (String path : paths) { - absLocalPaths.add(path); - } - newContents.add(fileManager.addLocalFilesDirs(absLocalPaths, progUpdater)); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Errors occurred while running add logical files. ", ex); - hasCritError = true; - errorString = ex.getMessage(); - } finally { - // process is over, doesn't need to be dealt with if cancel happens - cancelledWhileRunning.disable(); - //enqueue what would be in done() to EDT thread - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - postProcess(); - } - }); - } - return 0; - } - - /** - * - * (called by EventDispatch Thread after doInBackground finishes) - */ - protected void postProcess() { - progressBar.setIndeterminate(false); - setProgress(100); - - //clear updates - // progressPanel.getComponent().setProcessInvis(); - - if (interrupted || hasCritError) { - logger.log(Level.INFO, "Handling errors or interruption that occured in logical files process"); - if (hasCritError) { - //core error - progressPanel.getComponent().showErrors(errorString, true); - } - return; - } else { - if (errorString != null) { - //data error (non-critical) - logger.log(Level.INFO, "Handling non-critical errors that occured in logical files process"); - progressPanel.getComponent().showErrors(errorString, false); - } - } - try { - // When everything happens without an error: - if (errorString == null) { // complete progress bar - progressPanel.getComponent().setProgressBarTextAndColor("*Logical Files added.", 100, Color.black); - } - - // Get attention for the process finish - java.awt.Toolkit.getDefaultToolkit().beep(); //BEEP! - AddImageWizardAddingProgressVisual panel = progressPanel.getComponent(); - if (panel != null) { - Window w = SwingUtilities.getWindowAncestor(panel); - if (w != null) { - w.toFront(); - } - } - - progressPanel.setStateFinished(); - - //notify the case - if (!newContents.isEmpty()) { - Case.getCurrentCase().addLocalDataSource(newContents.get(0)); - } - - // Start ingest if we can - startIngest(); - - } catch (Exception ex) { - //handle unchecked exceptions - logger.log(Level.WARNING, "Unexpected errors occurred while running post add image cleanup. ", ex); - progressPanel.getComponent().setProgressBarTextAndColor("*Failed to add image.", 0, Color.black); // set error message - logger.log(Level.SEVERE, "Error adding image to case", ex); - } - } - - /** - * Updates the wizard status with logical file/folder - */ - private class LocalFilesAddProgressUpdater implements FileManager.FileAddProgressUpdater { - - private int count = 0; - private JProgressBar prog; - private AddImageWizardAddingProgressVisual wiz; - - LocalFilesAddProgressUpdater(JProgressBar prog, AddImageWizardAddingProgressVisual wiz) { - this.wiz = wiz; - this.prog = prog; - } - + private void startDataSourceProcessing(WizardDescriptor settings) { + + + + // Add a cleanup task to interrupt the background process if the + // wizard exits while the background process is running. + cleanupTask = addImageAction.new CleanupTask() { @Override - public void fileAdded(final AbstractFile newFile) { - if (count++ % 10 == 0 && (prog.getValue() < 100 || prog.isIndeterminate())) { - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - wiz.setCurrentDirText(newFile.getParentPath() + "/" + newFile.getName()); - } - }); - - } + void cleanup() throws Exception { + cancelDataSourceProcessing(); } - } + }; + + cleanupTask.enable(); + + // get the selected DSProcessor + dsProcessor = dataSourcePanel.getComponent().getCurrentDSProcessor(); + + DSPCallback cbObj = new DSPCallback () { + @Override + public void doneEDT(DSPCallback.DSP_Result result, List errList, List contents) { + dataSourceProcessorDone(result, errList, contents ); + } + + }; + + progressPanel.setStateStarted(); + + // Kick off the DSProcessor + dsProcessor.run(progressPanel.getDSPProgressMonitorImpl(), cbObj); + } - /** - * Thread that will make the JNI call to add image to database, and then - * kick-off ingest modules. + /* + * Cancels the data source processing - in case the users presses 'Cancel' */ - private class AddImageTask extends SwingWorker { - - private JProgressBar progressBar; - private Case currentCase; - // true if the process was requested to stop - private boolean interrupted = false; - private boolean hasCritError = false; - private String errorString = null; - private WizardDescriptor wizDescriptor; - private Logger logger = Logger.getLogger(AddImageTask.class.getName()); - - protected AddImageTask(WizardDescriptor settings) { - this.progressBar = progressPanel.getComponent().getProgressBar(); - currentCase = Case.getCurrentCase(); - this.wizDescriptor = settings; - } - - /** - * Starts the addImage process, but does not commit the results. - * - * @return - * - * @throws Exception - */ - @Override - protected Integer doInBackground() { - - this.setProgress(0); - - - // Add a cleanup task to interupt the backgroud process if the - // wizard exits while the background process is running. - AddImageAction.CleanupTask cancelledWhileRunning = action.new CleanupTask() { - @Override - void cleanup() throws Exception { - logger.log(Level.INFO, "Add image process interrupted."); - addImageTask.interrupt(); //it might take time to truly interrupt - } - }; - - - try { - //lock DB for writes in EWT thread - //wait until lock acquired in EWT - EventQueue.invokeAndWait(new Runnable() { - @Override - public void run() { - SleuthkitCase.dbWriteLock(); - } - }); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, "Errors occurred while running add image, could not acquire lock. ", ex); - return 0; - - } catch (InvocationTargetException ex) { - logger.log(Level.WARNING, "Errors occurred while running add image, could not acquire lock. ", ex); - return 0; - } - - process = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans); - fetcher = new CurrentDirectoryFetcher(this.progressBar, progressPanel.getComponent(), process); - cancelledWhileRunning.enable(); - try { - progressPanel.setStateStarted(); - fetcher.execute(); - process.run(new String[]{dataSourcePath}); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Core errors occurred while running add image. ", ex); - //critical core/system error and process needs to be interrupted - hasCritError = true; - errorString = ex.getMessage(); - } catch (TskDataException ex) { - logger.log(Level.WARNING, "Data errors occurred while running add image. ", ex); - errorString = ex.getMessage(); - } finally { - // process is over, doesn't need to be dealt with if cancel happens - cancelledWhileRunning.disable(); - - } - - return 0; - } - - /** - * Commit the finished AddImageProcess, and cancel the CleanupTask that - * would have reverted it. - * - * @param settings property set to get AddImageProcess and CleanupTask - * from - * - * @throws Exception if commit or adding the image to the case failed - */ - private void commitImage(WizardDescriptor settings) throws Exception { - - String contentPath = (String) settings.getProperty(AddImageAction.DATASOURCEPATH_PROP); - - String timezone = settings.getProperty(AddImageAction.TIMEZONE_PROP).toString(); - settings.putProperty(AddImageAction.IMAGEID_PROP, ""); - - long imageId = 0; - try { - imageId = process.commit(); - } catch (TskException e) { - logger.log(Level.WARNING, "Errors occured while committing the image", e); - } finally { - //commit done, unlock db write in EWT thread - //before doing anything else - SleuthkitCase.dbWriteUnlock(); - - if (imageId != 0) { - Image newImage = Case.getCurrentCase().addImage(contentPath, imageId, timezone); - - //while we have the image, verify the size of its contents - String verificationErrors = newImage.verifyImageSize(); - if (verificationErrors.equals("") == false) { - //data error (non-critical) - progressPanel.addErrors(verificationErrors, false); - } - - - newContents.add(newImage); - settings.putProperty(AddImageAction.IMAGEID_PROP, imageId); - } - - // Can't bail and revert image add after commit, so disable image cleanup - // task - cleanupImage.disable(); - settings.putProperty(AddImageAction.IMAGECLEANUPTASK_PROP, null); - - logger.log(Level.INFO, "Image committed, imageId: " + imageId); - logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); - + private void cancelDataSourceProcessing() { + dsProcessor.cancel(); + } + + /* + * Callback for the data source processor. + * Invoked by the DSP on the EDT thread, when it finishes processing the data source. + */ + private void dataSourceProcessorDone(DSPCallback.DSP_Result result, List errList, List contents) { + + // disable the cleanup task + cleanupTask.disable(); + + // Get attention for the process finish + java.awt.Toolkit.getDefaultToolkit().beep(); //BEEP! + AddImageWizardAddingProgressVisual panel = progressPanel.getComponent(); + if (panel != null) { + Window w = SwingUtilities.getWindowAncestor(panel); + if (w != null) { + w.toFront(); } } - - /** - * - * (called by EventDispatch Thread after doInBackground finishes) - */ - @Override - protected void done() { - //these are required to stop the CurrentDirectoryFetcher - progressBar.setIndeterminate(false); - setProgress(100); - - // attempt actions that might fail and force the process to stop - - if (interrupted || hasCritError) { - logger.log(Level.INFO, "Handling errors or interruption that occured in add image process"); - revert(); - if (hasCritError) { - //core error - progressPanel.addErrors(errorString, true); - } - return; - } - if (errorString != null) { - //data error (non-critical) - logger.log(Level.INFO, "Handling non-critical errors that occured in add image process"); - progressPanel.addErrors(errorString, false); - } - - - try { - // When everything happens without an error: - - // the add-image process needs to be reverted if the wizard doesn't finish - cleanupImage = action.new CleanupTask() { - //note, CleanupTask runs inside EWT thread - @Override - void cleanup() throws Exception { - logger.log(Level.INFO, "Running cleanup task after add image process"); - revert(); - } - }; - cleanupImage.enable(); - - if (errorString == null) { // complete progress bar - progressPanel.getComponent().setProgressBarTextAndColor("*Data Source added.", 100, Color.black); - } - - // Get attention for the process finish - java.awt.Toolkit.getDefaultToolkit().beep(); //BEEP! - AddImageWizardAddingProgressVisual panel = progressPanel.getComponent(); - if (panel != null) { - Window w = SwingUtilities.getWindowAncestor(panel); - if (w != null) { - w.toFront(); - } - } - - // Tell the panel we're done - progressPanel.setStateFinished(); - - // Commit the image - if (!newContents.isEmpty()) //already commited - { - logger.log(Level.INFO, "Assuming image already committed, will not commit."); - return; - } - - if (process != null) { // and if we're done configuring ingest - // commit anything - try { - commitImage(wizDescriptor); - } catch (Exception ex) { - // Log error/display warning - logger.log(Level.SEVERE, "Error adding image to case.", ex); - } - } else { - logger.log(Level.SEVERE, "Missing image process object"); - } - - - - - // Start ingest if we can - startIngest(); - - } catch (Exception ex) { - //handle unchecked exceptions post image add - - logger.log(Level.WARNING, "Unexpected errors occurred while running post add image cleanup. ", ex); - - progressPanel.getComponent().setProgressBarTextAndColor("*Failed to add image.", 0, Color.black); // set error message - - // Log error/display warning - - logger.log(Level.SEVERE, "Error adding image to case", ex); - } finally { - } + // Tell the panel we're done + progressPanel.setStateFinished(); + + + //check the result and display to user + if (result == DSPCallback.DSP_Result.NO_ERRORS) + progressPanel.getComponent().setProgressBarTextAndColor("*Data Source added.", 100, Color.black); + else + progressPanel.getComponent().setProgressBarTextAndColor("*Errors encountered in adding Data Source.", 100, Color.red); + + + //if errors, display them on the progress panel + boolean critErr = false; + if (result == DSPCallback.DSP_Result.CRITICAL_ERRORS) { + critErr = true; + } + for ( String err: errList ) { + // TBD: there probably should be an error level for each error + progressPanel.addErrors(err, critErr); + } + + newContents.clear(); + newContents.addAll(contents); + + //notify the UI of the new content added to the case + if (!newContents.isEmpty()) { + + Case.getCurrentCase().notifyNewDataSource(newContents.get(0)); } - void interrupt() throws Exception { - interrupted = true; - try { - logger.log(Level.INFO, "interrupt() add image process"); - process.stop(); //it might take time to truly stop processing and writing to db - } catch (TskException ex) { - throw new Exception("Error stopping add-image process.", ex); - } - } - - //runs in EWT - void revert() { - try { - logger.log(Level.INFO, "Revert after add image process"); - try { - process.revert(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error reverting add image process", ex); - } - } finally { - //unlock db write within EWT thread - SleuthkitCase.dbWriteUnlock(); - } - } + + // Start ingest if we can + progressPanel.setStateStarted(); + startIngest(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java index 2528f0b582..b786b5b171 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java @@ -47,11 +47,16 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator> getPanels() { if (panels == null) { - AddImageWizardAddingProgressPanel wizPanel = new AddImageWizardAddingProgressPanel(); panels = new ArrayList>(); - panels.add(new AddImageWizardChooseDataSourcePanel()); - panels.add(new AddImageWizardIngestConfigPanel(action, wizPanel)); - panels.add(wizPanel); + + AddImageWizardAddingProgressPanel progressPanel = new AddImageWizardAddingProgressPanel(); + + AddImageWizardChooseDataSourcePanel dsPanel = new AddImageWizardChooseDataSourcePanel(progressPanel); + AddImageWizardIngestConfigPanel ingestConfigPanel = new AddImageWizardIngestConfigPanel(dsPanel, action, progressPanel); + + panels.add(dsPanel); + panels.add(ingestConfigPanel); + panels.add(progressPanel); String[] steps = new String[panels.size()]; for (int i = 0; i < panels.size(); i++) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java new file mode 100644 index 0000000000..34b7a32089 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java @@ -0,0 +1,185 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Thread that will add logical files to database, and then kick-off ingest + * modules. Note: the add logical files task cannot currently be reverted as + * the add image task can. This is a separate task from AddImgTask because + * it is much simpler and does not require locks, since the underlying file + * manager methods acquire the locks for each transaction when adding + * logical files. + */ +public class AddLocalFilesTask implements Runnable { + + private Logger logger = Logger.getLogger(AddLocalFilesTask.class.getName()); + + private String dataSourcePath; + private DSPProgressMonitor progressMonitor; + private DSPCallback callbackObj; + + private Case currentCase; + // true if the process was requested to stop + private volatile boolean cancelled = false; + private boolean hasCritError = false; + + private List errorList = new ArrayList(); + private final List newContents = Collections.synchronizedList(new ArrayList()); + + + protected AddLocalFilesTask(String dataSourcePath, DSPProgressMonitor aProgressMonitor, DSPCallback cbObj) { + + currentCase = Case.getCurrentCase(); + + this.dataSourcePath = dataSourcePath; + this.callbackObj = cbObj; + this.progressMonitor = aProgressMonitor; + } + + /** + * Starts the addImage process, but does not commit the results. + * + * @return + * + * @throws Exception + */ + @Override + public void run() { + + errorList.clear(); + + final LocalFilesAddProgressUpdater progUpdater = new LocalFilesAddProgressUpdater(progressMonitor); + try { + + progressMonitor.setIndeterminate(true); + progressMonitor.setProgress(0); + + final FileManager fileManager = currentCase.getServices().getFileManager(); + String[] paths = dataSourcePath.split(LocalFilesPanel.FILES_SEP); + List absLocalPaths = new ArrayList(); + for (String path : paths) { + absLocalPaths.add(path); + } + newContents.add(fileManager.addLocalFilesDirs(absLocalPaths, progUpdater)); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Errors occurred while running add logical files. ", ex); + hasCritError = true; + errorList.add(ex.getMessage()); + } finally { + + } + + // handle done + postProcess(); + + return; + } + + /** + * + * (called by EventDispatch Thread after doInBackground finishes) + */ + protected void postProcess() { + + if (cancelled || hasCritError) { + logger.log(Level.WARNING, "Handling errors or interruption that occured in logical files process"); + + } + if (!errorList.isEmpty()) { + //data error (non-critical) + logger.log(Level.WARNING, "Handling non-critical errors that occured in logical files process"); + } + + if (!(cancelled || hasCritError)) { + progressMonitor.setProgress(100); + progressMonitor.setIndeterminate(false); + } + + // invoke the callBack, unless the caller cancelled + if (!cancelled) { + doCallBack(); + } + + } + + /* + * Call the callback with results, new content, and errors, if any + */ + private void doCallBack() + { + DSPCallback.DSP_Result result; + + if (hasCritError) { + result = DSPCallback.DSP_Result.CRITICAL_ERRORS; + } + else if (!errorList.isEmpty()) { + result = DSPCallback.DSP_Result.NONCRITICAL_ERRORS; + } + else { + result = DSPCallback.DSP_Result.NO_ERRORS; + } + + // invoke the callback, passing it the result, list of new contents, and list of errors + callbackObj.done(result, errorList, newContents); + } + + /* + * cancel the files addition, if possible + */ + public void cancelTask() { + cancelled = true; + + } + + /** + * Updates the wizard status with logical file/folder + */ + private class LocalFilesAddProgressUpdater implements FileManager.FileAddProgressUpdater { + + private int count = 0; + private DSPProgressMonitor progressMonitor; + + + LocalFilesAddProgressUpdater(DSPProgressMonitor progressMonitor) { + + this.progressMonitor = progressMonitor; + } + + @Override + public void fileAdded(final AbstractFile newFile) { + if (count++ % 10 == 0) { + progressMonitor.setProgressText("Adding: " + newFile.getParentPath() + "/" + newFile.getName()); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index ffb3261dd5..60d1f4e228 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -115,7 +115,6 @@ ImageFilePanel.browseButton.text=Browse ImageFilePanel.pathTextField.text= LocalDiskPanel.diskLabel.text=Select a local disk: MissingImageDialog.selectButton.text=Select Image -MissingImageDialog.typeTabel.text=Select input type to add: MissingImageDialog.titleLabel.text=Search for missing image MissingImageDialog.cancelButton.text=Cancel LocalDiskPanel.errorLabel.text=Error Label @@ -133,18 +132,23 @@ LocalFilesPanel.localFileChooser.approveButtonToolTipText= LocalFilesPanel.selectButton.actionCommand=Add AddImageWizardIngestConfigVisual.subtitleLabel.text=Configure the ingest modules you would like to run on this data source. AddImageWizardIngestConfigVisual.titleLabel.text=Configure Ingest Modules -AddImageWizardAddingProgressVisual.statusLabel.text=File system has been added to the local database. Files are being analyzed. -AddImageWizardChooseDataSourceVisual.noFatOrphansCheckbox.toolTipText= -AddImageWizardChooseDataSourceVisual.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems -AddImageWizardChooseDataSourceVisual.descLabel.text=(faster results, although some data will not be searched) +AddImageWizardAddingProgressVisual.statusLabel.text=Data source has been added to the local database. Files are being analyzed. AddImageWizardChooseDataSourceVisual.typeTabel.text=Select source type to add: AddImageWizardChooseDataSourceVisual.jLabel2.text=jLabel2 -AddImageWizardChooseDataSourceVisual.timeZoneLabel.text=Please select the input timezone: AddImageWizardChooseDataSourceVisual.nextLabel.text= Press 'Next' to analyze the input data, extract volume and file system data, and populate a local database. AddImageWizardChooseDataSourceVisual.imgInfoLabel.text=Enter Data Source Information: AddImageWizardAddingProgressVisual.progressLabel.text= -AddImageWizardAddingProgressVisual.TextArea_CurrentDirectory.border.title=Currently Adding: AddImageWizardAddingProgressVisual.viewLogButton.text=View Log AddImageWizardAddingProgressVisual.titleLabel.text=Adding Data Source -AddImageWizardAddingProgressVisual.subTitle1Label.text=File system information is being added to a local database. File analysis will start when this finishes. -AddImageWizardAddingProgressVisual.subTitle2Label.text=Processing Data Source and Adding to Database +AddImageWizardAddingProgressVisual.subTitle1Label.text=Processing data source and adding it to a local database. File analysis will start when this finishes. +ImageFilePanel.timeZoneLabel.text=Please select the input timezone: +ImageFilePanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems +ImageFilePanel.noFatOrphansCheckbox.toolTipText= +ImageFilePanel.descLabel.text=(faster results, although some data will not be searched) +LocalDiskPanel.timeZoneLabel.text=Please select the input timezone: +LocalDiskPanel.noFatOrphansCheckbox.toolTipText= +LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems +LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched) +MissingImageDialog.browseButton.text=Browse +MissingImageDialog.pathNameTextField.text= +AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 08ec802b95..2caf6cbf78 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -384,7 +384,9 @@ public class Case implements SleuthkitCase.ErrorObserver { + "\nPlease note that you will still be able to browse directories and generate reports\n" + "if you choose No, but you will not be able to view file content or run the ingest process.", "Missing Image", JOptionPane.YES_NO_OPTION); if (ret == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, db); + } else { logger.log(Level.WARNING, "Selected image files don't match old files!"); } @@ -401,6 +403,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * @param imgId the ID of the image that being added * @param timeZone the timeZone of the image where it's added */ + @Deprecated public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException { logger.log(Level.INFO, "Adding image to Case. imgPath: {0} ID: {1} TimeZone: {2}", new Object[]{imgPath, imgId, timeZone}); @@ -420,11 +423,23 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @param newDataSource new data source added */ + @Deprecated void addLocalDataSource(Content newDataSource) { pcs.firePropertyChange(CASE_ADD_DATA_SOURCE, null, newDataSource); CoreComponentControl.openCoreWindows(); } + /** + * Notifies the UI that a new data source has been added. + * + * + * @param newDataSource new data source added + */ + void notifyNewDataSource(Content newDataSource) { + pcs.firePropertyChange(CASE_ADD_DATA_SOURCE, null, newDataSource); + CoreComponentControl.openCoreWindows(); + } + /** * @return The Services object for this case. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ContentTypePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ContentTypePanel.java deleted file mode 100644 index 07664d9401..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ContentTypePanel.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2012 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.beans.PropertyChangeListener; -import javax.swing.JPanel; - -abstract class ContentTypePanel extends JPanel { - - public enum ContentType{IMAGE, DISK, LOCAL}; - - /** - * Returns a list off all the panels extending ImageTypePanel. - * @return list of all ImageTypePanels - */ - public static ContentTypePanel[] getPanels() { - return new ContentTypePanel[] {ImageFilePanel.getDefault(), LocalDiskPanel.getDefault(), LocalFilesPanel.getDefault() }; - } - - /** - * Return the path of the selected content in this panel. - * @return paths to selected content (one or more if multiselect supported) - */ - abstract public String getContentPaths(); - - /** - * Set the selected content in this panel to the provided path. - * This function is optional. - * @param s path to selected content - */ - abstract public void setContentPath(String s); - - /** - * Get content type (image, disk, local file) of the source this wizard panel is for - * @return ContentType of the source panel - */ - abstract public ContentType getContentType(); - - /** - * Returns if the next button should be enabled in the current wizard. - * @return true if the next button should be enabled, false otherwise - */ - abstract public boolean enableNext(); - - /** - * Tells this panel to reset itself - */ - abstract public void reset(); - - /** - * Tells this panel it has been selected. - */ - abstract public void select(); - - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java index b550a0a275..9b9a091466 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule; import java.io.File; import java.util.List; +import java.util.Arrays; import javax.swing.filechooser.FileFilter; /** @@ -28,6 +29,16 @@ import javax.swing.filechooser.FileFilter; */ public class GeneralFilter extends FileFilter{ + + // Extensions & Descriptions for commonly used filters + public static final List RAW_IMAGE_EXTS = Arrays.asList(new String[]{".img", ".dd", ".001", ".aa", ".raw", ".bin"}); + public static final String RAW_IMAGE_DESC = "Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin)"; + + public static final List ENCASE_IMAGE_EXTS = Arrays.asList(new String[]{".e01"}); + public static final String ENCASE_IMAGE_DESC = "Encase Images (*.e01)"; + + + private List extensions; private String desc; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java new file mode 100644 index 0000000000..a60cf66468 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -0,0 +1,218 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + + +import java.util.logging.Level; +import javax.swing.JPanel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.filechooser.FileFilter; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; + +/** + * Image data source processor. + * Handles the addition of "disk images" to Autopsy. + * + * An instance of this class is created via the Netbeans Lookup() method. + * + */ +@ServiceProvider(service = DataSourceProcessor.class) +public class ImageDSProcessor implements DataSourceProcessor { + + + + static final Logger logger = Logger.getLogger(ImageDSProcessor.class.getName()); + + // Data source type handled by this processor + protected final static String dsType = "Image File"; + + // The Config UI panel that plugins into the Choose Data Source Wizard + private ImageFilePanel imageFilePanel; + + // The Background task that does the actual work of adding the image + private AddImageTask addImageTask; + + // true of cancelled by the caller + private boolean cancelled = false; + + DSPCallback callbackObj = null; + + // set to TRUE if the image options have been set via API and config Jpanel should be ignored + private boolean imageOptionsSet = false; + + // image options + private String imagePath; + private String timeZone; + private boolean noFatOrphans; + + + + + static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC); + static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC); + + static final List allExt = new ArrayList(); + static { + allExt.addAll(GeneralFilter.RAW_IMAGE_EXTS); + allExt.addAll(GeneralFilter.ENCASE_IMAGE_EXTS); + } + static final String allDesc = "All Supported Types"; + static final GeneralFilter allFilter = new GeneralFilter(allExt, allDesc); + + static final List filtersList = new ArrayList(); + + static { + filtersList.add(allFilter); + filtersList.add(rawFilter); + filtersList.add(encaseFilter); + } + + + /* + * A no argument constructor is required for the NM lookup() method to create an object + */ + public ImageDSProcessor() { + + // Create the config panel + imageFilePanel = ImageFilePanel.createInstance(ImageDSProcessor.class.getName(), filtersList); + + } + + /** + * Returns the Data source type (string) handled by this DSP + * + * @return String the data source type + **/ + @Override + public String getType() { + return dsType; + } + + /** + * Returns the JPanel for collecting the Data source information + * + * @return JPanel the config panel + **/ + @Override + public JPanel getPanel() { + + + imageFilePanel.readSettings(); + imageFilePanel.select(); + + return imageFilePanel; + } + /** + * Validates the data collected by the JPanel + * + * @return String returns NULL if success, error string if there is any errors + **/ + @Override + public boolean validatePanel() { + + return imageFilePanel.validatePanel(); + } + /** + * Runs the data source processor. + * This must kick off processing the data source in background + * + * @param progressMonitor Progress monitor to report progress during processing + * @param cbObj callback to call when processing is done. + **/ + @Override + public void run(DSPProgressMonitor progressMonitor, DSPCallback cbObj) { + + callbackObj = cbObj; + cancelled = false; + + if (!imageOptionsSet) + { + //tell the panel to save the current settings + imageFilePanel.storeSettings(); + + // get the image options from the panel + imagePath = imageFilePanel.getContentPaths(); + timeZone = imageFilePanel.getTimeZone(); + noFatOrphans = imageFilePanel.getNoFatOrphans(); + } + + addImageTask = new AddImageTask(imagePath, timeZone, noFatOrphans, progressMonitor, cbObj); + new Thread(addImageTask).start(); + + return; + } + + /** + * Cancel the data source processing + **/ + @Override + public void cancel() { + + cancelled = true; + + addImageTask.cancelTask(); + + return; + } + + /** + * Reset the data source processor + **/ + @Override + public void reset() { + + // reset the config panel + imageFilePanel.reset(); + + // reset state + imageOptionsSet = false; + imagePath = null; + timeZone = null; + noFatOrphans = false; + + return; + } + + /** + * Sets the data source options externally. + * To be used by a client that does not have a UI and does not use the JPanel to + * collect this information from a user. + * + * @param imgPath path to thew image or first image + * @param String timeZone + * @param noFat whether to parse FAT orphans + **/ + public void setDataSourceOptions(String imgPath, String tz, boolean noFat) { + + this.imagePath = imgPath; + this.timeZone = tz; + this.noFatOrphans = noFat; + + imageOptionsSet = true; + + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form index 313ae952b8..886767fa57 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form @@ -31,8 +31,20 @@ - - + + + + + + + + + + + + + + @@ -45,6 +57,16 @@ + + + + + + + + + + @@ -74,5 +96,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index a71ad31681..03dd184e8c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -22,41 +22,71 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; import java.util.List; +import java.util.SimpleTimeZone; +import java.util.TimeZone; import javax.swing.JFileChooser; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import javax.swing.JPanel; +import javax.swing.filechooser.FileFilter; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; + /** * ImageTypePanel for adding an image file such as .img, .E0x, .00x, etc. */ -public class ImageFilePanel extends ContentTypePanel implements DocumentListener { - private static ImageFilePanel instance = null; +public class ImageFilePanel extends JPanel implements DocumentListener { + + private final String PROP_LASTIMAGE_PATH = "LBL_LastImage_PATH"; + private PropertyChangeSupport pcs = null; private JFileChooser fc = new JFileChooser(); + + // Externally supplied name is used to store settings + private String contextName; /** * Creates new form ImageFilePanel + * @param context a string context name used to read/store last used settings + * @param fileChooserFilters a list of filters to be used with the FileChooser */ - public ImageFilePanel() { + private ImageFilePanel(String context, List fileChooserFilters) { initComponents(); fc.setDragEnabled(false); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(false); - fc.addChoosableFileFilter(AddImageWizardChooseDataSourceVisual.rawFilter); - fc.addChoosableFileFilter(AddImageWizardChooseDataSourceVisual.encaseFilter); - fc.setFileFilter(AddImageWizardChooseDataSourceVisual.allFilter); + + boolean firstFilter = true; + for (FileFilter filter: fileChooserFilters ) { + if (firstFilter) { // set the first on the list as the default selection + fc.setFileFilter(filter); + firstFilter = false; + } + else { + fc.addChoosableFileFilter(filter); + } + } + + this.contextName = context; + pcs = new PropertyChangeSupport(this); + + createTimeZoneList(); } /** - * Returns the default instance of a ImageFilePanel. + * Creates and returns an instance of a ImageFilePanel. */ - public static synchronized ImageFilePanel getDefault() { - if (instance == null) { - instance = new ImageFilePanel(); - instance.postInit(); - } - return instance; + public static synchronized ImageFilePanel createInstance(String context, List fileChooserFilters) { + + ImageFilePanel instance = new ImageFilePanel(context, fileChooserFilters ); + + instance.postInit(); + + return instance; } //post-constructor initialization to properly initialize listener support @@ -79,6 +109,10 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener pathLabel = new javax.swing.JLabel(); browseButton = new javax.swing.JButton(); pathTextField = new javax.swing.JTextField(); + timeZoneLabel = new javax.swing.JLabel(); + timeZoneComboBox = new javax.swing.JComboBox(); + noFatOrphansCheckbox = new javax.swing.JCheckBox(); + descLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(403, 65)); @@ -94,6 +128,15 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener pathTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.pathTextField.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.timeZoneLabel.text")); // NOI18N + + timeZoneComboBox.setMaximumRowCount(30); + + org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.noFatOrphansCheckbox.text")); // NOI18N + noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.noFatOrphansCheckbox.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.descLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -104,8 +147,17 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener .addComponent(browseButton) .addGap(2, 2, 2)) .addGroup(layout.createSequentialGroup() - .addComponent(pathLabel) - .addGap(0, 284, Short.MAX_VALUE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(pathLabel) + .addComponent(noFatOrphansCheckbox) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(descLabel))) + .addGap(0, 20, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -114,7 +166,16 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(browseButton) - .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(timeZoneLabel) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(noFatOrphansCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(descLabel) + .addContainerGap(33, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -131,20 +192,23 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener String path = fc.getSelectedFile().getPath(); pathTextField.setText(path); } - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.FOCUS_NEXT.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString(), false, true); }//GEN-LAST:event_browseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseButton; + private javax.swing.JLabel descLabel; + private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JLabel pathLabel; private javax.swing.JTextField pathTextField; + private javax.swing.JComboBox timeZoneComboBox; + private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables /** * Get the path of the user selected image. * @return the image path */ - @Override public String getContentPaths() { return pathTextField.getText(); } @@ -152,33 +216,37 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener /** * Set the path of the image file. */ - @Override public void setContentPath(String s) { pathTextField.setText(s); } - @Override - public ContentType getContentType() { - return ContentType.IMAGE; + public String getTimeZone() { + String tz = timeZoneComboBox.getSelectedItem().toString(); + return tz.substring(tz.indexOf(")") + 2).trim(); + } + + public boolean getNoFatOrphans() { + return noFatOrphansCheckbox.isSelected(); + } + + - @Override public void reset() { - //nothing to reset + //reset the UI elements to default + pathTextField.setText(null); } - - /** * Should we enable the next button of the wizard? * @return true if a proper image has been selected, false otherwise */ - @Override - public boolean enableNext() { + public boolean validatePanel() { String path = getContentPaths(); if (path == null || path.isEmpty()) { return false; } + boolean isExist = Case.pathExists(path); boolean isPhysicalDrive = Case.isPhysicalDrive(path); boolean isPartition = Case.isPartition(path); @@ -186,6 +254,57 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener return (isExist || isPhysicalDrive || isPartition); } + + public void storeSettings() { + String imagePathName = getContentPaths(); + if (null != imagePathName ) { + String imagePath = imagePathName.substring(0, imagePathName.lastIndexOf(File.separator) + 1); + ModuleSettings.setConfigSetting(contextName, PROP_LASTIMAGE_PATH, imagePath); + } + } + + public void readSettings() { + String lastImagePath = ModuleSettings.getConfigSetting(contextName, PROP_LASTIMAGE_PATH); + if (null != lastImagePath) { + if (!lastImagePath.isEmpty()) + pathTextField.setText(lastImagePath); + } + } + /** + * Creates the drop down list for the time zones and then makes the local + * machine time zone to be selected. + */ + public void createTimeZoneList() { + // load and add all timezone + String[] ids = SimpleTimeZone.getAvailableIDs(); + for (String id : ids) { + TimeZone zone = TimeZone.getTimeZone(id); + int offset = zone.getRawOffset() / 1000; + int hour = offset / 3600; + int minutes = (offset % 3600) / 60; + String item = String.format("(GMT%+d:%02d) %s", hour, minutes, id); + + /* + * DateFormat dfm = new SimpleDateFormat("z"); + * dfm.setTimeZone(zone); boolean hasDaylight = + * zone.useDaylightTime(); String first = dfm.format(new Date(2010, + * 1, 1)); String second = dfm.format(new Date(2011, 6, 6)); int mid + * = hour * -1; String result = first + Integer.toString(mid); + * if(hasDaylight){ result = result + second; } + * timeZoneComboBox.addItem(item + " (" + result + ")"); + */ + timeZoneComboBox.addItem(item); + } + // get the current timezone + TimeZone thisTimeZone = Calendar.getInstance().getTimeZone(); + int thisOffset = thisTimeZone.getRawOffset() / 1000; + int thisHour = thisOffset / 3600; + int thisMinutes = (thisOffset % 3600) / 60; + String formatted = String.format("(GMT%+d:%02d) %s", thisHour, thisMinutes, thisTimeZone.getID()); + + // set the selected timezone + timeZoneComboBox.setSelectedItem(formatted); + } /** * Update functions are called by the pathTextField which has this set * as it's DocumentEventListener. Each update function fires a property change @@ -194,34 +313,26 @@ public class ImageFilePanel extends ContentTypePanel implements DocumentListener */ @Override public void insertUpdate(DocumentEvent e) { - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } @Override public void removeUpdate(DocumentEvent e) { - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } @Override public void changedUpdate(DocumentEvent e) { - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } /** * Set the focus to the pathTextField. */ - @Override public void select() { pathTextField.requestFocusInWindow(); } - /** - * @return the string form of this panel - */ - @Override - public String toString() { - return "Image File"; - } @Override public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java new file mode 100644 index 0000000000..cbaa520249 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -0,0 +1,184 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.casemodule; + +import javax.swing.JPanel; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.Logger; + + +@ServiceProvider(service = DataSourceProcessor.class) +public class LocalDiskDSProcessor implements DataSourceProcessor { + + static final Logger logger = Logger.getLogger(ImageDSProcessor.class.getName()); + + // Data source type handled by this processor + static protected final String dsType = "Local Disk"; + + // The Config UI panel that plugins into the Choose Data Source Wizard + private LocalDiskPanel localDiskPanel; + + // The Background task that does the actual work of adding the local Disk + // Adding a local disk is exactly same as adding an Image. + private AddImageTask addDiskTask; + + // true if cancelled by the caller + private boolean cancelled = false; + + DSPCallback callbackObj = null; + + // set to TRUE if the image options have been set via API and config Jpanel should be ignored + private boolean localDiskOptionsSet = false; + + // data source options + private String localDiskPath; + private String timeZone; + private boolean noFatOrphans; + + + + /* + * A no argument constructor is required for the NM lookup() method to create an object + */ + public LocalDiskDSProcessor() { + + // Create the config panel + localDiskPanel = LocalDiskPanel.getDefault(); + + } + + /** + * Returns the Data source type (string) handled by this DSP + * + * @return String the data source type + **/ + @Override + public String getType() { + return dsType; + } + + /** + * Returns the JPanel for collecting the Data source information + * + * @return JPanel the config panel + **/ + @Override + public JPanel getPanel() { + + localDiskPanel.select(); + return localDiskPanel; + } + /** + * Validates the data collected by the JPanel + * + * @return String returns NULL if success, error string if there is any errors + **/ + @Override + public boolean validatePanel() { + return localDiskPanel.validatePanel(); + } + + + + /** + * Runs the data source processor. + * This must kick off processing the data source in background + * + * @param progressMonitor Progress monitor to report progress during processing + * @param cbObj callback to call when processing is done. + **/ + @Override + public void run(DSPProgressMonitor progressMonitor, DSPCallback cbObj) { + + callbackObj = cbObj; + cancelled = false; + + if (!localDiskOptionsSet) { + // get the image options from the panel + localDiskPath = localDiskPanel.getContentPaths(); + timeZone = localDiskPanel.getTimeZone(); + noFatOrphans = localDiskPanel.getNoFatOrphans(); + } + + addDiskTask = new AddImageTask(localDiskPath, timeZone, noFatOrphans, progressMonitor, cbObj); + new Thread(addDiskTask).start(); + + return; + } + + + + + + + + /** + * Cancel the data source processing + **/ + @Override + public void cancel() { + + cancelled = true; + + addDiskTask.cancelTask(); + + return; + } + + /** + * Reset the data source processor + **/ + @Override + public void reset() { + + // reset the config panel + localDiskPanel.reset(); + + // reset state + localDiskOptionsSet = false; + localDiskPath = null; + timeZone = null; + noFatOrphans = false; + + return; + } + + /** + * Sets the data source options externally. + * To be used by a client that does not have a UI and does not use the JPanel to + * collect this information from a user. + * + * @param diskPath path to the local disk + * @param String timeZone + * @param noFat whether to parse FAT orphans + **/ + public void setDataSourceOptions(String diskPath, String tz, boolean noFat) { + + this.localDiskPath = diskPath; + this.timeZone = tz; + this.noFatOrphans = noFat; + + localDiskOptionsSet = true; + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index a0b5f3f431..58e493e793 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -29,8 +29,18 @@ + + + + + + + + + + - + @@ -40,8 +50,18 @@ - + + + + + + + + + + + @@ -71,5 +91,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 6a51382940..ba746b7595 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -25,7 +25,10 @@ import java.awt.Font; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; +import java.util.SimpleTimeZone; +import java.util.TimeZone; import java.util.concurrent.CancellationException; import java.util.logging.Level; import javax.swing.ComboBoxModel; @@ -37,13 +40,16 @@ import javax.swing.ListCellRenderer; import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import javax.swing.event.ListDataListener; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or C:. */ -public class LocalDiskPanel extends ContentTypePanel { +public class LocalDiskPanel extends JPanel { + private static final Logger logger = Logger.getLogger(LocalDiskPanel.class.getName()); + private static LocalDiskPanel instance; private PropertyChangeSupport pcs = null; private List disks = new ArrayList(); @@ -56,6 +62,9 @@ public class LocalDiskPanel extends ContentTypePanel { public LocalDiskPanel() { initComponents(); customInit(); + + createTimeZoneList(); + } /** @@ -73,8 +82,10 @@ public class LocalDiskPanel extends ContentTypePanel { model = new LocalDiskModel(); diskComboBox.setModel(model); diskComboBox.setRenderer(model); + errorLabel.setText(""); diskComboBox.setEnabled(false); + } /** @@ -89,6 +100,10 @@ public class LocalDiskPanel extends ContentTypePanel { diskLabel = new javax.swing.JLabel(); diskComboBox = new javax.swing.JComboBox(); errorLabel = new javax.swing.JLabel(); + timeZoneLabel = new javax.swing.JLabel(); + timeZoneComboBox = new javax.swing.JComboBox(); + noFatOrphansCheckbox = new javax.swing.JCheckBox(); + descLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(485, 65)); @@ -98,6 +113,15 @@ public class LocalDiskPanel extends ContentTypePanel { errorLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.errorLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.timeZoneLabel.text")); // NOI18N + + timeZoneComboBox.setMaximumRowCount(30); + + org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.noFatOrphansCheckbox.text")); // NOI18N + noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.noFatOrphansCheckbox.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -106,8 +130,16 @@ public class LocalDiskPanel extends ContentTypePanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(diskLabel) .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(errorLabel)) - .addGap(0, 140, Short.MAX_VALUE)) + .addComponent(errorLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(noFatOrphansCheckbox) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(descLabel))) + .addGap(0, 102, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -115,21 +147,34 @@ public class LocalDiskPanel extends ContentTypePanel { .addComponent(diskLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(13, 13, 13) + .addComponent(errorLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(timeZoneLabel) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addComponent(noFatOrphansCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(errorLabel)) + .addComponent(descLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel descLabel; private javax.swing.JComboBox diskComboBox; private javax.swing.JLabel diskLabel; private javax.swing.JLabel errorLabel; + private javax.swing.JCheckBox noFatOrphansCheckbox; + private javax.swing.JComboBox timeZoneComboBox; + private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables /** * Return the currently selected disk path. * @return String selected disk path */ - @Override + //@Override public String getContentPaths() { if(disks.size() > 0) { LocalDisk selected = (LocalDisk) diskComboBox.getSelectedItem(); @@ -143,7 +188,7 @@ public class LocalDiskPanel extends ContentTypePanel { /** * Set the selected disk. */ - @Override + // @Override public void setContentPath(String s) { for(int i=0; i physical = new ArrayList(); List partitions = new ArrayList(); //private String SELECT = "Select a local disk:"; private String LOADING = "Loading local disks..."; + LocalDiskThread worker = null; + private void loadDisks() { + + // if there is a worker already building the lists, then cancel it first. + if (loadingDisks && worker != null) { + worker.cancel(false); + } + // Clear the lists errorLabel.setText(""); disks = new ArrayList(); @@ -224,9 +315,13 @@ public class LocalDiskPanel extends ContentTypePanel { partitions = new ArrayList(); diskComboBox.setEnabled(false); ready = false; - - LocalDiskThread worker = new LocalDiskThread(); + enableNext = false; + loadingDisks = true; + + worker = new LocalDiskThread(); worker.execute(); + + } @Override @@ -234,7 +329,7 @@ public class LocalDiskPanel extends ContentTypePanel { if(ready) { selected = anItem; enableNext = true; - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } } @@ -260,7 +355,7 @@ public class LocalDiskPanel extends ContentTypePanel { @Override public void removeListDataListener(ListDataListener l) { } - + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JPanel panel = new JPanel(new BorderLayout()); @@ -300,8 +395,6 @@ public class LocalDiskPanel extends ContentTypePanel { // Populate the lists physical = PlatformUtil.getPhysicalDrives(); partitions = PlatformUtil.getPartitions(); - disks.addAll(physical); - disks.addAll(partitions); return null; } @@ -337,6 +430,11 @@ public class LocalDiskPanel extends ContentTypePanel { enableNext = false; displayErrors(); ready = true; + worker = null; + loadingDisks = false; + + disks.addAll(physical); + disks.addAll(partitions); if(disks.size() > 0) { diskComboBox.setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java new file mode 100644 index 0000000000..3c6089d4a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java @@ -0,0 +1,167 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.casemodule; + +import javax.swing.JPanel; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DSPProgressMonitor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.Logger; + +@ServiceProvider(service = DataSourceProcessor.class) +public class LocalFilesDSProcessor implements DataSourceProcessor { + + static final Logger logger = Logger.getLogger(LocalFilesDSProcessor.class.getName()); + + // Data source type handled by this processor + protected static final String dsType = "Logical Files"; + + // The Config UI panel that plugins into the Choose Data Source Wizard + private LocalFilesPanel localFilesPanel; + + // The Background task that does the actual work of adding the files + private AddLocalFilesTask addFilesTask; + + // true if cancelled by the caller + private boolean cancelled = false; + + DSPCallback callbackObj = null; + + // set to TRUE if the image options have been set via API and config Jpanel should be ignored + private boolean localFilesOptionsSet = false; + + // data source options + private String localFilesPath; + + + + /* + * A no argument constructor is required for the NM lookup() method to create an object + */ + public LocalFilesDSProcessor() { + + // Create the config panel + localFilesPanel = LocalFilesPanel.getDefault(); + } + + /** + * Returns the Data source type (string) handled by this DSP + * + * @return String the data source type + **/ + @Override + public String getType() { + return dsType; + } + + /** + * Returns the JPanel for collecting the Data source information + * + * @return JPanel the config panel + **/ + @Override + public JPanel getPanel() { + localFilesPanel.select(); + return localFilesPanel; + } + /** + * Validates the data collected by the JPanel + * + * @return String returns NULL if success, error string if there is any errors + **/ + @Override + public boolean validatePanel() { + return localFilesPanel.validatePanel(); + } + + + + /** + * Runs the data source processor. + * This must kick off processing the data source in background + * + * @param progressMonitor Progress monitor to report progress during processing + * @param cbObj callback to call when processing is done. + **/ + @Override + public void run(DSPProgressMonitor progressMonitor, DSPCallback cbObj) { + + callbackObj = cbObj; + cancelled = false; + + if (!localFilesOptionsSet) { + // get the selected file paths from the panel + localFilesPath = localFilesPanel.getContentPaths(); + } + + addFilesTask = new AddLocalFilesTask(localFilesPath, progressMonitor, cbObj); + new Thread(addFilesTask).start(); + + return; + } + + /** + * Cancel the data source processing + **/ + @Override + public void cancel() { + + cancelled = true; + addFilesTask.cancelTask(); + + return; + } + + /** + * Reset the data source processor + **/ + @Override + public void reset() { + + // reset the config panel + localFilesPanel.reset(); + + // reset state + localFilesOptionsSet = false; + localFilesPath = null; + + + return; + } + + /** + * Sets the data source options externally. + * To be used by a client that does not have a UI and does not use the JPanel to + * collect this information from a user. + * + * @param filesPath PATH_SEP list of paths to local files + * + **/ + public void setDataSourceOptions(String filesPath) { + + this.localFilesPath = filesPath; + + localFilesOptionsSet = true; + + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 7d07033ac1..c9cff7b1d0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -24,11 +24,13 @@ import java.io.File; import java.util.Set; import java.util.TreeSet; import javax.swing.JFileChooser; +import javax.swing.JPanel; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; /** * Add input wizard subpanel for adding local files / dirs to the case */ -public class LocalFilesPanel extends ContentTypePanel { +public class LocalFilesPanel extends JPanel { private PropertyChangeSupport pcs = null; private Set currentFiles = new TreeSet(); //keep currents in a set to disallow duplicates per add @@ -57,7 +59,7 @@ public class LocalFilesPanel extends ContentTypePanel { } - @Override + //@Override public String getContentPaths() { //TODO consider interface change to return list of paths instead @@ -72,36 +74,37 @@ public class LocalFilesPanel extends ContentTypePanel { return b.toString(); } - @Override + //@Override public void setContentPath(String s) { //for the local file panel we don't need to restore the last paths used //when the wizard restarts } - @Override - public ContentType getContentType() { - return ContentType.LOCAL; + //@Override + public String getContentType() { + return "LOCAL"; } - @Override - public boolean enableNext() { + //@Override + public boolean validatePanel() { return enableNext; } - @Override + //@Override public void select() { reset(); } - @Override + //@Override public void reset() { currentFiles.clear(); selectedPaths.setText(""); enableNext = false; - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + + //pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); } - @Override + @Override public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { super.addPropertyChangeListener(pcl); @@ -231,7 +234,7 @@ public class LocalFilesPanel extends ContentTypePanel { else { enableNext = false; } - pcs.firePropertyChange(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString(), false, true); + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); }//GEN-LAST:event_selectButtonActionPerformed private void clearButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form index 24dd272951..d730da08dc 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.form @@ -110,65 +110,49 @@ - - - - - - - - - - + + + + + - + - - + + - - - + - - - - - - - - + - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java index 22d9f11a2f..d54c52fc4b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java @@ -18,49 +18,71 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; + import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; import java.util.logging.Level; -import javax.swing.ComboBoxModel; -import javax.swing.JDialog; +import java.io.File; +import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.event.ListDataListener; -import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.GeneralFilter; + import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; + + public class MissingImageDialog extends javax.swing.JDialog { private static final Logger logger = Logger.getLogger(MissingImageDialog.class.getName()); long obj_id; SleuthkitCase db; - ContentTypePanel currentPanel; - ImageTypeModel model; + + + + static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC); + static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC); + + static final List allExt = new ArrayList(); + static { + allExt.addAll(GeneralFilter.RAW_IMAGE_EXTS); + allExt.addAll(GeneralFilter.ENCASE_IMAGE_EXTS); + } + static final String allDesc = "All Supported Types"; + static final GeneralFilter allFilter = new GeneralFilter(allExt, allDesc); + + private JFileChooser fc = new JFileChooser(); private MissingImageDialog(long obj_id, SleuthkitCase db) { super(new JFrame(), true); this.obj_id = obj_id; this.db = db; initComponents(); + + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + + fc.addChoosableFileFilter(rawFilter); + fc.addChoosableFileFilter(encaseFilter); + fc.setFileFilter(allFilter); + + customInit(); } - /** - * Client call to create a MissingImageDialog. - * - * @param obj_id obj_id of the missing image - * @param db the current SleuthkitCase connected to a db - */ +// +// * Client call to create a MissingImageDialog. +// * +// * @param obj_id obj_id of the missing image +// * @param db the current SleuthkitCase connected to a db +// static void makeDialog(long obj_id, SleuthkitCase db) { final MissingImageDialog dialog = new MissingImageDialog(obj_id, db); dialog.addWindowListener(new WindowAdapter() { @@ -73,11 +95,8 @@ public class MissingImageDialog extends javax.swing.JDialog { } private void customInit() { - model = new ImageTypeModel(); - typeComboBox.setModel(model); - typeComboBox.setSelectedIndex(0); - typePanel.setLayout(new BorderLayout()); - updateCurrentPanel(ImageFilePanel.getDefault()); + + selectButton.setEnabled(false); } private void display() { @@ -92,54 +111,31 @@ public class MissingImageDialog extends javax.swing.JDialog { this.setVisible(true); } - /** - * Refresh this panel. - * @param panel current typepanel - */ - private void updateCurrentPanel(ContentTypePanel panel) { - currentPanel = panel; - typePanel.removeAll(); - typePanel.add((JPanel) currentPanel, BorderLayout.CENTER); - typePanel.validate(); - typePanel.repaint(); - this.validate(); - this.repaint(); - currentPanel.addPropertyChangeListener(new PropertyChangeListener() { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if(evt.getPropertyName().equals(AddImageWizardChooseDataSourceVisual.EVENT.UPDATE_UI.toString())) { - updateSelectButton(); - } - if(evt.getPropertyName().equals(AddImageWizardChooseDataSourceVisual.EVENT.FOCUS_NEXT.toString())) { - moveFocusToSelect(); - } - } - - }); - currentPanel.select(); - updateSelectButton(); - } - - /** - * Focuses the select button for easy enter-pressing access. - */ +// +// * Focuses the select button for easy enter-pressing access. +// private void moveFocusToSelect() { this.selectButton.requestFocusInWindow(); } - /** - * Enables/disables the select button based off the current panel. - */ +// +// * Enables/disables the select button based off the current panel. +// private void updateSelectButton() { - this.selectButton.setEnabled(currentPanel.enableNext()); + + // Enable this based on whether there is a valid path + if (!pathNameTextField.getText().isEmpty()) { + String filePath = pathNameTextField.getText(); + boolean isExist = Case.pathExists(filePath) || Case.driveExists(filePath); + selectButton.setEnabled(isExist); + } } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ +// +// * This method is called from within the constructor to initialize the form. +// * WARNING: Do NOT modify this code. The content of this method is always +// * regenerated by the Form Editor. +// @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { @@ -148,9 +144,8 @@ public class MissingImageDialog extends javax.swing.JDialog { selectButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); containerPanel = new javax.swing.JPanel(); - typeComboBox = new javax.swing.JComboBox(); - typeTabel = new javax.swing.JLabel(); - typePanel = new javax.swing.JPanel(); + pathNameTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); titleLabel = new javax.swing.JLabel(); titleSeparator = new javax.swing.JSeparator(); @@ -191,43 +186,39 @@ public class MissingImageDialog extends javax.swing.JDialog { .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - org.openide.awt.Mnemonics.setLocalizedText(typeTabel, org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.typeTabel.text")); // NOI18N + pathNameTextField.setText(org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.pathNameTextField.text")); // NOI18N + pathNameTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pathNameTextFieldActionPerformed(evt); + } + }); - javax.swing.GroupLayout typePanelLayout = new javax.swing.GroupLayout(typePanel); - typePanel.setLayout(typePanelLayout); - typePanelLayout.setHorizontalGroup( - typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - typePanelLayout.setVerticalGroup( - typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 57, Short.MAX_VALUE) - ); + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); javax.swing.GroupLayout containerPanelLayout = new javax.swing.GroupLayout(containerPanel); containerPanel.setLayout(containerPanelLayout); containerPanelLayout.setHorizontalGroup( containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(containerPanelLayout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(containerPanelLayout.createSequentialGroup() - .addComponent(typeTabel) - .addGap(18, 18, 18) - .addComponent(typeComboBox, 0, 298, Short.MAX_VALUE))) - .addContainerGap()) + .addContainerGap() + .addComponent(pathNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 285, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(browseButton) + .addContainerGap(83, Short.MAX_VALUE)) ); containerPanelLayout.setVerticalGroup( containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(containerPanelLayout.createSequentialGroup() - .addGap(0, 0, 0) + .addGap(18, 18, 18) .addGroup(containerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(typeTabel) - .addComponent(typeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addContainerGap()) + .addComponent(pathNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addContainerGap(62, Short.MAX_VALUE)) ); titleLabel.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N @@ -268,7 +259,7 @@ public class MissingImageDialog extends javax.swing.JDialog { private void selectButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectButtonActionPerformed try { - String newPath = currentPanel.getContentPaths(); + String newPath = pathNameTextField.getText(); //TODO handle local files db.setImagePaths(obj_id, Arrays.asList(new String[]{newPath})); } catch (TskCoreException ex) { @@ -281,21 +272,48 @@ public class MissingImageDialog extends javax.swing.JDialog { cancel(); }//GEN-LAST:event_cancelButtonActionPerformed + private void pathNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathNameTextFieldActionPerformed + // TODO add your handling code here: + + updateSelectButton(); + }//GEN-LAST:event_pathNameTextFieldActionPerformed + + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + + + + String oldText = pathNameTextField.getText(); + + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathNameTextField.setText(path); + } + //pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString(), false, true); + + updateSelectButton(); + }//GEN-LAST:event_browseButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; private javax.swing.JPanel buttonPanel; private javax.swing.JButton cancelButton; private javax.swing.JPanel containerPanel; + private javax.swing.JTextField pathNameTextField; private javax.swing.JButton selectButton; private javax.swing.JLabel titleLabel; private javax.swing.JSeparator titleSeparator; - private javax.swing.JComboBox typeComboBox; - private javax.swing.JPanel typePanel; - private javax.swing.JLabel typeTabel; // End of variables declaration//GEN-END:variables - /** - * Verify the user wants to cancel searching for the image. - */ +// +// * Verify the user wants to cancel searching for the image. +// void cancel() { int ret = JOptionPane.showConfirmDialog(null, "No image file has been selected, are you sure you\n" + @@ -306,40 +324,5 @@ public class MissingImageDialog extends javax.swing.JDialog { } } - /** - * ComboBoxModel to control typeComboBox and supply ImageTypePanels. - */ - private class ImageTypeModel implements ComboBoxModel { - ContentTypePanel selected; - ContentTypePanel[] types = ContentTypePanel.getPanels(); - @Override - public void setSelectedItem(Object anItem) { - selected = (ContentTypePanel) anItem; - updateCurrentPanel(selected); - } - - @Override - public Object getSelectedItem() { - return selected; - } - - @Override - public int getSize() { - return types.length; - } - - @Override - public Object getElementAt(int index) { - return types[index]; - } - - @Override - public void addListDataListener(ListDataListener l) { - } - - @Override - public void removeListDataListener(ListDataListener l) { - } - } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java old mode 100644 new mode 100755 index 8718406d5c..069b13ef2e --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2013 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -37,21 +37,29 @@ public class Services implements Closeable { // NOTE: all new services added to Services class must be added to this list // of services. - private List services = new ArrayList(); + private List services = new ArrayList<>(); // services private FileManager fileManager; + private TagsManager tagsManager; public Services(SleuthkitCase tskCase) { this.tskCase = tskCase; //create and initialize FileManager as early as possibly in the new/opened Case fileManager = new FileManager(tskCase); services.add(fileManager); + + tagsManager = new TagsManager(tskCase); + services.add(tagsManager); } public FileManager getFileManager() { return fileManager; } + + public TagsManager getTagsManager() { + return tagsManager; + } @Override public void close() throws IOException { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java new file mode 100755 index 0000000000..67788f8300 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -0,0 +1,464 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.services; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A per case instance of this class functions as an Autopsy service that + * manages the creation, updating, and deletion of tags applied to content and + * blackboard artifacts by users. + */ +public class TagsManager implements Closeable { + private static final String TAGS_SETTINGS_NAME = "Tags"; + private static final String TAG_NAMES_SETTING_KEY = "TagNames"; + private final SleuthkitCase tskCase; + private final HashMap uniqueTagNames = new HashMap<>(); + private boolean tagNamesInitialized = false; // @@@ This is part of a work around to be removed when database access on the EDT is correctly synchronized. + + // Use this exception and the member hash map to manage uniqueness of hash + // names. This is deemed more proactive and informative than leaving this to + // the UNIQUE constraint on the display_name field of the tag_names table in + // the case database. + public class TagNameAlreadyExistsException extends Exception { + } + + /** + * Package-scope constructor for use of the Services class. An instance of + * TagsManager should be created for each case that is opened. + * @param [in] tskCase The SleuthkitCase object for the current case. + */ + TagsManager(SleuthkitCase tskCase) { + this.tskCase = tskCase; + // @@@ The removal of this call is a work around until database access on the EDT is correctly synchronized. + // getExistingTagNames(); + } + + /** + * Gets a list of all tag names currently available for tagging content or + * blackboard artifacts. + * @return A list, possibly empty, of TagName data transfer objects (DTOs). + * @throws TskCoreException + */ + public synchronized List getAllTagNames() throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getAllTagNames(); + } + + /** + * Gets a list of all tag names currently used for tagging content or + * blackboard artifacts. + * @return A list, possibly empty, of TagName data transfer objects (DTOs). + * @throws TskCoreException + */ + public synchronized List getTagNamesInUse() throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getTagNamesInUse(); + } + + /** + * Checks whether a tag name with a given display name exists. + * @param [in] tagDisplayName The display name for which to check. + * @return True if the tag name exists, false otherwise. + */ + public synchronized boolean tagNameExists(String tagDisplayName) { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return uniqueTagNames.containsKey(tagDisplayName); + } + + /** + * Adds a new tag name to the current case and to the tags settings. + * @param [in] displayName The display name for the new tag name. + * @return A TagName data transfer object (DTO) representing the new tag name. + * @throws TagNameAlreadyExistsException, TskCoreException + */ + public TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException { + return addTagName(displayName, "", TagName.HTML_COLOR.NONE); + } + + /** + * Adds a new tag name to the current case and to the tags settings. + * @param [in] displayName The display name for the new tag name. + * @param [in] description The description for the new tag name. + * @return A TagName data transfer object (DTO) representing the new tag name. + * @throws TagNameAlreadyExistsException, TskCoreException + */ + public TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException { + return addTagName(displayName, description, TagName.HTML_COLOR.NONE); + } + + /** + * Adds a new tag name to the current case and to the tags settings. + * @param [in] displayName The display name for the new tag name. + * @param [in] description The description for the new tag name. + * @param [in] color The HTML color to associate with the new tag name. + * @return A TagName data transfer object (DTO) representing the new tag name. + * @throws TagNameAlreadyExistsException, TskCoreException + */ + public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + if (uniqueTagNames.containsKey(displayName)) { + throw new TagNameAlreadyExistsException(); + } + + // Add the tag name to the case. + TagName newTagName = tskCase.addTagName(displayName, description, color); + + // Add the tag name to the tags settings. + uniqueTagNames.put(newTagName.getDisplayName(), newTagName); + saveTagNamesToTagsSettings(); + + return newTagName; + } + + /** + * Tags a content object. + * @param [in] content The content to tag. + * @param [in] tagName The name to use for the tag. + * @return A ContentTag data transfer object (DTO) representing the new tag. + * @throws TskCoreException + */ + public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException { + return addContentTag(content, tagName, "", -1, -1); + } + + /** + * Tags a content object. + * @param [in] content The content to tag. + * @param [in] tagName The name to use for the tag. + * @param [in] comment A comment to store with the tag. + * @return A ContentTag data transfer object (DTO) representing the new tag. + * @throws TskCoreException + */ + public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException { + return addContentTag(content, tagName, comment, -1, -1); + } + + /** + * Tags a content object or a section of a content object. + * @param [in] content The content to tag. + * @param [in] tagName The name to use for the tag. + * @param [in] comment A comment to store with the tag. + * @param [in] beginByteOffset Designates the beginning of a tagged section. + * @param [in] endByteOffset Designates the end of a tagged section. + * @return A ContentTag data transfer object (DTO) representing the new tag. + * @throws IllegalArgumentException, TskCoreException + */ + public synchronized ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + if (beginByteOffset >= 0 && endByteOffset >= 1) { + if (beginByteOffset > content.getSize() - 1) { + throw new IllegalArgumentException("beginByteOffset = " + beginByteOffset + " out of content size range (0 - " + (content.getSize() - 1) + ")"); + } + + if (endByteOffset > content.getSize() - 1) { + throw new IllegalArgumentException("endByteOffset = " + endByteOffset + " out of content size range (0 - " + (content.getSize() - 1) + ")"); + } + + if (endByteOffset < beginByteOffset) { + throw new IllegalArgumentException("endByteOffset < beginByteOffset"); + } + } + + return tskCase.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); + } + + /** + * Deletes a content tag. + * @param [in] tag The tag to delete. + * @throws TskCoreException + */ + public synchronized void deleteContentTag(ContentTag tag) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + tskCase.deleteContentTag(tag); + } + + /** + * Gets all content tags for the current case. + * @return A list, possibly empty, of content tags. + * @throws TskCoreException + */ + public List getAllContentTags() throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getAllContentTags(); + } + + /** + * Gets content tags count by tag name. + * @param [in] tagName The tag name of interest. + * @return A count of the content tags with the specified tag name. + * @throws TskCoreException + */ + public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getContentTagsCountByTagName(tagName); + } + + /** + * Gets content tags by tag name. + * @param [in] tagName The tag name of interest. + * @return A list, possibly empty, of the content tags with the specified tag name. + * @throws TskCoreException + */ + public synchronized List getContentTagsByTagName(TagName tagName) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getContentTagsByTagName(tagName); + } + + /** + * Gets content tags count by content. + * @param [in] content The content of interest. + * @return A list, possibly empty, of the tags that have been applied to the artifact. + * @throws TskCoreException + */ + public synchronized List getContentTagsByContent(Content content) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getContentTagsByContent(content); + } + + /** + * Tags a blackboard artifact object. + * @param [in] artifact The blackboard artifact to tag. + * @param [in] tagName The name to use for the tag. + * @return A BlackboardArtifactTag data transfer object (DTO) representing the new tag. + * @throws TskCoreException + */ + public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException { + return addBlackboardArtifactTag(artifact, tagName, ""); + } + + /** + * Tags a blackboard artifact object. + * @param [in] artifact The blackboard artifact to tag. + * @param [in] tagName The name to use for the tag. + * @param [in] comment A comment to store with the tag. + * @return A BlackboardArtifactTag data transfer object (DTO) representing the new tag. + * @throws TskCoreException + */ + public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.addBlackboardArtifactTag(artifact, tagName, comment); + } + + /** + * Deletes a blackboard artifact tag. + * @param [in] tag The tag to delete. + * @throws TskCoreException + */ + public synchronized void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + tskCase.deleteBlackboardArtifactTag(tag); + } + + /** + * Gets all blackboard artifact tags for the current case. + * @return A list, possibly empty, of blackboard artifact tags. + * @throws TskCoreException + */ + public List getAllBlackboardArtifactTags() throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getAllBlackboardArtifactTags(); + } + + /** + * Gets blackboard artifact tags count by tag name. + * @param [in] tagName The tag name of interest. + * @return A count of the blackboard artifact tags with the specified tag name. + * @throws TskCoreException + */ + public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getBlackboardArtifactTagsCountByTagName(tagName); + } + + /** + * Gets blackboard artifact tags by tag name. + * @param [in] tagName The tag name of interest. + * @return A list, possibly empty, of the blackboard artifact tags with the specified tag name. + * @throws TskCoreException + */ + public synchronized List getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getBlackboardArtifactTagsByTagName(tagName); + } + + /** + * Gets blackboard artifact tags for a particular blackboard artifact. + * @param [in] artifact The blackboard artifact of interest. + * @return A list, possibly empty, of the tags that have been applied to the artifact. + * @throws TskCoreException + */ + public synchronized List getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + return tskCase.getBlackboardArtifactTagsByArtifact(artifact); + } + + @Override + public void close() throws IOException { + saveTagNamesToTagsSettings(); + } + + private void getExistingTagNames() { + getTagNamesFromCurrentCase(); + getTagNamesFromTagsSettings(); + getPredefinedTagNames(); + saveTagNamesToTagsSettings(); + tagNamesInitialized = true; // @@@ This is part of a work around to be removed when database access on the EDT is correctly synchronized. + } + + private void getTagNamesFromCurrentCase() { + try { + List currentTagNames = tskCase.getAllTagNames(); + for (TagName tagName : currentTagNames) { + uniqueTagNames.put(tagName.getDisplayName(), tagName); + } + } + catch (TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag types from the current case", ex); + } + } + + private void getTagNamesFromTagsSettings() { + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); + if (null != setting && !setting.isEmpty()) { + // Read the tag name setting and break it into tag name tuples. + List tagNameTuples = Arrays.asList(setting.split(";")); + + // Parse each tuple and add the tag names to the current case, one + // at a time to gracefully discard any duplicates or corrupt tuples. + for (String tagNameTuple : tagNameTuples) { + String[] tagNameAttributes = tagNameTuple.split(","); + if (!uniqueTagNames.containsKey(tagNameAttributes[0])) { + try { + TagName tagName = tskCase.addTagName(tagNameAttributes[0], tagNameAttributes[1], TagName.HTML_COLOR.getColorByName(tagNameAttributes[2])); + uniqueTagNames.put(tagName.getDisplayName(), tagName); + } + catch (TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add saved tag name " + tagNameAttributes[0], ex); + } + } + } + } + } + + private void getPredefinedTagNames() { + if (!uniqueTagNames.containsKey("Bookmark")) { + try { + TagName tagName = tskCase.addTagName("Bookmark", "", TagName.HTML_COLOR.NONE); + uniqueTagNames.put(tagName.getDisplayName(), tagName); + } + catch (TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add predefined 'Bookmark' tag name", ex); + } + } + } + + private void saveTagNamesToTagsSettings() { + if (!uniqueTagNames.isEmpty()) { + StringBuilder setting = new StringBuilder(); + for (TagName tagName : uniqueTagNames.values()) { + if (setting.length() != 0) { + setting.append(";"); + } + setting.append(tagName.getDisplayName()).append(","); + setting.append(tagName.getDescription()).append(","); + setting.append(tagName.getColor().name()); + } + ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java index 6fa7960527..96f3f92497 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java @@ -157,7 +157,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer @Override public String getToolTip() { - return ""; + return "Displays metadata about the file."; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 0d61453cd4..da690cc868 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -26,8 +26,8 @@ import javafx.embed.swing.JFXPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.modules.ModuleInstall; import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * Wrapper over Installers in packages in Core module This is the main @@ -39,6 +39,41 @@ public class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static volatile boolean javaFxInit = false; + static { + loadDynLibraries(); + } + + private static void loadDynLibraries() { + if (PlatformUtil.isWindowsOS()) { + try { + //on windows force loading ms crt dependencies first + //in case linker can't find them on some systems + //Note: if shipping with a different CRT version, this will only print a warning + //and try to use linker mechanism to find the correct versions of libs. + //We should update this if we officially switch to a new version of CRT/compiler + System.loadLibrary("msvcr100"); + System.loadLibrary("msvcp100"); + logger.log(Level.INFO, "MS CRT libraries loaded"); + } catch (UnsatisfiedLinkError e) { + logger.log(Level.SEVERE, "Error loading ms crt libraries, ", e); + } + + try { + System.loadLibrary("zlib"); + logger.log(Level.INFO, "ZLIB library loaded loaded"); + } catch (UnsatisfiedLinkError e) { + logger.log(Level.SEVERE, "Error loading ZLIB library, ", e); + } + + try { + System.loadLibrary("libewf"); + logger.log(Level.INFO, "EWF library loaded"); + } catch (UnsatisfiedLinkError e) { + logger.log(Level.SEVERE, "Error loading EWF library, ", e); + } + } + } + public Installer() { logger.log(Level.INFO, "core installer created"); javaFxInit = false; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ContextMenuActionsProvider.java new file mode 100755 index 0000000000..b6a96eb1aa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/ContextMenuActionsProvider.java @@ -0,0 +1,38 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.corecomponentinterfaces; + +import java.util.List; +import javax.swing.Action; + +/** + * Implementers of this interface provide Actions that will be added to context + * menus in Autopsy. + */ +public interface ContextMenuActionsProvider { + /** + * Gets context menu Actions for the currently selected data model objects + * exposed by the NetBeans Lookup of the active TopComponent. Implementers + * should discover the selected objects by calling + * org.openide.util.Utilities.actionsGlobalContext().lookupAll() for the + * org.sleuthkit.datamodel classes of interest to the provider. + * @return A list, possibly empty, of Action objects. + */ + public List getActions(); +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPCallback.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPCallback.java new file mode 100644 index 0000000000..f3bdf028da --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPCallback.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.corecomponentinterfaces; + +import java.awt.EventQueue; +import java.util.List; +import org.sleuthkit.datamodel.Content; + +/** + * Abstract class for a callback for a DataSourceProcessor. + * + * Ensures that DSP invokes the caller overridden method, doneEDT(), + * in the EDT thread. + * + */ +public abstract class DSPCallback { + + public enum DSP_Result + { + NO_ERRORS, + CRITICAL_ERRORS, + NONCRITICAL_ERRORS, + }; + + /* + * Invoke the caller supplied callback function on the EDT thread + */ + public void done(DSP_Result result, List errList, List newContents) + { + + final DSP_Result resultf = result; + final List errListf = errList; + final List newContentsf = newContents; + + // Invoke doneEDT() that runs on the EDT . + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + doneEDT(resultf, errListf, newContentsf ); + + } + }); + } + + /* + * calling code overrides to provide its own calllback + */ + public abstract void doneEDT(DSP_Result result, List errList, List newContents); +}; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPProgressMonitor.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPProgressMonitor.java new file mode 100644 index 0000000000..88d6b5e04c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DSPProgressMonitor.java @@ -0,0 +1,33 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.corecomponentinterfaces; + +/* + * An GUI agnostic DSPProgressMonitor interface for DataSourceProcesssors to + * indicate progress. + * It models after a JProgressbar though it could use any underlying implementation + */ +public interface DSPProgressMonitor { + + void setIndeterminate(boolean indeterminate); + + void setProgress(int progress); + + void setProgressText(String text); +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataSourceProcessor.java new file mode 100644 index 0000000000..af67e78b68 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataSourceProcessor.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2013 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.corecomponentinterfaces; + + +import javax.swing.JPanel; + +/* + * Defines an interface used by the Add DataSource wizard to discover different + * Data SourceProcessors. + * + * Each data source may have its unique attributes and may need to be processed + * differently. + * + * The DataSourceProcessor interface defines a uniform mechanism for the Autopsy UI + * to: + * - collect details for the data source to be processed. + * - Process the data source in the background + * - Be notified when the processing is complete + */ +public interface DataSourceProcessor { + + /* + * The DSP Panel may fire Property change events + * The caller must enure to add itself as a listener and + * then react appropriately to the events + */ + enum DSP_PANEL_EVENT { + + UPDATE_UI, // the content of JPanel has changed that MAY warrant updates to the caller UI + FOCUS_NEXT // the caller UI may move focus the the next UI element, floowing the panel. + }; + + + /** + * Returns the type of Data Source it handles. + * This name gets displayed in the drop-down listbox + **/ + String getType(); + + /** + * Returns the picker panel to be displayed along with any other + * runtime options supported by the data source handler. + **/ + JPanel getPanel(); + + /** + * Called to validate the input data in the panel. + * Returns true if no errors, or + * Returns false if there is an error. + **/ + boolean validatePanel(); + + /** + * Called to invoke the handling of Data source in the background. + * Returns after starting the background thread + * @param settings wizard settings to read/store properties + * @param progressPanel progress panel to be updated while processing + * + **/ + void run(DSPProgressMonitor progressPanel, DSPCallback dspCallback); + + + /** + * Called to cancel the background processing. + **/ + void cancel(); + + /** + * Called to reset/reinitialize the DSP. + * + **/ + void reset(); + + +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java index a867c9d955..f1c4c4016d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java @@ -299,7 +299,6 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont currentPage = page; long offset = (currentPage - 1) * pageLength; - // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); @@ -344,13 +343,13 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont // set the output view if (errorText == null) { int showLength = bytesRead < pageLength ? bytesRead : (int) pageLength; - outputViewPane.setText(DataConversion.byteArrayToHex(data, showLength, offset, outputViewPane.getFont())); + outputViewPane.setText(DataConversion.byteArrayToHex(data, showLength, offset)); } else { outputViewPane.setText(errorText); } - outputViewPane.moveCaretPosition(0); + outputViewPane.setCaretPosition(0); this.setCursor(null); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 1e0c9c5621..d243bab86b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -362,6 +362,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C this.rootNode.addNodeListener(dummyNodeListener); } + resetTabs(selectedNode); setupTabs(selectedNode); if (selectedNode != null) { @@ -369,58 +370,42 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C this.numberMatchLabel.setText(Integer.toString(childrenCount)); } this.numberMatchLabel.setVisible(true); - - - resetTabs(selectedNode); - - // set the display on the current active tab - int currentActiveTab = this.dataResultTabbedPanel.getSelectedIndex(); - if (currentActiveTab != -1) { - UpdateWrapper drv = viewers.get(currentActiveTab); - drv.setNode(selectedNode); - } } - private void setupTabs(final Node selectedNode) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - //update/disable tabs based on if supported for this node - int drvC = 0; - for (UpdateWrapper drv : viewers) { + private void setupTabs(Node selectedNode) { + //update/disable tabs based on if supported for this node + int drvC = 0; + for (UpdateWrapper drv : viewers) { - if (drv.isSupported(selectedNode)) { - dataResultTabbedPanel.setEnabledAt(drvC, true); - } else { - dataResultTabbedPanel.setEnabledAt(drvC, false); - } - ++drvC; - } + if (drv.isSupported(selectedNode)) { + dataResultTabbedPanel.setEnabledAt(drvC, true); + } else { + dataResultTabbedPanel.setEnabledAt(drvC, false); + } + ++drvC; + } - // if the current tab is no longer enabled, then find one that is - boolean hasViewerEnabled = true; - int currentActiveTab = dataResultTabbedPanel.getSelectedIndex(); - if ((currentActiveTab == -1) || (dataResultTabbedPanel.isEnabledAt(currentActiveTab) == false)) { - hasViewerEnabled = false; - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) { - if (dataResultTabbedPanel.isEnabledAt(i)) { - currentActiveTab = i; - hasViewerEnabled = true; - break; - } - } - - if (hasViewerEnabled) { - dataResultTabbedPanel.setSelectedIndex(currentActiveTab); - } - } - - if (hasViewerEnabled) { - viewers.get(currentActiveTab).setNode(selectedNode); + // if the current tab is no longer enabled, then find one that is + boolean hasViewerEnabled = true; + int currentActiveTab = dataResultTabbedPanel.getSelectedIndex(); + if ((currentActiveTab == -1) || (dataResultTabbedPanel.isEnabledAt(currentActiveTab) == false)) { + hasViewerEnabled = false; + for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) { + if (dataResultTabbedPanel.isEnabledAt(i)) { + currentActiveTab = i; + hasViewerEnabled = true; + break; } } - }); - + + if (hasViewerEnabled) { + dataResultTabbedPanel.setSelectedIndex(currentActiveTab); + } + } + + if (hasViewerEnabled) { + viewers.get(currentActiveTab).setNode(selectedNode); + } } @Override @@ -622,12 +607,22 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } @Override - public void childrenAdded(NodeMemberEvent nme) { + public void childrenAdded(final NodeMemberEvent nme) { Node[] delta = nme.getDelta(); if (load && containsReal(delta)) { load = false; - setupTabs(nme.getNode()); - updateMatches(); + if (SwingUtilities.isEventDispatchThread()) { + setupTabs(nme.getNode()); + updateMatches(); + } else { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + setupTabs(nme.getNode()); + updateMatches(); + } + }); + } } } @@ -645,14 +640,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * */ private void updateMatches() { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (rootNode != null && rootNode.getChildren() != null) { - setNumMatches(rootNode.getChildren().getNodesCount()); - } - } - }); + if (rootNode != null && rootNode.getChildren() != null) { + setNumMatches(rootNode.getChildren().getNodesCount()); + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 5b59f12812..6d87ee41d7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -215,18 +215,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ private void getAllChildPropertyHeadersRec(Node parent, int rows) { Children children = parent.getChildren(); - int total = Math.min(rows, children.getNodesCount()); - for (int i = 0; i < total; i++) { - Node child = children.getNodeAt(i); + int childCount = 0; + for (Node child : children.getNodes()) { + if (++childCount > rows) { + break; + } for (PropertySet ps : child.getPropertySets()) { - //if (ps.getName().equals(Sheet.PROPERTIES)) { - //return ps.getProperties(); final Property[] props = ps.getProperties(); final int propsNum = props.length; for (int j = 0; j < propsNum; ++j) { propertiesAcc.add(props[j]); } - //} } getAllChildPropertyHeadersRec(child, rows); } @@ -278,137 +277,131 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * @param root The parent Node of the ContentNodes */ private void setupTable(final Node root) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - //wrap to filter out children - //note: this breaks the tree view mode in this generic viewer, - //so wrap nodes earlier if want 1 level view - //if (!(root instanceof TableFilterNode)) { - /// root = new TableFilterNode(root, true); - //} + //wrap to filter out children + //note: this breaks the tree view mode in this generic viewer, + //so wrap nodes earlier if want 1 level view + //if (!(root instanceof TableFilterNode)) { + /// root = new TableFilterNode(root, true); + //} - em.setRootContext(root); + em.setRootContext(root); - final OutlineView ov = ((OutlineView) DataResultViewerTable.this.tableScrollPanel); + final OutlineView ov = ((OutlineView) DataResultViewerTable.this.tableScrollPanel); + + if (ov == null) { + return; + } + + propertiesAcc.clear(); + + DataResultViewerTable.this.getAllChildPropertyHeadersRec(root, 100); + List props = new ArrayList(propertiesAcc); + if (props.size() > 0) { + Node.Property prop = props.remove(0); + ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); + } + + + // *********** Make the TreeTableView to be sortable *************** + + //First property column is sortable, but also sorted initially, so + //initially this one will have the arrow icon: + if (props.size() > 0) { + props.get(0).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. + props.get(0).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. + } + + // The rest of the columns are sortable, but not initially sorted, + // so initially will have no arrow icon: + String[] propStrings = new String[props.size() * 2]; + for (int i = 0; i < props.size(); i++) { + props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); + propStrings[2 * i] = props.get(i).getName(); + propStrings[2 * i + 1] = props.get(i).getDisplayName(); + } + + ov.setPropertyColumns(propStrings); + // ***************************************************************** + + // // set the first entry + // Children test = root.getChildren(); + // Node firstEntryNode = test.getNodeAt(0); + // try { + // this.getExplorerManager().setSelectedNodes(new Node[]{firstEntryNode}); + // } catch (PropertyVetoException ex) {} + + + // show the horizontal scroll panel and show all the content & header + + int totalColumns = props.size(); + + //int scrollWidth = ttv.getWidth(); + int margin = 4; + int startColumn = 1; - if (ov == null) { - return; - } - - propertiesAcc.clear(); - - DataResultViewerTable.this.getAllChildPropertyHeadersRec(root, 100); - List props = new ArrayList(propertiesAcc); - if (props.size() > 0) { - Node.Property prop = props.remove(0); - ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); - } - - - // *********** Make the TreeTableView to be sortable *************** - - //First property column is sortable, but also sorted initially, so - //initially this one will have the arrow icon: - if (props.size() > 0) { - props.get(0).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. - props.get(0).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. - } - - // The rest of the columns are sortable, but not initially sorted, - // so initially will have no arrow icon: - String[] propStrings = new String[props.size() * 2]; - for (int i = 0; i < props.size(); i++) { - props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); - propStrings[2 * i] = props.get(i).getName(); - propStrings[2 * i + 1] = props.get(i).getDisplayName(); - } - - ov.setPropertyColumns(propStrings); - // ***************************************************************** - - // // set the first entry - // Children test = root.getChildren(); - // Node firstEntryNode = test.getNodeAt(0); - // try { - // this.getExplorerManager().setSelectedNodes(new Node[]{firstEntryNode}); - // } catch (PropertyVetoException ex) {} - - - // show the horizontal scroll panel and show all the content & header - - int totalColumns = props.size(); - - //int scrollWidth = ttv.getWidth(); - int margin = 4; - int startColumn = 1; - - // If there is only one column (which was removed from props above) - // Just let the table resize itself. - ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS); + // If there is only one column (which was removed from props above) + // Just let the table resize itself. + ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS); - // get first 100 rows values for the table - Object[][] content = null; - content = getRowValues(root, 100); + // get first 100 rows values for the table + Object[][] content = null; + content = getRowValues(root, 100); - if (content != null) { - // get the fontmetrics - final Graphics graphics = ov.getGraphics(); - if (graphics != null) { - final FontMetrics metrics = graphics.getFontMetrics(); + if (content != null) { + // get the fontmetrics + final Graphics graphics = ov.getGraphics(); + if (graphics != null) { + final FontMetrics metrics = graphics.getFontMetrics(); - // for the "Name" column - int nodeColWidth = Math.min(getMaxColumnWidth(0, metrics, margin, 40, firstColumnLabel, content), 250); // Note: 40 is the width of the icon + node lines. Change this value if those values change! - ov.getOutline().getColumnModel().getColumn(0).setPreferredWidth(nodeColWidth); + // for the "Name" column + int nodeColWidth = Math.min(getMaxColumnWidth(0, metrics, margin, 40, firstColumnLabel, content), 250); // Note: 40 is the width of the icon + node lines. Change this value if those values change! + ov.getOutline().getColumnModel().getColumn(0).setPreferredWidth(nodeColWidth); - // get the max for each other column - for (int colIndex = startColumn; colIndex <= totalColumns; colIndex++) { - int colWidth = Math.min(getMaxColumnWidth(colIndex, metrics, margin, 8, props, content), 350); - ov.getOutline().getColumnModel().getColumn(colIndex).setPreferredWidth(colWidth); - } - } - } - - // if there's no content just auto resize all columns - if (!(content.length > 0)) { - // turn on the auto resize - ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - } - } - }); - } - - private static Object[][] getRowValues(Node node, int rows) { - // how many rows are we returning - int maxRows = Math.min(rows, node.getChildren().getNodesCount()); - - Object[][] objs = new Object[maxRows][]; - - for (int i = 0; i < maxRows; i++) { - PropertySet[] props = node.getChildren().getNodeAt(i).getPropertySets(); - if (props.length == 0) //rare special case - { - continue; - } - Property[] property = props[0].getProperties(); - objs[i] = new Object[property.length]; - - - for (int j = 0; j < property.length; j++) { - try { - objs[i][j] = property[j].getValue(); - } catch (IllegalAccessException ignore) { - objs[i][j] = "n/a"; - } catch (InvocationTargetException ignore) { - objs[i][j] = "n/a"; + // get the max for each other column + for (int colIndex = startColumn; colIndex <= totalColumns; colIndex++) { + int colWidth = Math.min(getMaxColumnWidth(colIndex, metrics, margin, 8, props, content), 350); + ov.getOutline().getColumnModel().getColumn(colIndex).setPreferredWidth(colWidth); } } } - return objs; + + // if there's no content just auto resize all columns + if (!(content.length > 0)) { + // turn on the auto resize + ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + } + } + + // Populate a two-dimensional array with rows of property values for up + // to maxRows children of the node passed in. + private static Object[][] getRowValues(Node node, int maxRows) { + Object[][] rowValues = new Object[Math.min(maxRows, node.getChildren().getNodesCount())][]; + int rowCount = 0; + for (Node child : node.getChildren().getNodes()) { + if (rowCount >= maxRows) { + break; + } + PropertySet[] propertySets = child.getPropertySets(); + if (propertySets.length > 0) + { + Property[] properties = propertySets[0].getProperties(); + rowValues[rowCount] = new Object[properties.length]; + for (int j = 0; j < properties.length; ++j) { + try { + rowValues[rowCount][j] = properties[j].getValue(); + } + catch (IllegalAccessException | InvocationTargetException ignore) { + rowValues[rowCount][j] = "n/a"; + } + } + } + ++rowCount; + } + return rowValues; } @Override @@ -492,11 +485,20 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } @Override - public void childrenAdded(NodeMemberEvent nme) { + public void childrenAdded(final NodeMemberEvent nme) { Node[] delta = nme.getDelta(); if (load && containsReal(delta)) { load = false; - setupTable(nme.getNode()); + if (SwingUtilities.isEventDispatchThread()) { + setupTable(nme.getNode()); + } else { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + setupTable(nme.getNode()); + } + }); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ContextMenuExtensionPoint.java b/Core/src/org/sleuthkit/autopsy/coreutils/ContextMenuExtensionPoint.java new file mode 100755 index 0000000000..10cb36ac33 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ContextMenuExtensionPoint.java @@ -0,0 +1,50 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.coreutils; + +import org.openide.util.Lookup; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.Action; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; + +/** + * This class implements the ContextMenuActionsProvider extension point. + */ +public class ContextMenuExtensionPoint { + /** + * Gets all of the Actions provided by registered implementers of the + * ContextMenuActionsProvider interface. + * @return A list, possibly empty, of Action objects. + */ + static public List getActions() { + ArrayList actions = new ArrayList<>(); + Collection actionProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); + for (ContextMenuActionsProvider provider : actionProviders) { + List providerActions = provider.getActions(); + if (!providerActions.isEmpty()) { + actions.add(null); // Separator to set off this provider's actions. + actions.addAll(provider.getActions()); + actions.add(null); // Separator to set off this provider's actions. + } + } + return actions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index eed0c43ea3..eb9001575c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -146,6 +146,12 @@ public abstract class AbstractAbstractFileNode extends A return "Known"; } }, + HASHSETS { + @Override + public String toString() { + return "In Hashsets"; + } + }, MD5HASH { @Override public String toString() { @@ -188,6 +194,7 @@ public abstract class AbstractAbstractFileNode extends A map.put(AbstractFilePropertyType.TYPE_DIR.toString(), content.getDirType().getLabel()); map.put(AbstractFilePropertyType.TYPE_META.toString(), content.getMetaType().toString()); map.put(AbstractFilePropertyType.KNOWN.toString(), content.getKnown().getName()); + map.put(AbstractFilePropertyType.HASHSETS.toString(), HashsetHits.getList(content.getSleuthkitCase(), content.getId())); map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 97d9e99524..eaccbc35e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsRootNode; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; @@ -157,8 +156,8 @@ abstract class AbstractContentChildren extends Keys { } @Override - public AbstractNode visit(Tags t) { - return t.new TagsRootNode(); + public AbstractNode visit(TagsNodeKey tagsNodeKey) { + return new TagsNode(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactTypeNode.java index c5d347ae6e..32367d2d1c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactTypeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactTypeNode.java @@ -128,11 +128,6 @@ public class ArtifactTypeNode extends DisplayableItemNode { return "artifact-icon.png"; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index 428db99c76..f57388ae65 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -52,14 +52,14 @@ public interface AutopsyItemVisitor { T visit(EmailExtracted ee); - T visit(Tags t); - + T visit(TagsNodeKey tagsNodeKey); + T visit(DataSources i); T visit(Views v); T visit(Results r); - + static abstract public class Default implements AutopsyItemVisitor { protected abstract T defaultVisit(AutopsyVisitableItem ec); @@ -135,8 +135,8 @@ public interface AutopsyItemVisitor { } @Override - public T visit(Tags t) { - return defaultVisit(t); + public T visit(TagsNodeKey tagsNodeKey) { + return defaultVisit(tagsNodeKey); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 27caebc469..77249b1a1d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; @@ -140,7 +141,12 @@ public class BlackboardArtifactNode extends DisplayableItemNode { } else { String dataSource = ""; try { - dataSource = associated.getImage().getName(); + Image image = associated.getImage(); + if (image != null) { + dataSource = image.getName(); + } else { + dataSource = getRootParentName(); + } } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to get image name from " + associated.getName()); } @@ -153,6 +159,20 @@ public class BlackboardArtifactNode extends DisplayableItemNode { return s; } + + private String getRootParentName() { + String parentName = associated.getName(); + Content parent = associated; + try { + while ((parent = parent.getParent()) != null) { + parentName = parent.getName(); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to get parent name from " + associated.getName()); + return ""; + } + return parentName; + } /** * Add an additional custom node property to that node before it is @@ -334,11 +354,6 @@ public class BlackboardArtifactNode extends DisplayableItemNode { return "artifact-icon.png"; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java new file mode 100755 index 0000000000..3503776324 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.Action; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class wrap BlackboardArtifactTag objects. In the Autopsy + * presentation of the SleuthKit data model, they are leaf nodes of a sub-tree + * organized as follows: there is a tags root node with tag name child nodes; + * tag name nodes have tag type child nodes; tag type nodes are the parents of + * either content or blackboard artifact tag nodes. + */ +public class BlackboardArtifactTagNode extends DisplayableItemNode { + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; + private final BlackboardArtifactTag tag; + + public BlackboardArtifactTagNode(BlackboardArtifactTag tag) { + super(Children.LEAF, Lookups.fixed(tag, tag.getArtifact(), tag.getContent())); + super.setName(tag.getContent().getName()); + super.setDisplayName(tag.getContent().getName()); + this.setIconBaseWithExtension(ICON_PATH); + this.tag = tag; + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("Source File", "Source File", "", tag.getContent().getName())); + String contentPath; + try { + contentPath = tag.getContent().getUniquePath(); + } + catch (TskCoreException ex) { + Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); + contentPath = "Unavailable"; + } + properties.put(new NodeProperty("Source File Path", "Source File Path", "", contentPath)); + properties.put(new NodeProperty("Result Type", "Result Type", "", tag.getArtifact().getDisplayName())); + properties.put(new NodeProperty("Comment", "Comment", "", tag.getComment())); + + return propertySheet; + } + + @Override + public Action[] getActions(boolean context) { + List actions = DataModelActionsFactory.getActions(tag.getContent(), true); + actions.add(null); // Adds a menu item separator. + actions.add(DeleteBlackboardArtifactTagAction.getInstance()); + return actions.toArray(new Action[0]); + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } +} + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bookmarks.java b/Core/src/org/sleuthkit/autopsy/datamodel/Bookmarks.java deleted file mode 100644 index e9d8036573..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bookmarks.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2012 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import java.awt.event.ActionEvent; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.Action; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Support for bookmark (file and result/artifact) nodes and displaying - * bookmarks in the directory tree Bookmarks are divided into file and result - * children bookmarks. - * - * Bookmarks are specialized tags - TSK_TAG_NAME starts with File Bookmark or - * Result Bookmark - * - * @deprecated cosolidated under Tags - * - * TODO bookmark hierarchy support (TSK_TAG_NAME with slashes) - */ -@Deprecated -public class Bookmarks implements AutopsyVisitableItem { - - public static final String NAME = "Bookmarks"; - private static final String FILE_BOOKMARKS_LABEL_NAME = "File Bookmarks"; - private static final String RESULT_BOOKMARKS_LABEL_NAME = "Result Bookmarks"; - //bookmarks are specializations of tags - public static final String BOOKMARK_TAG_NAME = "Bookmark"; - private static final String BOOKMARK_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; - private static final Logger logger = Logger.getLogger(Bookmarks.class.getName()); - private SleuthkitCase skCase; - private final Map> data = - new EnumMap>(BlackboardArtifact.ARTIFACT_TYPE.class); - - public Bookmarks(SleuthkitCase skCase) { - this.skCase = skCase; - - } - - @Override - public T accept(AutopsyItemVisitor v) { - return null; //v.visit(this); - } - - /** - * bookmarks root node with file/result bookmarks - */ - public class BookmarksRootNode extends DisplayableItemNode { - - public BookmarksRootNode() { - super(Children.create(new BookmarksRootChildren(), true), Lookups.singleton(NAME)); - super.setName(NAME); - super.setDisplayName(NAME); - this.setIconBaseWithExtension(BOOKMARK_ICON_PATH); - initData(); - } - - private void initData() { - data.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE, null); - data.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT, null); - - try { - - //filter out tags that are not bookmarks - //we get bookmarks that have tag names that start with predefined names, preserving the bookmark hierarchy - List tagFiles = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME, - BOOKMARK_TAG_NAME); - List tagArtifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME, - BOOKMARK_TAG_NAME); - - data.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE, tagFiles); - data.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT, tagArtifacts); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Count not initialize bookmark nodes, ", ex); - } - - - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return null; // v.visit(this); - } - - @Override - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty("Name", - "Name", - "no description", - getName())); - - return s; - } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - } - - /** - * bookmarks root child node creating types of bookmarks nodes - */ - private class BookmarksRootChildren extends ChildFactory { - - @Override - protected boolean createKeys(List list) { - for (BlackboardArtifact.ARTIFACT_TYPE artType : data.keySet()) { - list.add(artType); - } - - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact.ARTIFACT_TYPE key) { - return new BookmarksNodeRoot(key, data.get(key)); - } - } - - /** - * Bookmarks node representation (file or result) - */ - public class BookmarksNodeRoot extends DisplayableItemNode { - - public BookmarksNodeRoot(BlackboardArtifact.ARTIFACT_TYPE bookType, List bookmarks) { - super(Children.create(new BookmarksChildrenNode(bookmarks), true), Lookups.singleton(bookType.getDisplayName())); - - String name = null; - if (bookType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE)) { - name = FILE_BOOKMARKS_LABEL_NAME; - } else if (bookType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT)) { - name = RESULT_BOOKMARKS_LABEL_NAME; - } - - super.setName(name); - super.setDisplayName(name + " (" + bookmarks.size() + ")"); - - this.setIconBaseWithExtension(BOOKMARK_ICON_PATH); - } - - @Override - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty("Name", - "Name", - "no description", - getName())); - - return s; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return null; //v.visit(this); - } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - } - - /** - * Node representing mail folder content (mail messages) - */ - private class BookmarksChildrenNode extends ChildFactory { - - private List bookmarks; - - private BookmarksChildrenNode(List bookmarks) { - super(); - this.bookmarks = bookmarks; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(bookmarks); - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact artifact) { - BlackboardArtifactNode bookmarkNode = null; - - int artifactTypeID = artifact.getArtifactTypeID(); - if (artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - final BlackboardArtifact sourceResult = Tags.getArtifactFromTag(artifact.getArtifactID()); - bookmarkNode = new BlackboardArtifactNode(artifact, BOOKMARK_ICON_PATH) { - @Override - public Action[] getActions(boolean bln) { - //Action [] actions = super.getActions(bln); //To change body of generated methods, choose Tools | Templates. - Action[] actions = new Action[1]; - actions[0] = new AbstractAction("View Source Result") { - @Override - public void actionPerformed(ActionEvent e) { - //open the source artifact in dir tree - if (sourceResult != null) { - BlackboardResultViewer v = Lookup.getDefault().lookup(BlackboardResultViewer.class); - v.viewArtifact(sourceResult); - } - } - }; - return actions; - } - }; - - //add custom property - final String NO_DESCR = "no description"; - String resultType = sourceResult.getDisplayName(); - NodeProperty resultTypeProp = new NodeProperty("Source Result Type", - "Result Type", - NO_DESCR, - resultType); - bookmarkNode.addNodeProperty(resultTypeProp); - - } else { - //file bookmark, no additional action - bookmarkNode = new BlackboardArtifactNode(artifact, BOOKMARK_ICON_PATH); - - } - return bookmarkNode; - } - } - - /** - * Links existing blackboard artifact (a tag) to this artifact. Linkage is - * made using TSK_TAGGED_ARTIFACT attribute. - */ - void addArtifactTag(BlackboardArtifact art, BlackboardArtifact tag) throws TskCoreException { - if (art.equals(tag)) { - throw new TskCoreException("Cannot tag the same artifact: id" + art.getArtifactID()); - } - BlackboardAttribute attrLink = new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID(), - "", art.getArtifactID()); - tag.addAttribute(attrLink); - } - - /** - * Get tag artifacts linked to the artifact - * - * @param art artifact to get tags for - * @return list of children artifacts or an empty list - * @throws TskCoreException exception thrown if a critical error occurs - * within tsk core and child artifact could not be queried - */ - List getTagArtifacts(BlackboardArtifact art) throws TskCoreException { - return skCase.getBlackboardArtifacts(ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT, art.getArtifactID()); - } -} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java new file mode 100755 index 0000000000..352b040888 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.datamodel; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.Action; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.actions.DeleteContentTagAction; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class wrap ContentTag objects. In the Autopsy + * presentation of the SleuthKit data model, they are leaf nodes of a tree + * consisting of content and blackboard artifact tags, grouped first by tag + * type, then by tag name. + */ +public class ContentTagNode extends DisplayableItemNode { + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; + private final ContentTag tag; + + public ContentTagNode(ContentTag tag) { + super(Children.LEAF, Lookups.fixed(tag, tag.getContent())); + super.setName(tag.getContent().getName()); + super.setDisplayName(tag.getContent().getName()); + this.setIconBaseWithExtension(ICON_PATH); + this.tag = tag; + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("File", "File", "", tag.getContent().getName())); + String contentPath; + try { + contentPath = tag.getContent().getUniquePath(); + } + catch (TskCoreException ex) { + Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); + contentPath = "Unavailable"; + } + properties.put(new NodeProperty("File Path", "File Path", "", contentPath)); + properties.put(new NodeProperty("Comment", "Comment", "", tag.getComment())); + + return propertySheet; + } + + @Override + public Action[] getActions(boolean context) { + List actions = DataModelActionsFactory.getActions(tag.getContent(), false); + actions.add(null); // Adds a menu item separator. + actions.add(DeleteContentTagAction.getInstance()); + return actions.toArray(new Action[0]); + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagTypeNode.java new file mode 100755 index 0000000000..41dad8ac5b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagTypeNode.java @@ -0,0 +1,108 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class are are elements of a directory tree sub-tree + * consisting of content and blackboard artifact tags, grouped first by tag type, + * then by tag name. + */ +public class ContentTagTypeNode extends DisplayableItemNode { + private static final String DISPLAY_NAME = "File Tags"; + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; + + public ContentTagTypeNode(TagName tagName) { + super(Children.create(new ContentTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + DISPLAY_NAME)); + + long tagsCount = 0; + try { + tagsCount = Case.getCurrentCase().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + } + catch (TskCoreException ex) { + Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); + } + + super.setName(DISPLAY_NAME); + super.setDisplayName(DISPLAY_NAME + " (" + tagsCount + ")"); + this.setIconBaseWithExtension(ICON_PATH); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("Name", "Name", "", getName())); + + return propertySheet; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + private static class ContentTagNodeFactory extends ChildFactory { + private final TagName tagName; + + ContentTagNodeFactory(TagName tagName) { + this.tagName = tagName; + } + + @Override + protected boolean createKeys(List keys) { + // Use the content tags bearing the specified tag name as the keys. + try { + keys.addAll(Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName)); + } + catch (TskCoreException ex) { + Logger.getLogger(ContentTagTypeNode.ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(ContentTag key) { + // The content tags to be wrapped are used as the keys. + return new ContentTagNode(key); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataConversion.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataConversion.java index 876feaefad..b4672bbb55 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataConversion.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataConversion.java @@ -20,79 +20,106 @@ package org.sleuthkit.autopsy.datamodel; import java.awt.Font; import java.util.Arrays; +import java.util.Formatter; /** * Helper methods for converting data. */ public class DataConversion { - public static String byteArrayToHex(byte[] array, int length, long offset, Font font) { + final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + /** + * Return the hex-dump layout of the passed in byte array. + * Deprecated because we don't need font + * @param array Data to display + * @param length Amount of data in array to display + * @param arrayOffset Offset of where data in array begins as part of a bigger file (used for arrayOffset column) + * @param font Font that will be used to display the text + * @return + */ + @Deprecated + public static String byteArrayToHex(byte[] array, int length, long arrayOffset, Font font) { + return byteArrayToHex(array, length, arrayOffset); + } + + /** + * Return the hex-dump layout of the passed in byte array. + * @param array Data to display + * @param length Amount of data in array to display + * @param arrayOffset Offset of where data in array begins as part of a bigger file (used for arrayOffset column) + * @return + */ + public static String byteArrayToHex(byte[] array, int length, long arrayOffset) { if (array == null) { return ""; - } else { - String base = new String(array, 0, length); + } + else { + StringBuilder outputStringBuilder = new StringBuilder(); + + // loop through the file in 16-byte increments + for (int curOffset = 0; curOffset < length; curOffset += 16) { + // how many bytes are we displaying on this line + int lineLen = 16; + if (length - curOffset < 16) { + lineLen = length - curOffset; + } + + // print the offset column + //outputStringBuilder.append("0x"); + outputStringBuilder.append(String.format("0x%08x: ", arrayOffset + curOffset)); + //outputStringBuilder.append(": "); - StringBuilder buff = new StringBuilder(); - int count = 0; - int extra = base.length() % 16; - String sub = ""; - char subchar; - - //commented out code can be used as a base for generating hex length based on - //offset/length/file size - //String hex = Long.toHexString(length + offset); - //double hexMax = Math.pow(16, hex.length()); - double hexMax = Math.pow(16, 6); - while (count < base.length() - extra) { - buff.append("0x"); - buff.append(Long.toHexString((long) (offset + count + hexMax)).substring(1)); - buff.append(": "); + // print the hex columns for (int i = 0; i < 16; i++) { - buff.append(Integer.toHexString((((int) base.charAt(count + i)) & 0xff) + 256).substring(1).toUpperCase()); - buff.append(" "); - if (i == 7) { - buff.append(" "); + if (i < lineLen) { + int v = array[curOffset + i] & 0xFF; + outputStringBuilder.append(hexArray[v >>> 4]); + outputStringBuilder.append(hexArray[v & 0x0F]); + } + else { + outputStringBuilder.append(" "); + } + + // someday we'll offer the option of these two styles... + if (true) { + outputStringBuilder.append(" "); + if (i % 4 == 3) { + outputStringBuilder.append(" "); + } + if (i == 7) { + outputStringBuilder.append(" "); + } + } + // xxd style + else { + if (i % 2 == 1) { + outputStringBuilder.append(" "); + } } } - sub = base.substring(count, count + 16); + + outputStringBuilder.append(" "); + + // print the ascii columns + String ascii = new String(array, curOffset, lineLen); for (int i = 0; i < 16; i++) { - subchar = sub.charAt(i); - if (!font.canDisplay(subchar)) { - sub.replace(subchar, '.'); - } - - // replace all unprintable characters with "." - int dec = (int) subchar; - if (dec < 32 || dec > 126) { - sub = sub.replace(subchar, '.'); + char c = ' '; + if (i < lineLen) { + c = ascii.charAt(i); + int dec = (int) c; + + if (dec < 32 || dec > 126) { + c = '.'; + } } + outputStringBuilder.append(c); } - buff.append(" " + sub + "\n"); - count += 16; - + + outputStringBuilder.append("\n"); } - if (base.length() % 16 != 0) { - buff.append("0x" + Long.toHexString((long) (offset + count + hexMax)).substring(1) + ": "); - } - for (int i = 0; i < 16; i++) { - if (i < extra) { - buff.append(Integer.toHexString((((int) base.charAt(count + i)) & 0xff) + 256).substring(1) + " "); - } else { - buff.append(" "); - } - if (i == 7) { - buff.append(" "); - } - } - sub = base.substring(count, count + extra); - for (int i = 0; i < extra; i++) { - subchar = sub.charAt(i); - if (!font.canDisplay(subchar)) { - sub.replace(subchar, '.'); - } - } - buff.append(" " + sub); - return buff.toString(); + + return outputStringBuilder.toString(); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java new file mode 100755 index 0000000000..6660d61868 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -0,0 +1,182 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.HashSearchAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.VirtualDirectory; + +/** + * This class provides methods for creating sets of actions for data model objects. + */ +// TODO: All of the methods below that deal with classes derived from AbstractFile are the same except for the creation of wrapper nodes to pass to actions. +// 1. Do the types of the wrapper nodes really need to vary? If not, it would mean a single +// static List getActions(AbstrctFile file, boolean isArtifactSource) +// method could be implemented. If the different nodes are necessary, is it merely because of some misuse of the Visitor pattern somewhere? +// 2. All of this would be much improved by not constructing nodes with actions, but this might be necessary with pushing of nodes rather than use of lookups to +// handle selections. +class DataModelActionsFactory { + static List getActions(File file, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), file)); + final FileNode fileNode = new FileNode(file); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", fileNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", fileNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(new HashSearchAction("Search for files with the same MD5 hash", fileNode)); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(LayoutFile file, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), file)); + LayoutFileNode layoutFileNode = new LayoutFileNode(file); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", layoutFileNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", layoutFileNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance());// + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(Directory directory, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), directory)); + DirectoryNode directoryNode = new DirectoryNode(directory); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", directoryNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", directoryNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(VirtualDirectory directory, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), directory)); + VirtualDirectoryNode directoryNode = new VirtualDirectoryNode(directory); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", directoryNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", directoryNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(LocalFile file, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), file)); + final LocalFileNode localFileNode = new LocalFileNode(file); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", localFileNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", localFileNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(DerivedFile file, boolean isArtifactSource) { + List actions = new ArrayList<>(); + actions.add(new ViewContextAction((isArtifactSource ? "View Source File in Directory" : "View File in Directory"), file)); + final LocalFileNode localFileNode = new LocalFileNode(file); + actions.add(null); // creates a menu separator + actions.add(new NewWindowViewAction("View in New Window", localFileNode)); + actions.add(new ExternalViewerAction("Open in External Viewer", localFileNode)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actions.add(AddBlackboardArtifactTagAction.getInstance()); + } + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions; + } + + static List getActions(Content content, boolean isArtifactSource) { + if (content instanceof File) { + return getActions((File)content, isArtifactSource); + } + else if (content instanceof LayoutFile) { + return getActions((LayoutFile)content, isArtifactSource); + } + else if (content instanceof Directory) { + return getActions((Directory)content, isArtifactSource); + } + else if (content instanceof VirtualDirectory) { + return getActions((VirtualDirectory)content, isArtifactSource); + } + else if (content instanceof LocalFile) { + return getActions((LocalFile)content, isArtifactSource); + } + else if (content instanceof DerivedFile) { + return getActions((DerivedFile)content, isArtifactSource); + } + else { + return new ArrayList<>(); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 280563e63a..c8f2e93e29 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -39,10 +39,10 @@ public class DataSourcesNode extends DisplayableItemNode { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 66390045ee..bf85dd703a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -109,10 +109,10 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -199,11 +199,6 @@ public class DeletedContent implements AutopsyVisitableItem { return s; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 3c4d33c253..624facfebc 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -1,97 +1,99 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import java.util.ArrayList; -import java.util.List; -import javax.swing.Action; -import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; -import org.sleuthkit.autopsy.directorytree.ViewContextAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; - -/** - * This class is used to represent the "Node" for the directory. Its children - * are more directories. - */ -public class DirectoryNode extends AbstractFsContentNode { - - public static final String DOTDOTDIR = "[parent folder]"; - public static final String DOTDIR = "[current folder]"; - - public DirectoryNode(Directory dir) { - this(dir, true); - - setIcon(dir); - } - - public DirectoryNode(AbstractFile dir, boolean directoryBrowseMode) { - super(dir, directoryBrowseMode); - - setIcon(dir); - } - - private void setIcon(AbstractFile dir) { - // set name, display name, and icon - if (dir.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-deleted.png"); - } else { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); - } - } - - /** - * Right click action for this node - * - * @param popup - * @return - */ - @Override - public Action[] getActions(boolean popup) { - List actions = new ArrayList<>(); - if (!getDirectoryBrowseMode()) { - actions.add(new ViewContextAction("View File in Directory", this)); - actions.add(null); // creates a menu separator - } - actions.add(new NewWindowViewAction("View in New Window", this)); - actions.add(null); // creates a menu separator - actions.add(ExtractAction.getInstance()); - actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - return actions.toArray(new Action[0]); - } - - @Override - public T accept(ContentNodeVisitor v) { - return v.visit(this); - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; + +/** + * This class is used to represent the "Node" for the directory. Its children + * are more directories. + */ +public class DirectoryNode extends AbstractFsContentNode { + + public static final String DOTDOTDIR = "[parent folder]"; + public static final String DOTDIR = "[current folder]"; + + public DirectoryNode(Directory dir) { + this(dir, true); + + setIcon(dir); + } + + public DirectoryNode(AbstractFile dir, boolean directoryBrowseMode) { + super(dir, directoryBrowseMode); + + setIcon(dir); + } + + private void setIcon(AbstractFile dir) { + // set name, display name, and icon + if (dir.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-deleted.png"); + } else { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); + } + } + + /** + * Right click action for this node + * + * @param popup + * @return + */ + @Override + public Action[] getActions(boolean popup) { + List actions = new ArrayList<>(); + if (!getDirectoryBrowseMode()) { + actions.add(new ViewContextAction("View File in Directory", this)); + actions.add(null); // creates a menu separator + } + actions.add(new NewWindowViewAction("View in New Window", this)); + actions.add(null); // creates a menu separator + actions.add(ExtractAction.getInstance()); + actions.add(null); // creates a menu separator + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); + return actions.toArray(new Action[0]); + } + + @Override + public T accept(ContentNodeVisitor v) { + return v.visit(this); + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java index f5c4613bf7..9d150d53ad 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,9 @@ */ package org.sleuthkit.autopsy.datamodel; -import java.awt.datatransfer.Transferable; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.util.Lookup; -import org.openide.util.datatransfer.PasteType; - /** * Interface for all displayable Nodes @@ -37,35 +34,7 @@ public abstract class DisplayableItemNode extends AbstractNode { public DisplayableItemNode(Children children, Lookup lookup) { super(children, lookup); } - - - /** - * Possible sub-implementations - */ - public enum TYPE { - CONTENT, ///< content node, such as file, image - ARTIFACT, ///< artifact data node - META, ///< top-level category node, such as view, filters, etc. - }; - - /** - * Get possible subtype of the displayable item node - * @return - */ - public abstract TYPE getDisplayableItemNodeType(); - - public boolean isLeafTypeNode() { - return false; - } - - /** - * Visitor pattern support. - * - * @param v visitor - * @return visitor's visit return value - */ - public abstract T accept(DisplayableItemNodeVisitor v); - - - + + abstract public boolean isLeafTypeNode(); + public abstract T accept(DisplayableItemNodeVisitor v); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 6e42c7d611..e2cef3846e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,12 +30,10 @@ import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetHitsSetNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsKeywordNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsListNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsRootNode; -import org.sleuthkit.autopsy.datamodel.Tags.TagNodeRoot; -import org.sleuthkit.autopsy.datamodel.Tags.TagsNodeRoot; -import org.sleuthkit.autopsy.datamodel.Tags.TagsRootNode; +import org.sleuthkit.autopsy.directorytree.BlackboardArtifactTagTypeNode; /** - * Visitor pattern for DisplayableItemNodes + * Visitor pattern implementation for DisplayableItemNodes */ public interface DisplayableItemNodeVisitor { @@ -85,11 +83,17 @@ public interface DisplayableItemNodeVisitor { T visit(EmailExtractedFolderNode eefn); - T visit(TagsRootNode bksrn); + T visit(TagsNode node); - T visit(TagsNodeRoot bksrn); + T visit(TagNameNode node); - T visit(TagNodeRoot tnr); + T visit(ContentTagTypeNode node); + + T visit(ContentTagNode node); + + T visit(BlackboardArtifactTagTypeNode node); + + T visit(BlackboardArtifactTagNode node); T visit(ViewsNode vn); @@ -265,18 +269,33 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(TagsRootNode bksrn) { - return defaultVisit(bksrn); + public T visit(TagsNode node) { + return defaultVisit(node); } @Override - public T visit(TagsNodeRoot bksnr) { - return defaultVisit(bksnr); + public T visit(TagNameNode node) { + return defaultVisit(node); } @Override - public T visit(TagNodeRoot tnr) { - return defaultVisit(tnr); + public T visit(ContentTagTypeNode node) { + return defaultVisit(node); + } + + @Override + public T visit(ContentTagNode node) { + return defaultVisit(node); + } + + @Override + public T visit(BlackboardArtifactTagTypeNode node) { + return defaultVisit(node); + } + + @Override + public T visit(BlackboardArtifactTagNode node) { + return defaultVisit(node); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index ea813ac189..c5311b3d20 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -133,10 +133,10 @@ public class EmailExtracted implements AutopsyVisitableItem { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { //return v.visit(this); @@ -215,10 +215,10 @@ public class EmailExtracted implements AutopsyVisitableItem { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -272,11 +272,6 @@ public class EmailExtracted implements AutopsyVisitableItem { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account-icon-16.png"); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -294,6 +289,11 @@ public class EmailExtracted implements AutopsyVisitableItem { return s; } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -337,11 +337,6 @@ public class EmailExtracted implements AutopsyVisitableItem { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-16.png"); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentNode.java index 6d37c4392a..bd63cdb7e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentNode.java @@ -38,6 +38,11 @@ public class ExtractedContentNode extends DisplayableItemNode { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/extracted_content.png"); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -58,9 +63,4 @@ public class ExtractedContentNode extends DisplayableItemNode { NAME)); return s; } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 9ca87fd8a7..f16325609a 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -1,180 +1,177 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import java.util.ArrayList; -import java.util.List; -import javax.swing.Action; -import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; -import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; -import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; -import org.sleuthkit.autopsy.directorytree.ViewContextAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; -import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; - -/** - * This class is used to represent the "Node" for the file. It may have derived - * files children. - */ -public class FileNode extends AbstractFsContentNode { - - /** - * @param file underlying Content - */ - public FileNode(AbstractFile file) { - this(file, true); - - setIcon(file); - } - - public FileNode(AbstractFile file, boolean directoryBrowseMode) { - super(file, directoryBrowseMode); - - setIcon(file); - } - - private void setIcon(AbstractFile file) { - // set name, display name, and icon - if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); - } else { - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); - } - } else { - this.setIconBaseWithExtension(getIconForFileType(file)); - } - } - - /** - * Right click action for this node - * - * @param popup - * @return - */ - @Override - public Action[] getActions(boolean popup) { - List actionsList = new ArrayList<>(); - if (!this.getDirectoryBrowseMode()) { - actionsList.add(new ViewContextAction("View File in Directory", this)); - actionsList.add(null); // creates a menu separator - } - actionsList.add(new NewWindowViewAction("View in New Window", this)); - actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); - actionsList.add(null); // creates a menu separator - actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); - actionsList.add(null); // creates a menu separator - actionsList.add(TagAbstractFileAction.getInstance()); - return actionsList.toArray(new Action[0]); - } - - @Override - public T accept(ContentNodeVisitor< T> v) { - return v.visit(this); - } - - @Override - public T accept(DisplayableItemNodeVisitor< T> v) { - return v.visit(this); - } - - // Given a file, returns the correct icon for said - // file based off it's extension - static String getIconForFileType(AbstractFile file) { - // Get the name, extension - String name = file.getName(); - int dotIndex = name.lastIndexOf("."); - if (dotIndex == -1) { - return "org/sleuthkit/autopsy/images/file-icon.png"; - } - String ext = name.substring(dotIndex).toLowerCase(); - - // Images - for (String s : FileTypeExtensions.getImageExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/image-file.png"; - } - } - // Videos - for (String s : FileTypeExtensions.getVideoExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/video-file.png"; - } - } - // Audio Files - for (String s : FileTypeExtensions.getAudioExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/audio-file.png"; - } - } - // Documents - for (String s : FileTypeExtensions.getDocumentExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/doc-file.png"; - } - } - // Executables / System Files - for (String s : FileTypeExtensions.getExecutableExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/exe-file.png"; - } - } - // Text Files - for (String s : FileTypeExtensions.getTextExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/text-file.png"; - } - } - // Web Files - for (String s : FileTypeExtensions.getWebExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/web-file.png"; - } - } - // PDFs - for (String s : FileTypeExtensions.getPDFExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/pdf-file.png"; - } - } - // Archives - for (String s : FileTypeExtensions.getArchiveExtensions()) { - if (ext.equals(s)) { - return "org/sleuthkit/autopsy/images/archive-file.png"; - } - } - // Else return the default - return "org/sleuthkit/autopsy/images/file-icon.png"; - - } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } - - @Override - public boolean isLeafTypeNode() { - return true; //false; - } -} +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.Action; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.HashSearchAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; + +/** + * This class is used to represent the "Node" for the file. It may have derived + * files children. + */ +public class FileNode extends AbstractFsContentNode { + + /** + * @param file underlying Content + */ + public FileNode(AbstractFile file) { + this(file, true); + + setIcon(file); + } + + public FileNode(AbstractFile file, boolean directoryBrowseMode) { + super(file, directoryBrowseMode); + + setIcon(file); + } + + private void setIcon(AbstractFile file) { + // set name, display name, and icon + if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { + if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); + } else { + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); + } + } else { + this.setIconBaseWithExtension(getIconForFileType(file)); + } + } + + /** + * Right click action for this node + * + * @param popup + * @return + */ + @Override + public Action[] getActions(boolean popup) { + List actionsList = new ArrayList<>(); + if (!this.getDirectoryBrowseMode()) { + actionsList.add(new ViewContextAction("View File in Directory", this)); + actionsList.add(null); // creates a menu separator + } + actionsList.add(new NewWindowViewAction("View in New Window", this)); + actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList.toArray(new Action[0]); + } + + @Override + public T accept(ContentNodeVisitor< T> v) { + return v.visit(this); + } + + @Override + public T accept(DisplayableItemNodeVisitor< T> v) { + return v.visit(this); + } + + // Given a file, returns the correct icon for said + // file based off it's extension + static String getIconForFileType(AbstractFile file) { + // Get the name, extension + String name = file.getName(); + int dotIndex = name.lastIndexOf("."); + if (dotIndex == -1) { + return "org/sleuthkit/autopsy/images/file-icon.png"; + } + String ext = name.substring(dotIndex).toLowerCase(); + + // Images + for (String s : FileTypeExtensions.getImageExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/image-file.png"; + } + } + // Videos + for (String s : FileTypeExtensions.getVideoExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/video-file.png"; + } + } + // Audio Files + for (String s : FileTypeExtensions.getAudioExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/audio-file.png"; + } + } + // Documents + for (String s : FileTypeExtensions.getDocumentExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/doc-file.png"; + } + } + // Executables / System Files + for (String s : FileTypeExtensions.getExecutableExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/exe-file.png"; + } + } + // Text Files + for (String s : FileTypeExtensions.getTextExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/text-file.png"; + } + } + // Web Files + for (String s : FileTypeExtensions.getWebExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/web-file.png"; + } + } + // PDFs + for (String s : FileTypeExtensions.getPDFExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/pdf-file.png"; + } + } + // Archives + for (String s : FileTypeExtensions.getArchiveExtensions()) { + if (ext.equals(s)) { + return "org/sleuthkit/autopsy/images/archive-file.png"; + } + } + // Else return the default + return "org/sleuthkit/autopsy/images/file-icon.png"; + + } + + @Override + public boolean isLeafTypeNode() { + return true; + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index cb2b6d0965..2d8b44f8e5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -111,10 +111,10 @@ public class FileSize implements AutopsyVisitableItem { } @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.META; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -201,11 +201,6 @@ public class FileSize implements AutopsyVisitableItem { return s; } - @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.META; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java index 3af3340bac..02c8b89e1c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java @@ -78,11 +78,6 @@ public class FileTypeNode extends DisplayableItemNode { return s; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java index b8198c0d14..98b64f7d9b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java @@ -52,6 +52,11 @@ public class FileTypesNode extends DisplayableItemNode { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -72,9 +77,4 @@ public class FileTypesNode extends DisplayableItemNode { getName())); return s; } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index ca15f6d2e7..e390503246 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -94,6 +94,43 @@ public class HashsetHits implements AutopsyVisitableItem { } } + static public String getList(SleuthkitCase skCase, long objId) { + ResultSet rs = null; + String strList = ""; + + try { + int setNameId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(); + int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(); + String query = "SELECT value_text,blackboard_attributes.artifact_id,attribute_type_id " + + "FROM blackboard_attributes,blackboard_artifacts WHERE " + + "attribute_type_id=" + setNameId + + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" + + " AND blackboard_artifacts.artifact_type_id=" + artId + + " AND blackboard_artifacts.obj_id=" + objId; + rs = skCase.runQuery(query); + int i = 0; + while (rs.next()) { + if (i++ > 0) { + strList += ", "; + } + strList += rs.getString("value_text"); + } + + } catch (SQLException ex) { + logger.log(Level.WARNING, "SQL Exception occurred: ", ex); + } + finally { + if (rs != null) { + try { + skCase.closeRunQuery(rs); + } catch (SQLException ex) { + logger.log(Level.WARNING, "Error closing result set after getting hashset hits", ex); + } + } + } + return strList; + } + @Override public T accept(AutopsyItemVisitor v) { return v.visit(this); @@ -113,10 +150,10 @@ public class HashsetHits implements AutopsyVisitableItem { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -163,11 +200,6 @@ public class HashsetHits implements AutopsyVisitableItem { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 7e02f807a1..bea81eebc7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -56,11 +56,6 @@ public class ImageNode extends AbstractContentNode { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hard-drive-icon.jpg"); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } - /** * Right click action for this node * @@ -98,6 +93,11 @@ public class ImageNode extends AbstractContentNode { return v.visit(this); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index d41cdb831c..59055bdcff 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -171,16 +171,16 @@ public class KeywordHits implements AutopsyVisitableItem { //logger.info("Process took " + (finish-start) + " ms" ); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -231,11 +231,6 @@ public class KeywordHits implements AutopsyVisitableItem { this.children = children; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -259,6 +254,11 @@ public class KeywordHits implements AutopsyVisitableItem { return s; } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -312,11 +312,6 @@ public class KeywordHits implements AutopsyVisitableItem { return v.visit(this); } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.ARTIFACT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 9224131ad6..a896442f04 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 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,11 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskData; @@ -62,11 +63,6 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -95,6 +91,11 @@ public class LayoutFileNode extends AbstractAbstractFileNode { return v.visit(this); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -102,13 +103,14 @@ public class LayoutFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { - List actionsList = new ArrayList(); + List actionsList = new ArrayList<>(); actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(null); // creates a menu separator - actionsList.add(TagAbstractFileAction.getInstance()); + actionsList.add(AddContentTagAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 78711736b9..02121410da 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -25,12 +25,12 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.datamodel.DisplayableItemNode.TYPE; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.datamodel.AbstractFile; /** @@ -55,11 +55,6 @@ public class LocalFileNode extends AbstractAbstractFileNode { } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } - @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -92,7 +87,8 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(ExtractAction.getInstance()); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); actionsList.add(null); // creates a menu separator - actionsList.add(TagAbstractFileAction.getInstance()); + actionsList.add(AddContentTagAction.getInstance()); + actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java index a2b9bf9d46..6646eb24c3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java @@ -79,11 +79,6 @@ public class RecentFilesFilterNode extends DisplayableItemNode { return s; } - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } - @Override public boolean isLeafTypeNode() { return true; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java index 91958d0b97..ecacf715e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesNode.java @@ -42,10 +42,10 @@ public class RecentFilesNode extends DisplayableItemNode { } @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 7ebe1d8674..d87f1f2670 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.datamodel; import java.util.Arrays; -import org.openide.nodes.AbstractNode; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; import org.sleuthkit.datamodel.SleuthkitCase; @@ -36,13 +35,18 @@ public class ResultsNode extends DisplayableItemNode { new KeywordHits(sleuthkitCase), new HashsetHits(sleuthkitCase), new EmailExtracted(sleuthkitCase), - new Tags(sleuthkitCase) //TODO move to the top of the tree + new TagsNodeKey() )), Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -63,9 +67,4 @@ public class ResultsNode extends DisplayableItemNode { NAME)); return s; } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java index 1d9bc7fb42..8b1e2ecc47 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java @@ -79,19 +79,12 @@ public class RootContentChildren extends AbstractContentChildren { case TSK_EMAIL_MSG: if (o instanceof EmailExtracted) this.refreshKey(o); - break; - - //TODO check + break; case TSK_TAG_FILE: - if (o instanceof Tags) + case TSK_TAG_ARTIFACT: + if (o instanceof TagsNodeKey) this.refreshKey(o); - break; - - //TODO check - case TSK_TAG_ARTIFACT: - if (o instanceof Tags) - this.refreshKey(o); - break; + break; default: if (o instanceof ExtractedContent) this.refreshKey(o); @@ -105,7 +98,7 @@ public class RootContentChildren extends AbstractContentChildren { this.refreshKey(o); else if (o instanceof EmailExtracted) this.refreshKey(o); - else if (o instanceof Tags) + else if (o instanceof TagsNodeKey) this.refreshKey(o); else if (o instanceof ExtractedContent) this.refreshKey(o); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagNameNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagNameNode.java new file mode 100755 index 0000000000..38d140b039 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagNameNode.java @@ -0,0 +1,123 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.BlackboardArtifactTagTypeNode; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class are elements of Node hierarchies consisting of + * content and blackboard artifact tags, grouped first by tag type, then by + * tag name. + */ +public class TagNameNode extends DisplayableItemNode { + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; + private static final String BOOKMARK_TAG_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; + private final TagName tagName; + + public TagNameNode(TagName tagName) { + super(Children.create(new TagTypeNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " Tags")); + this.tagName = tagName; + + long tagsCount = 0; + try { + tagsCount = Case.getCurrentCase().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount += Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + } + catch (TskCoreException ex) { + Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); + } + + super.setName(tagName.getDisplayName()); + super.setDisplayName(tagName.getDisplayName() + " (" + tagsCount + ")"); + if (tagName.getDisplayName().equals("Bookmark")) { + setIconBaseWithExtension(BOOKMARK_TAG_ICON_PATH); + } + else { + setIconBaseWithExtension(ICON_PATH); + } + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("Name", "Name", tagName.getDescription(), getName())); + + return propertySheet; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + // See classes derived from DisplayableItemNodeVisitor + // for behavior added using the Visitor pattern. + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static class TagTypeNodeFactory extends ChildFactory { + private static final String CONTENT_TAG_TYPE_NODE_KEY = "Content Tags"; + private static final String BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY = "Result Tags"; + private final TagName tagName; + + TagTypeNodeFactory(TagName tagName) { + this.tagName = tagName; + } + + @Override + protected boolean createKeys(List keys) { + keys.add(CONTENT_TAG_TYPE_NODE_KEY); + keys.add(BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + switch (key) { + case CONTENT_TAG_TYPE_NODE_KEY: + return new ContentTagTypeNode(tagName); + case BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY: + return new BlackboardArtifactTagTypeNode(tagName); + default: + Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "{0} not a recognized key", key); + return null; + } + } + } +} + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java deleted file mode 100644 index 129091ed21..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import java.awt.event.ActionEvent; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; -import java.util.logging.Level; -import javax.swing.AbstractAction; -import javax.swing.Action; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - * Support for tags in the directory tree. Tag nodes representing file and - * result tags, encapsulate TSK_TAG_FILE and TSK_TAG_ARTIFACT typed artifacts. - * - * The class implements querying of data model and populating node hierarchy - * using child factories. - * - */ -public class Tags implements AutopsyVisitableItem { - - private static final Logger logger = Logger.getLogger(Tags.class.getName()); - private static final String FILE_TAG_LABEL_NAME = "File Tags"; - private static final String RESULT_TAG_LABEL_NAME = "Result Tags"; - private SleuthkitCase skCase; - public static final String NAME = "Tags"; - private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; - //bookmarks are specializations of tags - public static final String BOOKMARK_TAG_NAME = "Bookmark"; - private static final String BOOKMARK_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; - private Map>> tags; - private static final String EMPTY_COMMENT = ""; - private static final String APP_SETTINGS_FILE_NAME = "app"; // @@@ TODO: Need a general app settings or user preferences file, this will do for now. - private static final String TAG_NAMES_SETTING_KEY = "tag_names"; - private static final HashSet appSettingTagNames = new HashSet<>(); - private static final StringBuilder tagNamesAppSetting = new StringBuilder(); - - // When this class is loaded, either create an new app settings file or - // get the tag names setting from the existing app settings file. - static { - String setting = ModuleSettings.getConfigSetting(APP_SETTINGS_FILE_NAME, TAG_NAMES_SETTING_KEY); - if (null != setting && !setting.isEmpty()) { - // Make a speedy lookup for the tag names in the setting to aid in the - // detection of new tag names. - List tagNamesFromAppSettings = Arrays.asList(setting.split(",")); - for (String tagName : tagNamesFromAppSettings) { - appSettingTagNames.add(tagName); - } - - // Load the raw comma separated values list from the setting into a - // string builder to facilitate adding new tag names to the list and writing - // it back to the app settings file. - tagNamesAppSetting.append(setting); - } - } - - Tags(SleuthkitCase skCase) { - this.skCase = skCase; - } - - @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); - } - - /** - * Root of all Tag nodes. This node is shown directly under Results in the - * directory tree. - */ - public class TagsRootNode extends DisplayableItemNode { - - public TagsRootNode() { - super(Children.create(new Tags.TagsRootChildren(), true), Lookups.singleton(NAME)); - super.setName(NAME); - super.setDisplayName(NAME); - this.setIconBaseWithExtension(TAG_ICON_PATH); - initData(); - } - - private void initData() { - try { - // Get all file and artifact tags - - //init data - tags = new EnumMap<>(BlackboardArtifact.ARTIFACT_TYPE.class); - tags.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE, new HashMap>()); - tags.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT, new HashMap>()); - - //populate - for (BlackboardArtifact.ARTIFACT_TYPE artType : tags.keySet()) { - final Map> artTags = tags.get(artType); - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(artType)) { - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID()) { - String tagName = attribute.getValueString(); - if (artTags.containsKey(tagName)) { - List artifacts = artTags.get(tagName); - artifacts.add(artifact); - } else { - List artifacts = new ArrayList<>(); - artifacts.add(artifact); - artTags.put(tagName, artifacts); - } - break; - } - } - } - } - - - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Count not initialize tag nodes", ex); - } - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty("Name", - "Name", - "no description", - getName())); - - return s; - } - - @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.ARTIFACT; - } - } - - /** - * bookmarks root child node creating types of bookmarks nodes - */ - private class TagsRootChildren extends ChildFactory { - - @Override - protected boolean createKeys(List list) { - for (BlackboardArtifact.ARTIFACT_TYPE artType : tags.keySet()) { - list.add(artType); - } - - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact.ARTIFACT_TYPE key) { - return new TagsNodeRoot(key, tags.get(key)); - } - } - - /** - * Tag node representation (file or result) - */ - public class TagsNodeRoot extends DisplayableItemNode { - - TagsNodeRoot(BlackboardArtifact.ARTIFACT_TYPE tagType, Map> subTags) { - super(Children.create(new TagRootChildren(tagType, subTags), true), Lookups.singleton(tagType.getDisplayName())); - - String name = null; - if (tagType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE)) { - name = FILE_TAG_LABEL_NAME; - } else if (tagType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT)) { - name = RESULT_TAG_LABEL_NAME; - } - - super.setName(name); - super.setDisplayName(name + " (" + subTags.values().size() + ")"); - - this.setIconBaseWithExtension(TAG_ICON_PATH); - } - - @Override - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty("Name", - "Name", - "no description", - getName())); - - return s; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - } - - /** - * Child factory to add all the Tag artifacts to a TagsRootNode with the tag - * name. - */ - private class TagRootChildren extends ChildFactory { - - private Map> subTags; - private BlackboardArtifact.ARTIFACT_TYPE tagType; - - TagRootChildren(BlackboardArtifact.ARTIFACT_TYPE tagType, Map> subTags) { - super(); - this.tagType = tagType; - this.subTags = subTags; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(subTags.keySet()); - - return true; - } - - @Override - protected Node createNodeForKey(String key) { - return new Tags.TagNodeRoot(tagType, key, subTags.get(key)); - } - } - - /** - * Node for each unique tag name. Shown directly under Results > Tags. - */ - public class TagNodeRoot extends DisplayableItemNode { - - TagNodeRoot(BlackboardArtifact.ARTIFACT_TYPE tagType, String tagName, List artifacts) { - super(Children.create(new Tags.TagsChildrenNode(tagType, tagName, artifacts), true), Lookups.singleton(tagName)); - - super.setName(tagName); - super.setDisplayName(tagName + " (" + artifacts.size() + ")"); - - if (tagName.equals(BOOKMARK_TAG_NAME)) { - this.setIconBaseWithExtension(BOOKMARK_ICON_PATH); - } else { - this.setIconBaseWithExtension(TAG_ICON_PATH); - } - } - - @Override - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty("Name", - "Name", - "no description", - getName())); - - return s; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.ARTIFACT; - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - } - - /** - * Node representing an individual Tag artifact. For each TagsNodeRoot under - * Results > Tags, this is one of the nodes listed in the result viewer. - */ - private class TagsChildrenNode extends ChildFactory { - - private List artifacts; - private BlackboardArtifact.ARTIFACT_TYPE tagType; - private String tagName; - - private TagsChildrenNode(BlackboardArtifact.ARTIFACT_TYPE tagType, String tagName, List artifacts) { - super(); - this.tagType = tagType; - this.tagName = tagName; - this.artifacts = artifacts; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(artifacts); - return true; - } - - @Override - protected Node createNodeForKey(final BlackboardArtifact artifact) { - //create node with action - BlackboardArtifactNode tagNode = null; - - String iconPath; - if (tagName.equals(BOOKMARK_TAG_NAME)) { - iconPath = BOOKMARK_ICON_PATH; - } else { - iconPath = TAG_ICON_PATH; - } - - //create actions here where Tag logic belongs - //instead of DataResultFilterNode w/visitors, which is much less pluggable and cluttered - if (tagType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT)) { - //in case of result tag, add a action by sublcassing bb art node - //this action will be merged with other actions set DataResultFIlterNode - //otherwise in case of - tagNode = new BlackboardArtifactNode(artifact, iconPath) { - @Override - public Action[] getActions(boolean bln) { - //Action [] actions = super.getActions(bln); //To change body of generated methods, choose Tools | Templates. - Action[] actions = new Action[1]; - actions[0] = new AbstractAction("View Source Result") { - @Override - public void actionPerformed(ActionEvent e) { - //open the source artifact in dir tree - BlackboardArtifact sourceArt = Tags.getArtifactFromTag(artifact.getArtifactID()); - if (sourceArt != null) { - BlackboardResultViewer v = Lookup.getDefault().lookup(BlackboardResultViewer.class); - v.viewArtifact(sourceArt); - } - } - }; - return actions; - } - }; - } else { - //for file tag, don't subclass to add the additional actions - tagNode = new BlackboardArtifactNode(artifact, iconPath); - } - - //add some additional node properties - int artifactTypeID = artifact.getArtifactTypeID(); - final String NO_DESCR = "no description"; - if (artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - BlackboardArtifact sourceResult = Tags.getArtifactFromTag(artifact.getArtifactID()); - String resultType = sourceResult.getDisplayName(); - - NodeProperty resultTypeProp = new NodeProperty("Source Result Type", - "Result Type", - NO_DESCR, - resultType); - - - tagNode.addNodeProperty(resultTypeProp); - - } - - return tagNode; - } - } - - /** - * Create a tag for a file with TSK_TAG_NAME as tagName. - * - * @param file to create tag for - * @param tagName TSK_TAG_NAME - * @param comment the tag comment, or null if not present - */ - public static void createTag(AbstractFile file, String tagName, String comment) { - try { - final BlackboardArtifact bookArt = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); - List attrs = new ArrayList<>(); - - - BlackboardAttribute attr1 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID(), - "", tagName); - attrs.add(attr1); - - if (comment != null && !comment.isEmpty()) { - BlackboardAttribute attr2 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(), - "", comment); - attrs.add(attr2); - } - bookArt.addAttributes(attrs); - - updateTagNamesAppSetting(tagName); - } - catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to create tag for " + file.getName(), ex); - } - } - - /** - * Create a tag for an artifact with TSK_TAG_NAME as tagName. - * - * @param artifact to create tag for - * @param tagName TSK_TAG_NAME - * @param comment the tag comment or null if not present - */ - public static void createTag(BlackboardArtifact artifact, String tagName, String comment) { - try { - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - - AbstractFile file = skCase.getAbstractFileById(artifact.getObjectID()); - final BlackboardArtifact bookArt = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); - List attrs = new ArrayList<>(); - - - BlackboardAttribute attr1 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID(), - "", tagName); - - if (comment != null && !comment.isEmpty()) { - BlackboardAttribute attr2 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(), - "", comment); - attrs.add(attr2); - } - - BlackboardAttribute attr3 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID(), - "", artifact.getArtifactID()); - attrs.add(attr1); - - attrs.add(attr3); - bookArt.addAttributes(attrs); - - updateTagNamesAppSetting(tagName); - } - catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to create tag for artifact " + artifact.getArtifactID(), ex); - } - } - - private static void updateTagNamesAppSetting(String tagName) { - // If this tag name is not in the current tag names app setting... - if (!appSettingTagNames.contains(tagName)) { - // Add it to the lookup. - appSettingTagNames.add(tagName); - - // Add it to the setting and write the setting back to the app settings file. - if (tagNamesAppSetting.length() != 0) { - tagNamesAppSetting.append(","); - } - tagNamesAppSetting.append(tagName); - ModuleSettings.setConfigSetting(APP_SETTINGS_FILE_NAME, TAG_NAMES_SETTING_KEY, tagNamesAppSetting.toString()); - } - } - - /** - * Create a bookmark tag for a file. - * - * @param file to create bookmark tag for - * @param comment the bookmark comment - */ - public static void createBookmark(AbstractFile file, String comment) { - createTag(file, Tags.BOOKMARK_TAG_NAME, comment); - } - - /** - * Create a bookmark tag for an artifact. - * - * @param artifact to create bookmark tag for - * @param comment the bookmark comment - */ - public static void createBookmark(BlackboardArtifact artifact, String comment) { - createTag(artifact, Tags.BOOKMARK_TAG_NAME, comment); - } - - /** - * Get a list of all the bookmarks. - * - * @return a list of all bookmark artifacts - */ - static List getBookmarks() { - try { - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - return skCase.getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME, Tags.BOOKMARK_TAG_NAME); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get list of artifacts from the case", ex); - } - return new ArrayList<>(); - } - - /** - * Get a list of all the unique tag names associated with the current case plus any - * tag names stored in the application settings file. - * - * @return A collection of tag names. - */ - public static TreeSet getAllTagNames() { - // Use a TreeSet<> so the union of the tag names from the two sources will be sorted. - TreeSet tagNames = getTagNamesFromCurrentCase(); - tagNames.addAll(appSettingTagNames); - - // Make sure the book mark tag is always included. - tagNames.add(BOOKMARK_TAG_NAME); - - return tagNames; - } - - /** - * Get a list of all the unique tag names associated with the current case. - * Uses a custom query for speed when dealing with thousands of tags. - * - * @return A collection of tag names. - */ - @SuppressWarnings("deprecation") - public static TreeSet getTagNamesFromCurrentCase() { - TreeSet tagNames = new TreeSet<>(); - - ResultSet rs = null; - SleuthkitCase skCase = null; - try { - skCase = Case.getCurrentCase().getSleuthkitCase(); - rs = skCase.runQuery("SELECT value_text" - + " FROM blackboard_attributes" - + " WHERE attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID() - + " GROUP BY value_text" - + " ORDER BY value_text"); - while (rs.next()) { - tagNames.add(rs.getString("value_text")); - } - } - catch (IllegalStateException ex) { - // Case.getCurrentCase() throws IllegalStateException if there is no current autopsy case. - } - catch (SQLException ex) { - logger.log(Level.SEVERE, "Failed to query the blackboard for tag names", ex); - } - finally { - if (null != skCase && null != rs) { - try { - skCase.closeRunQuery(rs); - } catch (SQLException ex) { - logger.log(Level.SEVERE, "Failed to close the query for blackboard for tag names", ex); - } - } - } - - // Make sure the book mark tag is always included. - tagNames.add(BOOKMARK_TAG_NAME); - - return tagNames; - } - - /** - * Get the tag comment for a specified tag. - * - * @param tagArtifactId artifact id of the tag - * @return the tag comment - */ - static String getCommentFromTag(long tagArtifactId) { - try { - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - - BlackboardArtifact artifact = skCase.getBlackboardArtifact(tagArtifactId); - if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() - || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - List attributes = artifact.getAttributes(); - for (BlackboardAttribute att : attributes) { - if (att.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID()) { - return att.getValueString(); - } - } - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get artifact " + tagArtifactId + " from case", ex); - } - - return EMPTY_COMMENT; - } - - /** - * Get the artifact for a result tag. - * - * @param tagArtifactId artifact id of the tag - * @return the tag's artifact - */ - static BlackboardArtifact getArtifactFromTag(long tagArtifactId) { - try { - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - - BlackboardArtifact artifact = skCase.getBlackboardArtifact(tagArtifactId); - if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() - || artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - List attributes = artifact.getAttributes(); - for (BlackboardAttribute att : attributes) { - if (att.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()) { - return skCase.getBlackboardArtifact(att.getValueLong()); - } - } - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get artifact " + tagArtifactId + " from case."); - } - - return null; - } - - /** - * Looks up the tag names associated with either a tagged artifact or a tag artifact. - * - * @param artifact The artifact - * @return A set of unique tag names - */ - public static HashSet getUniqueTagNamesForArtifact(BlackboardArtifact artifact) { - return getUniqueTagNamesForArtifact(artifact.getArtifactID(), artifact.getArtifactTypeID()); - } - - /** - * Looks up the tag names associated with either a tagged artifact or a tag artifact. - * - * @param artifactID The ID of the artifact - * @param artifactTypeID The ID of the artifact type - * @return A set of unique tag names - */ - public static HashSet getUniqueTagNamesForArtifact(long artifactID, int artifactTypeID) { - HashSet tagNames = new HashSet<>(); - - try { - ArrayList tagArtifactIDs = new ArrayList<>(); - if (artifactTypeID == ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() || - artifactTypeID == ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - tagArtifactIDs.add(artifactID); - } else { - List tags = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT, artifactID); - for (BlackboardArtifact tag : tags) { - tagArtifactIDs.add(tag.getArtifactID()); - } - } - - for (Long tagArtifactID : tagArtifactIDs) { - String whereClause = "WHERE artifact_id = " + tagArtifactID + " AND attribute_type_id = " + ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID(); - List attributes = Case.getCurrentCase().getSleuthkitCase().getMatchingAttributes(whereClause); - for (BlackboardAttribute attr : attributes) { - tagNames.add(attr.getValueString()); - } - } - } - catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get tags for artifact " + artifactID, ex); - } - - return tagNames; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagsNode.java new file mode 100755 index 0000000000..37669308bd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagsNode.java @@ -0,0 +1,91 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class are the root nodes of tree that is a sub-tree of the + * Autopsy presentation of the SleuthKit data model. The sub-tree consists of + * content and blackboard artifact tags, grouped first by tag type, then by + * tag name. + */ +public class TagsNode extends DisplayableItemNode { + private static final String DISPLAY_NAME = "Tags"; + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; + + public TagsNode() { + super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME)); + super.setName(DISPLAY_NAME); + super.setDisplayName(DISPLAY_NAME); + this.setIconBaseWithExtension(ICON_PATH); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("Name", "Name", "", getName())); + + return propertySheet; + } + + private static class TagNameNodeFactory extends ChildFactory { + @Override + protected boolean createKeys(List keys) { + try { + keys.addAll(Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse()); + } + catch (TskCoreException ex) { + Logger.getLogger(TagNameNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(TagName key) { + return new TagNameNode(key); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagsNodeKey.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagsNodeKey.java new file mode 100755 index 0000000000..cfdd31731d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagsNodeKey.java @@ -0,0 +1,34 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +/** + * Instances of this class act as keys for use by instances of the + * RootContentChildren class. RootContentChildren is a NetBeans child node + * factory built on top of the NetBeans Children.Keys class. + */ +public class TagsNodeKey implements AutopsyVisitableItem { + // Creation of a TagsNode object corresponding to a TagsNodeKey object is done + // by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren + // override of Children.Keys.createNodes(). + @Override + public T accept(AutopsyItemVisitor v) { + return v.visit(this); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 652ae959a2..1690eb310b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -45,6 +45,11 @@ public class ViewsNode extends DisplayableItemNode { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); @@ -65,9 +70,4 @@ public class ViewsNode extends DisplayableItemNode { NAME)); return s; } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.META; - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 756a376d11..d1a66fd25c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,10 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.TskData; @@ -81,7 +81,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode { return v.visit(this); } + @Override + public boolean isLeafTypeNode() { + return false; + } + @Override public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); } - - @Override - public TYPE getDisplayableItemNodeType() { - return TYPE.CONTENT; - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/BlackboardArtifactTagTypeNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/BlackboardArtifactTagTypeNode.java new file mode 100755 index 0000000000..067b68a49c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/BlackboardArtifactTagTypeNode.java @@ -0,0 +1,113 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactTagNode; +import org.sleuthkit.autopsy.datamodel.ContentTagTypeNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Instances of this class are elements in a sub-tree of the Autopsy + * presentation of the SleuthKit data model. The sub-tree consists of content + * and blackboard artifact tags, grouped first by tag type, then by tag name. + */ +public class BlackboardArtifactTagTypeNode extends DisplayableItemNode { + private static final String DISPLAY_NAME = "Result Tags"; + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; + + public BlackboardArtifactTagTypeNode(TagName tagName) { + super(Children.create(new BlackboardArtifactTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + DISPLAY_NAME)); + + long tagsCount = 0; + try { + tagsCount = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + } + catch (TskCoreException ex) { + Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); + } + + super.setName(DISPLAY_NAME); + super.setDisplayName(DISPLAY_NAME + " (" + tagsCount + ")"); + this.setIconBaseWithExtension(ICON_PATH); + } + + @Override + protected Sheet createSheet() { + Sheet propertySheet = super.createSheet(); + Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + propertySheet.put(properties); + } + + properties.put(new NodeProperty("Name", "Name", "", getName())); + + return propertySheet; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + private static class BlackboardArtifactTagNodeFactory extends ChildFactory { + private final TagName tagName; + + BlackboardArtifactTagNodeFactory(TagName tagName) { + this.tagName = tagName; + } + + @Override + protected boolean createKeys(List keys) { + try { + // Use the blackboard artifact tags bearing the specified tag name as the keys. + keys.addAll(Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + } + catch (TskCoreException ex) { + Logger.getLogger(BlackboardArtifactTagTypeNode.BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifactTag key) { + // The blackboard artifact tags to be wrapped are used as the keys. + return new BlackboardArtifactTagNode(key); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 481f8e930e..12abeb5237 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -47,17 +47,3 @@ ImageDetailsPanel.imgSectorSizeLabel.text=Sector Size: ImageDetailsPanel.imgSectorSizeValue.text=... DirectoryTreeTopComponent.backButton.text= DirectoryTreeTopComponent.forwardButton.text= -CreateTagDialog.cancelButton.text=Cancel -CreateTagDialog.okButton.text=OK -CreateTagDialog.tagNameField.text= -CreateTagDialog.tagNameLabel.text=Tag Name: -CreateTagDialog.preexistingLabel.text=Pre-existing Tags: -CreateTagDialog.newTagPanel.border.title=New Tag -TagAndCommentDialog.tagLabel.text=Tag: -TagAndCommentDialog.tagCombo.toolTipText=Select tag to use -TagAndCommentDialog.cancelButton.text=Cancel -TagAndCommentDialog.okButton.text=OK -TagAndCommentDialog.commentText.toolTipText=Enter an optional tag comment or leave blank -TagAndCommentDialog.commentText.text= -TagAndCommentDialog.commentLabel.text=Comment: -TagAndCommentDialog.newTagButton.text=New Tag diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 8ccc8a7405..833f7e97e6 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.directorytree; +import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import java.awt.event.ActionEvent; import java.beans.PropertyVetoException; import java.util.ArrayList; @@ -33,10 +35,12 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.ArtifactTypeNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.ContentTagTypeNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; @@ -61,8 +65,7 @@ import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.RecentFilesFilterNode; import org.sleuthkit.autopsy.datamodel.RecentFilesNode; import org.sleuthkit.autopsy.datamodel.FileTypesNode; -import org.sleuthkit.autopsy.datamodel.Tags.TagNodeRoot; -import org.sleuthkit.autopsy.datamodel.Tags.TagsNodeRoot; +import org.sleuthkit.autopsy.datamodel.TagNameNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -168,6 +171,8 @@ public class DataResultFilterNode extends FilterNode { //set up actions for artifact node based on its Content object //TODO all actions need to be consolidated in single place! //they should be set in individual Node subclass and using a utility to get Actions per Content sub-type + // TODO UPDATE: There is now a DataModelActionsFactory utility; also tags are no longer artifacts so conditionals + // can be removed. List actions = new ArrayList<>(); @@ -182,10 +187,13 @@ public class DataResultFilterNode extends FilterNode { || artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { actions.add(new ViewContextAction("View File in Directory", ban)); } else { + // if the artifact links to another file, add an action to go to + // that file Content c = findLinked(ban); if (c != null) { actions.add(new ViewContextAction("View File in Directory", c)); } + // action to go to the source file of the artifact actions.add(new ViewContextAction("View Source File in Directory", ban)); } File f = ban.getLookup().lookup(File.class); @@ -206,8 +214,9 @@ public class DataResultFilterNode extends FilterNode { if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - actions.add(TagBlackboardArtifactAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.add(AddBlackboardArtifactTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); } } if ((d = ban.getLookup().lookup(Directory.class)) != null) { @@ -222,8 +231,9 @@ public class DataResultFilterNode extends FilterNode { if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - actions.add(TagBlackboardArtifactAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.add(AddBlackboardArtifactTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); } } if ((vd = ban.getLookup().lookup(VirtualDirectory.class)) != null) { @@ -238,8 +248,9 @@ public class DataResultFilterNode extends FilterNode { if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - actions.add(TagBlackboardArtifactAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.add(AddBlackboardArtifactTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); } } else if ((lf = ban.getLookup().lookup(LayoutFile.class)) != null) { LayoutFileNode lfn = new LayoutFileNode(lf); @@ -253,8 +264,9 @@ public class DataResultFilterNode extends FilterNode { if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - actions.add(TagBlackboardArtifactAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.add(AddBlackboardArtifactTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); } } else if ((locF = ban.getLookup().lookup(LocalFile.class)) != null || (locF = ban.getLookup().lookup(DerivedFile.class)) != null) { @@ -269,8 +281,9 @@ public class DataResultFilterNode extends FilterNode { if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); - actions.add(TagBlackboardArtifactAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.add(AddBlackboardArtifactTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); } } @@ -404,15 +417,20 @@ public class DataResultFilterNode extends FilterNode { } @Override - public AbstractAction visit(TagNodeRoot tnr) { - return openChild(tnr); + public AbstractAction visit(TagNameNode node) { + return openChild(node); } @Override - public AbstractAction visit(TagsNodeRoot tnr) { - return openChild(tnr); + public AbstractAction visit(ContentTagTypeNode node) { + return openChild(node); } + @Override + public AbstractAction visit(BlackboardArtifactTagTypeNode node) { + return openChild(node); + } + @Override public AbstractAction visit(DirectoryNode dn) { if (dn.getDisplayName().equals(DirectoryNode.DOTDOTDIR)) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 8f9ec67a99..b50a5c2122 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -27,6 +27,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; @@ -804,7 +805,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refreshes the nodes in the tree to reflect updates in the database should * be called in the gui thread */ - void refreshTree(final BlackboardArtifact.ARTIFACT_TYPE... types) { + public void refreshTree(final BlackboardArtifact.ARTIFACT_TYPE... types) { //save current selection Node selectedNode = getSelectedNode(); final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); @@ -856,38 +857,53 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } /** - * Set selected node using the previously saved selection path to the - * selected node + * Set the selected node using a path to a previously selected node. * - * @param path node path with node names - * @param rootNodeName name of the root node to match or null if any + * @param previouslySelectedNodePath Path to a previously selected node. + * @param rootNodeName Name of the root node to match, may be null. */ - private void setSelectedNode(final String[] path, final String rootNodeName) { - if (path == null) { + private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) { + if (previouslySelectedNodePath == null) { return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - - if (path.length > 0 && (rootNodeName == null || path[0].equals(rootNodeName))) { - try { - Node newSelection = NodeOp.findPath(em.getRootContext(), path); - - if (newSelection != null) { - if (rootNodeName != null) { - //called from tree auto refresh context - //remove last from backlist, because auto select will result in duplication - backList.pollLast(); - } - em.setExploredContextAndSelection(newSelection, new Node[]{newSelection}); + if (previouslySelectedNodePath.length > 0 && (rootNodeName == null || previouslySelectedNodePath[0].equals(rootNodeName))) { + Node selectedNode = null; + ArrayList selectedNodePath = new ArrayList<>(Arrays.asList(previouslySelectedNodePath)); + while (null == selectedNode && !selectedNodePath.isEmpty()) { + try { + selectedNode = NodeOp.findPath(em.getRootContext(), selectedNodePath.toArray(new String[0])); + } + catch (NodeNotFoundException ex) { + // The selected node may have been deleted (e.g., a deleted tag), so truncate the path and try again. + if (selectedNodePath.size() > 1) { + selectedNodePath.remove(selectedNodePath.size() - 1); + } + else { + StringBuilder nodePath = new StringBuilder(); + for (int i = 0; i < previouslySelectedNodePath.length; ++i) { + nodePath.append(previouslySelectedNodePath[i]).append("/"); + } + logger.log(Level.WARNING, "Failed to find any nodes to select on path " + nodePath.toString(), ex); + break; + } + } + } + + if (null != selectedNode) { + if (rootNodeName != null) { + //called from tree auto refresh context + //remove last from backlist, because auto select will result in duplication + backList.pollLast(); + } + try { + em.setExploredContextAndSelection(selectedNode, new Node[]{selectedNode}); + } + catch (PropertyVetoException ex) { + logger.log(Level.WARNING, "Property veto from ExplorerManager setting selection to " + selectedNode.getName(), ex); } - - // We need to set the selection, which will refresh dataresult and get rid of the oob exception - } catch (NodeNotFoundException ex) { - logger.log(Level.WARNING, "Node not found", ex); - } catch (PropertyVetoException ex) { - logger.log(Level.WARNING, "Property Veto", ex); } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index 72601900a9..82b374eb99 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.directorytree; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import java.awt.Toolkit; import java.awt.Dimension; import java.awt.Font; @@ -35,6 +36,7 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.table.DefaultTableModel; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -100,40 +102,44 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Directory d) { - List actions = new ArrayList(); - actions.add(TagAbstractFileAction.getInstance()); + List actions = new ArrayList<>(); + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } @Override public List visit(final VirtualDirectory d) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(ExtractAction.getInstance()); - actions.add(TagAbstractFileAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } @Override public List visit(final DerivedFile d) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(ExtractAction.getInstance()); - actions.add(TagAbstractFileAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } @Override public List visit(final LocalFile d) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(ExtractAction.getInstance()); - actions.add(TagAbstractFileAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } @Override public List visit(final org.sleuthkit.datamodel.File d) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(ExtractAction.getInstance()); - actions.add(TagAbstractFileAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java deleted file mode 100755 index 657673ac74..0000000000 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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.util.Collection; -import javax.swing.AbstractAction; -import javax.swing.JMenuItem; -import org.openide.util.Utilities; -import org.openide.util.actions.Presenter; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.datamodel.AbstractFile; - -public class TagAbstractFileAction extends AbstractAction implements Presenter.Popup { - // 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 TagAbstractFileAction instance; - - public static synchronized TagAbstractFileAction getInstance() { - if (null == instance) { - instance = new TagAbstractFileAction(); - } - return instance; - } - - private TagAbstractFileAction() { - } - - @Override - public JMenuItem getPopupPresenter() { - return new TagAbstractFileMenu(); - } - - @Override - public void actionPerformed(ActionEvent e) { - // Do nothing - this action should never be performed. - // Submenu actions are invoked instead. - } - - private static class TagAbstractFileMenu extends TagMenu { - public TagAbstractFileMenu() { - super(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size() > 1 ? "Tag Files" : "Tag File"); - } - - @Override - protected void applyTag(String tagName, String comment) { - Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); - for (AbstractFile file : selectedFiles) { - Tags.createTag(file, tagName, comment); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java deleted file mode 100755 index 3d1a9641b3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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.util.Collection; -import javax.swing.AbstractAction; -import javax.swing.JMenuItem; -import org.openide.util.Utilities; -import org.openide.util.actions.Presenter; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.datamodel.BlackboardArtifact; - -public class TagBlackboardArtifactAction extends AbstractAction implements Presenter.Popup { - // 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 TagBlackboardArtifactAction instance; - - public static synchronized TagBlackboardArtifactAction getInstance() { - if (null == instance) { - instance = new TagBlackboardArtifactAction(); - } - return instance; - } - - private TagBlackboardArtifactAction() { - } - - @Override - public JMenuItem getPopupPresenter() { - return new TagBlackboardArtifactMenu(); - } - - @Override - public void actionPerformed(ActionEvent e) { - // Do nothing - this action should never be performed. - // Submenu actions are invoked instead. - } - - - private static class TagBlackboardArtifactMenu extends TagMenu { - public TagBlackboardArtifactMenu() { - super(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).size() > 1 ? "Tag Results" : "Tag Result"); - } - - @Override - protected void applyTag(String tagName, String comment) { - Collection selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); - for (BlackboardArtifact artifact : selectedArtifacts) { - Tags.createTag(artifact, tagName, comment); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java deleted file mode 100644 index de6b9c5033..0000000000 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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.ActionListener; -import java.util.TreeSet; -import javax.swing.JMenu; -import javax.swing.JMenuItem; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * The menu that results when one right-clicks on a file or artifact. - */ -public abstract class TagMenu extends JMenu { - public TagMenu(String menuItemText) { - super(menuItemText); - - // Create the 'Quick Tag' sub-menu and add it to the tag menu. - JMenu quickTagMenu = new JMenu("Quick Tag"); - add(quickTagMenu); - - // Get the existing tag names. - TreeSet tagNames = Tags.getAllTagNames(); - if (tagNames.isEmpty()) { - JMenuItem empty = new JMenuItem("No tags"); - empty.setEnabled(false); - quickTagMenu.add(empty); - } - - // Add a menu item for each existing tag name to the 'Quick Tag' menu. - for (final String tagName : tagNames) { - JMenuItem tagNameItem = new JMenuItem(tagName); - tagNameItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - applyTag(tagName, ""); - refreshDirectoryTree(); - } - }); - quickTagMenu.add(tagNameItem); - } - - quickTagMenu.addSeparator(); - - // Create the 'New Tag' menu item and add it to the 'Quick Tag' menu. - JMenuItem newTagMenuItem = new JMenuItem("New Tag"); - newTagMenuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String tagName = CreateTagDialog.getNewTagNameDialog(null); - if (tagName != null) { - applyTag(tagName, ""); - refreshDirectoryTree(); - } - } - }); - quickTagMenu.add(newTagMenuItem); - - // Create the 'Tag and Comment' menu item and add it to the tag menu. - JMenuItem tagAndCommentItem = new JMenuItem("Tag and Comment"); - tagAndCommentItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - TagAndCommentDialog.CommentedTag commentedTag = TagAndCommentDialog.doDialog(); - if (null != commentedTag) { - applyTag(commentedTag.getName(), commentedTag.getComment()); - refreshDirectoryTree(); - } - } - }); - add(tagAndCommentItem); - } - - private void refreshDirectoryTree() { - //TODO instead should send event to node children, which will call its refresh() / refreshKeys() - DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance(); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); - } - - protected abstract void applyTag(String tagName, String comment); -} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index e03e869a97..e959e7e3ed 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -24,12 +24,14 @@ import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; +import javax.swing.SwingWorker; +import org.openide.nodes.AbstractNode; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; -import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; @@ -44,7 +46,14 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VolumeSystem; /** - * View the directory content associated with the given Artifact + * View the directory content associated with the given Artifact in the DataResultViewer. + * + * 1. Expands the Directory Tree to the location of the parent Node of the + * associated Content. + * 2. Selects the parent Node of the associated Content in the Directory Tree, + * which causes the parent Node's Children to be visible in the DataResultViewer. + * 3. Waits for all the Children to be contentNode in the DataResultViewer and + * selects the Node that represents the Content. */ public class ViewContextAction extends AbstractAction { @@ -81,61 +90,116 @@ public class ViewContextAction extends AbstractAction { Node generated = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(hierarchy)), true); Children genChilds = generated.getChildren(); - final DirectoryTreeTopComponent directoryTree = DirectoryTreeTopComponent.findInstance(); - TreeView tree = directoryTree.getTree(); - ExplorerManager man = directoryTree.getExplorerManager(); - Node dirRoot = man.getRootContext(); - Children dirChilds = dirRoot.getChildren(); - Node imagesRoot = dirChilds.findChild(DataSourcesNode.NAME); - dirChilds = imagesRoot.getChildren(); + final DirectoryTreeTopComponent dirTree = DirectoryTreeTopComponent.findInstance(); + TreeView dirTreeView = dirTree.getTree(); + ExplorerManager dirTreeExplorerManager = dirTree.getExplorerManager(); + Node dirTreeRootNode = dirTreeExplorerManager.getRootContext(); + Children dirChilds = dirTreeRootNode.getChildren(); + Children currentChildren = dirChilds.findChild(DataSourcesNode.NAME).getChildren(); Node dirExplored = null; + // Find the parent node of the content in the directory tree for (int i = 0; i < genChilds.getNodesCount() - 1; i++) { Node currentGeneratedNode = genChilds.getNodeAt(i); - for (int j = 0; j < dirChilds.getNodesCount(); j++) { - Node currentDirectoryTreeNode = dirChilds.getNodeAt(j); + for (int j = 0; j < currentChildren.getNodesCount(); j++) { + Node currentDirectoryTreeNode = currentChildren.getNodeAt(j); if (currentGeneratedNode.getDisplayName().equals(currentDirectoryTreeNode.getDisplayName())) { dirExplored = currentDirectoryTreeNode; - tree.expandNode(dirExplored); - dirChilds = currentDirectoryTreeNode.getChildren(); + dirTreeView.expandNode(dirExplored); + currentChildren = currentDirectoryTreeNode.getChildren(); break; } } } + // Set the parent node of the content as the selection in the + // directory tree try { if (dirExplored != null) { - tree.expandNode(dirExplored); - man.setExploredContextAndSelection(dirExplored, new Node[]{dirExplored}); + dirTreeView.expandNode(dirExplored); + dirTreeExplorerManager.setExploredContextAndSelection(dirExplored, new Node[]{dirExplored}); } - } catch (PropertyVetoException ex) { logger.log(Level.WARNING, "Couldn't set selected node", ex); } - // Another thread is needed because we have to wait for dataResult to populate + EventQueue.invokeLater(new Runnable() { @Override public void run() { - DataResultTopComponent dataResult = directoryTree.getDirectoryListing(); - Node resultRoot = dataResult.getRootNode(); - Children resultChilds = resultRoot.getChildren(); - Node generated = content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); - for (int i = 0; i < resultChilds.getNodesCount(); i++) { - Node current = resultChilds.getNodeAt(i); - if (generated.getName().equals(current.getName())) { - dataResult.requestActive(); - dataResult.setSelectedNodes(new Node[]{current}); - DirectoryTreeTopComponent.getDefault().fireViewerComplete(); - break; - } - } + DataResultTopComponent dataResultTC = dirTree.getDirectoryListing(); + Node currentRootNodeOfDataResultTC = dataResultTC.getRootNode(); + Node contentNode = content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); + new SelectionWorker(dataResultTC, contentNode.getName(), currentRootNodeOfDataResultTC).execute(); } }); } }); } + + /** + * Waits for a Node's children to be generated, regardless of whether they + * are lazily loaded, then sets the correct selection in a specified + * DataResultTopComponent. + */ + private class SelectionWorker extends SwingWorker { + + DataResultTopComponent dataResultTC; + String nameOfNodeToSelect; + Node originalRootNodeOfDataResultTC; + + SelectionWorker(DataResultTopComponent dataResult, String nameToSelect, Node originalRoot) { + this.dataResultTC = dataResult; + this.nameOfNodeToSelect = nameToSelect; + this.originalRootNodeOfDataResultTC = originalRoot; + } + + @Override + protected Node[] doInBackground() throws Exception { + // Calls to Children::getNodes(true) block until all child Nodes have + // been created, regardless of whether they are created lazily. + // This means that this call will return the actual child Nodes + // and will *NEVER* return a proxy wait Node. This is done on the + // background thread to ensure we are not hanging the ui as it could + // be a lengthy operation. + return originalRootNodeOfDataResultTC.getChildren().getNodes(true); + } + + @Override + protected void done() { + Node[] nodesDisplayedInDataResultViewer; + try { + nodesDisplayedInDataResultViewer = get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.WARNING, "Failed to get nodes in selection worker.", ex); + return; + } + + // It is possible the user selected a different Node to be displayed + // in the DataResultViewer while the child Nodes were being generated. + // In that case, we don't want to set the selection because it the + // nodes returned from get() won't be in the DataResultTopComponent's + // ExplorerManager. If we did call setSelectedNodes, it would clear + // the current selection, which is not good. + if (dataResultTC.getRootNode().equals(originalRootNodeOfDataResultTC) == false) { + return; + } + + // Find the correct node to select from the nodes that are displayed + // in the data result viewer and set it as the selection of the + // DataResultTopComponent. + for (Node node : nodesDisplayedInDataResultViewer) { + if (nameOfNodeToSelect.equals(node.getName())) { + dataResultTC.requestActive(); + dataResultTC.setSelectedNodes(new Node[]{node}); + DirectoryTreeTopComponent.getDefault().fireViewerComplete(); + break; + } + } + } + + } /** * The ReverseHierarchyVisitor class is designed to return a list of Content diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties index 4bf164e3de..afeec73469 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties @@ -24,7 +24,7 @@ FileSearchTopComponent.dateCheckBox1.text=Date: FileSearchTopComponent.dateFiltersButton1.text=Date Filters KnownStatusSearchPanel.knownCheckBox.text=Known Status: KnownStatusSearchPanel.knownBadOptionCheckBox.text=Known bad -KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL) +KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL or other) KnownStatusSearchPanel.unknownOptionCheckBox.text=Unknown DateSearchPanel.dateCheckBox.text=Date: DateSearchPanel.jLabel4.text=Timezone: diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java index e790852a39..501a13c440 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,7 +55,7 @@ class KnownStatusSearchFilter extends AbstractFileSearchFilter +
@@ -64,6 +64,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java index 28475e690c..e78d81a43c 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java @@ -74,6 +74,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { knownOptionCheckBox.setSelected(true); knownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownOptionCheckBox.text")); // NOI18N + knownOptionCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownOptionCheckBoxActionPerformed(evt); + } + }); knownBadOptionCheckBox.setSelected(true); knownBadOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownBadOptionCheckBox.text")); // NOI18N @@ -102,6 +107,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { .addComponent(knownBadOptionCheckBox)) ); }// //GEN-END:initComponents + + private void knownOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownOptionCheckBoxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_knownOptionCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox knownBadOptionCheckBox; private javax.swing.JCheckBox knownCheckBox; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GeneralIngestConfigurator.java b/Core/src/org/sleuthkit/autopsy/ingest/GeneralIngestConfigurator.java index fd6edf91b6..eaeb0eefc6 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/GeneralIngestConfigurator.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/GeneralIngestConfigurator.java @@ -69,6 +69,7 @@ public class GeneralIngestConfigurator implements IngestConfigurator { || moduleName.equals("MBox Parser")) { moduleName = "Email Parser"; } + IngestModuleAbstract moduleFound = null; for (IngestModuleAbstract module : allModules) { if (moduleName.equals(module.getName())) { @@ -80,7 +81,7 @@ public class GeneralIngestConfigurator implements IngestConfigurator { enabledModules.add(moduleFound); } else { - messages.add("Unable to enable ingest module: " + moduleName); + messages.add(moduleName + " was previously enabled, but could not be found"); } } ingestDialogPanel.setEnabledIngestModules(enabledModules); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 9a3842b662..7b1b766fa0 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -206,7 +206,13 @@ public class IngestManager { } static synchronized void fireModuleEvent(String eventType, String moduleName) { - pcs.firePropertyChange(eventType, moduleName, null); + try { + pcs.firePropertyChange(eventType, moduleName, null); + } + catch (Exception e) { + logger.log(Level.SEVERE, "Ingest manager listener threw exception", e); + MessageNotifyUtil.Notify.show("Module Error", "A module caused an error listening to Ingest Manager updates. See log to determine which module. Some data could be incomplete.", MessageNotifyUtil.MessageType.ERROR); + } } @@ -215,7 +221,13 @@ public class IngestManager { * @param objId ID of file that is done */ static synchronized void fireFileDone(long objId) { - pcs.firePropertyChange(IngestModuleEvent.FILE_DONE.toString(), objId, null); + try { + pcs.firePropertyChange(IngestModuleEvent.FILE_DONE.toString(), objId, null); + } + catch (Exception e) { + logger.log(Level.SEVERE, "Ingest manager listener threw exception", e); + MessageNotifyUtil.Notify.show("Module Error", "A module caused an error listening to Ingest Manager updates. See log to determine which module. Some data could be incomplete.", MessageNotifyUtil.MessageType.ERROR); + } } @@ -224,7 +236,13 @@ public class IngestManager { * @param moduleDataEvent */ static synchronized void fireModuleDataEvent(ModuleDataEvent moduleDataEvent) { - pcs.firePropertyChange(IngestModuleEvent.DATA.toString(), moduleDataEvent, null); + try { + pcs.firePropertyChange(IngestModuleEvent.DATA.toString(), moduleDataEvent, null); + } + catch (Exception e) { + logger.log(Level.SEVERE, "Ingest manager listener threw exception", e); + MessageNotifyUtil.Notify.show("Module Error", "A module caused an error listening to Ingest Manager updates. See log to determine which module. Some data could be incomplete.", MessageNotifyUtil.MessageType.ERROR); + } } /** @@ -232,7 +250,13 @@ public class IngestManager { * @param moduleContentEvent */ static synchronized void fireModuleContentEvent(ModuleContentEvent moduleContentEvent) { - pcs.firePropertyChange(IngestModuleEvent.CONTENT_CHANGED.toString(), moduleContentEvent, null); + try { + pcs.firePropertyChange(IngestModuleEvent.CONTENT_CHANGED.toString(), moduleContentEvent, null); + } + catch (Exception e) { + logger.log(Level.SEVERE, "Ingest manager listener threw exception", e); + MessageNotifyUtil.Notify.show("Module Error", "A module caused an error listening to Ingest Manager updates. See log to determine which module. Some data could be incomplete.", MessageNotifyUtil.MessageType.ERROR); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java index 9ffe0b3db9..9b88c24047 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java @@ -487,7 +487,7 @@ class IngestScheduler { AbstractFile childFile = (AbstractFile) c; ProcessTask childTask = new ProcessTask(parentTask, childFile); - if (childFile.isDir()) { + if (childFile.hasChildren()) { this.curDirProcessTasks.add(childTask); } else if (shouldEnqueueTask(childTask)) { diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.form b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.form index 4a2908fff3..cd714f81c8 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.form +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.form @@ -6,6 +6,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java index dd4e8fc542..b283da2e79 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java @@ -43,8 +43,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.TskCoreException; public class ArtifactSelectionDialog extends javax.swing.JDialog { - private static final Logger logger = Logger.getLogger(ArtifactSelectionDialog.class.getName()); - private static ArtifactSelectionDialog instance; private ArtifactModel model; private ArtifactRenderer renderer; @@ -68,25 +66,24 @@ public class ArtifactSelectionDialog extends javax.swing.JDialog { try { ArrayList doNotReport = new ArrayList(); doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO); + doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); // Obsolete artifact type + doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); // Obsolete artifact type - artifacts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifactTypesInUse(); - - artifacts.removeAll(doNotReport); - + artifacts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifactTypesInUse(); + artifacts.removeAll(doNotReport); Collections.sort(artifacts, new Comparator() { @Override public int compare(ARTIFACT_TYPE o1, ARTIFACT_TYPE o2) { return o1.getDisplayName().compareTo(o2.getDisplayName()); } }); - - artifactStates = new EnumMap(BlackboardArtifact.ARTIFACT_TYPE.class); + + artifactStates = new EnumMap<>(BlackboardArtifact.ARTIFACT_TYPE.class); for (BlackboardArtifact.ARTIFACT_TYPE type : artifacts) { artifactStates.put(type, Boolean.TRUE); } } catch (TskCoreException ex) { Logger.getLogger(ArtifactSelectionDialog.class.getName()).log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage()); - return; } } @@ -108,28 +105,7 @@ public class ArtifactSelectionDialog extends javax.swing.JDialog { } }); } - - /** - * Returns a list of the artifact types we want to report on. - */ - static List getImportantArtifactTypes() { - List types = new ArrayList(); - types.add(ARTIFACT_TYPE.TSK_WEB_BOOKMARK); - types.add(ARTIFACT_TYPE.TSK_WEB_COOKIE); - types.add(ARTIFACT_TYPE.TSK_WEB_HISTORY); - types.add(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD); - types.add(ARTIFACT_TYPE.TSK_RECENT_OBJECT); - types.add(ARTIFACT_TYPE.TSK_INSTALLED_PROG); - types.add(ARTIFACT_TYPE.TSK_KEYWORD_HIT); - types.add(ARTIFACT_TYPE.TSK_HASHSET_HIT); - types.add(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED); - types.add(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY); - types.add(ARTIFACT_TYPE.TSK_METADATA_EXIF); - types.add(ARTIFACT_TYPE.TSK_TAG_FILE); - types.add(ARTIFACT_TYPE.TSK_TAG_ARTIFACT); - return types; - } - + /** * Display this dialog, and return the selected artifacts. */ @@ -296,8 +272,6 @@ public class ArtifactSelectionDialog extends javax.swing.JDialog { return this; } return new JLabel(); - } - + } } - } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index fbf1ba19f6..5a221e6ef6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -121,40 +121,6 @@ public class ReportExcel implements TableReportModule { } } - - /** - * Start a new worksheet for the given data type. - * @param name data type name - */ - @Override - public void startDataType(String name) { - // Create a worksheet for the data type (assumed to be an artifact type). - name = escapeForExcel(name); - sheet = wb.createSheet(name); - sheet.setAutobreaks(true); - rowIndex = 0; - artifactsCount = 0; - - // Add a title row to the worksheet. - Row row = sheet.createRow(rowIndex); - row.setRowStyle(setStyle); - row.createCell(0).setCellValue(name); - ++rowIndex; - - // Add an artifacts count row. The actual count will be filled in later. - row = sheet.createRow(rowIndex); - row.setRowStyle(setStyle); - row.createCell(0).setCellValue("Number of artifacts:"); - ++rowIndex; - - // Add an empty row as a separator. - sheet.createRow(rowIndex); - ++rowIndex; - - // There will be at least two columns, one each for the artifacts count and its label. - sheetColCount = 2; - } - /** * Start a new worksheet for the given data type. * Note: This method is a temporary workaround to avoid modifying the TableReportModule interface. @@ -162,7 +128,8 @@ public class ReportExcel implements TableReportModule { * @param name Name of the data type * @param comment Comment on the data type, may be the empty string */ - public void startDataType(String name, String comment) { + @Override + public void startDataType(String name, String description) { // Create a worksheet for the data type (assumed to be an artifact type). name = escapeForExcel(name); sheet = wb.createSheet(name); @@ -183,10 +150,10 @@ public class ReportExcel implements TableReportModule { ++rowIndex; // Add a comment row, if a comment was supplied. - if (!comment.isEmpty()) { + if (!description.isEmpty()) { row = sheet.createRow(rowIndex); row.setRowStyle(setStyle); - row.createCell(0).setCellValue(comment); + row.createCell(0).setCellValue(description); ++rowIndex; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index e0a8c486fe..d96c066291 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -51,24 +50,22 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; -import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Generates all TableReportModules and GeneralReportModules, given whether each module for both - * types is enabled or disabled, and the base report path to save them at. - * - * After creating an instance of ReportGenerator, one must tell it which reports to run, - * TableReportModules on Tags or Artifacts, and the GeneralReportModules. - * Then, one calls displayProgressPanels() to display the progress to the user. + * Instances of this class use GeneralReportModules, TableReportModules and + * FileReportModules to generate a report. If desired, displayProgressPanels() + * can be called to show report generation progress using ReportProgressPanel + * objects displayed using a dialog box. */ public class ReportGenerator { private static final Logger logger = Logger.getLogger(ReportGenerator.class.getName()); @@ -107,17 +104,24 @@ public class ReportGenerator { } /** - * For every ReportModule which the user enabled, create a ReportProgressPanel for that report. + * Create a ReportProgressPanel for each report generation module selected by the user. * - * @param tableModuleStates the enabled/disabled state of each TableReportModule - * @param generalModuleStates the enabled/disabled state of each GeneralReportModule + * @param tableModuleStates The enabled/disabled state of each TableReportModule + * @param generalModuleStates The enabled/disabled state of each GeneralReportModule + * @param fileListModuleStates The enabled/disabled state of each FileReportModule */ private void setupProgressPanels(Map tableModuleStates, Map generalModuleStates, Map fileListModuleStates) { if (null != tableModuleStates) { for (Entry entry : tableModuleStates.entrySet()) { if (entry.getValue()) { TableReportModule module = entry.getKey(); - tableProgress.put(module, panel.addReport(module.getName(), reportPath + module.getFilePath())); + String moduleFilePath = module.getFilePath(); + if (moduleFilePath != null) { + tableProgress.put(module, panel.addReport(module.getName(), reportPath + moduleFilePath)); + } + else { + tableProgress.put(module, panel.addReport(module.getName(), null)); + } } } } @@ -126,7 +130,13 @@ public class ReportGenerator { for (Entry entry : generalModuleStates.entrySet()) { if (entry.getValue()) { GeneralReportModule module = entry.getKey(); - generalProgress.put(module, panel.addReport(module.getName(), reportPath + module.getFilePath())); + String moduleFilePath = module.getFilePath(); + if (moduleFilePath != null) { + generalProgress.put(module, panel.addReport(module.getName(), reportPath + moduleFilePath)); + } + else { + generalProgress.put(module, panel.addReport(module.getName(), null)); + } } } } @@ -135,7 +145,13 @@ public class ReportGenerator { for(Entry entry : fileListModuleStates.entrySet()) { if (entry.getValue()) { FileReportModule module = entry.getKey(); - fileProgress.put(module, panel.addReport(module.getName(), reportPath + module.getFilePath())); + String moduleFilePath = module.getFilePath(); + if (moduleFilePath != null) { + fileProgress.put(module, panel.addReport(module.getName(), reportPath + moduleFilePath)); + } + else { + fileProgress.put(module, panel.addReport(module.getName(), null)); + } } } } @@ -175,28 +191,28 @@ public class ReportGenerator { } /** - * Generate the GeneralReportModule reports in a new SwingWorker. + * Run the GeneralReportModules using a SwingWorker. */ public void generateGeneralReports() { - GeneralWorker worker = new GeneralWorker(); + GeneralReportsWorker worker = new GeneralReportsWorker(); worker.execute(); } /** - * Generate the TableReportModule reports on Blackboard Artifacts in a new SwingWorker. + * Run the TableReportModules using a SwingWorker. * * @param artifactTypeSelections the enabled/disabled state of the artifact types to be included in the report - * @param tagSelections the enabled/disabled state of the tags to be included in the report + * @param tagSelections the enabled/disabled state of the tag names to be included in the report */ - public void generateArtifactTableReports(Map artifactTypeSelections, Map tagSelections) { + public void generateBlackboardArtifactsReports(Map artifactTypeSelections, Map tagNameSelections) { if (!tableProgress.isEmpty() && null != artifactTypeSelections) { - ArtifactsReportsWorker worker = new ArtifactsReportsWorker(artifactTypeSelections, tagSelections); + TableReportsWorker worker = new TableReportsWorker(artifactTypeSelections, tagNameSelections); worker.execute(); } } /** - * Generate the FileReportModule reports in a new SwingWorker. + * Run the FileReportModules using a SwingWorker. * * @param enabledInfo the Information that should be included about each file * in the report. @@ -215,9 +231,9 @@ public class ReportGenerator { } /** - * SwingWorker to generate a report on all GeneralReportModules. + * SwingWorker to run GeneralReportModules. */ - private class GeneralWorker extends SwingWorker { + private class GeneralReportsWorker extends SwingWorker { @Override protected Integer doInBackground() throws Exception { @@ -233,7 +249,7 @@ public class ReportGenerator { } /** - * SwingWorker to generate a FileReport. + * SwingWorker to run FileReportModules. */ private class FileReportsWorker extends SwingWorker { private List enabledInfo = Arrays.asList(FileReportDataTypes.values()); @@ -318,15 +334,15 @@ public class ReportGenerator { } /** - * SwingWorker to generate reports on blackboard artifacts. + * SwingWorker to run TableReportModules to report on blackboard artifacts, + * content tags, and blackboard artifact tags. */ - private class ArtifactsReportsWorker extends SwingWorker { + private class TableReportsWorker extends SwingWorker { private List tableModules = new ArrayList<>(); private List artifactTypes = new ArrayList<>(); private HashSet tagNamesFilter = new HashSet<>(); - // Create an ArtifactWorker with the enabled/disabled state of all Artifacts - ArtifactsReportsWorker(Map artifactTypeSelections, Map tagSelections) { + TableReportsWorker(Map artifactTypeSelections, Map tagNameSelections) { // Get the report modules selected by the user. for (Entry entry : tableProgress.entrySet()) { tableModules.add(entry.getKey()); @@ -339,9 +355,9 @@ public class ReportGenerator { } } - // Get the tags selected by the user. - if (null != tagSelections) { - for (Entry entry : tagSelections.entrySet()) { + // Get the tag names selected by the user and make a tag names filter. + if (null != tagNameSelections) { + for (Entry entry : tagNameSelections.entrySet()) { if (entry.getValue() == true) { tagNamesFilter.add(entry.getKey()); } @@ -351,39 +367,50 @@ public class ReportGenerator { @Override protected Integer doInBackground() throws Exception { - // Start the report + // Start the progress indicators for each active TableReportModule. for (TableReportModule module : tableModules) { ReportProgressPanel progress = tableProgress.get(module); if (progress.getStatus() != ReportStatus.CANCELED) { module.startReport(reportPath); progress.start(); progress.setIndeterminate(false); - progress.setMaximumProgress(ARTIFACT_TYPE.values().length); + progress.setMaximumProgress(ARTIFACT_TYPE.values().length + 2); // +2 for content and blackboard artifact tags } } + + makeBlackboardArtifactTables(); + makeContentTagsTables(); + makeBlackboardArtifactTagsTables(); - // Make a comment on the tags filter. + for (TableReportModule module : tableModules) { + tableProgress.get(module).complete(); + module.endReport(); + } + + return 0; + } + + private void makeBlackboardArtifactTables() { + // Make a comment string describing the tag names filter in effect. StringBuilder comment = new StringBuilder(); if (!tagNamesFilter.isEmpty()) { - comment.append("This report only includes files and artifacts tagged with: "); + comment.append("This report only includes results tagged with: "); comment.append(makeCommaSeparatedList(tagNamesFilter)); } - - // For every enabled artifact type + + // Add a table to the report for every enabled blackboard artifact type. for (ARTIFACT_TYPE type : artifactTypes) { - // Check to see if all the TableReportModules have been canceled + // Check for cancellaton. + removeCancelledTableReportModules(); if (tableModules.isEmpty()) { - break; + return; } - Iterator iter = tableModules.iterator(); - while (iter.hasNext()) { - TableReportModule module = iter.next(); - if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { - iter.remove(); - } + + for (TableReportModule module : tableModules) { + tableProgress.get(module).updateStatusLabel("Now processing " + type.getDisplayName() + "..."); } - // If the type is keyword hit or hashset hit, use the helper + // Keyword hits and hashset hit artifacts get sepcial handling. if (type.equals(ARTIFACT_TYPE.TSK_KEYWORD_HIT)) { writeKeywordHits(tableModules, comment.toString(), tagNamesFilter); continue; @@ -395,19 +422,19 @@ public class ReportGenerator { List unsortedArtifacts = getFilteredArtifacts(type, tagNamesFilter); if (unsortedArtifacts.isEmpty()) { - // Don't report on this artifact type if there are no results continue; } + // The most efficient way to sort all the Artifacts is to add them to a List, and then // sort that List based off a Comparator. Adding to a TreeMap/Set/List sorts the list // each time an element is added, which adds unnecessary overhead if we only need it sorted once. Collections.sort(unsortedArtifacts); // Get the column headers appropriate for the artifact type. - /* @@@ BC: Seems like a better design here woudl be to have a method that - * takes in teh artifact as an argument andreturns the attributes. We then use that - * to make the headers and to make each row afterwards so that we don't ahve artifact-specific - * logic in both getArtifactTableCoumnHeaders and getArtifactRow() + /* @@@ BC: Seems like a better design here would be to have a method that + * takes in the artifact as an argument and returns the attributes. We then use that + * to make the headers and to make each row afterwards so that we don't have artifact-specific + * logic in both getArtifactTableCoumnHeaders and ArtifactData.getRow() */ List columnHeaders = getArtifactTableColumnHeaders(type.getTypeID()); if (columnHeaders == null) { @@ -417,59 +444,26 @@ public class ReportGenerator { } for (TableReportModule module : tableModules) { - tableProgress.get(module).updateStatusLabel("Now processing " + type.getDisplayName() + "..."); - - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(type.getDisplayName(), comment.toString()); - htmlReportModule.startTable(columnHeaders, type); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(type.getDisplayName(), comment.toString()); - excelReportModule.startTable(columnHeaders); - } - else { - module.startDataType(type.getDisplayName()); - module.startTable(columnHeaders); - } + module.startDataType(type.getDisplayName(), comment.toString()); + module.startTable(columnHeaders); } boolean msgSent = false; for(ArtifactData artifactData : unsortedArtifacts) { - HashSet tags = artifactData.getTags(); - - String tagsList = makeCommaSeparatedList(tags); - // Add the row data to all of the reports. for (TableReportModule module : tableModules) { // Get the row data for this type of artifact. - List rowData; - rowData = getArtifactRow(artifactData, module); - if (rowData == null) { + List rowData = artifactData.getRow(); + if (rowData.isEmpty()) { if (msgSent == false) { MessageNotifyUtil.Notify.show("Skipping artifact rows for type " + type + " in reports", "Unknown columns to report on", MessageNotifyUtil.MessageType.ERROR); msgSent = true; } continue; } - // Add the list of tag names if the artifact is not itself as tag. - if (artifactData.getArtifact().getArtifactTypeID() != ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID() && - artifactData.getArtifact().getArtifactTypeID() != ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID()) - { - rowData.add(tagsList); - } - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.addRow(rowData, artifactData.getArtifact()); - } - else { - module.addRow(rowData); - } + module.addRow(rowData); } } @@ -479,27 +473,144 @@ public class ReportGenerator { module.endTable(); module.endDataType(); } - } - - // End the report - for (TableReportModule module : tableModules) { - tableProgress.get(module).complete(); - module.endReport(); - } - - return 0; + } } + + private void makeContentTagsTables() { + // Check for cancellaton. + removeCancelledTableReportModules(); + if (tableModules.isEmpty()) { + return; + } + + // Get the content tags. + List tags; + try { + tags = Case.getCurrentCase().getServices().getTagsManager().getAllContentTags(); + } + catch (TskCoreException ex) { + logger.log(Level.SEVERE, "failed to get content tags", ex); + return; + } + + // Tell the modules reporting on content tags is beginning. + for (TableReportModule module : tableModules) { + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + // @@@ Alos Using the obsolete ARTIFACT_TYPE.TSK_TAG_FILE is also an expedient hack. + tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName() + "..."); + ArrayList columnHeaders = new ArrayList<>(Arrays.asList("File", "Tag", "Comment")); + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append("This report only includes file tagged with: "); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + if (module instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML)module; + htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + htmlReportModule.startContentTagsTable(columnHeaders); + } + else { + module.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + module.startTable(columnHeaders); + } + } + + // Give the modules the rows for the content tags. + for (ContentTag tag : tags) { + if (passesTagNamesFilter(tag.getName().getDisplayName())) { + ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getContent().getName(), tag.getName().getDisplayName(), tag.getComment())); + for (TableReportModule module : tableModules) { + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + if (module instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML)module; + htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); + } + else { + module.addRow(rowData); + } + } + } + } + + // The the modules content tags reporting is ended. + for (TableReportModule module : tableModules) { + tableProgress.get(module).increment(); + module.endTable(); + module.endDataType(); + } + } + + private void makeBlackboardArtifactTagsTables() { + // Check for cancellaton. + removeCancelledTableReportModules(); + if (tableModules.isEmpty()) { + return; + } + + List tags; + try { + tags = Case.getCurrentCase().getServices().getTagsManager().getAllBlackboardArtifactTags(); + } + catch (TskCoreException ex) { + logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); + return; + } + + // Tell the modules reporting on blackboard artifact tags data type is beginning. + // @@@ Using the obsolete ARTIFACT_TYPE.TSK_TAG_ARTIFACT is an expedient hack. + for (TableReportModule module : tableModules) { + tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName() + "..."); + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append("This report only includes results tagged with: "); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString()); + module.startTable(new ArrayList<>(Arrays.asList("Result Type", "Tag", "Comment", "Source File"))); + } + + // Give the modules the rows for the content tags. + for (BlackboardArtifactTag tag : tags) { + if (passesTagNamesFilter(tag.getName().getDisplayName())) { + for (TableReportModule module : tableModules) { + module.addRow(new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName()))); + } + } + } + + // The the modules blackboard artifact tags reporting is ended. + for (TableReportModule module : tableModules) { + tableProgress.get(module).increment(); + module.endTable(); + module.endDataType(); + } + } + + boolean passesTagNamesFilter(String tagName) { + return tagNamesFilter.isEmpty() || tagNamesFilter.contains(tagName); + } + + void removeCancelledTableReportModules() { + Iterator iter = tableModules.iterator(); + while (iter.hasNext()) { + TableReportModule module = iter.next(); + if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { + iter.remove(); + } + } + } } - - private Boolean failsTagFilter(HashSet tags, HashSet tagsFilter) + + /// @@@ Should move the methods specific to TableReportsWorker into that scope. + private Boolean failsTagFilter(HashSet tagNames, HashSet tagsNamesFilter) { - if (null == tagsFilter || tagsFilter.isEmpty()) { + if (null == tagsNamesFilter || tagsNamesFilter.isEmpty()) { return false; } - HashSet filteredTags = new HashSet<>(tags); - filteredTags.retainAll(tagsFilter); - return filteredTags.isEmpty(); + HashSet filteredTagNames = new HashSet<>(tagNames); + filteredTagNames.retainAll(tagsNamesFilter); + return filteredTagNames.isEmpty(); } /** @@ -512,14 +623,17 @@ public class ReportGenerator { private List getFilteredArtifacts(ARTIFACT_TYPE type, HashSet tagNamesFilter) { List artifacts = new ArrayList<>(); try { - // For every artifact of the current type, add it and it's attributes to a list for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(type)) { - HashSet tags = Tags.getUniqueTagNamesForArtifact(artifact); - if(failsTagFilter(tags, tagNamesFilter)) { + List tags = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact); + HashSet uniqueTagNames = new HashSet<>(); + for (BlackboardArtifactTag tag : tags) { + uniqueTagNames.add(tag.getName().getDisplayName()); + } + if(failsTagFilter(uniqueTagNames, tagNamesFilter)) { continue; } try { - artifacts.add(new ArtifactData(artifact, skCase.getBlackboardAttributes(artifact), tags)); + artifacts.add(new ArtifactData(artifact, skCase.getBlackboardAttributes(artifact), uniqueTagNames)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to get Blackboard Attributes when generating report.", ex); } @@ -557,18 +671,7 @@ public class ReportGenerator { // Make keyword data type and give them set index for (TableReportModule module : tableModules) { - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); - } - else { - module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName()); - } + module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); module.addSetIndex(lists); tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName() + "..."); @@ -615,11 +718,15 @@ public class ReportGenerator { } // Get any tags that associated with this artifact and apply the tag filter. - HashSet tags = Tags.getUniqueTagNamesForArtifact(rs.getLong("artifact_id"), ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()); - if (failsTagFilter(tags, tagNamesFilter)) { - continue; - } - String tagsList = makeCommaSeparatedList(tags); + HashSet uniqueTagNames = new HashSet<>(); + ResultSet tagNameRows = skCase.runQuery("SELECT display_name FROM tag_names WHERE artifact_id = " + rs.getLong("artifact_id")); + while (tagNameRows.next()) { + uniqueTagNames.add(tagNameRows.getString("display_name")); + } + if(failsTagFilter(uniqueTagNames, tagNamesFilter)) { + continue; + } + String tagsList = makeCommaSeparatedList(uniqueTagNames); Long objId = rs.getLong("obj_id"); String keyword = rs.getString("keyword"); @@ -707,18 +814,7 @@ public class ReportGenerator { } for (TableReportModule module : tableModules) { - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); - } - else { - module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName()); - } + module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); module.addSetIndex(lists); tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName() + "..."); @@ -758,12 +854,16 @@ public class ReportGenerator { } } - // Get any tags that associated with this artifact and apply the tag filter. - HashSet tags = Tags.getUniqueTagNamesForArtifact(rs.getLong("artifact_id"), ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()); - if (failsTagFilter(tags, tagNamesFilter)) { + // Get any tags that associated with this artifact and apply the tag filter. + HashSet uniqueTagNames = new HashSet<>(); + ResultSet tagNameRows = skCase.runQuery("SELECT display_name FROM tag_names WHERE artifact_id = " + rs.getLong("artifact_id")); + while (tagNameRows.next()) { + uniqueTagNames.add(tagNameRows.getString("display_name")); + } + if(failsTagFilter(uniqueTagNames, tagNamesFilter)) { continue; } - String tagsList = makeCommaSeparatedList(tags); + String tagsList = makeCommaSeparatedList(uniqueTagNames); Long objId = rs.getLong("obj_id"); String set = rs.getString("setname"); @@ -816,7 +916,7 @@ public class ReportGenerator { } } } - + /** * For a given artifact type ID, return the list of the row titles we're reporting on. * @@ -825,9 +925,8 @@ public class ReportGenerator { */ private List getArtifactTableColumnHeaders(int artifactTypeId) { ArrayList columnHeaders; - - BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactTypeId); - + + BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactTypeId); switch (type) { case TSK_WEB_BOOKMARK: columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"URL", "Title", "Date Created", "Program", "Source File"})); @@ -862,12 +961,6 @@ public class ReportGenerator { case TSK_METADATA_EXIF: columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Date Taken", "Device Manufacturer", "Device Model", "Latitude", "Longitude", "Source File"})); break; - case TSK_TAG_FILE: - columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"File", "Tag", "Comment"})); - break; - case TSK_TAG_ARTIFACT: - columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Result Type", "Tag", "Comment", "Source File"})); - break; case TSK_CONTACT: columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Person Name", "Phone Number", "Phone Number (Home)", "Phone Number (Office)", "Phone Number (Mobile)", "Email", "Source File" })); break; @@ -881,25 +974,25 @@ public class ReportGenerator { columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Calendar Entry Type", "Description", "Start Date/Time", "End Date/Time", "Location", "Source File" })); break; case TSK_SPEED_DIAL_ENTRY: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Short Cut", "Person Name", "Phone Number", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Short Cut", "Person Name", "Phone Number", "Source File" })); break; case TSK_BLUETOOTH_PAIRING: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Device Name", "Device Address", "Date/Time", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Device Name", "Device Address", "Date/Time", "Source File" })); break; case TSK_GPS_TRACKPOINT: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); break; case TSK_GPS_BOOKMARK: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); break; case TSK_GPS_LAST_KNOWN_LOCATION: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); break; case TSK_GPS_SEARCH: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Latitude", "Longitude", "Altitude", "Name", "Location Address", "Date/Time", "Source File" })); break; case TSK_SERVICE_ACCOUNT: - columnHeaders = new ArrayList(Arrays.asList(new String[] {"Category", "User ID", "Password", "Person Name", "App Name", "URL", "App Path", "Description", "ReplyTo Address", "Mail Server", "Source File" })); + columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Category", "User ID", "Password", "Person Name", "App Name", "URL", "App Path", "Description", "ReplyTo Address", "Mail Server", "Source File" })); break; case TSK_TOOL_OUTPUT: columnHeaders = new ArrayList<>(Arrays.asList(new String[] {"Program Name", "Text", "Source File"})); @@ -907,11 +1000,7 @@ public class ReportGenerator { default: return null; } - - if (artifactTypeId != ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && - artifactTypeId != ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { - columnHeaders.add("Tags"); - } + columnHeaders.add("Tags"); return columnHeaders; } @@ -977,242 +1066,6 @@ public class ReportGenerator { } return list; } - - /** - * Get a list of Strings with all the row values for a given BlackboardArtifact and - * list of BlackboardAttributes Entry, basing the date/time field on the given TableReportModule. - * - * @param entry BlackboardArtifact and list of BlackboardAttributes - * @param module TableReportModule for which the row is desired - * @return List row values - * @throws TskCoreException - */ - private List getArtifactRow(ArtifactData artifactData, TableReportModule module) throws TskCoreException { - Map attributes = getMappedAttributes(artifactData.getAttributes(), module); - - BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactData.getArtifact().getArtifactTypeID()); - - switch (type) { - case TSK_WEB_BOOKMARK: - List bookmark = new ArrayList<>(); - bookmark.add(attributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); - bookmark.add(attributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID())); - bookmark.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID())); - bookmark.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - bookmark.add(getFileUniquePath(artifactData.getObjectID())); - return bookmark; - case TSK_WEB_COOKIE: - List cookie = new ArrayList<>(); - cookie.add(attributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); - cookie.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - cookie.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - cookie.add(attributes.get(ATTRIBUTE_TYPE.TSK_VALUE.getTypeID())); - cookie.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - cookie.add(getFileUniquePath(artifactData.getObjectID())); - return cookie; - case TSK_WEB_HISTORY: - List history = new ArrayList<>(); - history.add(attributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); - history.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); - history.add(attributes.get(ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID())); - history.add(attributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID())); - history.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - history.add(getFileUniquePath(artifactData.getObjectID())); - return history; - case TSK_WEB_DOWNLOAD: - List download = new ArrayList<>(); - download.add(attributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); - download.add(attributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); - download.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); - download.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - download.add(getFileUniquePath(artifactData.getObjectID())); - return download; - case TSK_RECENT_OBJECT: - List recent = new ArrayList<>(); - recent.add(attributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); - recent.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - recent.add(getFileUniquePath(artifactData.getObjectID())); - return recent; - case TSK_INSTALLED_PROG: - List installed = new ArrayList<>(); - installed.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - installed.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - installed.add(getFileUniquePath(artifactData.getObjectID())); - return installed; - case TSK_DEVICE_ATTACHED: - List devices = new ArrayList<>(); - devices.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID())); - devices.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID())); - devices.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - devices.add(getFileUniquePath(artifactData.getObjectID())); - return devices; - case TSK_WEB_SEARCH_QUERY: - List search = new ArrayList<>(); - search.add(attributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); - search.add(attributes.get(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID())); - search.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); - search.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - search.add(getFileUniquePath(artifactData.getObjectID())); - return search; - case TSK_METADATA_EXIF: - List exif = new ArrayList<>(); - exif.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - exif.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE.getTypeID())); - exif.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID())); - exif.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); - exif.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); - exif.add(getFileUniquePath(artifactData.getObjectID())); - return exif; - case TSK_TAG_FILE: - List taggedFileRow = new ArrayList<>(); - AbstractFile taggedFile = getAbstractFile(artifactData.getObjectID()); - if (taggedFile != null) { - taggedFileRow.add(taggedFile.getUniquePath()); - } else { - taggedFileRow.add(""); - } - taggedFileRow.add(attributes.get(ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID())); - taggedFileRow.add(attributes.get(ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID())); - return taggedFileRow; - case TSK_TAG_ARTIFACT: - List taggedArtifactRow = new ArrayList<>(); - String taggedArtifactType = ""; - for (BlackboardAttribute attr : artifactData.getAttributes()) { - if (attr.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()) { - BlackboardArtifact taggedArtifact = getArtifact(attr.getValueLong()); - if (taggedArtifact != null) { - taggedArtifactType = taggedArtifact.getDisplayName(); - } - break; - } - } - taggedArtifactRow.add(taggedArtifactType); - taggedArtifactRow.add(attributes.get(ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID())); - taggedArtifactRow.add(attributes.get(ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID())); - AbstractFile sourceFile = getAbstractFile(artifactData.getObjectID()); - if (sourceFile != null) { - taggedArtifactRow.add(sourceFile.getUniquePath()); - } else { - taggedArtifactRow.add(""); - } - return taggedArtifactRow; - case TSK_CONTACT: - List contact = new ArrayList<>(); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME.getTypeID())); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE.getTypeID())); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE.getTypeID())); - contact.add(attributes.get(ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID())); - contact.add(getFileUniquePath(artifactData.getObjectID())); - return contact; - case TSK_MESSAGE: - List message = new ArrayList<>(); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID())); - message.add(attributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); - message.add(getFileUniquePath(artifactData.getObjectID())); - return message; - case TSK_CALLLOG: - List call_log = new ArrayList<>(); - call_log.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); - call_log.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); - call_log.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - call_log.add(attributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID())); - call_log.add(getFileUniquePath(artifactData.getObjectID())); - return call_log; - case TSK_CALENDAR_ENTRY: - List calEntry = new ArrayList<>(); - calEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE.getTypeID())); - calEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID())); - calEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID())); - calEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID())); - calEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); - calEntry.add(getFileUniquePath(artifactData.getObjectID())); - return calEntry; - case TSK_SPEED_DIAL_ENTRY: - List speedDialEntry = new ArrayList(); - speedDialEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_SHORTCUT.getTypeID())); - speedDialEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); - speedDialEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); - speedDialEntry.add(getFileUniquePath(artifactData.getObjectID())); - return speedDialEntry; - case TSK_BLUETOOTH_PAIRING: - List bluetoothEntry = new ArrayList(); - bluetoothEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_NAME.getTypeID())); - bluetoothEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID())); - bluetoothEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - bluetoothEntry.add(getFileUniquePath(artifactData.getObjectID())); - return bluetoothEntry; - case TSK_GPS_TRACKPOINT: - List gpsTrackpoint = new ArrayList(); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); - gpsTrackpoint.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - gpsTrackpoint.add(getFileUniquePath(artifactData.getObjectID())); - return gpsTrackpoint; - case TSK_GPS_BOOKMARK: - List gpsBookmarkEntry = new ArrayList(); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); - gpsBookmarkEntry.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - gpsBookmarkEntry.add(getFileUniquePath(artifactData.getObjectID())); - return gpsBookmarkEntry; - case TSK_GPS_LAST_KNOWN_LOCATION: - List gpsLastLocation = new ArrayList(); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); - gpsLastLocation.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - gpsLastLocation.add(getFileUniquePath(artifactData.getObjectID())); - return gpsLastLocation; - case TSK_GPS_SEARCH: - List gpsSearch = new ArrayList(); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); - gpsSearch.add(attributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); - gpsSearch.add(getFileUniquePath(artifactData.getObjectID())); - return gpsSearch; - case TSK_SERVICE_ACCOUNT: - List appAccount = new ArrayList(); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_USER_ID.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_PASSWORD.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO.getTypeID())); - appAccount.add(attributes.get(ATTRIBUTE_TYPE.TSK_SERVER_NAME.getTypeID())); - appAccount.add(getFileUniquePath(artifactData.getObjectID())); - return appAccount; - case TSK_TOOL_OUTPUT: - List row = new ArrayList<>(); - row.add(attributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); - row.add(attributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); - row.add(getFileUniquePath(artifactData.getObjectID())); - return row; - } - return null; - } /** * Given a tsk_file's obj_id, return the unique path of that file. @@ -1228,52 +1081,7 @@ public class ReportGenerator { } return ""; } - - /** - * Given a tsk_file's obj_id, return the name of that file. - * - * @param objId tsk_file obj_id - * @return String name - */ - private String getFileName(long objId) { - try { - return skCase.getAbstractFileById(objId).getName(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); - } - return ""; - } - - /** - * Return the file associated with a tsk_file obj_id. - * - * @param objId tsk_file obj_id - * @return AbstractFile associated with objId - */ - private AbstractFile getAbstractFile(long objId) { - try { - return skCase.getAbstractFileById(objId); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); - } - return null; - } - - /** - * Get a BlackboardArtifact. - * - * @param long artifactId An artifact id - * @return The BlackboardArtifact associated with the artifact id - */ - private BlackboardArtifact getArtifact(long artifactId) { - try { - return skCase.getBlackboardArtifact(artifactId); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get blackboard artifact by ID.", ex); - } - return null; - } - + /** * Container class that holds data about an Artifact to eliminate duplicate * calls to the Sleuthkit database. @@ -1282,6 +1090,7 @@ public class ReportGenerator { private BlackboardArtifact artifact; private List attributes; private HashSet tags; + private List rowData = null; ArtifactData(BlackboardArtifact artifact, List attrs, HashSet tags) { this.artifact = artifact; @@ -1299,7 +1108,7 @@ public class ReportGenerator { public long getObjectID() { return artifact.getObjectID(); } - @Override + /** * Compares ArtifactData objects by the first attribute they have in * common in their List. @@ -1308,21 +1117,237 @@ public class ReportGenerator { * compared by their artifact id. Should only be used with attributes * of the same type. */ - public int compareTo(ArtifactData data) { - // Get all the attributes for each artifact - int size = ATTRIBUTE_TYPE.values().length; - Map att1 = getMappedAttributes(this.attributes); - Map att2 = getMappedAttributes(data.getAttributes()); - // Compare the attributes one-by-one looking for differences - for(int i=0; i < size; i++) { - String a1 = att1.get(i); - String a2 = att2.get(i); - if((!a1.equals("") && !a2.equals("")) && a1.compareTo(a2) != 0) { - return a1.compareTo(a2); + @Override + public int compareTo(ArtifactData otherArtifactData) { + List thisRow = getRow(); + List otherRow = otherArtifactData.getRow(); + for (int i = 0; i < thisRow.size(); i++) { + int compare = thisRow.get(i).compareTo(otherRow.get(i)); + if (compare != 0) { + return compare; } } // If all attributes are the same, they're most likely duplicates so sort by artifact ID - return ((Long)this.getArtifactID()).compareTo((Long)data.getArtifactID()); + return ((Long) this.getArtifactID()).compareTo((Long) otherArtifactData.getArtifactID()); + } + + /** + * Get the values for each row in the table report. + */ + public List getRow() { + if (rowData == null) { + try { + rowData = getOrderedRowDataAsStrings(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Core exception while generating row data for artifact report.", ex); + rowData = Collections.emptyList(); + } + } + return rowData; + } + + /** + * Get a list of Strings with all the row values for the Artifact in the + * correct order to be written to the report. + * + * @return List row values + * @throws TskCoreException + */ + private List getOrderedRowDataAsStrings() throws TskCoreException { + Map mappedAttributes = getMappedAttributes(); + List orderedRowData = new ArrayList<>(); + BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID()); + switch (type) { + case TSK_WEB_BOOKMARK: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_WEB_COOKIE: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_VALUE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_WEB_HISTORY: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_WEB_DOWNLOAD: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_RECENT_OBJECT: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_INSTALLED_PROG: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_DEVICE_ATTACHED: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_WEB_SEARCH_QUERY: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_METADATA_EXIF: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_CONTACT: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_MESSAGE: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_CALLLOG: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_CALENDAR_ENTRY: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_SPEED_DIAL_ENTRY: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SHORTCUT.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_BLUETOOTH_PAIRING: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_GPS_TRACKPOINT: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_GPS_BOOKMARK: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_GPS_LAST_KNOWN_LOCATION: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_GPS_SEARCH: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_SERVICE_ACCOUNT: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_USER_ID.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PASSWORD.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SERVER_NAME.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + case TSK_TOOL_OUTPUT: + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())); + orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID())); + orderedRowData.add(getFileUniquePath(getObjectID())); + break; + } + orderedRowData.add(makeCommaSeparatedList(getTags())); + + return orderedRowData; + } + + /** + * Returns a mapping of Attribute Type ID to the String representation + * of an Attribute Value. + */ + private Map getMappedAttributes() { + return ReportGenerator.this.getMappedAttributes(attributes); + } + + /** + * Get a BlackboardArtifact. + * + * @param long artifactId An artifact id + * @return The BlackboardArtifact associated with the artifact id + */ + private BlackboardArtifact getArtifactByID(long artifactId) { + try { + return skCase.getBlackboardArtifact(artifactId); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to get blackboard artifact by ID.", ex); + } + return null; } } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index aa48b125f0..85f76bb31f 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -28,7 +28,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.FileInputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; @@ -36,7 +35,6 @@ import java.io.Writer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -45,17 +43,15 @@ import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; public class ReportHTML implements TableReportModule { @@ -91,7 +87,7 @@ public class ReportHTML implements TableReportModule { currentCase = Case.getCurrentCase(); skCase = currentCase.getSleuthkitCase(); - dataTypes = new TreeMap(); + dataTypes = new TreeMap<>(); path = ""; currentDataType = ""; @@ -129,10 +125,10 @@ public class ReportHTML implements TableReportModule { { String iconFilePath; String iconFileName; - InputStream in = null; + InputStream in; OutputStream output = null; - logger.log(Level.INFO, "useDataTypeIcon: dataType = " + dataType); + logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); // find the artifact with matching display name BlackboardArtifact.ARTIFACT_TYPE artifactType = null; @@ -296,39 +292,6 @@ public class ReportHTML implements TableReportModule { } } } - - /** - * Start a new HTML page for the given data type. Update the output stream to this page, - * and setup the web page header. - * @param title title of the data type - */ - @Override - public void startDataType(String title) { - String fTitle = dataTypeToFileName(title); - // Make a new out for this page - try { - //escape out slashes tha that appear in title - - out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + fTitle + getExtension()), "UTF-8")); - } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "File not found: {0}", ex); - } catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Unrecognized encoding"); - } - - // Write the beginnings of a page - // Like , header, title, any content divs - try { - StringBuilder page = new StringBuilder(); - page.append("\n\n\t").append(title).append("\n\t\n\n\n\n"); - page.append("
").append(title).append("
\n
\n"); - out.write(page.toString()); - currentDataType = title; - rowCount = 0; - } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); - } - } /** * Start a new HTML page for the given data type. Update the output stream to this page, @@ -338,7 +301,8 @@ public class ReportHTML implements TableReportModule { * @param name Name of the data type * @param comment Comment on the data type, may be the empty string */ - public void startDataType(String name, String comment) { + @Override + public void startDataType(String name, String description) { String title = dataTypeToFileName(name); try { out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + title + getExtension()), "UTF-8")); @@ -352,9 +316,9 @@ public class ReportHTML implements TableReportModule { StringBuilder page = new StringBuilder(); page.append("\n\n\t").append(name).append("\n\t\n\n\n\n"); page.append("
").append(name).append("
\n
\n"); - if (!comment.isEmpty()) { + if (!description.isEmpty()) { page.append("

"); - page.append(comment); + page.append(description); page.append("

\n"); } out.write(page.toString()); @@ -477,17 +441,17 @@ public class ReportHTML implements TableReportModule { * @param columnHeaders column headers * @param sourceArtifact source blackboard artifact for the table data */ - public void startTable(List columnHeaders, ARTIFACT_TYPE artifactType) { + public void startContentTagsTable(List columnHeaders) { StringBuilder htmlOutput = new StringBuilder(); htmlOutput.append("\n\n\t\n"); + + // Add the specified columns. for(String columnHeader : columnHeaders) { htmlOutput.append("\t\t\n"); } - // For file tag artifacts, add a column for a hyperlink to a local copy of the tagged file. - if (artifactType.equals(ARTIFACT_TYPE.TSK_TAG_FILE)) { - htmlOutput.append("\t\t\n"); - } + // Add a column for a hyperlink to a local copy of the tagged content. + htmlOutput.append("\t\t\n"); htmlOutput.append("\t\n\n"); @@ -527,20 +491,75 @@ public class ReportHTML implements TableReportModule { try { out.write(builder.toString()); } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to write row to out."); + logger.log(Level.SEVERE, "Failed to write row to out.", ex); } catch (NullPointerException ex) { - logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing."); + logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); } } /** - * Add a row to the current table. + * Saves a local copy of a tagged file and adds a row with a hyper link to + * the file. * - * @param row values for each cell in the row - * @param sourceArtifact source blackboard artifact for the table data + * @param row Values for each data cell in the row. + * @param contentTag A content tag to use to make the hyper link. */ - public void addRow(List row, BlackboardArtifact sourceArtifact) { - addRowDataForSourceArtifact(row, sourceArtifact); + public void addRowWithTaggedContentHyperlink(List row, ContentTag contentTag) { + // Only handling AbstractFiles at present. + AbstractFile file; + if (contentTag.getContent() instanceof AbstractFile) { + file = (AbstractFile)contentTag.getContent(); + } + else { + return; + } + + // Don't make a local copy of the file if it is a directory or unallocated space. + if (file.isDir() || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { + row.add(""); + return; + } + + // Make a folder for the local file with the same name as the tag. + StringBuilder localFilePath = new StringBuilder(); + localFilePath.append(path); + localFilePath.append(contentTag.getName().getDisplayName()); + File localFileFolder = new File(localFilePath.toString()); + if (!localFileFolder.exists()) { + localFileFolder.mkdirs(); + } + + // Construct a file name for the local file that incorporates the file id to ensure uniqueness. + String fileName = file.getName(); + String objectIdSuffix = "_" + file.getId(); + int lastDotIndex = fileName.lastIndexOf("."); + if (lastDotIndex != -1 && lastDotIndex != 0) { + // The file name has a conventional extension. Insert the object id before the '.' of the extension. + fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length()); + } + else { + // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file. + // Add the object id to the end of the file name. + fileName += objectIdSuffix; + } + localFilePath.append(File.separator); + localFilePath.append(fileName); + + // If the local file doesn't already exist, create it now. + // The existence check is necessary because it is possible to apply multiple tags with the same name to a file. + File localFile = new File(localFilePath.toString()); + if (!localFile.exists()) { + ExtractFscContentVisitor.extract(file, localFile, null, null); + } + + // Add the hyperlink to the row. A column header for it was created in startTable(). + StringBuilder localFileLink = new StringBuilder(); + localFileLink.append("View File"); + row.add(localFileLink.toString()); StringBuilder builder = new StringBuilder(); builder.append("\t\n"); @@ -561,90 +580,6 @@ public class ReportHTML implements TableReportModule { } } - /** - * Add cells particular to a type of artifact associated with the row. Assumes that the overload of startTable() that takes an artifact type was called. - * - * @param row The row. - * @param sourceArtifact The artifact associated with the row. - */ - private void addRowDataForSourceArtifact(List row, BlackboardArtifact sourceArtifact) { - int artifactTypeID = sourceArtifact.getArtifactTypeID(); - BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactTypeID); - switch (type) { - case TSK_TAG_FILE: - addRowDataForFileTagArtifact(row, sourceArtifact); - break; - default: - break; - } - } - - /** - * Saves a local copy of a tagged file and adds a hyper link to the file to the row. - * - * @param row The row. - * @param sourceArtifact The artifact associated with the row. - */ - private void addRowDataForFileTagArtifact(List row, BlackboardArtifact sourceArtifact) { - try { - AbstractFile file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(sourceArtifact.getObjectID()); - - // Don't make a local copy of the file if it is a directory or unallocated space. - if (file.isDir() || - file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || - file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { - row.add(""); - return; - } - - // Make a folder for the local file with the same name as the tag. - StringBuilder localFilePath = new StringBuilder(); - localFilePath.append(path); - HashSet tagNames = Tags.getUniqueTagNamesForArtifact(sourceArtifact); - if (!tagNames.isEmpty()) { - localFilePath.append(tagNames.iterator().next()); - } - File localFileFolder = new File(localFilePath.toString()); - if (!localFileFolder.exists()) { - localFileFolder.mkdirs(); - } - - // Construct a file name for the local file that incorporates the corresponding object id to ensure uniqueness. - String fileName = file.getName(); - String objectIdSuffix = "_" + sourceArtifact.getObjectID(); - int lastDotIndex = fileName.lastIndexOf("."); - if (lastDotIndex != -1 && lastDotIndex != 0) { - // The file name has a conventional extension. Insert the object id before the '.' of the extension. - fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length()); - } - else { - // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file. - // Add the object id to the end of the file name. - fileName += objectIdSuffix; - } - localFilePath.append(File.separator); - localFilePath.append(fileName); - - // If the local file doesn't already exist, create it now. - // The existence check is necessary because it is possible to apply multiple tags with the same name to a file. - File localFile = new File(localFilePath.toString()); - if (!localFile.exists()) { - ExtractFscContentVisitor.extract(file, localFile, null, null); - } - - // Add the hyperlink to the row. A column header for it was created in startTable(). - StringBuilder localFileLink = new StringBuilder(); - localFileLink.append("View File"); - row.add(localFileLink.toString()); - } - catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get AbstractFile by ID.", ex); - row.add(""); - } - } - /** * Return a String date for the long date given. * @param date date as a long @@ -704,11 +639,11 @@ public class ReportHTML implements TableReportModule { "table tr:nth-child(even) td {background: #f3f3f3;}"; cssOut.write(css); } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "Could not find index.css file to write to."); + logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); } catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css."); + logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); } catch (IOException ex) { - logger.log(Level.SEVERE, "Error creating Writer for index.css."); + logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); } finally { try { if(cssOut != null) { diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportModule.java b/Core/src/org/sleuthkit/autopsy/report/ReportModule.java index d01b994985..2a3a677b29 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportModule.java @@ -23,37 +23,30 @@ package org.sleuthkit.autopsy.report; /** - * Interface extended by TableReportModule and GeneralReportModule. - * Contains vital report information to be used by every report. + * Interface got report modules that plug in to the reporting infrastructure. */ public interface ReportModule { /** - * Returns a basic string name for the report. What is 'officially' titled. - * - * @return the report name + * Get the name of the report this module generates. */ public String getName(); /** - * Returns a one line user friendly description of the type of report this - * module generates - * @return user-friendly report description + * Gets a one-line, user friendly description of the type of report this + * module generates. */ public String getDescription(); /** - * Returns the extension that is used for the report - * - * @return String the extension the file will be saved as - * + * Gets the extension of the report file, if any, generated by this module. + * @return File name extension, may be null. */ public String getExtension(); /** - * Returns the path to the main (or only) file for the report. - * - * @return String path to the report file + * Gets the path of the report file, if any, generated by this module. + * @return File path, may be null. */ public String getFilePath(); } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java index 452fd28d7e..af083db23d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java @@ -48,56 +48,61 @@ public class ReportProgressPanel extends javax.swing.JPanel { } private void customInit(String reportName, String reportPath) { - reportLabel.setText(reportName); - pathLabel.setText("" + shortenPath(reportPath) + ""); - pathLabel.setToolTipText(reportPath); - reportProgressBar.setIndeterminate(true); reportProgressBar.setMaximum(100); - + + reportLabel.setText(reportName); processingLabel.setText("Queuing..."); STATUS = ReportStatus.QUEUING; + if (reportPath != null) { + pathLabel.setText("" + shortenPath(reportPath) + ""); + pathLabel.setToolTipText(reportPath); + // Add the "link" effect to the pathLabel - final String linkPath = reportPath; - pathLabel.addMouseListener(new MouseListener() { + final String linkPath = reportPath; + pathLabel.addMouseListener(new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - } + @Override + public void mouseClicked(MouseEvent e) { + } - @Override - public void mousePressed(MouseEvent e) { - } + @Override + public void mousePressed(MouseEvent e) { + } - @Override - public void mouseReleased(MouseEvent e) { - File file = new File(linkPath); - try { - Desktop.getDesktop().open(file); - } catch (IOException ex) { - } catch (IllegalArgumentException ex) { + @Override + public void mouseReleased(MouseEvent e) { + File file = new File(linkPath); try { - // try to open the parent path if the file doens't exist - Desktop.getDesktop().open(file.getParentFile()); - } catch (IOException ex1) { + Desktop.getDesktop().open(file); + } catch (IOException ex) { + } catch (IllegalArgumentException ex) { + try { + // try to open the parent path if the file doens't exist + Desktop.getDesktop().open(file.getParentFile()); + } catch (IOException ex1) { + } } } - } - @Override - public void mouseEntered(MouseEvent e) { - pathLabel.setForeground(Color.DARK_GRAY); - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } + @Override + public void mouseEntered(MouseEvent e) { + pathLabel.setForeground(Color.DARK_GRAY); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } - @Override - public void mouseExited(MouseEvent e) { - pathLabel.setForeground(Color.BLACK); - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - - }); + @Override + public void mouseExited(MouseEvent e) { + pathLabel.setForeground(Color.BLACK); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + }); + } + else { + pathLabel.setText("No report file"); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java index 972bb3eb0e..a9da09980a 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java @@ -238,7 +238,7 @@ public final class ReportVisualPanel1 extends JPanel implements ListSelectionLis boolean generalModuleSelected = false; if (module instanceof GeneralReportModule) { JPanel generalPanel = ((GeneralReportModule) module).getConfigurationPanel(); - panel = (generalPanel == null) ? new JPanel() : panel; + panel = (generalPanel == null) ? new JPanel() : generalPanel; generalModuleSelected = true; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java index e374135aa9..8ccd2b0770 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java @@ -38,13 +38,12 @@ import javax.swing.ListModel; import javax.swing.event.ListDataListener; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; public final class ReportVisualPanel2 extends JPanel { - private static final Logger logger = Logger.getLogger(ReportVisualPanel2.class.getName()); private ReportWizardPanel2 wizPanel; private Map tagStates = new LinkedHashMap<>(); @@ -73,8 +72,17 @@ public final class ReportVisualPanel2 extends JPanel { // Initialize the list of Tags private void initTags() { - for(String tag : Tags.getTagNamesFromCurrentCase()) { - tagStates.put(tag, Boolean.FALSE); + List tagNamesInUse; + try { + tagNamesInUse = Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse(); + } + catch (TskCoreException ex) { + Logger.getLogger(ReportVisualPanel2.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); + return; + } + + for(TagName tagName : tagNamesInUse) { + tagStates.put(tagName.getDisplayName(), Boolean.FALSE); } tags.addAll(tagStates.keySet()); @@ -95,16 +103,17 @@ public final class ReportVisualPanel2 extends JPanel { list.repaint(); updateFinishButton(); } - }); - + }); } // Initialize the list of Artifacts private void initArtifactTypes() { try { - ArrayList doNotReport = new ArrayList(); + ArrayList doNotReport = new ArrayList(); doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO); + doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); // Obsolete artifact type + doNotReport.add(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); // Obsolete artifact type artifacts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifactTypesInUse(); @@ -116,7 +125,6 @@ public final class ReportVisualPanel2 extends JPanel { } } catch (TskCoreException ex) { Logger.getLogger(ReportVisualPanel2.class.getName()).log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage(), ex); - return; } } @@ -355,8 +363,6 @@ public final class ReportVisualPanel2 extends JPanel { return this; } return new JLabel(); - } - + } } - } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 67a91e6521..23d9020de1 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -71,7 +71,7 @@ public final class ReportWizardAction extends CallableSystemAction implements P ReportGenerator generator = new ReportGenerator((Map)wiz.getProperty("tableModuleStates"), (Map)wiz.getProperty("generalModuleStates"), (Map)wiz.getProperty("fileModuleStates")); - generator.generateArtifactTableReports((Map)wiz.getProperty("artifactStates"), (Map)wiz.getProperty("tagStates")); + generator.generateBlackboardArtifactsReports((Map)wiz.getProperty("artifactStates"), (Map)wiz.getProperty("tagStates")); generator.generateFileListReports((Map)wiz.getProperty("fileReportOptions")); generator.generateGeneralReports(); generator.displayProgressPanels(); diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java b/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java index 13397d4cd5..4a7bc9ac50 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java @@ -47,12 +47,13 @@ public interface TableReportModule extends ReportModule { /** * Start a new data type for the report. This is how the report will differentiate between - * the start and end of a certain type of data, such as a Blackboard Artifact Type. + * the start and end of a certain type of data, such as a blackboard artifact Type. * It is up to the report how the differentiation is shown. * * @param title String name of the data type + * @param description Description of the data type */ - public void startDataType(String title); + public void startDataType(String title, String description); /** * End the current data type and prepare for either the end of the report diff --git a/ExifParser/manifest.mf b/ExifParser/manifest.mf index dbf05fee2f..44ad288f51 100644 --- a/ExifParser/manifest.mf +++ b/ExifParser/manifest.mf @@ -1,6 +1,6 @@ -Manifest-Version: 1.0 -AutoUpdate-Show-In-Client: true -OpenIDE-Module: org.sleuthkit.autopsy.exifparser/3 -OpenIDE-Module-Implementation-Version: 9 -OpenIDE-Module-Layer: org/sleuthkit/autopsy/exifparser/layer.xml -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/exifparser/Bundle.properties +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.sleuthkit.autopsy.exifparser/3 +OpenIDE-Module-Implementation-Version: 9 +OpenIDE-Module-Layer: org/sleuthkit/autopsy/exifparser/layer.xml +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/exifparser/Bundle.properties diff --git a/FileTypeId/build.xml b/FileTypeId/build.xml new file mode 100644 index 0000000000..9771a82a0b --- /dev/null +++ b/FileTypeId/build.xml @@ -0,0 +1,8 @@ + + + + + + Builds, tests, and runs the project org.sleuthkit.autopsy.filetypeid. + + diff --git a/FileTypeId/manifest.mf b/FileTypeId/manifest.mf new file mode 100644 index 0000000000..eac3c30420 --- /dev/null +++ b/FileTypeId/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.sleuthkit.autopsy.filetypeid +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/filetypeid/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/FileTypeId/nbproject/build-impl.xml b/FileTypeId/nbproject/build-impl.xml new file mode 100644 index 0000000000..ec094e8ceb --- /dev/null +++ b/FileTypeId/nbproject/build-impl.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + You must set 'suite.dir' to point to your containing module suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FileTypeId/nbproject/platform.properties b/FileTypeId/nbproject/platform.properties new file mode 100644 index 0000000000..a9fa87f749 --- /dev/null +++ b/FileTypeId/nbproject/platform.properties @@ -0,0 +1,120 @@ +branding.token=autopsy +netbeans-plat-version=7.3.1 +suite.dir=${basedir} +nbplatform.active.dir=${suite.dir}/netbeans-plat/${netbeans-plat-version} +harness.dir=${nbplatform.active.dir}/harness +bootstrap.url=http://deadlock.netbeans.org/hudson/job/nbms-and-javadoc/lastStableBuild/artifact/nbbuild/netbeans/harness/tasks.jar +autoupdate.catalog.url=http://dlc.sun.com.edgesuite.net/netbeans/updates/${netbeans-plat-version}/uc/final/distribution/catalog.xml.gz +cluster.path=\ + ${nbplatform.active.dir}/harness:\ + ${nbplatform.active.dir}/java:\ + ${nbplatform.active.dir}/platform +disabled.modules=\ + org.apache.tools.ant.module,\ + org.netbeans.api.debugger.jpda,\ + org.netbeans.api.java,\ + org.netbeans.lib.nbjavac,\ + org.netbeans.libs.cglib,\ + org.netbeans.libs.javacapi,\ + org.netbeans.libs.javacimpl,\ + org.netbeans.libs.springframework,\ + org.netbeans.modules.ant.browsetask,\ + org.netbeans.modules.ant.debugger,\ + org.netbeans.modules.ant.freeform,\ + org.netbeans.modules.ant.grammar,\ + org.netbeans.modules.ant.kit,\ + org.netbeans.modules.beans,\ + org.netbeans.modules.classfile,\ + org.netbeans.modules.dbschema,\ + org.netbeans.modules.debugger.jpda,\ + org.netbeans.modules.debugger.jpda.ant,\ + org.netbeans.modules.debugger.jpda.kit,\ + org.netbeans.modules.debugger.jpda.projects,\ + org.netbeans.modules.debugger.jpda.ui,\ + org.netbeans.modules.debugger.jpda.visual,\ + org.netbeans.modules.findbugs.installer,\ + org.netbeans.modules.form,\ + org.netbeans.modules.form.binding,\ + org.netbeans.modules.form.j2ee,\ + org.netbeans.modules.form.kit,\ + org.netbeans.modules.form.nb,\ + org.netbeans.modules.form.refactoring,\ + org.netbeans.modules.hibernate,\ + org.netbeans.modules.hibernatelib,\ + org.netbeans.modules.hudson.ant,\ + org.netbeans.modules.hudson.maven,\ + org.netbeans.modules.i18n,\ + org.netbeans.modules.i18n.form,\ + org.netbeans.modules.j2ee.core.utilities,\ + org.netbeans.modules.j2ee.eclipselink,\ + org.netbeans.modules.j2ee.eclipselinkmodelgen,\ + org.netbeans.modules.j2ee.jpa.refactoring,\ + org.netbeans.modules.j2ee.jpa.verification,\ + org.netbeans.modules.j2ee.metadata,\ + org.netbeans.modules.j2ee.metadata.model.support,\ + org.netbeans.modules.j2ee.persistence,\ + org.netbeans.modules.j2ee.persistence.kit,\ + org.netbeans.modules.j2ee.persistenceapi,\ + org.netbeans.modules.java.api.common,\ + org.netbeans.modules.java.debug,\ + org.netbeans.modules.java.editor,\ + org.netbeans.modules.java.editor.lib,\ + org.netbeans.modules.java.examples,\ + org.netbeans.modules.java.freeform,\ + org.netbeans.modules.java.guards,\ + org.netbeans.modules.java.helpset,\ + org.netbeans.modules.java.hints,\ + org.netbeans.modules.java.hints.declarative,\ + org.netbeans.modules.java.hints.declarative.test,\ + org.netbeans.modules.java.hints.legacy.spi,\ + org.netbeans.modules.java.hints.test,\ + org.netbeans.modules.java.hints.ui,\ + org.netbeans.modules.java.j2seplatform,\ + org.netbeans.modules.java.j2seproject,\ + org.netbeans.modules.java.kit,\ + org.netbeans.modules.java.lexer,\ + org.netbeans.modules.java.navigation,\ + org.netbeans.modules.java.platform,\ + org.netbeans.modules.java.preprocessorbridge,\ + org.netbeans.modules.java.project,\ + org.netbeans.modules.java.source,\ + org.netbeans.modules.java.source.ant,\ + org.netbeans.modules.java.source.queries,\ + org.netbeans.modules.java.source.queriesimpl,\ + org.netbeans.modules.java.sourceui,\ + org.netbeans.modules.java.testrunner,\ + org.netbeans.modules.javadoc,\ + org.netbeans.modules.javawebstart,\ + org.netbeans.modules.junit,\ + org.netbeans.modules.maven,\ + org.netbeans.modules.maven.checkstyle,\ + org.netbeans.modules.maven.coverage,\ + org.netbeans.modules.maven.embedder,\ + org.netbeans.modules.maven.grammar,\ + org.netbeans.modules.maven.graph,\ + org.netbeans.modules.maven.hints,\ + org.netbeans.modules.maven.indexer,\ + org.netbeans.modules.maven.junit,\ + org.netbeans.modules.maven.kit,\ + org.netbeans.modules.maven.model,\ + org.netbeans.modules.maven.osgi,\ + org.netbeans.modules.maven.persistence,\ + org.netbeans.modules.maven.refactoring,\ + org.netbeans.modules.maven.repository,\ + org.netbeans.modules.maven.search,\ + org.netbeans.modules.maven.spring,\ + org.netbeans.modules.projectimport.eclipse.core,\ + org.netbeans.modules.projectimport.eclipse.j2se,\ + org.netbeans.modules.refactoring.java,\ + org.netbeans.modules.spellchecker.bindings.java,\ + org.netbeans.modules.spring.beans,\ + org.netbeans.modules.testng,\ + org.netbeans.modules.testng.ant,\ + org.netbeans.modules.testng.maven,\ + org.netbeans.modules.websvc.jaxws21,\ + org.netbeans.modules.websvc.jaxws21api,\ + org.netbeans.modules.websvc.saas.codegen.java,\ + org.netbeans.modules.xml.jaxb,\ + org.netbeans.modules.xml.tools.java,\ + org.netbeans.spi.java.hints + diff --git a/FileTypeId/nbproject/project.properties b/FileTypeId/nbproject/project.properties new file mode 100644 index 0000000000..2dfc44febe --- /dev/null +++ b/FileTypeId/nbproject/project.properties @@ -0,0 +1,3 @@ +file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar +javac.source=1.7 +javac.compilerargs=-Xlint -Xlint:-serial diff --git a/FileTypeId/nbproject/project.xml b/FileTypeId/nbproject/project.xml new file mode 100644 index 0000000000..bcf4cb3009 --- /dev/null +++ b/FileTypeId/nbproject/project.xml @@ -0,0 +1,101 @@ + + + org.netbeans.modules.apisupport.project + + + org.sleuthkit.autopsy.filetypeid + + + + org.netbeans.api.progress + + + + 1 + 1.32.1 + + + + org.netbeans.modules.options.api + + + + 1 + 1.31.2 + + + + org.openide.awt + + + + 7.55.1 + + + + org.openide.dialogs + + + + 7.28.1 + + + + org.openide.nodes + + + + 7.33.2 + + + + org.openide.util + + + + 8.29.3 + + + + org.openide.util.lookup + + + + 8.19.1 + + + + org.openide.windows + + + + 6.60.1 + + + + org.sleuthkit.autopsy.core + + + + 9 + 7.0 + + + + org.sleuthkit.autopsy.corelibs + + + + 3 + 1.1 + + + + + + ext/tika-core-1.2.jar + release/modules/ext/tika-core-1.2.jar + + + + diff --git a/FileTypeId/nbproject/suite.properties b/FileTypeId/nbproject/suite.properties new file mode 100644 index 0000000000..29d7cc9bd6 --- /dev/null +++ b/FileTypeId/nbproject/suite.properties @@ -0,0 +1 @@ +suite.dir=${basedir}/.. diff --git a/FileTypeId/release/modules/ext/tika-core-1.2.jar b/FileTypeId/release/modules/ext/tika-core-1.2.jar new file mode 100644 index 0000000000..e1491ab5f2 Binary files /dev/null and b/FileTypeId/release/modules/ext/tika-core-1.2.jar differ diff --git a/FileTypeId/src-alt/JMimeMagicFileTypeDetector.java b/FileTypeId/src-alt/JMimeMagicFileTypeDetector.java new file mode 100644 index 0000000000..246fadc81e --- /dev/null +++ b/FileTypeId/src-alt/JMimeMagicFileTypeDetector.java @@ -0,0 +1,68 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.filetypeid; + +import org.sleuthkit.datamodel.AbstractFile; +import net.sf.jmimemagic.Magic; +import net.sf.jmimemagic.MagicMatch; +import net.sf.jmimemagic.MagicMatchNotFoundException; +import org.openide.util.Exceptions; + +/** + * + */ +public class JMimeMagicFileTypeDetector implements FileTypeDetectionInterface { + + @Override + public FileIdInfo attemptMatch(AbstractFile abstractFile) { + try { + FileIdInfo ret = new FileIdInfo(); + final int maxBytesInitial = 3000; //how many bytes to read on first pass + byte buffer[] = new byte[maxBytesInitial]; + ///@todo decide to use max bytes or give the whole file + int len = abstractFile.read(buffer, 0, maxBytesInitial); + + try { + MagicMatch match = Magic.getMagicMatch(buffer); + if (match != null) { + String matchStr = match.getMimeType(); + if (matchStr.equals("???")) { + String desc = match.getDescription(); + if (!desc.isEmpty()) { + ret.type = desc; + } + } else { + ret.type = matchStr; + } + ret.extension = match.getExtension(); + } + } catch (MagicMatchNotFoundException ex) { + //do nothing + } + + return ret; + + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + return new FileIdInfo(); + } + } + +} diff --git a/FileTypeId/src-alt/MimeUtilFileTypeDetector.java b/FileTypeId/src-alt/MimeUtilFileTypeDetector.java new file mode 100644 index 0000000000..40d5a74b58 --- /dev/null +++ b/FileTypeId/src-alt/MimeUtilFileTypeDetector.java @@ -0,0 +1,67 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.filetypeid; + +import eu.medsea.mimeutil.MimeException; +import eu.medsea.mimeutil.MimeType; +import eu.medsea.mimeutil.detector.MagicMimeMimeDetector; +import java.util.Iterator; +import java.util.LinkedHashSet; +import org.openide.util.Exceptions; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * + */ +public class MimeUtilFileTypeDetector implements FileTypeDetectionInterface { + private static MagicMimeMimeDetector mimeUtil = new MagicMimeMimeDetector(); + + + @Override + public FileIdInfo attemptMatch(AbstractFile abstractFile) { + try { + FileIdInfo ret = new FileIdInfo(); + final int maxBytesInitial = 3000; //how many bytes to read on first pass + byte buffer[] = new byte[maxBytesInitial]; + int len = abstractFile.read(buffer, 0, maxBytesInitial); + + try { + LinkedHashSet mimeSet = (LinkedHashSet)mimeUtil.getMimeTypesByteArray(buffer); + + Iterator it = mimeSet.iterator(); + while (it.hasNext()) { + MimeType mt = (MimeType)it.next(); + ret.type = mt.getMediaType() + "/" + mt.getSubType(); + break; //just take the first one for now + } + + } catch (MimeException ex) { + //do nothing + } + + return ret; + + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + return new FileIdInfo(); + } + } + +} diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/Bundle.properties b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/Bundle.properties new file mode 100644 index 0000000000..980042212f --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/Bundle.properties @@ -0,0 +1,3 @@ +OpenIDE-Module-Name=FileTypeId +FileTypeIdSimpleConfigPanel.skipKnownCheckBox.toolTipText=Depending on how many files have known hashes, checking this box will improve the speed of file type identification. +FileTypeIdSimpleConfigPanel.skipKnownCheckBox.text=Skip Known Files diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeDetectionInterface.java b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeDetectionInterface.java new file mode 100644 index 0000000000..a05091c139 --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeDetectionInterface.java @@ -0,0 +1,40 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.filetypeid; + +import org.sleuthkit.datamodel.AbstractFile; + +/* + * This will allow us to swap and or compare alternative libraries + * + * For extension, an implementation could use a custom lookup table mapping type + * to an extension string list instead of the third-party library's extension + * reporting. + */ +public interface FileTypeDetectionInterface { + + // Struct to hold multiple values for return + public class FileIdInfo { + public String type; + public String extension; + } + + // You only have one job + FileIdInfo attemptMatch(AbstractFile abstractFile); +} diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdIngestModule.java b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdIngestModule.java new file mode 100644 index 0000000000..2663d1d981 --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdIngestModule.java @@ -0,0 +1,178 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.filetypeid; + +import java.util.Collections; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModuleAbstractFile; +import org.sleuthkit.autopsy.ingest.IngestModuleInit; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.ingest.PipelineContext; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskData.FileKnown; +import org.sleuthkit.datamodel.TskException; + +/** + * File Type Identification + */ +public class FileTypeIdIngestModule extends org.sleuthkit.autopsy.ingest.IngestModuleAbstractFile { + private static FileTypeIdIngestModule defaultInstance = null; + public final static String MODULE_NAME = "File Type Identification"; + public final static String MODULE_DESCRIPTION = "Matches file types based on binary signatures."; + public final static String MODULE_VERSION = Version.getVersion(); + private static final Logger logger = Logger.getLogger(FileTypeIdIngestModule.class.getName()); + private static long matchTime = 0; + private static int messageId = 0; + private static long numFiles = 0; + private static boolean skipKnown = false; + + private FileTypeIdSimpleConfigPanel simpleConfigPanel; + private IngestServices services; + + // The detector. Swap out with a different implementation of FileTypeDetectionInterface as needed. + // If desired in the future to be more knowledgable about weird files or rare formats, we could + // actually have a list of detectors which are called in order until a match is found. + private FileTypeDetectionInterface detector = new TikaFileTypeDetector(); + //private FileTypeDetectionInterface detector = new JMimeMagicFileTypeDetector(); + //private FileTypeDetectionInterface detector = new MimeUtilFileTypeDetector(); + + // Private to ensure Singleton status + private FileTypeIdIngestModule() { + } + + // File-level ingest modules are currently singleton -- this is required + public static synchronized FileTypeIdIngestModule getDefault() { + //defaultInstance is a private static class variable + if (defaultInstance == null) { + defaultInstance = new FileTypeIdIngestModule(); + } + return defaultInstance; + } + + + @Override + public void init(IngestModuleInit initContext) { + services = IngestServices.getDefault(); + } + + @Override + public ProcessResult process(PipelineContext pipelineContext, AbstractFile abstractFile) { + // skip non-files + if ((abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || + (abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) { + + return ProcessResult.OK; + } + + if (skipKnown && ((abstractFile.getKnown() == FileKnown.KNOWN) || (abstractFile.getKnown() == FileKnown.BAD))) { + return ProcessResult.OK; + } + + try + { + long startTime = System.currentTimeMillis(); + FileTypeDetectionInterface.FileIdInfo fileId = detector.attemptMatch(abstractFile); + matchTime += (System.currentTimeMillis() - startTime); + numFiles++; + + if (!fileId.type.isEmpty()) { + // add artifact + BlackboardArtifact bart = abstractFile.newArtifact(ARTIFACT_TYPE.TSK_GEN_INFO); + BlackboardAttribute batt = new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID(), MODULE_NAME, fileId.type); + bart.addAttribute(batt); + + services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, ARTIFACT_TYPE.TSK_GEN_INFO, Collections.singletonList(bart))); + } + return ProcessResult.OK; + } catch (TskException ex) { + logger.log(Level.WARNING, "Error matching file signature", ex); + return ProcessResult.ERROR; + } + } + + + @Override + public void complete() { + StringBuilder detailsSb = new StringBuilder(); + //details + detailsSb.append("
").append(columnHeader).append("
"); + + detailsSb.append(""); + + detailsSb.append("\n"); + detailsSb.append("\n"); + detailsSb.append("
"+MODULE_DESCRIPTION+"
Total Processing Time").append(matchTime).append("
Total Files Processed").append(numFiles).append("
"); + + services.postMessage(IngestMessage.createMessage(++messageId, IngestMessage.MessageType.INFO, this, "File Type Id Results", detailsSb.toString())); + } + + @Override + public void stop() { + //do nothing + } + + @Override + public String getName() { + return MODULE_NAME; + } + + @Override + public String getDescription() { + return MODULE_DESCRIPTION; + } + + @Override + public String getVersion() { + return MODULE_VERSION; + } + + @Override + public boolean hasSimpleConfiguration() { + return true; + } + + @Override + public javax.swing.JPanel getSimpleConfiguration(String context) { + if (simpleConfigPanel == null) { + simpleConfigPanel = new FileTypeIdSimpleConfigPanel(); + } + + return simpleConfigPanel; + } + + @Override + public boolean hasBackgroundJobsRunning() { + // we're single threaded... + return false; + } + + public static void setSkipKnown(boolean flag) { + skipKnown = flag; + } +} \ No newline at end of file diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.form b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.form new file mode 100644 index 0000000000..f687142df3 --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.form @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.java b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.java new file mode 100644 index 0000000000..6b3967f562 --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/FileTypeIdSimpleConfigPanel.java @@ -0,0 +1,80 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.filetypeid; + + +/** + * Instances of this class provide a simplified UI for managing the hash sets configuration. + */ +public class FileTypeIdSimpleConfigPanel extends javax.swing.JPanel { + + public FileTypeIdSimpleConfigPanel() { + initComponents(); + customizeComponents(); + } + + private void customizeComponents() { + } + + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + skipKnownCheckBox = new javax.swing.JCheckBox(); + + skipKnownCheckBox.setText(org.openide.util.NbBundle.getMessage(FileTypeIdSimpleConfigPanel.class, "FileTypeIdSimpleConfigPanel.skipKnownCheckBox.text")); // NOI18N + skipKnownCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(FileTypeIdSimpleConfigPanel.class, "FileTypeIdSimpleConfigPanel.skipKnownCheckBox.toolTipText")); // NOI18N + skipKnownCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + skipKnownCheckBoxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(skipKnownCheckBox) + .addContainerGap(174, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(11, 11, 11) + .addComponent(skipKnownCheckBox) + .addContainerGap(175, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void skipKnownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipKnownCheckBoxActionPerformed + FileTypeIdIngestModule.setSkipKnown(skipKnownCheckBox.isSelected()); + }//GEN-LAST:event_skipKnownCheckBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox skipKnownCheckBox; + // End of variables declaration//GEN-END:variables +} diff --git a/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/TikaFileTypeDetector.java b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/TikaFileTypeDetector.java new file mode 100644 index 0000000000..365584db7f --- /dev/null +++ b/FileTypeId/src/org/sleuthkit/autopsy/filetypeid/TikaFileTypeDetector.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.filetypeid; +import org.openide.util.Exceptions; +import org.apache.tika.Tika; + +import org.sleuthkit.datamodel.AbstractFile; + +public class TikaFileTypeDetector implements FileTypeDetectionInterface { + + private static Tika tikaInst = new Tika(); + + @Override + public FileTypeDetectionInterface.FileIdInfo attemptMatch(AbstractFile abstractFile) { + try { + FileTypeDetectionInterface.FileIdInfo ret = new FileTypeDetectionInterface.FileIdInfo(); + final int maxBytesInitial = 100; //how many bytes to read on first pass + byte buffer[] = new byte[maxBytesInitial]; + int len = abstractFile.read(buffer, 0, maxBytesInitial); + + try { + String mimetype = tikaInst.detect(buffer); + + // Remove tika's name out of the general types like msoffice and ooxml + ret.type = mimetype.replace("tika-", ""); + } catch (Exception ex) { + //do nothing + } + + return ret; + + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + return new FileTypeDetectionInterface.FileIdInfo(); + } + } + +} diff --git a/HashDatabase/manifest.mf b/HashDatabase/manifest.mf index ba201a294c..b8c105413c 100644 --- a/HashDatabase/manifest.mf +++ b/HashDatabase/manifest.mf @@ -1,7 +1,7 @@ -Manifest-Version: 1.0 -AutoUpdate-Show-In-Client: true -OpenIDE-Module: org.sleuthkit.autopsy.hashdatabase/3 -OpenIDE-Module-Implementation-Version: 9 -OpenIDE-Module-Layer: org/sleuthkit/autopsy/hashdatabase/layer.xml -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/hashdatabase/Bundle.properties - +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.sleuthkit.autopsy.hashdatabase/3 +OpenIDE-Module-Implementation-Version: 9 +OpenIDE-Module-Layer: org/sleuthkit/autopsy/hashdatabase/layer.xml +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/hashdatabase/Bundle.properties + diff --git a/HashDatabase/nbproject/project.properties b/HashDatabase/nbproject/project.properties index 88f75855cb..e633758f8a 100644 --- a/HashDatabase/nbproject/project.properties +++ b/HashDatabase/nbproject/project.properties @@ -1,6 +1,6 @@ -javac.source=1.7 -javac.compilerargs=-Xlint -Xlint:-serial -license.file=../LICENSE-2.0.txt -nbm.homepage=http://www.sleuthkit.org/autopsy/ -nbm.needs.restart=true -spec.version.base=1.3 +javac.source=1.7 +javac.compilerargs=-Xlint -Xlint:-serial +license.file=../LICENSE-2.0.txt +nbm.homepage=http://www.sleuthkit.org/autopsy/ +nbm.needs.restart=true +spec.version.base=1.3 diff --git a/HashDatabase/nbproject/project.xml b/HashDatabase/nbproject/project.xml index b650bf0ecc..40499aa189 100644 --- a/HashDatabase/nbproject/project.xml +++ b/HashDatabase/nbproject/project.xml @@ -81,10 +81,23 @@ 7.0 + + org.sleuthkit.autopsy.corelibs + + + + 3 + 1.1 + + org.sleuthkit.autopsy.hashdatabase + + openide + org + diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/AddContentToHashDbAction.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/AddContentToHashDbAction.java new file mode 100755 index 0000000000..9326fd61cd --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/AddContentToHashDbAction.java @@ -0,0 +1,162 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.hashdatabase; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import org.openide.util.Utilities; +import org.openide.util.Lookup; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.ingest.IngestConfigurator; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; +import static org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; + +/** + * Instances of this Action allow users to content to a hash database. + */ +final class AddContentToHashDbAction extends AbstractAction implements Presenter.Popup { + private static AddContentToHashDbAction instance; + + /** + * AddContentToHashDbAction is a singleton to support multi-selection of nodes, since + * org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action from a node + * if every node in the nodes array returns a reference to the same action object from + * Node.getActions(boolean). + */ + public static synchronized AddContentToHashDbAction getInstance() { + if (null == instance) { + instance = new AddContentToHashDbAction(); + } + return instance; + } + + private AddContentToHashDbAction() { + } + + @Override + public JMenuItem getPopupPresenter() { + return new AddContentToHashDbMenu(); + } + + @Override + public void actionPerformed(ActionEvent event) { + } + + // Instances of this class are used to implement the a pop up menu for this + // action. + private final class AddContentToHashDbMenu extends JMenu { + private final static String SINGLE_SELECTION_NAME = "Add file to hash database"; + private final static String MULTIPLE_SELECTION_NAME = "Add files to hash database"; + + AddContentToHashDbMenu() { + super(SINGLE_SELECTION_NAME); + + // Disable the menu if file ingest is in progress. + IngestConfigurator ingestConfigurator = Lookup.getDefault().lookup(IngestConfigurator.class); + if (null != ingestConfigurator && ingestConfigurator.isIngestRunning()) { + setEnabled(false); + return; + } + + // Get any AbstractFile objects from the lookup of the currently focused top component. + final Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + if (selectedFiles.isEmpty()) { + setEnabled(false); + return; + } + else if (selectedFiles.size() > 1) { + setText(MULTIPLE_SELECTION_NAME); + } + + // Disable the menu if hashes have not been calculated. + for (AbstractFile file : selectedFiles) { + if (null == file.getMd5Hash()) { + setEnabled(false); + return; + } + } + + // Get the current set of updateable hash databases and add each + // one to the menu as a separate menu item. Selecting a hash database + // adds the selected files to the selected database. + final List hashDatabases = HashDbManager.getInstance().getUpdateableHashSets(); + if (!hashDatabases.isEmpty()) { + for (final HashDb database : hashDatabases) { + JMenuItem databaseItem = add(database.getHashSetName()); + databaseItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addFilesToHashSet(selectedFiles, database); + } + }); + } + } + else { + JMenuItem empty = new JMenuItem("No hash databases configured"); + empty.setEnabled(false); + add(empty); + } + + // Add a "New Hash Set..." menu item. Selecting this item invokes a + // a hash database creation dialog and adds the selected files to the + // the new database. + addSeparator(); + JMenuItem newHashSetItem = new JMenuItem("Create database..."); + newHashSetItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase(); + if (null != hashDb) { + HashDbManager.getInstance().save(); + addFilesToHashSet(selectedFiles, hashDb); + } + } + }); + add(newHashSetItem); + } + + private void addFilesToHashSet(final Collection files, HashDb hashSet) { + for (AbstractFile file : files) { + String md5Hash = file.getMd5Hash(); + if (null != md5Hash) { + try { + hashSet.addHashes(file); + } + catch (TskCoreException ex) { + Logger.getLogger(AddContentToHashDbAction.class.getName()).log(Level.SEVERE, "Error adding to hash database", ex); + JOptionPane.showMessageDialog(null, "Unable to add " + file.getName() + " to the hash database.", "Add to Hash Database Error", JOptionPane.ERROR_MESSAGE); + } + } + else { + JOptionPane.showMessageDialog(null, "Unable to add the " + (files.size() > 1 ? "files" : "file") + " to the hash database. Hashes have not been calculated. Please configure and run an appropriate ingest module.", "Add to Hash Database Error", JOptionPane.ERROR_MESSAGE); + break; + } + } + } + } +} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/Bundle.properties b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/Bundle.properties index a9aead73cb..7e2fa92971 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/Bundle.properties +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/Bundle.properties @@ -1,64 +1,81 @@ -OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=\ - Hash Database ingest module. \n\n\ - The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL database lookup for "known" files) and "bad / interesting" (based on one or more databases supplied by the user).\n\n\ - The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash database configuration. -OpenIDE-Module-Name=HashDatabase -HashDbSimplePanel.knownLabel.text=NSRL Database: -HashDbSimplePanel.notableLabel.text=Known Bad Database(s): -HashDbSimplePanel.knownValLabel.text=- -HashDbSimplePanel.notableValLabel.text=- -HashDbSimplePanel.jLabel1.text=Enable known bad databases for ingest: -HashDbAddDatabaseDialog.cancelButton.text=Cancel -HashDbAddDatabaseDialog.okButton.text=OK -HashDbAddDatabaseDialog.nsrlRadioButton.text=NSRL -HashDbAddDatabaseDialog.knownBadRadioButton.text=Known Bad -HashDbAddDatabaseDialog.databasePathTextField.text= -HashDbAddDatabaseDialog.browseButton.text=Browse -HashDbAddDatabaseDialog.jLabel1.text=Enter the name of the database: -HashDbAddDatabaseDialog.databaseNameTextField.text= -HashDbAddDatabaseDialog.jLabel2.text=Select the type of database: -HashDbAddDatabaseDialog.useForIngestCheckbox.text=Enable for ingest -HashDbAddDatabaseDialog.sendInboxMessagesCheckbox.text=Enable sending messages to inbox during ingest -HashDbSearchPanel.hashTable.columnModel.title0=MD5 Hashes -HashDbSearchPanel.hashTable.columnModel.title3=Title 4 -HashDbSearchPanel.hashTable.columnModel.title2=Title 3 -HashDbSearchPanel.hashTable.columnModel.title1=Title 2 -HashDbSearchPanel.addButton.text=Add Hash -HashDbSearchPanel.hashField.text= -HashDbSearchPanel.hashLabel.text=MD5 hash: -HashDbSearchPanel.searchButton.text=Search -HashDbSearchPanel.removeButton.text=Remove Selected -HashDbSearchPanel.titleLabel.text=Search for files with the following MD5 hash(es): -HashDbSearchPanel.errorField.text=Error: Not all files have been hashed. -HashDbSearchPanel.saveBox.text=Remember Hashes -HashDbSearchPanel.cancelButton.text=Cancel -HashDbSimplePanel.calcHashesButton.text=Calculate hashes even if no hash database is selected -HashDbSimplePanel.nsrlDbLabel.text=NSRL Database: -HashDbSimplePanel.nsrlDbLabelVal.text=- -HashDbManagementPanel.hashDbIndexStatusLabel.text=No database selected -HashDbManagementPanel.jLabel2.text=Name: -HashDbManagementPanel.showInboxMessagesCheckBox.text=Enable sending messages to inbox during ingest -HashDbManagementPanel.useForIngestCheckbox.text=Enable for ingest -HashDbManagementPanel.indexButton.text=Index -HashDbManagementPanel.indexLabel.text=Index Status: -HashDbManagementPanel.optionsLabel.text=Options -HashDbManagementPanel.jLabel4.text=Location: -HashDbManagementPanel.jLabel6.text=Type: -HashDbManagementPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. -HashDbManagementPanel.hashDbTypeLabel.text=No database selected -HashDbManagementPanel.typeLabel.text=Type: -HashDbManagementPanel.deleteButton.text=Delete Database -HashDbManagementPanel.importButton.text=Import Database -HashDbManagementPanel.hashDbNameLabel.text=No database selected -HashDbManagementPanel.nameLabel.text=Name: -HashDbManagementPanel.jButton3.text=Import Database -HashDbManagementPanel.locationLabel.text=Location: -HashDbManagementPanel.hashDbLocationLabel.text=No database selected -HashDbManagementPanel.informationLabel.text=Information -HashDbManagementPanel.hashDatabasesLabel.text=Hash Databases: -OpenIDE-Module-Short-Description=Hash Database Ingest Module and hash db tools -ModalNoButtons.CURRENTLYON_LABEL.text=Currently Indexing x of y -ModalNoButtons.GO_GET_COFFEE_LABEL.text=Hash databases are currently being indexed, this may take some time. -ModalNoButtons.CURRENTDB_LABEL.text=(CurrentDb) -ModalNoButtons.CANCEL_BUTTON.text=Cancel +OpenIDE-Module-Display-Category=Ingest Module +OpenIDE-Module-Long-Description=\ + Hash Database ingest module. \n\n\ + The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL database lookup for "known" files) and "bad / interesting" (based on one or more databases supplied by the user).\n\n\ + The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash database configuration. +OpenIDE-Module-Name=HashDatabase +HashDbSimplePanel.knownLabel.text=NSRL Database: +HashDbSimplePanel.notableLabel.text=Known Bad Database(s): +HashDbSimplePanel.knownValLabel.text=- +HashDbSimplePanel.notableValLabel.text=- +HashDbSearchPanel.hashTable.columnModel.title0=MD5 Hashes +HashDbSearchPanel.hashTable.columnModel.title3=Title 4 +HashDbSearchPanel.hashTable.columnModel.title2=Title 3 +HashDbSearchPanel.hashTable.columnModel.title1=Title 2 +HashDbSearchPanel.addButton.text=Add Hash +HashDbSearchPanel.hashField.text= +HashDbSearchPanel.hashLabel.text=MD5 hash: +HashDbSearchPanel.searchButton.text=Search +HashDbSearchPanel.removeButton.text=Remove Selected +HashDbSearchPanel.titleLabel.text=Search for files with the following MD5 hash(es): +HashDbSearchPanel.errorField.text=Error: Not all files have been hashed. +HashDbSearchPanel.saveBox.text=Remember Hashes +HashDbSearchPanel.cancelButton.text=Cancel +OpenIDE-Module-Short-Description=Hash Database Ingest Module and hash db tools +HashDbImportDatabaseDialog.jLabel1.text=Hash Set Name: +HashDbImportDatabaseDialog.databasePathTextField.text= +HashDbImportDatabaseDialog.knownBadRadioButton.text=Known Bad +HashDbImportDatabaseDialog.jLabel2.text=Type of database: +HashDbImportDatabaseDialog.okButton.text=OK +HashDbImportDatabaseDialog.cancelButton.text=Cancel +HashDbCreateDatabaseDialog.jLabel2.text=Type: +HashDbCreateDatabaseDialog.knownBadRadioButton.text=Known Bad +HashDbCreateDatabaseDialog.cancelButton.text=Cancel +HashDbConfigPanel.nameLabel.text=Hash Set Name: +HashDbConfigPanel.hashDbNameLabel.text=No database selected +HashDbConfigPanel.hashDatabasesLabel.text=Hash Databases: +HashDbConfigPanel.hashDbLocationLabel.text=No database selected +HashDbConfigPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. +HashDbConfigPanel.jButton3.text=Import Database +HashDbConfigPanel.jLabel6.text=Type: +HashDbConfigPanel.jLabel4.text=Location: +HashDbConfigPanel.jLabel2.text=Name: +HashDbConfigPanel.optionsLabel.text=Options +HashDbConfigPanel.typeLabel.text=Type: +HashDbConfigPanel.locationLabel.text=Database Path: +HashDbConfigPanel.hashDbIndexStatusLabel.text=No database selected +HashDbConfigPanel.hashDbTypeLabel.text=No database selected +HashDbConfigPanel.indexButton.text=Index +HashDbConfigPanel.indexLabel.text=Index Status: +HashDbConfigPanel.informationLabel.text=Information +HashDbConfigPanel.importDatabaseButton.text=Import Database +HashDbConfigPanel.deleteDatabaseButton.text=Delete Database +HashDbConfigPanel.indexPathLabelLabel.text=Index Path: +HashDbConfigPanel.indexPathLabel.text=No database selected +ModalNoButtons.CURRENTDB_LABEL.text=(CurrentDb) +ModalNoButtons.CURRENTLYON_LABEL.text=Currently Indexing x of y +ModalNoButtons.GO_GET_COFFEE_LABEL.text=Hash databases are currently being indexed, this may take some time. +ModalNoButtons.cancelButton.text=Cancel +ModalNoButtons.CANCEL_BUTTON.text=Cancel +HashDbSimpleConfigPanel.knownBadHashDbsLabel.text=Enable known bad databases for ingest: +HashDbSimpleConfigPanel.knownHashDbsLabel.text=Enable known hash databases for ingest: +HashDbImportDatabaseDialog.knownRadioButton.text=Known (NSRL or other) +HashDbCreateDatabaseDialog.knownRadioButton.text=Known +HashDbCreateDatabaseDialog.jLabel1.text=Database Path: +HashDbCreateDatabaseDialog.saveAsButton.text=Save As... +HashDbCreateDatabaseDialog.hashSetNameTextField.text= +HashDbImportDatabaseDialog.jLabel3.text=Database Path: +HashDbCreateDatabaseDialog.searchDuringIngestCheckbox.text=Search during ingest +HashDbCreateDatabaseDialog.searchDuringIngestCheckbox.toolTipText= +HashDbCreateDatabaseDialog.sendIngestMessagesCheckbox.text=Send ingest messages +HashDbImportDatabaseDialog.searchDuringIngestCheckbox.text=Search during ingest +HashDbImportDatabaseDialog.sendIngestMessagesCheckbox.text=Send ingest messages +HashDbImportDatabaseDialog.hashSetNameTextField.text= +HashDbConfigPanel.createDatabaseButton.text=Create Database +HashDbImportDatabaseDialog.openButton.text=Open... +HashDbSimpleConfigPanel.alwaysCalcHashesCheckbox.text=Calculate hashes even if no hash database is selected +HashDbCreateDatabaseDialog.jLabel3.text=Hash Set Name: +HashDbCreateDatabaseDialog.okButton.text=OK +HashDbCreateDatabaseDialog.databasePathTextField.text= +HashDbConfigPanel.searchDuringIngestCheckbox.text=Search during ingest +HashDbConfigPanel.sendIngestMessagesCheckBox.text=Send ingest messages diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDatabaseOptionsPanelController.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDatabaseOptionsPanelController.java index 2492db7d5f..102eaa21b1 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDatabaseOptionsPanelController.java +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDatabaseOptionsPanelController.java @@ -36,7 +36,7 @@ id = "HashDatabase") @org.openide.util.NbBundle.Messages({"OptionsCategory_Name_HashDatabase=Hash Database", "OptionsCategory_Keywords_HashDatabase=Hash Database"}) public final class HashDatabaseOptionsPanelController extends OptionsPanelController { - private HashDbManagementPanel panel; + private HashDbConfigPanel panel; private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private boolean changed; @@ -54,8 +54,7 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro @Override public void cancel() { - // Reset the XML on cancel - HashDbXML.getCurrent().reload(); + getPanel().cancel(); } @Override @@ -88,9 +87,9 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro pcs.removePropertyChangeListener(l); } - private HashDbManagementPanel getPanel() { + private HashDbConfigPanel getPanel() { if (panel == null) { - panel = new HashDbManagementPanel(); + panel = new HashDbConfigPanel(); } return panel; } diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDb.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDb.java deleted file mode 100644 index 9a08234eab..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDb.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.hashdatabase; - -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.io.File; -import java.util.List; -import java.util.logging.Level; -import javax.swing.SwingWorker; -import org.netbeans.api.progress.ProgressHandle; -import org.netbeans.api.progress.ProgressHandleFactory; -import org.openide.util.Cancellable; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.SleuthkitJNI; -import org.sleuthkit.datamodel.TskException; - -/** - * Hash database representation of NSRL and Known Bad hash databases - * with indexing capability - * - */ -public class HashDb implements Comparable { - - enum EVENT {INDEXING_DONE }; - private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); - - - public enum DBType{ - NSRL("NSRL"), KNOWN_BAD("Known Bad"); - - private String displayName; - - private DBType(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return this.displayName; - } - } - - // Suffix added to the end of a database name to get its index file - private static final String INDEX_SUFFIX = "-md5.idx"; - - private String name; - private List databasePaths; // TODO: Length limited to one for now... - private boolean useForIngest; - private boolean showInboxMessages; - private boolean indexing; - private DBType type; - - public HashDb(String name, List databasePaths, boolean useForIngest, boolean showInboxMessages, DBType type) { - this.name = name; - this.databasePaths = databasePaths; - this.useForIngest = useForIngest; - this.showInboxMessages = showInboxMessages; - this.type = type; - this.indexing = false; - } - - void addPropertyChangeListener(PropertyChangeListener pcl) { - pcs.addPropertyChangeListener(pcl); - } - - void removePropertyChangeListener(PropertyChangeListener pcl) { - pcs.removePropertyChangeListener(pcl); - } - - boolean getUseForIngest() { - return useForIngest; - } - - boolean getShowInboxMessages() { - return showInboxMessages; - } - - DBType getDbType() { - return type; - } - - String getName() { - return name; - } - - List getDatabasePaths() { - return databasePaths; - } - - void setUseForIngest(boolean useForIngest) { - this.useForIngest = useForIngest; - } - - void setShowInboxMessages(boolean showInboxMessages) { - this.showInboxMessages = showInboxMessages; - } - - void setName(String name) { - this.name = name; - } - - void setDatabasePaths(List databasePaths) { - this.databasePaths = databasePaths; - } - - void setDbType(DBType type) { - this.type = type; - } - - /** - * Checks if the database exists. - * @return true if a file exists at the database path, else false - */ - boolean databaseExists() { - return databaseFile().exists(); - } - - /** - * Checks if Sleuth Kit can open the index for the database path. - * @return true if the index was found and opened successfully, else false - */ - boolean indexExists() { - try { - return hasIndex(databasePaths.get(0)); // TODO: support multiple paths - } catch (TskException ex) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Error checking if index exists.", ex); - return false; - } - } - - /** - * Gets the database file. - * @return a File initialized with the database path - */ - File databaseFile() { - return new File(databasePaths.get(0)); // TODO: support multiple paths - } - - /** - * Gets the index file - * @return a File initialized with an index path derived from the database - * path - */ - File indexFile() { - return new File(toIndexPath(databasePaths.get(0))); // TODO: support multiple paths - } - - /** - * Checks if the index file is older than the database file - * @return true if there is are files at the index path and the database - * path, and the index file has an older modified-time than the database - * file, else false - */ - boolean isOutdated() { - File i = indexFile(); - File db = databaseFile(); - - return i.exists() && db.exists() && isOlderThan(i, db); - } - - /** - * Checks if the database is being indexed - */ - boolean isIndexing() { - return indexing; - } - - /** - * Returns the status of the HashDb as determined from indexExists(), - * databaseExists(), and isOutdated() - * @return IndexStatus enum according to their definitions - */ - IndexStatus status() { - boolean i = this.indexExists(); - boolean db = this.databaseExists(); - - if(indexing) - return IndexStatus.INDEXING; - if (i) { - if (db) { - return this.isOutdated() ? IndexStatus.INDEX_OUTDATED : IndexStatus.INDEX_CURRENT; - } else { - return IndexStatus.NO_DB; - } - } else { - return db ? IndexStatus.NO_INDEX : IndexStatus.NONE; - } - } - - /** - * Tries to index the database (overwrites any existing index) - * @throws TskException if an error occurs in the SleuthKit bindings - */ - void createIndex() throws TskException { - indexing = true; - CreateIndex creator = new CreateIndex(); - creator.execute(); - } - - /** - * Checks if one file is older than an other - * @param a first file - * @param b second file - * @return true if the first file's last modified data is before the second - * file's last modified date - */ - private static boolean isOlderThan(File a, File b) { - return a.lastModified() < b.lastModified(); - } - - /** - * Determines if a path points to an index by checking the suffix - * @param path - * @return true if index - */ - static boolean isIndexPath(String path) { - return path.endsWith(INDEX_SUFFIX); - } - - /** - * Derives database path from an image path by removing the suffix. - * @param indexPath - * @return - */ - static String toDatabasePath(String indexPath) { - return indexPath.substring(0, indexPath.lastIndexOf(INDEX_SUFFIX)); - } - - /** - * Derives image path from an database path by appending the suffix. - * @param databasePath - * @return - */ - static String toIndexPath(String databasePath) { - return databasePath.concat(INDEX_SUFFIX); - } - - /** - * Calls Sleuth Kit method via JNI to determine whether there is an - * index for the given path - * @param databasePath path Path for the database the index is of - * (database doesn't have to actually exist)' - * @return true if index exists - * @throws TskException if there is an error in the JNI call - */ - static boolean hasIndex(String databasePath) throws TskException { - return SleuthkitJNI.lookupIndexExists(databasePath); - } - - @Override - public int compareTo(HashDb o) { - return this.name.compareTo(o.name); - } - - /* Thread that creates a database's index */ - private class CreateIndex extends SwingWorker { - - private ProgressHandle progress; - - CreateIndex(){}; - - @Override - protected Object doInBackground() throws Exception { - progress = ProgressHandleFactory.createHandle("Indexing " + name); - - /** We need proper cancel support in TSK to make the task cancellable - new Cancellable() { - Override - public boolean cancel() { - return CreateIndex.this.cancel(true); - } - }); - */ - progress.start(); - progress.switchToIndeterminate(); - SleuthkitJNI.createLookupIndex(databasePaths.get(0)); - return null; - } - - /* clean up or start the worker threads */ - @Override - protected void done() { - indexing = false; - progress.finish(); - pcs.firePropertyChange(EVENT.INDEXING_DONE.toString(), null, name); - } - } -} \ No newline at end of file diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.java deleted file mode 100644 index cb635520fe..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.hashdatabase; - -import java.awt.Dimension; -import java.awt.Toolkit; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; -import org.sleuthkit.autopsy.hashdatabase.HashDb.DBType; -import org.sleuthkit.datamodel.SleuthkitJNI; -import org.sleuthkit.datamodel.TskException; - -/** - * - * @author dfickling - */ -final class HashDbAddDatabaseDialog extends javax.swing.JDialog { - - private JFileChooser fc = new JFileChooser(); - private String databaseName; - private static final Logger logger = Logger.getLogger(HashDbAddDatabaseDialog.class.getName()); - /** - * Creates new form HashDbAddDatabaseDialog - */ - HashDbAddDatabaseDialog() { - super(new javax.swing.JFrame(), "Add Hash Database", true); - setResizable(false); - initComponents(); - customizeComponents(); - } - - void customizeComponents() { - fc.setDragEnabled(false); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - String[] EXTENSION = new String[] { "txt", "idx", "hash", "Hash", "hsh"}; - FileNameExtensionFilter filter = new FileNameExtensionFilter( - "Hash Database File", EXTENSION); - fc.setFileFilter(filter); - fc.setMultiSelectionEnabled(false); - } - - String display() { - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - - // set the popUp window / JFrame - int w = this.getSize().width; - int h = this.getSize().height; - - // set the location of the popUp Window on the center of the screen - setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2); - - this.setVisible(true); - return databaseName; - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - buttonGroup1 = new javax.swing.ButtonGroup(); - okButton = new javax.swing.JButton(); - cancelButton = new javax.swing.JButton(); - databasePathTextField = new javax.swing.JTextField(); - browseButton = new javax.swing.JButton(); - nsrlRadioButton = new javax.swing.JRadioButton(); - knownBadRadioButton = new javax.swing.JRadioButton(); - jLabel1 = new javax.swing.JLabel(); - databaseNameTextField = new javax.swing.JTextField(); - jLabel2 = new javax.swing.JLabel(); - useForIngestCheckbox = new javax.swing.JCheckBox(); - sendInboxMessagesCheckbox = new javax.swing.JCheckBox(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - - org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.okButton.text")); // NOI18N - okButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - okButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.cancelButton.text")); // NOI18N - cancelButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cancelButtonActionPerformed(evt); - } - }); - - databasePathTextField.setText(org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.databasePathTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.browseButton.text")); // NOI18N - browseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - browseButtonActionPerformed(evt); - } - }); - - buttonGroup1.add(nsrlRadioButton); - org.openide.awt.Mnemonics.setLocalizedText(nsrlRadioButton, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.nsrlRadioButton.text")); // NOI18N - nsrlRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - nsrlRadioButtonActionPerformed(evt); - } - }); - - buttonGroup1.add(knownBadRadioButton); - knownBadRadioButton.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(knownBadRadioButton, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.knownBadRadioButton.text")); // NOI18N - knownBadRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - knownBadRadioButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.jLabel1.text")); // NOI18N - - databaseNameTextField.setText(org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.databaseNameTextField.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.jLabel2.text")); // NOI18N - - useForIngestCheckbox.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(useForIngestCheckbox, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.useForIngestCheckbox.text")); // NOI18N - useForIngestCheckbox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - useForIngestCheckboxActionPerformed(evt); - } - }); - - sendInboxMessagesCheckbox.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(sendInboxMessagesCheckbox, org.openide.util.NbBundle.getMessage(HashDbAddDatabaseDialog.class, "HashDbAddDatabaseDialog.sendInboxMessagesCheckbox.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(okButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(databasePathTextField) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(databaseNameTextField)) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel2) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - .addGroup(layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(knownBadRadioButton) - .addComponent(nsrlRadioButton)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(useForIngestCheckbox) - .addComponent(sendInboxMessagesCheckbox)) - .addGap(0, 135, Short.MAX_VALUE)))) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(databasePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(browseButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(databaseNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(nsrlRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(knownBadRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(useForIngestCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(sendInboxMessagesCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(okButton) - .addComponent(cancelButton)) - .addContainerGap()) - ); - - pack(); - }// //GEN-END:initComponents - - private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed - String oldText = databasePathTextField.getText(); - // set the current directory of the FileChooser if the databasePath Field is valid - File currentDir = new File(oldText); - if (currentDir.exists()) { - fc.setCurrentDirectory(currentDir); - } - int retval = fc.showOpenDialog(this); - if (retval == JFileChooser.APPROVE_OPTION) { - File f = fc.getSelectedFile(); - try { - String filePath = f.getCanonicalPath(); - if (HashDb.isIndexPath(filePath)) { - filePath = HashDb.toDatabasePath(filePath); - } - String derivedName = SleuthkitJNI.getDatabaseName(filePath); - databasePathTextField.setText(filePath); - databaseNameTextField.setText(derivedName); - if (derivedName.toLowerCase().contains("nsrl")) { - nsrlRadioButton.setSelected(true); - nsrlRadioButtonActionPerformed(null); - } - } catch (IOException ex) { - logger.log(Level.WARNING, "Couldn't get selected file path.", ex); - } catch (TskException ex) { - logger.log(Level.WARNING, "Invalid database: ", ex); - int tryAgain = JOptionPane.showConfirmDialog(this, "Database file you chose cannot be opened.\n" + "If it was just an index, please try to recreate it from the database.\n" + "Would you like to choose another database?", "Invalid File", JOptionPane.YES_NO_OPTION); - if (tryAgain == JOptionPane.YES_OPTION) { - browseButtonActionPerformed(evt); - } - } - } - }//GEN-LAST:event_browseButtonActionPerformed - - private void nsrlRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nsrlRadioButtonActionPerformed - sendInboxMessagesCheckbox.setSelected(false); - sendInboxMessagesCheckbox.setEnabled(false); - }//GEN-LAST:event_nsrlRadioButtonActionPerformed - - private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed - sendInboxMessagesCheckbox.setSelected(true); - sendInboxMessagesCheckbox.setEnabled(true); - }//GEN-LAST:event_knownBadRadioButtonActionPerformed - - private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - this.dispose(); - }//GEN-LAST:event_cancelButtonActionPerformed - - private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - if(databasePathTextField.getText().isEmpty()) { - JOptionPane.showMessageDialog(this, "Database path cannot be empty"); - return; - } - if(databaseNameTextField.getText().isEmpty()) { - JOptionPane.showMessageDialog(this, "Database name cannot be empty"); - return; - } - try { - File db = new File(databasePathTextField.getText()); - File idx = new File(databasePathTextField.getText() + "-md5.idx"); - if (!db.exists() && !idx.exists()) { - JOptionPane.showMessageDialog(this, "Selected file does not exist"); - return; - } - String path = db.getCanonicalPath(); - SleuthkitJNI.getDatabaseName(path); - } catch (Exception ex) { - JOptionPane.showMessageDialog(this, "Database file you chose cannot be opened.\n" + "If it was just an index, please try to recreate it from the database"); - return; - } - DBType type; - if(nsrlRadioButton.isSelected()) { - type = DBType.NSRL; - } else { - type = DBType.KNOWN_BAD; - } - HashDb db = new HashDb(databaseNameTextField.getText(), - Arrays.asList(new String[] {databasePathTextField.getText()}), - useForIngestCheckbox.isSelected(), - sendInboxMessagesCheckbox.isSelected(), - type); - if(type == DBType.KNOWN_BAD) { - HashDbXML.getCurrent().addKnownBadSet(db); - } else if(type == DBType.NSRL) { - HashDbXML.getCurrent().setNSRLSet(db); - } - databaseName = databaseNameTextField.getText(); - this.dispose(); - }//GEN-LAST:event_okButtonActionPerformed - - private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useForIngestCheckboxActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_useForIngestCheckboxActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton browseButton; - private javax.swing.ButtonGroup buttonGroup1; - private javax.swing.JButton cancelButton; - private javax.swing.JTextField databaseNameTextField; - private javax.swing.JTextField databasePathTextField; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JRadioButton knownBadRadioButton; - private javax.swing.JRadioButton nsrlRadioButton; - private javax.swing.JButton okButton; - private javax.swing.JCheckBox sendInboxMessagesCheckbox; - private javax.swing.JCheckBox useForIngestCheckbox; - // End of variables declaration//GEN-END:variables -} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.form b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.form similarity index 67% rename from HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.form rename to HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.form index ab3578fe41..a56c3f2b57 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.form +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.form @@ -5,21 +5,21 @@ - + - + - + @@ -29,7 +29,7 @@ - + @@ -60,63 +60,75 @@ - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + @@ -140,44 +152,51 @@ - - - - - - + - + - - + + - + + + + + + + + + + + + + - - - + - + - - + + + + @@ -189,7 +208,7 @@ - + @@ -223,13 +242,13 @@ - + - + @@ -242,16 +261,16 @@ - + - + - + @@ -264,76 +283,76 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -341,37 +360,37 @@ - + - + - + - + - + - + - + - + @@ -379,5 +398,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.java new file mode 100644 index 0000000000..4942ce9b86 --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbConfigPanel.java @@ -0,0 +1,810 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.hashdatabase; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Frame; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb.KnownFilesType; + +/** + * Instances of this class provide a comprehensive UI for managing the hash sets configuration. + */ +public final class HashDbConfigPanel extends javax.swing.JPanel implements OptionsPanel { + private static final String NO_SELECTION_TEXT = "No database selected"; + private static final String ERROR_GETTING_PATH_TEXT = "Error occurred getting path"; + private static final String ERROR_GETTING_INDEX_STATUS_TEXT = "Error occurred getting status"; + private static final String LEGACY_INDEX_FILE_EXTENSION = "-md5.idx"; + private HashDbManager hashSetManager = HashDbManager.getInstance(); + private HashSetTableModel hashSetTableModel = new HashSetTableModel(); + + public HashDbConfigPanel() { + initComponents(); + customizeComponents(); + updateComponentsForNoSelection(); + + // Listen to the ingest modules to refresh the enabled/disabled state of + // the components in sync with file ingest. + IngestManager.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (isFileIngestStatusChangeEvent(evt)) { + updateComponents(); + } + } + }); + } + + private void customizeComponents() { + setName("Hash Set Configuration"); + this.ingestWarningLabel.setVisible(false); + this.hashSetTable.setModel(hashSetTableModel); + this.hashSetTable.setTableHeader(null); + hashSetTable.getParent().setBackground(hashSetTable.getBackground()); + hashSetTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + hashSetTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + updateComponents(); + } + } + }); + } + + private void updateComponents() { + HashDb db = ((HashSetTable)hashSetTable).getSelection(); + if (db != null) { + updateComponentsForSelection(db); + } + else { + updateComponentsForNoSelection(); + } + } + + private void updateComponentsForNoSelection() { + boolean ingestIsRunning = IngestManager.getDefault().isIngestRunning(); + + // Update descriptive labels. + hashDbNameLabel.setText(NO_SELECTION_TEXT); + hashDbTypeLabel.setText(NO_SELECTION_TEXT); + hashDbLocationLabel.setText(NO_SELECTION_TEXT); + indexPathLabel.setText(NO_SELECTION_TEXT); + + // Update indexing components. + hashDbIndexStatusLabel.setText(NO_SELECTION_TEXT); + hashDbIndexStatusLabel.setForeground(Color.black); + indexButton.setText("Index"); + indexButton.setEnabled(false); + + // Update ingest options. + searchDuringIngestCheckbox.setSelected(false); + searchDuringIngestCheckbox.setEnabled(false); + sendIngestMessagesCheckBox.setSelected(false); + sendIngestMessagesCheckBox.setEnabled(false); + optionsLabel.setEnabled(false); + optionsSeparator.setEnabled(false); + + // Update database action buttons. + createDatabaseButton.setEnabled(true); + importDatabaseButton.setEnabled(true); + deleteDatabaseButton.setEnabled(false); + + // Update ingest in progress warning label. + ingestWarningLabel.setVisible(ingestIsRunning); + } + + private void updateComponentsForSelection(HashDb db) { + boolean ingestIsRunning = IngestManager.getDefault().isIngestRunning(); + + // Update descriptive labels. + hashDbNameLabel.setText(db.getHashSetName()); + hashDbTypeLabel.setText(db.getKnownFilesType().getDisplayName()); + + try { + hashDbLocationLabel.setText(shortenPath(db.getDatabasePath())); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbConfigPanel.class.getName()).log(Level.SEVERE, "Error getting database path of " + db.getHashSetName() + " hash database", ex); + hashDbLocationLabel.setText(ERROR_GETTING_PATH_TEXT); + } + + try { + indexPathLabel.setText(shortenPath(db.getIndexPath())); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbConfigPanel.class.getName()).log(Level.SEVERE, "Error getting index path of " + db.getHashSetName() + " hash database", ex); + indexPathLabel.setText(ERROR_GETTING_PATH_TEXT); + } + + // Update indexing components. + try { + if (db.isIndexing()) { + indexButton.setText("Indexing"); + hashDbIndexStatusLabel.setText("Index is currently being generated"); + hashDbIndexStatusLabel.setForeground(Color.black); + indexButton.setEnabled(false); + } + else if (db.hasLookupIndex()) { + if (db.hasIndexOnly()) { + hashDbIndexStatusLabel.setText("Index only"); + } + else if (db.getIndexPath().endsWith(LEGACY_INDEX_FILE_EXTENSION)) { + hashDbIndexStatusLabel.setText("Indexed (old format)"); + } + else { + hashDbIndexStatusLabel.setText("Indexed"); + } + hashDbIndexStatusLabel.setForeground(Color.black); + if (db.canBeReIndexed()) { + indexButton.setText("Re-Index"); + indexButton.setEnabled(true); + } + else { + indexButton.setText("Index"); + indexButton.setEnabled(false); + } + } + else { + hashDbIndexStatusLabel.setText("No index"); + hashDbIndexStatusLabel.setForeground(Color.red); + indexButton.setText("Index"); + indexButton.setEnabled(true); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbConfigPanel.class.getName()).log(Level.SEVERE, "Error getting index state of hash database", ex); + hashDbIndexStatusLabel.setText(ERROR_GETTING_INDEX_STATUS_TEXT); + hashDbIndexStatusLabel.setForeground(Color.red); + indexButton.setText("Index"); + indexButton.setEnabled(false); + } + + // Disable the indexing button if ingest is in progress. + if (ingestIsRunning) { + indexButton.setEnabled(false); + } + + // Update ingest option components. + searchDuringIngestCheckbox.setSelected(db.getSearchDuringIngest()); + searchDuringIngestCheckbox.setEnabled(!ingestIsRunning); + sendIngestMessagesCheckBox.setSelected(db.getSendIngestMessages()); + sendIngestMessagesCheckBox.setEnabled(!ingestIsRunning && db.getSearchDuringIngest() && db.getKnownFilesType().equals(KnownFilesType.KNOWN_BAD)); + optionsLabel.setEnabled(!ingestIsRunning); + optionsSeparator.setEnabled(!ingestIsRunning); + + // Update database action buttons. + createDatabaseButton.setEnabled(true); + importDatabaseButton.setEnabled(true); + deleteDatabaseButton.setEnabled(!ingestIsRunning); + + // Update ingest in progress warning label. + ingestWarningLabel.setVisible(ingestIsRunning); + } + + private static String shortenPath(String path) { + String shortenedPath = path; + if (shortenedPath.length() > 50){ + shortenedPath = shortenedPath.substring(0, 10 + shortenedPath.substring(10).indexOf(File.separator) + 1) + "..." + shortenedPath.substring((shortenedPath.length() - 20) + shortenedPath.substring(shortenedPath.length() - 20).indexOf(File.separator)); + } + return shortenedPath; + } + + private boolean isFileIngestStatusChangeEvent(PropertyChangeEvent evt) { + return evt.getPropertyName().equals(IngestManager.IngestModuleEvent.STARTED.toString()) || evt.getPropertyName().equals(IngestManager.IngestModuleEvent.COMPLETED.toString()) || evt.getPropertyName().equals(IngestManager.IngestModuleEvent.STOPPED.toString()); + } + + @Override + public void load() { + hashSetTable.clearSelection(); + hashSetTableModel.refreshModel(); + } + + @Override + public void store() { + //Checking for for any unindexed databases + List unindexed = new ArrayList<>(); + for (HashDb hashSet : hashSetManager.getAllHashSets()) { + try { + if (!hashSet.hasLookupIndex()) { + unindexed.add(hashSet); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbConfigPanel.class.getName()).log(Level.SEVERE, "Error getting index info for hash database", ex); + } + } + + //If unindexed ones are found, show a popup box that will either index them, or remove them. + if (unindexed.size() == 1){ + showInvalidIndex(false, unindexed); + } + else if (unindexed.size() > 1){ + showInvalidIndex(true, unindexed); + } + + hashSetManager.save(); + } + + public void cancel() { + HashDbManager.getInstance().loadLastSavedConfiguration(); + } + + void removeThese(List toRemove) { + for (HashDb hashDb : toRemove) { + hashSetManager.removeHashDatabase(hashDb); + } + hashSetTableModel.refreshModel(); + } + + /** + * Displays the popup box that tells user that some of his databases are unindexed, along with solutions. + * This method is related to ModalNoButtons, to be removed at a later date. + * @param plural Whether or not there are multiple unindexed databases + * @param unindexed The list of unindexed databases. Can be of size 1. + */ + private void showInvalidIndex(boolean plural, List unindexed){ + String total = ""; + String message; + for(HashDb hdb : unindexed){ + total+= "\n" + hdb.getHashSetName(); + } + if(plural){ + message = "The following databases are not indexed, would you like to index them now? \n " + total; + } + else{ + message = "The following database is not indexed, would you like to index it now? \n" + total; + } + int res = JOptionPane.showConfirmDialog(this, message, "Unindexed databases", JOptionPane.YES_NO_OPTION); + if(res == JOptionPane.YES_OPTION){ + ModalNoButtons indexingDialog = new ModalNoButtons(this, new Frame(),unindexed); + indexingDialog.setLocationRelativeTo(null); + indexingDialog.setVisible(true); + indexingDialog.setModal(true); + hashSetTableModel.refreshModel(); + } + if(res == JOptionPane.NO_OPTION){ + JOptionPane.showMessageDialog(this, "All unindexed databases will be removed the list"); + removeThese(unindexed); + } + } + + boolean valid() { + return true; + } + + /** + * This class implements a table for displaying configured hash sets. + */ + private class HashSetTable extends JTable { + @Override + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + // Use the hash set name as the cell text. + JComponent cellRenderer = (JComponent)super.prepareRenderer(renderer, row, column); + cellRenderer.setToolTipText((String)getValueAt(row, column)); + + // Give the user a visual indication of any hash sets with a hash + // database that needs to be indexed by displaying the hash set name + // in red. + if (hashSetTableModel.indexExists(row)){ + cellRenderer.setForeground(Color.black); + } + else{ + cellRenderer.setForeground(Color.red); + } + + return cellRenderer; + } + + public HashDb getSelection() { + return hashSetTableModel.getHashSetAt(getSelectionModel().getMinSelectionIndex()); + } + + public void setSelection(int index) { + if (index >= 0 && index < hashSetTable.getRowCount()) { + getSelectionModel().setSelectionInterval(index, index); + } + } + + public void selectRowByName(String name) { + setSelection(hashSetTableModel.getIndexByName(name)); + } + } + + /** + * This class implements the table model for the table used to display + * configured hash sets. + */ + private class HashSetTableModel extends AbstractTableModel { + List hashSets = HashDbManager.getInstance().getAllHashSets(); + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public int getRowCount() { + return hashSets.size(); + } + + @Override + public String getColumnName(int column) { + return "Name"; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return hashSets.get(rowIndex).getHashSetName(); + } + + private boolean indexExists(int rowIndex){ + try { + return hashSets.get(rowIndex).hasLookupIndex(); + } + catch (TskCoreException ex) { + Logger.getLogger(HashSetTableModel.class.getName()).log(Level.SEVERE, "Error getting index info for hash database", ex); + return false; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + throw new UnsupportedOperationException("Editing of cells is not supported"); + } + + @Override + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + + HashDb getHashSetAt(int index) { + if (!hashSets.isEmpty() && index >= 0 && index < hashSets.size()) { + return hashSets.get(index); + } + else { + return null; + } + } + + int getIndexByName(String name) { + for (int i = 0; i < hashSets.size(); ++i) { + if (hashSets.get(i).getHashSetName().equals(name)) { + return i; + } + } + return -1; + } + + void refreshModel() { + hashSets = HashDbManager.getInstance().getAllHashSets(); + refreshDisplay(); + } + + void refreshDisplay() { + fireTableDataChanged(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel2 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + jLabel6 = new javax.swing.JLabel(); + jButton3 = new javax.swing.JButton(); + ingestWarningLabel = new javax.swing.JLabel(); + jScrollPane1 = new javax.swing.JScrollPane(); + hashSetTable = new HashSetTable(); + deleteDatabaseButton = new javax.swing.JButton(); + importDatabaseButton = new javax.swing.JButton(); + hashDatabasesLabel = new javax.swing.JLabel(); + nameLabel = new javax.swing.JLabel(); + hashDbNameLabel = new javax.swing.JLabel(); + hashDbLocationLabel = new javax.swing.JLabel(); + locationLabel = new javax.swing.JLabel(); + typeLabel = new javax.swing.JLabel(); + hashDbTypeLabel = new javax.swing.JLabel(); + hashDbIndexStatusLabel = new javax.swing.JLabel(); + indexLabel = new javax.swing.JLabel(); + indexButton = new javax.swing.JButton(); + searchDuringIngestCheckbox = new javax.swing.JCheckBox(); + sendIngestMessagesCheckBox = new javax.swing.JCheckBox(); + informationLabel = new javax.swing.JLabel(); + optionsLabel = new javax.swing.JLabel(); + informationSeparator = new javax.swing.JSeparator(); + optionsSeparator = new javax.swing.JSeparator(); + createDatabaseButton = new javax.swing.JButton(); + indexPathLabelLabel = new javax.swing.JLabel(); + indexPathLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.jLabel2.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.jLabel4.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.jLabel6.text")); // NOI18N + + jButton3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.jButton3.text")); // NOI18N + + setMinimumSize(new java.awt.Dimension(700, 500)); + setPreferredSize(new java.awt.Dimension(700, 500)); + + ingestWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(ingestWarningLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.ingestWarningLabel.text")); // NOI18N + + hashSetTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + + } + )); + hashSetTable.setShowHorizontalLines(false); + hashSetTable.setShowVerticalLines(false); + hashSetTable.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyPressed(java.awt.event.KeyEvent evt) { + hashSetTableKeyPressed(evt); + } + }); + jScrollPane1.setViewportView(hashSetTable); + + deleteDatabaseButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/delete16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteDatabaseButton, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.deleteDatabaseButton.text")); // NOI18N + deleteDatabaseButton.setMaximumSize(new java.awt.Dimension(140, 25)); + deleteDatabaseButton.setMinimumSize(new java.awt.Dimension(140, 25)); + deleteDatabaseButton.setPreferredSize(new java.awt.Dimension(140, 25)); + deleteDatabaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteDatabaseButtonActionPerformed(evt); + } + }); + + importDatabaseButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/import16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(importDatabaseButton, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.importDatabaseButton.text")); // NOI18N + importDatabaseButton.setMaximumSize(new java.awt.Dimension(140, 25)); + importDatabaseButton.setMinimumSize(new java.awt.Dimension(140, 25)); + importDatabaseButton.setPreferredSize(new java.awt.Dimension(140, 25)); + importDatabaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + importDatabaseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(hashDatabasesLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.hashDatabasesLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.nameLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(hashDbNameLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.hashDbNameLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(hashDbLocationLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.hashDbLocationLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(locationLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.locationLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(typeLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.typeLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(hashDbTypeLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.hashDbTypeLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(hashDbIndexStatusLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.hashDbIndexStatusLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(indexLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.indexLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(indexButton, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.indexButton.text")); // NOI18N + indexButton.setEnabled(false); + indexButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + indexButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(searchDuringIngestCheckbox, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.searchDuringIngestCheckbox.text")); // NOI18N + searchDuringIngestCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchDuringIngestCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(sendIngestMessagesCheckBox, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.sendIngestMessagesCheckBox.text")); // NOI18N + sendIngestMessagesCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + sendIngestMessagesCheckBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(informationLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.informationLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.optionsLabel.text")); // NOI18N + + createDatabaseButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/new16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(createDatabaseButton, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.createDatabaseButton.text")); // NOI18N + createDatabaseButton.setMaximumSize(new java.awt.Dimension(140, 25)); + createDatabaseButton.setMinimumSize(new java.awt.Dimension(140, 25)); + createDatabaseButton.setPreferredSize(new java.awt.Dimension(140, 25)); + createDatabaseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createDatabaseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(indexPathLabelLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.indexPathLabelLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(indexPathLabel, org.openide.util.NbBundle.getMessage(HashDbConfigPanel.class, "HashDbConfigPanel.indexPathLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(informationLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(informationSeparator)) + .addComponent(ingestWarningLabel) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(locationLabel) + .addComponent(indexButton, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(typeLabel) + .addComponent(indexLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(indexPathLabelLabel)) + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(hashDbTypeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 241, Short.MAX_VALUE) + .addComponent(hashDbLocationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(indexPathLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(hashDbIndexStatusLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(nameLabel) + .addGap(53, 53, 53) + .addComponent(hashDbNameLabel)) + .addComponent(searchDuringIngestCheckbox) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(sendIngestMessagesCheckBox))) + .addGap(0, 0, Short.MAX_VALUE)))) + .addGroup(layout.createSequentialGroup() + .addComponent(optionsLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(optionsSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 324, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(hashDatabasesLabel) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(deleteDatabaseButton, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(createDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 137, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(importDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE))) + .addGap(24, 24, 24)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(hashDatabasesLabel) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(informationLabel) + .addGroup(layout.createSequentialGroup() + .addGap(7, 7, 7) + .addComponent(informationSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 3, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(7, 7, 7) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nameLabel) + .addComponent(hashDbNameLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(typeLabel) + .addComponent(hashDbTypeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(locationLabel) + .addComponent(hashDbLocationLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(indexPathLabelLabel) + .addComponent(indexPathLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(indexLabel) + .addComponent(hashDbIndexStatusLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(indexButton) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(optionsLabel) + .addComponent(optionsSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addComponent(searchDuringIngestCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(sendIngestMessagesCheckBox) + .addGap(18, 18, 18) + .addComponent(ingestWarningLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 391, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(importDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(createDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deleteDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void indexButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_indexButtonActionPerformed + final HashDb hashDb = ((HashSetTable)hashSetTable).getSelection(); + assert hashDb != null; + + // Add a listener for the INDEXING_DONE event. This listener will update + // the UI. + hashDb.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(HashDb.Event.INDEXING_DONE.toString())) { + HashDb selectedHashDb = ((HashSetTable)hashSetTable).getSelection(); + if (selectedHashDb != null && hashDb != null && hashDb.equals(selectedHashDb)) { + updateComponents(); + } + hashSetTableModel.refreshDisplay(); + } + } + }); + + // Display a modal dialog box to kick off the indexing on a worker thread + // and try to persuade the user to wait for the indexing task to finish. + // TODO: If the user waits, this defeats the purpose of doing the indexing on a worker thread. + // But if the user cancels the dialog, other operations on the database + // may be attempted when it is not in a suitable state. + ModalNoButtons indexDialog = new ModalNoButtons(this, new Frame(), hashDb); + indexDialog.setLocationRelativeTo(null); + indexDialog.setVisible(true); + indexDialog.setModal(true); + }//GEN-LAST:event_indexButtonActionPerformed + + private void deleteDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteDatabaseButtonActionPerformed + if (JOptionPane.showConfirmDialog(null, "This will remove the hash database for all cases. Do you want to proceed? ", "Delete Hash Database from Configuration", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { + HashDb hashDb = ((HashSetTable)hashSetTable).getSelection(); + if (hashDb != null) { + hashSetManager.removeHashDatabase(hashDb); + hashSetTableModel.refreshModel(); + } + } + }//GEN-LAST:event_deleteDatabaseButtonActionPerformed + + private void hashSetTableKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_hashSetTableKeyPressed + if (evt.getKeyCode() == KeyEvent.VK_DELETE) { + HashDb hashDb = ((HashSetTable)hashSetTable).getSelection(); + if (hashDb != null) { + hashSetManager.removeHashDatabase(hashDb); + hashSetTableModel.refreshModel(); + } + } + }//GEN-LAST:event_hashSetTableKeyPressed + + private void searchDuringIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchDuringIngestCheckboxActionPerformed + HashDb hashDb = ((HashSetTable)hashSetTable).getSelection(); + if (hashDb != null) { + hashDb.setSearchDuringIngest(searchDuringIngestCheckbox.isSelected()); + if (!searchDuringIngestCheckbox.isSelected()) { + sendIngestMessagesCheckBox.setSelected(false); + } + hashDb.setSendIngestMessages(sendIngestMessagesCheckBox.isSelected()); + } + }//GEN-LAST:event_searchDuringIngestCheckboxActionPerformed + + private void sendIngestMessagesCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sendIngestMessagesCheckBoxActionPerformed + HashDb hashDb = ((HashSetTable)hashSetTable).getSelection(); + if (hashDb != null) { + hashDb.setSendIngestMessages(sendIngestMessagesCheckBox.isSelected()); + } + }//GEN-LAST:event_sendIngestMessagesCheckBoxActionPerformed + + private void importDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importDatabaseButtonActionPerformed + HashDb hashDb = new HashDbImportDatabaseDialog().getHashDatabase(); + if (null != hashDb) { + hashSetTableModel.refreshModel(); + ((HashSetTable)hashSetTable).selectRowByName(hashDb.getHashSetName()); + } + }//GEN-LAST:event_importDatabaseButtonActionPerformed + + private void createDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createDatabaseButtonActionPerformed + HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase(); + if (null != hashDb) { + hashSetTableModel.refreshModel(); + ((HashSetTable)hashSetTable).selectRowByName(hashDb.getHashSetName()); + } + }//GEN-LAST:event_createDatabaseButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton createDatabaseButton; + private javax.swing.JButton deleteDatabaseButton; + private javax.swing.JLabel hashDatabasesLabel; + private javax.swing.JLabel hashDbIndexStatusLabel; + private javax.swing.JLabel hashDbLocationLabel; + private javax.swing.JLabel hashDbNameLabel; + private javax.swing.JLabel hashDbTypeLabel; + private javax.swing.JTable hashSetTable; + private javax.swing.JButton importDatabaseButton; + private javax.swing.JButton indexButton; + private javax.swing.JLabel indexLabel; + private javax.swing.JLabel indexPathLabel; + private javax.swing.JLabel indexPathLabelLabel; + private javax.swing.JLabel informationLabel; + private javax.swing.JSeparator informationSeparator; + private javax.swing.JLabel ingestWarningLabel; + private javax.swing.JButton jButton3; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel6; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JLabel locationLabel; + private javax.swing.JLabel nameLabel; + private javax.swing.JLabel optionsLabel; + private javax.swing.JSeparator optionsSeparator; + private javax.swing.JCheckBox searchDuringIngestCheckbox; + private javax.swing.JCheckBox sendIngestMessagesCheckBox; + private javax.swing.JLabel typeLabel; + // End of variables declaration//GEN-END:variables +} \ No newline at end of file diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbContextMenuActionsProvider.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbContextMenuActionsProvider.java new file mode 100755 index 0000000000..128416ff4b --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbContextMenuActionsProvider.java @@ -0,0 +1,41 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.hashdatabase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.Action; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; +import org.sleuthkit.datamodel.AbstractFile; + +@ServiceProvider(service = ContextMenuActionsProvider.class) +public class HashDbContextMenuActionsProvider implements ContextMenuActionsProvider { + @Override + public List getActions() { + ArrayList actions = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + if (!selectedFiles.isEmpty()) { + actions.add(AddContentToHashDbAction.getInstance()); + } + return actions; + } +} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.form b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.form similarity index 59% rename from HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.form rename to HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.form index f336266aed..047c483d48 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbAddDatabaseDialog.form +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.form @@ -10,6 +10,7 @@ + @@ -31,43 +32,48 @@ - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -76,85 +82,69 @@ + + + + + - - - - + - - + - + - + - - + + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - + - + - + @@ -164,7 +154,7 @@ - + @@ -174,42 +164,70 @@ - + - + - + - + - + - + + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.java new file mode 100644 index 0000000000..842f1d5e06 --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbCreateDatabaseDialog.java @@ -0,0 +1,369 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.hashdatabase; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JFrame; +import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb.KnownFilesType; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDatabaseFileAlreadyExistsException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.DuplicateHashSetNameException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDatabaseAlreadyAddedException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.IllegalHashDatabaseFileNameExtensionException; + +/** + * Instances of this class allow a user to create a new hash database and + * add it to the set of hash databases used to classify files as unknown, known + * or known bad. + */ +final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { + + private static final String DEFAULT_FILE_NAME = "hashset"; + private JFileChooser fileChooser = null; + private HashDb newHashDb = null; + + /** + * Displays a dialog that allows a user to create a new hash database and + * add it to the set of hash databases used to classify files as unknown, known + * or known bad. + */ + HashDbCreateDatabaseDialog() { + super(new JFrame(), "Create Hash Database", true); + initFileChooser(); + initComponents(); + display(); + } + + /** + * Get the hash database created by the user, if any. + * @return A HashDb object or null. + */ + HashDb getHashDatabase() { + return newHashDb; + } + + private void initFileChooser() { + fileChooser = new JFileChooser() { + @Override + public void approveSelection() { + File selectedFile = getSelectedFile(); + if (!FilenameUtils.getExtension(selectedFile.getName()).equalsIgnoreCase(HashDbManager.getHashDatabaseFileExtension())) { + if (JOptionPane.showConfirmDialog(this, "The hash database file must have a ." + HashDbManager.getHashDatabaseFileExtension() + " extension.", "File Name Error", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + cancelSelection(); + } + return; + } + if (selectedFile.exists()) { + if (JOptionPane.showConfirmDialog(this, "A file with this name already exists. Please choose a new file name.", "File Already Exists Error", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + cancelSelection(); + } + return; + } + super.approveSelection(); + } + }; + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setDragEnabled(false); + fileChooser.setMultiSelectionEnabled(false); + } + + private void display() { + Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation((screenDimension.width - getSize().width) / 2, (screenDimension.height - getSize().height) / 2); + setVisible(true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + saveAsButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + knownRadioButton = new javax.swing.JRadioButton(); + knownBadRadioButton = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); + hashSetNameTextField = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + searchDuringIngestCheckbox = new javax.swing.JCheckBox(); + sendIngestMessagesCheckbox = new javax.swing.JCheckBox(); + jLabel3 = new javax.swing.JLabel(); + databasePathTextField = new javax.swing.JTextField(); + okButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + org.openide.awt.Mnemonics.setLocalizedText(saveAsButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.saveAsButton.text")); // NOI18N + saveAsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + saveAsButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + buttonGroup1.add(knownRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(knownRadioButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.knownRadioButton.text")); // NOI18N + knownRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownRadioButtonActionPerformed(evt); + } + }); + + buttonGroup1.add(knownBadRadioButton); + knownBadRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(knownBadRadioButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.knownBadRadioButton.text")); // NOI18N + knownBadRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownBadRadioButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.jLabel1.text")); // NOI18N + + hashSetNameTextField.setText(org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.hashSetNameTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.jLabel2.text")); // NOI18N + + searchDuringIngestCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(searchDuringIngestCheckbox, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.searchDuringIngestCheckbox.text")); // NOI18N + searchDuringIngestCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.searchDuringIngestCheckbox.toolTipText")); // NOI18N + searchDuringIngestCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchDuringIngestCheckboxActionPerformed(evt); + } + }); + + sendIngestMessagesCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(sendIngestMessagesCheckbox, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.sendIngestMessagesCheckbox.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.jLabel3.text")); // NOI18N + + databasePathTextField.setEditable(false); + databasePathTextField.setText(org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.databasePathTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.okButton.text")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(sendIngestMessagesCheckbox)) + .addComponent(searchDuringIngestCheckbox)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(okButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton)) + .addComponent(jLabel2) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(knownRadioButton) + .addComponent(knownBadRadioButton))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(databasePathTextField)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 272, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(saveAsButton))) + .addContainerGap()))) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(2, 2, 2) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(databasePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(saveAsButton) + .addComponent(jLabel1)) + .addGap(7, 7, 7) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownBadRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(searchDuringIngestCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(sendIngestMessagesCheckbox) + .addGap(3, 3, 3) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(okButton)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed + searchDuringIngestCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setSelected(false); + sendIngestMessagesCheckbox.setEnabled(false); + }//GEN-LAST:event_knownRadioButtonActionPerformed + + private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed + searchDuringIngestCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setEnabled(true); + }//GEN-LAST:event_knownBadRadioButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void saveAsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsButtonActionPerformed + try { + StringBuilder path = new StringBuilder(); + if (!hashSetNameTextField.getText().isEmpty()) { + path.append(hashSetNameTextField.getText()); + } + else { + path.append(DEFAULT_FILE_NAME); + } + path.append(".").append(HashDbManager.getHashDatabaseFileExtension()); + fileChooser.setSelectedFile(new File(path.toString())); + if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { + File databaseFile = fileChooser.getSelectedFile(); + databasePathTextField.setText(databaseFile.getCanonicalPath()); + } + } + catch (IOException ex) { + Logger.getLogger(HashDbCreateDatabaseDialog.class.getName()).log(Level.WARNING, "Couldn't get selected file path.", ex); + } + }//GEN-LAST:event_saveAsButtonActionPerformed + + private void searchDuringIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchDuringIngestCheckboxActionPerformed + sendIngestMessagesCheckbox.setEnabled(searchDuringIngestCheckbox.isSelected()); + if (!searchDuringIngestCheckbox.isSelected()) { + sendIngestMessagesCheckbox.setSelected(false); + } + }//GEN-LAST:event_searchDuringIngestCheckboxActionPerformed + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + // Note that the error handlers in this method call return without disposing of the + // dialog to allow the user to try again, if desired. + + if (hashSetNameTextField.getText().isEmpty()) { + JOptionPane.showMessageDialog(this, "A hash set name must be entered.", "Create Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + if (databasePathTextField.getText().isEmpty()) { + JOptionPane.showMessageDialog(this, "A database path must be entered.", "Create Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + KnownFilesType type; + if (knownRadioButton.isSelected()) { + type = KnownFilesType.KNOWN; + } + else { + type = KnownFilesType.KNOWN_BAD; + } + + String errorMessage = "Hash database creation error"; + try + { + newHashDb = HashDbManager.getInstance().addNewHashDatabase(hashSetNameTextField.getText(), fileChooser.getSelectedFile().getCanonicalPath(), searchDuringIngestCheckbox.isSelected(), sendIngestMessagesCheckbox.isSelected(), type); + } + catch (IOException ex) { + Logger.getLogger(HashDbCreateDatabaseDialog.class.getName()).log(Level.WARNING, errorMessage, ex); + JOptionPane.showMessageDialog(this, "Cannot create a hash database file at the selected location.", "Create Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + catch (HashDatabaseFileAlreadyExistsException | DuplicateHashSetNameException | HashDatabaseAlreadyAddedException | IllegalHashDatabaseFileNameExtensionException ex) { + Logger.getLogger(HashDbCreateDatabaseDialog.class.getName()).log(Level.WARNING, errorMessage, ex); + JOptionPane.showMessageDialog(this, ex.getMessage(), "Create Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbCreateDatabaseDialog.class.getName()).log(Level.SEVERE, errorMessage, ex); + JOptionPane.showMessageDialog(this, "Failed to create the hash database.", "Create Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + dispose(); + }//GEN-LAST:event_okButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JButton cancelButton; + private javax.swing.JTextField databasePathTextField; + private javax.swing.JTextField hashSetNameTextField; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JRadioButton knownBadRadioButton; + private javax.swing.JRadioButton knownRadioButton; + private javax.swing.JButton okButton; + private javax.swing.JButton saveAsButton; + private javax.swing.JCheckBox searchDuringIngestCheckbox; + private javax.swing.JCheckBox sendIngestMessagesCheckbox; + // End of variables declaration//GEN-END:variables +} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.form b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.form new file mode 100644 index 0000000000..627f4090ba --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.form @@ -0,0 +1,235 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.java new file mode 100644 index 0000000000..33516bad99 --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbImportDatabaseDialog.java @@ -0,0 +1,359 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.hashdatabase; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.JFrame; +import org.sleuthkit.datamodel.TskCoreException; +import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb.KnownFilesType; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDatabaseDoesNotExistException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.DuplicateHashSetNameException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDatabaseAlreadyAddedException; + +/** + * Instances of this class allow a user to select an existing hash database and + * add it to the set of hash databases used to classify files as unknown, known, + * or known bad. + */ +final class HashDbImportDatabaseDialog extends javax.swing.JDialog { + + private JFileChooser fileChooser = new JFileChooser(); + private String selectedFilePath = ""; + private HashDb selectedHashDb = null; + + /** + * Displays a dialog that allows a user to select an existing hash database + * and add it to the set of hash databases used to classify files as unknown, + * known, or known bad. + */ + HashDbImportDatabaseDialog() { + super(new JFrame(), "Import Hash Database", true); + initFileChooser(); + initComponents(); + display(); + } + + /** + * Get the hash database imported by the user, if any. + * @return A HashDb object or null. + */ + HashDb getHashDatabase() { + return selectedHashDb; + } + + private void initFileChooser() { + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + String[] EXTENSION = new String[] { "txt", "kdb", "idx", "hash", "Hash", "hsh"}; + FileNameExtensionFilter filter = new FileNameExtensionFilter("Hash Database File", EXTENSION); + fileChooser.setFileFilter(filter); + fileChooser.setMultiSelectionEnabled(false); + } + + private void display() { + Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation((screenDimension.width - getSize().width) / 2, (screenDimension.height - getSize().height) / 2); + setVisible(true); + } + + private static String shortenPath(String path) { + String shortenedPath = path; + if (shortenedPath.length() > 50){ + shortenedPath = shortenedPath.substring(0, 10 + shortenedPath.substring(10).indexOf(File.separator) + 1) + "..." + shortenedPath.substring((shortenedPath.length() - 20) + shortenedPath.substring(shortenedPath.length() - 20).indexOf(File.separator)); + } + return shortenedPath; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + okButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + databasePathTextField = new javax.swing.JTextField(); + openButton = new javax.swing.JButton(); + knownRadioButton = new javax.swing.JRadioButton(); + knownBadRadioButton = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); + hashSetNameTextField = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + searchDuringIngestCheckbox = new javax.swing.JCheckBox(); + sendIngestMessagesCheckbox = new javax.swing.JCheckBox(); + jLabel3 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.okButton.text")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + databasePathTextField.setEditable(false); + databasePathTextField.setText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.databasePathTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(openButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.openButton.text")); // NOI18N + openButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openButtonActionPerformed(evt); + } + }); + + buttonGroup1.add(knownRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(knownRadioButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.knownRadioButton.text")); // NOI18N + knownRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownRadioButtonActionPerformed(evt); + } + }); + + buttonGroup1.add(knownBadRadioButton); + knownBadRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(knownBadRadioButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.knownBadRadioButton.text")); // NOI18N + knownBadRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownBadRadioButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.jLabel1.text")); // NOI18N + + hashSetNameTextField.setText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.hashSetNameTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.jLabel2.text")); // NOI18N + + searchDuringIngestCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(searchDuringIngestCheckbox, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.searchDuringIngestCheckbox.text")); // NOI18N + searchDuringIngestCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchDuringIngestCheckboxActionPerformed(evt); + } + }); + + sendIngestMessagesCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(sendIngestMessagesCheckbox, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.sendIngestMessagesCheckbox.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.jLabel3.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(sendIngestMessagesCheckbox)) + .addComponent(searchDuringIngestCheckbox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(okButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel2) + .addGroup(layout.createSequentialGroup() + .addGap(19, 19, 19) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(knownRadioButton) + .addComponent(knownBadRadioButton)))) + .addGap(0, 307, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(hashSetNameTextField)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(databasePathTextField))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(openButton))) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(openButton) + .addComponent(databasePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 119, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(okButton) + .addComponent(cancelButton)) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownBadRadioButton) + .addGap(13, 13, 13) + .addComponent(searchDuringIngestCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(sendIngestMessagesCheckbox) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + ); + + pack(); + }// //GEN-END:initComponents + + private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed + if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + File databaseFile = fileChooser.getSelectedFile(); + try { + selectedFilePath = databaseFile.getCanonicalPath(); + databasePathTextField.setText(shortenPath(selectedFilePath)); + hashSetNameTextField.setText(FilenameUtils.removeExtension(databaseFile.getName())); + if (hashSetNameTextField.getText().toLowerCase().contains("nsrl")) { + knownRadioButton.setSelected(true); + knownRadioButtonActionPerformed(null); + } + } + catch (IOException ex) { + Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Failed to get path of selected database", ex); + JOptionPane.showMessageDialog(this, "Failed to get the path of the selected database."); + } + } + }//GEN-LAST:event_openButtonActionPerformed + + private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed + searchDuringIngestCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setSelected(false); + sendIngestMessagesCheckbox.setEnabled(false); + }//GEN-LAST:event_knownRadioButtonActionPerformed + + private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed + searchDuringIngestCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setSelected(true); + sendIngestMessagesCheckbox.setEnabled(true); + }//GEN-LAST:event_knownBadRadioButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + this.dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + // Note that the error handlers in this method call return without disposing of the + // dialog to allow the user to try again, if desired. + + if(hashSetNameTextField.getText().isEmpty()) { + JOptionPane.showMessageDialog(this, "A hash set name must be entered.", "Import Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + if(selectedFilePath.isEmpty()) { + JOptionPane.showMessageDialog(this, "A hash database file path must be selected.", "Import Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + File file = new File(selectedFilePath); + if (!file.exists()) { + JOptionPane.showMessageDialog(this, "The selected hash database does not exist.", "Import Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + KnownFilesType type; + if (knownRadioButton.isSelected()) { + type = KnownFilesType.KNOWN; + } + else { + type = KnownFilesType.KNOWN_BAD; + } + + String errorMessage = "Failed to open hash database at " + selectedFilePath + "."; + try { + selectedHashDb = HashDbManager.getInstance().addExistingHashDatabase(hashSetNameTextField.getText(), selectedFilePath, searchDuringIngestCheckbox.isSelected(), sendIngestMessagesCheckbox.isSelected(), type); + } + catch (HashDatabaseDoesNotExistException | DuplicateHashSetNameException | HashDatabaseAlreadyAddedException ex) { + Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.WARNING, errorMessage, ex); + JOptionPane.showMessageDialog(this, ex.getMessage(), "Import Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbCreateDatabaseDialog.class.getName()).log(Level.SEVERE, errorMessage, ex); + JOptionPane.showMessageDialog(this, errorMessage, "Import Hash Database Error", JOptionPane.ERROR_MESSAGE); + return; + } + + dispose(); + }//GEN-LAST:event_okButtonActionPerformed + + private void searchDuringIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchDuringIngestCheckboxActionPerformed + sendIngestMessagesCheckbox.setEnabled(searchDuringIngestCheckbox.isSelected()); + if (!searchDuringIngestCheckbox.isSelected()) { + sendIngestMessagesCheckbox.setSelected(false); + } + }//GEN-LAST:event_searchDuringIngestCheckboxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JButton cancelButton; + private javax.swing.JTextField databasePathTextField; + private javax.swing.JTextField hashSetNameTextField; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JRadioButton knownBadRadioButton; + private javax.swing.JRadioButton knownRadioButton; + private javax.swing.JButton okButton; + private javax.swing.JButton openButton; + private javax.swing.JCheckBox searchDuringIngestCheckbox; + private javax.swing.JCheckBox sendIngestMessagesCheckbox; + // End of variables declaration//GEN-END:variables +} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java index 6cdb4764c1..f3f63ebd16 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2013 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,7 @@ package org.sleuthkit.autopsy.hashdatabase; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; @@ -43,30 +42,28 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; public class HashDbIngestModule extends IngestModuleAbstractFile { - private static HashDbIngestModule instance = null; public final static String MODULE_NAME = "Hash Lookup"; public final static String MODULE_DESCRIPTION = "Identifies known and notables files using supplied hash databases, such as a standard NSRL database."; final public static String MODULE_VERSION = Version.getVersion(); private static final Logger logger = Logger.getLogger(HashDbIngestModule.class.getName()); + private HashDbSimpleConfigPanel simpleConfigPanel; + private HashDbConfigPanel advancedConfigPanel; private IngestServices services; private SleuthkitCase skCase; private static int messageId = 0; - private int knownBadCount; - // Whether or not to do hash lookups (only set to true if there are dbs set) - private boolean nsrlIsSet; - private boolean knownBadIsSet; + private int knownBadCount = 0; private boolean calcHashesIsSet; + private List knownBadHashSets = new ArrayList<>(); + private List knownHashSets = new ArrayList<>(); static long calctime = 0; static long lookuptime = 0; - private Map knownBadSets = new HashMap<>(); - private HashDbManagementPanel panel; private final Hash hasher = new Hash(); private HashDbIngestModule() { - knownBadCount = 0; } public static synchronized HashDbIngestModule getDefault() { @@ -76,97 +73,6 @@ public class HashDbIngestModule extends IngestModuleAbstractFile { return instance; } - @Override - public void init(IngestModuleInit initContext) { - services = IngestServices.getDefault(); - this.skCase = Case.getCurrentCase().getSleuthkitCase(); - try { - HashDbXML hdbxml = HashDbXML.getCurrent(); - knownBadSets.clear(); - skCase.clearLookupDatabases(); - nsrlIsSet = false; - knownBadIsSet = false; - calcHashesIsSet = hdbxml.getCalculate(); - - HashDb nsrl = hdbxml.getNSRLSet(); - if (nsrl != null && nsrl.getUseForIngest() && IndexStatus.isIngestible(nsrl.status())) { - nsrlIsSet = true; - // @@@ Unchecked return value - skCase.setNSRLDatabase(nsrl.getDatabasePaths().get(0)); - } - - for (HashDb db : hdbxml.getKnownBadSets()) { - IndexStatus status = db.status(); - if (db.getUseForIngest() && IndexStatus.isIngestible(status)) { - knownBadIsSet = true; - int ret = skCase.addKnownBadDatabase(db.getDatabasePaths().get(0)); // TODO: support multiple paths - knownBadSets.put(ret, db); - } - } - - if (!nsrlIsSet) { - this.services.postMessage(IngestMessage.createWarningMessage(++messageId, this, "No NSRL database set", "Known file search will not be executed.")); - } - if (!knownBadIsSet) { - this.services.postMessage(IngestMessage.createWarningMessage(++messageId, this, "No known bad database set", "Known bad file search will not be executed.")); - } - - } catch (TskException ex) { - logger.log(Level.SEVERE, "Setting NSRL and Known database failed", ex); - this.services.postMessage(IngestMessage.createErrorMessage(++messageId, this, "Error Configuring Hash Databases", "Setting NSRL and Known database failed.")); - } - } - - @Override - public void complete() { - if ((knownBadIsSet) || (nsrlIsSet)) { - StringBuilder detailsSb = new StringBuilder(); - //details - detailsSb.append(""); - - detailsSb.append(""); - detailsSb.append(""); - - detailsSb.append("\n"); - detailsSb.append("\n"); - detailsSb.append("
Known bads found:").append(knownBadCount).append("
Total Calculation Time").append(calctime).append("
Total Lookup Time").append(lookuptime).append("
"); - - detailsSb.append("

Databases Used:

\n
    "); - for (HashDb db : knownBadSets.values()) { - detailsSb.append("
  • ").append(db.getName()).append("
  • \n"); - } - - detailsSb.append("
"); - services.postMessage(IngestMessage.createMessage(++messageId, IngestMessage.MessageType.INFO, this, "Hash Lookup Results", detailsSb.toString())); - clearHashDatabaseHandles(); - } - } - - private void clearHashDatabaseHandles() { - try { - skCase.clearLookupDatabases(); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error clearing hash database handles. ", ex); - } - this.nsrlIsSet = false; - this.knownBadIsSet = false; - } - - /** - * notification from manager to stop processing due to some interruption - * (user, error, exception) - */ - @Override - public void stop() { - clearHashDatabaseHandles(); - } - - /** - * get specific name of the module should be unique across modules, a - * user-friendly name of the module shown in GUI - * - * @return The name of this Ingest Module - */ @Override public String getName() { return MODULE_NAME; @@ -182,6 +88,96 @@ public class HashDbIngestModule extends IngestModuleAbstractFile { return MODULE_VERSION; } + @Override + public boolean hasSimpleConfiguration() { + return true; + } + + @Override + public javax.swing.JPanel getSimpleConfiguration(String context) { + if (null == simpleConfigPanel) { + simpleConfigPanel = new HashDbSimpleConfigPanel(); + } + else { + simpleConfigPanel.load(); + } + + return simpleConfigPanel; + } + + @Override + public void saveSimpleConfiguration() { + if (simpleConfigPanel != null) { + simpleConfigPanel.store(); + } + } + + @Override + public boolean hasAdvancedConfiguration() { + return true; + } + + @Override + public javax.swing.JPanel getAdvancedConfiguration(String context) { + if (advancedConfigPanel == null) { + advancedConfigPanel = new HashDbConfigPanel(); + } + + advancedConfigPanel.load(); + return advancedConfigPanel; + } + + @Override + public void saveAdvancedConfiguration() { + if (advancedConfigPanel != null) { + advancedConfigPanel.store(); + } + + if (simpleConfigPanel != null) { + simpleConfigPanel.load(); + } + } + + @Override + public void init(IngestModuleInit initContext) { + services = IngestServices.getDefault(); + skCase = Case.getCurrentCase().getSleuthkitCase(); + + HashDbManager hashDbManager = HashDbManager.getInstance(); + getHashSetsUsableForIngest(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets); + getHashSetsUsableForIngest(hashDbManager.getKnownFileHashSets(), knownHashSets); + calcHashesIsSet = hashDbManager.getAlwaysCalculateHashes(); + + if (knownHashSets.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage(++messageId, this, "No known hash database set", "Known file search will not be executed.")); + } + if (knownBadHashSets.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage(++messageId, this, "No known bad hash database set", "Known bad file search will not be executed.")); + } + } + + private void getHashSetsUsableForIngest(List hashDbs, List hashDbsForIngest) { + assert hashDbs != null; + assert hashDbsForIngest != null; + hashDbsForIngest.clear(); + for (HashDb db : hashDbs) { + if (db.getSearchDuringIngest()) { + try { + if (db.hasLookupIndex()) { + hashDbsForIngest.add(db); + } + } + catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting index status for " + db.getHashSetName() +" hash database", ex); + } + } + } + } + + @Override + public boolean hasBackgroundJobsRunning() { + return false; + } @Override public ProcessResult process(PipelineContextpipelineContext, AbstractFile file) { @@ -192,54 +188,90 @@ public class HashDbIngestModule extends IngestModuleAbstractFile { return processFile(file); } - - - @Override - public boolean hasBackgroundJobsRunning() { - return false; - } - - @Override - public boolean hasSimpleConfiguration() { - return true; - } - - @Override - public boolean hasAdvancedConfiguration() { - return true; - } - - @Override - public javax.swing.JPanel getSimpleConfiguration(String context) { - HashDbXML.getCurrent().reload(); - return new HashDbSimplePanel(); - } - - @Override - public javax.swing.JPanel getAdvancedConfiguration(String context) { - //return HashDbManagementPanel.getDefault(); - getPanel().load(); - return getPanel(); - } - - @Override - public void saveAdvancedConfiguration() { - getPanel().store(); - } - - private HashDbManagementPanel getPanel() { - if (panel == null) { - panel = new HashDbManagementPanel(); + + private ProcessResult processFile(AbstractFile file) { + // bail out if we have no hashes set + if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (calcHashesIsSet == false)) { + return ProcessResult.OK; } - return panel; + + // calc hash value + String name = file.getName(); + String md5Hash = file.getMd5Hash(); + if (md5Hash == null || md5Hash.isEmpty()) { + try { + long calcstart = System.currentTimeMillis(); + md5Hash = hasher.calculateMd5(file); + calctime += (System.currentTimeMillis() - calcstart); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error calculating hash of file " + name, ex); + services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Read Error: " + name, + "Error encountered while calculating the hash value for " + name + ".")); + return ProcessResult.ERROR; + } + } + + // look up in known bad first + boolean foundBad = false; + ProcessResult ret = ProcessResult.OK; + for (HashDb db : knownBadHashSets) { + try { + long lookupstart = System.currentTimeMillis(); + if (db.hasMd5HashOf(file)) { + foundBad = true; + knownBadCount += 1; + try { + skCase.setKnown(file, TskData.FileKnown.BAD); + } catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't set known bad state for file " + name + " - see sleuthkit log for details", ex); + services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, + "Error encountered while setting known bad state for " + name + ".")); + ret = ProcessResult.ERROR; + } + String hashSetName = db.getHashSetName(); + postHashSetHitToBlackboard(file, md5Hash, hashSetName, db.getSendIngestMessages()); + } + lookuptime += (System.currentTimeMillis() - lookupstart); + } catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't lookup known bad hash for file " + name + " - see sleuthkit log for details", ex); + services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, + "Error encountered while looking up known bad hash value for " + name + ".")); + ret = ProcessResult.ERROR; + } + } + + // If the file is not in the known bad sets, search for it in the known sets. + // Any hit is sufficient to classify it as known, and there is no need to create + // a hit artifact or send a message to the application inbox. + if (!foundBad) { + for (HashDb db : knownHashSets) { + try { + long lookupstart = System.currentTimeMillis(); + if (db.hasMd5HashOf(file)) { + try { + skCase.setKnown(file, TskData.FileKnown.KNOWN); + break; + } catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't set known state for file " + name + " - see sleuthkit log for details", ex); + services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, + "Error encountered while setting known state for " + name + ".")); + ret = ProcessResult.ERROR; + } + } + lookuptime += (System.currentTimeMillis() - lookupstart); + } catch (TskException ex) { + logger.log(Level.WARNING, "Couldn't lookup known hash for file " + name + " - see sleuthkit log for details", ex); + services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, + "Error encountered while looking up known hash value for " + name + ".")); + ret = ProcessResult.ERROR; + } + } + } + + return ret; } - @Override - public void saveSimpleConfiguration() { - HashDbXML.getCurrent().save(); - } - - private void processBadFile(AbstractFile abstractFile, String md5Hash, String hashSetName, boolean showInboxMessage) { + private void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, boolean showInboxMessage) { try { BlackboardArtifact badFile = abstractFile.newArtifact(ARTIFACT_TYPE.TSK_HASHSET_HIT); //TODO Revisit usage of deprecated constructor as per TSK-583 @@ -282,99 +314,32 @@ public class HashDbIngestModule extends IngestModuleAbstractFile { } } - private ProcessResult processFile(AbstractFile file) { - // bail out if we have no hashes set - if ((nsrlIsSet == false) && (knownBadIsSet == false) && (calcHashesIsSet == false)) { - return ProcessResult.OK; - } + + @Override + public void complete() { + if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty())) { + StringBuilder detailsSb = new StringBuilder(); + //details + detailsSb.append(""); - // calc hash value - String name = file.getName(); - String md5Hash = file.getMd5Hash(); - if (md5Hash == null || md5Hash.isEmpty()) { - try { - long calcstart = System.currentTimeMillis(); - md5Hash = hasher.calculateMd5(file); - calctime += (System.currentTimeMillis() - calcstart); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error calculating hash of file " + name, ex); - services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Read Error: " + name, - "Error encountered while calculating the hash value for " + name + ".")); - return ProcessResult.ERROR; - } - } - + detailsSb.append(""); + detailsSb.append(""); - // look up in known bad first - TskData.FileKnown status = TskData.FileKnown.UKNOWN; - boolean foundBad = false; - ProcessResult ret = ProcessResult.OK; + detailsSb.append("\n"); + detailsSb.append("\n"); + detailsSb.append("
Known bads found:").append(knownBadCount).append("
Total Calculation Time").append(calctime).append("
Total Lookup Time").append(lookuptime).append("
"); - if (knownBadIsSet) { - for (Map.Entry entry : knownBadSets.entrySet()) { - try { - long lookupstart = System.currentTimeMillis(); - status = skCase.knownBadLookupMd5(md5Hash, entry.getKey()); - lookuptime += (System.currentTimeMillis() - lookupstart); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't lookup known bad hash for file " + name + " - see sleuthkit log for details", ex); - services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, - "Error encountered while looking up known bad hash value for " + name + ".")); - ret = ProcessResult.ERROR; - } - - if (status.equals(TskData.FileKnown.BAD)) { - foundBad = true; - knownBadCount += 1; - try { - skCase.setKnown(file, TskData.FileKnown.BAD); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't set known bad state for file " + name + " - see sleuthkit log for details", ex); - services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, - "Error encountered while setting known bad state for " + name + ".")); - ret = ProcessResult.ERROR; - } - String hashSetName = entry.getValue().getName(); - processBadFile(file, md5Hash, hashSetName, entry.getValue().getShowInboxMessages()); - } - } - } - - // only do NSRL if we didn't find a known bad - if (!foundBad && nsrlIsSet) { - try { - long lookupstart = System.currentTimeMillis(); - status = skCase.nsrlLookupMd5(md5Hash); - lookuptime += (System.currentTimeMillis() - lookupstart); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't lookup NSRL hash for file " + name + " - see sleuthkit log for details", ex); - services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, - "Error encountered while looking up NSRL hash value for " + name + ".")); - ret = ProcessResult.ERROR; + detailsSb.append("

Databases Used:

\n
    "); + for (HashDb db : knownBadHashSets) { + detailsSb.append("
  • ").append(db.getHashSetName()).append("
  • \n"); } - if (status.equals(TskData.FileKnown.KNOWN)) { - try { - skCase.setKnown(file, TskData.FileKnown.KNOWN); - } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't set known state for file " + name + " - see sleuthkit log for details", ex); - services.postMessage(IngestMessage.createErrorMessage(++messageId, HashDbIngestModule.this, "Hash Lookup Error: " + name, - "Error encountered while setting known (NSRL) state for " + name + ".")); - ret = ProcessResult.ERROR; - } - } + detailsSb.append("
"); + services.postMessage(IngestMessage.createMessage(++messageId, IngestMessage.MessageType.INFO, this, "Hash Lookup Results", detailsSb.toString())); } - - return ret; } - public ArrayList getKnownBadSetNames() { - ArrayList knownBadSetNames = new ArrayList<>(); - HashDbXML hdbxml = HashDbXML.getCurrent(); - for (HashDb db : hdbxml.getKnownBadSets()) { - knownBadSetNames.add(db.getName()); - } - return knownBadSetNames; + @Override + public void stop() { } - } diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.java deleted file mode 100644 index d64b93ec92..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManagementPanel.java +++ /dev/null @@ -1,784 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.hashdatabase; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Frame; -import java.awt.event.KeyEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellRenderer; -import org.sleuthkit.autopsy.corecomponents.OptionsPanel; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; - -final class HashDbManagementPanel extends javax.swing.JPanel implements OptionsPanel { - - private HashSetTableModel hashSetTableModel; - private static final Logger logger = Logger.getLogger(HashDbManagementPanel.class.getName()); - private boolean ingestRunning = false; - - - //keep track of dbs being indexed, since HashDb objects are reloaded, - //we cannot rely on their status - private final MapindexingState = new HashMap(); - - HashDbManagementPanel() { - this.hashSetTableModel = new HashSetTableModel(); - initComponents(); - customizeComponents(); - - } - - private void customizeComponents() { - setName("Hash Database Configuration"); - this.ingestWarningLabel.setVisible(false); - this.hashSetTable.setModel(hashSetTableModel); - this.hashSetTable.setTableHeader(null); - hashSetTable.getParent().setBackground(hashSetTable.getBackground()); - hashSetTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - hashSetTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - - @Override - public void valueChanged(ListSelectionEvent e) { - ListSelectionModel listSelectionModel = (ListSelectionModel) e.getSource(); - if (!listSelectionModel.isSelectionEmpty()) { - int index = listSelectionModel.getMinSelectionIndex(); - listSelectionModel.setSelectionInterval(index, index); - HashDbXML loader = HashDbXML.getCurrent(); - HashDb current = loader.getAllSets().get(index); - initUI(current); - } else { - initUI(null); - } - } - }); - } - - private void initUI(HashDb db) { - boolean useForIngestEnabled = db != null && !ingestRunning; - boolean useForIngestSelected = db != null && db.getUseForIngest(); - boolean showInboxMessagesEnabled = db != null && !ingestRunning && useForIngestSelected && db.getDbType().equals(HashDb.DBType.KNOWN_BAD); - boolean showInboxMessagesSelected = db != null && db.getShowInboxMessages(); - boolean deleteButtonEnabled = db != null && !ingestRunning; - boolean importButtonEnabled = !ingestRunning; - if (db == null) { - setButtonFromIndexStatus(this.indexButton, this.hashDbIndexStatusLabel, IndexStatus.NONE); - this.hashDbLocationLabel.setText("No database selected"); - this.hashDbNameLabel.setText("No database selected"); - this.hashDbIndexStatusLabel.setText("No database selected"); - this.hashDbTypeLabel.setText("No database selected"); - } else { - //check if dn in indexing state - String dbName = db.getName(); - IndexStatus status = db.status(); - Boolean state = indexingState.get(dbName); - if (state != null && state.equals(Boolean.TRUE) ) { - status = IndexStatus.INDEXING; - } - - setButtonFromIndexStatus(this.indexButton, this.hashDbIndexStatusLabel, status); - String shortenPath = db.getDatabasePaths().get(0); - this.hashDbLocationLabel.setToolTipText(shortenPath); - if(shortenPath.length() > 50){ - shortenPath = shortenPath.substring(0, 10 + shortenPath.substring(10).indexOf(File.separator) + 1) + "..." + - shortenPath.substring((shortenPath.length() - 20) + shortenPath.substring(shortenPath.length() - 20).indexOf(File.separator)); - } - this.hashDbLocationLabel.setText(shortenPath); - this.hashDbNameLabel.setText(db.getName()); - this.hashDbTypeLabel.setText(db.getDbType().getDisplayName()); - } - this.useForIngestCheckbox.setSelected(useForIngestSelected); - this.useForIngestCheckbox.setEnabled(useForIngestEnabled); - this.showInboxMessagesCheckBox.setSelected(showInboxMessagesSelected); - this.showInboxMessagesCheckBox.setEnabled(showInboxMessagesEnabled); - this.deleteButton.setEnabled(deleteButtonEnabled); - this.importButton.setEnabled(importButtonEnabled); - this.optionsLabel.setEnabled(useForIngestEnabled || showInboxMessagesEnabled); - this.optionsSeparator.setEnabled(useForIngestEnabled || showInboxMessagesEnabled); - } - - /** - * Sets the current state of ingest. - * Don't allow any changes if ingest is running. - * @param running Whether ingest is running or not. - */ - private void setIngestStatus(boolean running) { - ingestRunning = running; - ingestWarningLabel.setVisible(running); - importButton.setEnabled(!running); - - int selection = getSelection(); - if(selection != -1) { - initUI(HashDbXML.getCurrent().getAllSets().get(selection)); - } - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - - jLabel2 = new javax.swing.JLabel(); - jLabel4 = new javax.swing.JLabel(); - jLabel6 = new javax.swing.JLabel(); - jButton3 = new javax.swing.JButton(); - ingestWarningLabel = new javax.swing.JLabel(); - jScrollPane1 = new javax.swing.JScrollPane(); - hashSetTable = new HashSetTable(); - deleteButton = new javax.swing.JButton(); - importButton = new javax.swing.JButton(); - hashDatabasesLabel = new javax.swing.JLabel(); - nameLabel = new javax.swing.JLabel(); - hashDbNameLabel = new javax.swing.JLabel(); - hashDbLocationLabel = new javax.swing.JLabel(); - locationLabel = new javax.swing.JLabel(); - typeLabel = new javax.swing.JLabel(); - hashDbTypeLabel = new javax.swing.JLabel(); - hashDbIndexStatusLabel = new javax.swing.JLabel(); - indexLabel = new javax.swing.JLabel(); - indexButton = new javax.swing.JButton(); - useForIngestCheckbox = new javax.swing.JCheckBox(); - showInboxMessagesCheckBox = new javax.swing.JCheckBox(); - informationLabel = new javax.swing.JLabel(); - optionsLabel = new javax.swing.JLabel(); - informationSeparator = new javax.swing.JSeparator(); - optionsSeparator = new javax.swing.JSeparator(); - - org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.jLabel2.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.jLabel4.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.jLabel6.text")); // NOI18N - - jButton3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.jButton3.text")); // NOI18N - - setMinimumSize(new java.awt.Dimension(700, 500)); - setPreferredSize(new java.awt.Dimension(700, 500)); - - ingestWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/warning16.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(ingestWarningLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.ingestWarningLabel.text")); // NOI18N - - hashSetTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - - } - )); - hashSetTable.setShowHorizontalLines(false); - hashSetTable.setShowVerticalLines(false); - hashSetTable.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyPressed(java.awt.event.KeyEvent evt) { - hashSetTableKeyPressed(evt); - } - }); - jScrollPane1.setViewportView(hashSetTable); - - deleteButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/delete16.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(deleteButton, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.deleteButton.text")); // NOI18N - deleteButton.setMaximumSize(new java.awt.Dimension(140, 25)); - deleteButton.setMinimumSize(new java.awt.Dimension(140, 25)); - deleteButton.setPreferredSize(new java.awt.Dimension(140, 25)); - deleteButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deleteButtonActionPerformed(evt); - } - }); - - importButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/hashdatabase/import16.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(importButton, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.importButton.text")); // NOI18N - importButton.setMaximumSize(new java.awt.Dimension(140, 25)); - importButton.setMinimumSize(new java.awt.Dimension(140, 25)); - importButton.setPreferredSize(new java.awt.Dimension(140, 25)); - importButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - importButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(hashDatabasesLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.hashDatabasesLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.nameLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(hashDbNameLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.hashDbNameLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(hashDbLocationLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.hashDbLocationLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(locationLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.locationLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(typeLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.typeLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(hashDbTypeLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.hashDbTypeLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(hashDbIndexStatusLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.hashDbIndexStatusLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(indexLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.indexLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(indexButton, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.indexButton.text")); // NOI18N - indexButton.setEnabled(false); - indexButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - indexButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(useForIngestCheckbox, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.useForIngestCheckbox.text")); // NOI18N - useForIngestCheckbox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - useForIngestCheckboxActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(showInboxMessagesCheckBox, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.showInboxMessagesCheckBox.text")); // NOI18N - showInboxMessagesCheckBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - showInboxMessagesCheckBoxActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(informationLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.informationLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(HashDbManagementPanel.class, "HashDbManagementPanel.optionsLabel.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(hashDatabasesLabel) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(importButton, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(deleteButton, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(informationLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(informationSeparator)) - .addGroup(layout.createSequentialGroup() - .addComponent(optionsLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(optionsSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 324, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(ingestWarningLabel) - .addGroup(layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(nameLabel) - .addComponent(locationLabel) - .addComponent(typeLabel)) - .addGap(40, 40, 40)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(indexLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18))) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(hashDbTypeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(hashDbLocationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(hashDbIndexStatusLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(hashDbNameLabel))) - .addComponent(useForIngestCheckbox) - .addComponent(showInboxMessagesCheckBox) - .addComponent(indexButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE)))))) - .addContainerGap(40, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(hashDatabasesLabel) - .addGap(6, 6, 6) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(informationLabel) - .addGroup(layout.createSequentialGroup() - .addGap(7, 7, 7) - .addComponent(informationSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 3, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(7, 7, 7) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nameLabel) - .addComponent(hashDbNameLabel)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(locationLabel) - .addComponent(hashDbLocationLabel)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(typeLabel) - .addComponent(hashDbTypeLabel)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(hashDbIndexStatusLabel) - .addComponent(indexLabel)) - .addGap(5, 5, 5) - .addComponent(indexButton) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(optionsLabel) - .addComponent(optionsSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(useForIngestCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(showInboxMessagesCheckBox) - .addGap(18, 18, 18) - .addComponent(ingestWarningLabel) - .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 422, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(importButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(deleteButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap()) - ); - }// //GEN-END:initComponents - - private void indexButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_indexButtonActionPerformed - int selected = getSelection(); - final HashDb current = HashDbXML.getCurrent().getAllSets().get(selected); - current.addPropertyChangeListener(new PropertyChangeListener() { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(HashDb.EVENT.INDEXING_DONE.toString())) { - //update tracking of indexing status - indexingState.put((String)evt.getNewValue(), Boolean.FALSE); - - setButtonFromIndexStatus(indexButton, hashDbIndexStatusLabel, current.status()); - resync(); - } - } - - }); - indexingState.put(current.getName(), Boolean.TRUE); - ModalNoButtons singleMNB = new ModalNoButtons(this, new Frame(), current); //Modal reference, to be removed later - singleMNB.setLocationRelativeTo(null); - singleMNB.setVisible(true); - singleMNB.setModal(true); //End Modal reference - indexingState.put(current.getName(), Boolean.FALSE); - setButtonFromIndexStatus(indexButton, this.hashDbIndexStatusLabel, current.status()); - }//GEN-LAST:event_indexButtonActionPerformed - - private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButtonActionPerformed - if (JOptionPane.showConfirmDialog(null, "This will remove the hash database entry globally (for all Cases). Do you want to proceed? ", - "Deleting a Hash Database Entry", - JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { - - int selected = getSelection(); - HashDbXML xmlHandle = HashDbXML.getCurrent(); - if (xmlHandle.getNSRLSet() != null) { - if (selected == 0) { - HashDbXML.getCurrent().removeNSRLSet(); - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(selected - 1); - } - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(selected); - } - hashSetTableModel.resync(); - } - }//GEN-LAST:event_deleteButtonActionPerformed - - private void hashSetTableKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_hashSetTableKeyPressed - if (evt.getKeyCode() == KeyEvent.VK_DELETE) { - int selected = getSelection(); - HashDbXML xmlHandle = HashDbXML.getCurrent(); - if (xmlHandle.getNSRLSet() != null) { - if (selected == 0) { - HashDbXML.getCurrent().removeNSRLSet(); - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(selected - 1); - } - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(selected); - } - } - hashSetTableModel.resync(); - }//GEN-LAST:event_hashSetTableKeyPressed - - private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useForIngestCheckboxActionPerformed - int selected = getSelection(); - HashDbXML xmlHandle = HashDbXML.getCurrent(); - if (xmlHandle.getNSRLSet() != null) { - if (selected == 0) { - HashDb current = HashDbXML.getCurrent().getNSRLSet(); - current.setUseForIngest(useForIngestCheckbox.isSelected()); - HashDbXML.getCurrent().setNSRLSet(current); - } else { - HashDb current = HashDbXML.getCurrent().getKnownBadSets().remove(selected - 1); - current.setUseForIngest(useForIngestCheckbox.isSelected()); - HashDbXML.getCurrent().addKnownBadSet(selected - 1, current); - this.showInboxMessagesCheckBox.setEnabled(useForIngestCheckbox.isSelected()); - } - } else { - HashDb current = HashDbXML.getCurrent().getKnownBadSets().remove(selected); - current.setUseForIngest(useForIngestCheckbox.isSelected()); - HashDbXML.getCurrent().addKnownBadSet(selected, current); - this.showInboxMessagesCheckBox.setEnabled(useForIngestCheckbox.isSelected()); - } - }//GEN-LAST:event_useForIngestCheckboxActionPerformed - - private void showInboxMessagesCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showInboxMessagesCheckBoxActionPerformed - int selected = getSelection(); - HashDbXML xmlHandle = HashDbXML.getCurrent(); - if (xmlHandle.getNSRLSet() != null) { - if (selected == 0) { - HashDb current = HashDbXML.getCurrent().getNSRLSet(); - current.setShowInboxMessages(showInboxMessagesCheckBox.isSelected()); - HashDbXML.getCurrent().setNSRLSet(current); - } else { - HashDb current = HashDbXML.getCurrent().getKnownBadSets().remove(selected - 1); - current.setShowInboxMessages(showInboxMessagesCheckBox.isSelected()); - HashDbXML.getCurrent().addKnownBadSet(selected - 1, current); - } - } else { - HashDb current = HashDbXML.getCurrent().getKnownBadSets().remove(selected); - current.setShowInboxMessages(showInboxMessagesCheckBox.isSelected()); - HashDbXML.getCurrent().addKnownBadSet(selected, current); - } - }//GEN-LAST:event_showInboxMessagesCheckBoxActionPerformed - - private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed - importHashSet(evt); - }//GEN-LAST:event_importButtonActionPerformed - - @Override - public void load() { - hashSetTable.clearSelection(); // Deselect all rows - HashDbXML.getCurrent().reload(); // Reload XML - initUI(null); // Update the UI - hashSetTableModel.resync(); // resync the table - setIngestStatus(IngestManager.getDefault().isIngestRunning()); // check if ingest is running - } - - @Override - /** - * Saves the HashDb's current state. - * This version of store is modified heavily to make use of the ModalNoButtons class. - * The only call that matters is the final HashDbXML.getCurrent().save() - */ - public void store() { - //Checking for for any unindexed databases - List unindexed = new ArrayList(); - for(int i = 0; i < hashSetTableModel.getRowCount(); i++){ - if(! hashSetTableModel.indexExists(i)){ - unindexed.add(hashSetTableModel.getDBAt(i)); - } - } - //If unindexed ones are found, show a popup box that will either index them, or remove them. - if (unindexed.size() == 1){ - showInvalidIndex(false, unindexed); - } - else if (unindexed.size() > 1){ - showInvalidIndex(true, unindexed); - } - HashDbXML.getCurrent().save(); - - } - - - /** - * Removes a list of HashDbs from the dialog panel that do not have a companion -md5.idx file. - * Occurs when user clicks "No" to the dialog popup box. - * @param toRemove a list of HashDbs that are unindexed - */ - void removeThese(List toRemove) { - HashDbXML xmlHandle = HashDbXML.getCurrent(); - for (HashDb hdb : toRemove) { - for (int i = 0; i < hashSetTableModel.getRowCount(); i++) { - if (hashSetTableModel.getDBAt(i).equals(hdb)) { - if (xmlHandle.getNSRLSet() != null) { - if (i == 0) { - HashDbXML.getCurrent().removeNSRLSet(); - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(i - 1); - } - } else { - HashDbXML.getCurrent().removeKnownBadSetAt(i); - } - hashSetTableModel.resync(); - } - } - } - } - - /** - * Displays the popup box that tells user that some of his databases are unindexed, along with solutions. - * This method is related to ModalNoButtons, to be removed at a later date. - * @param plural Whether or not there are multiple unindexed databases - * @param unindexed The list of unindexed databases. Can be of size 1. - */ - private void showInvalidIndex(boolean plural, List unindexed){ - String total = ""; - String message; - for(HashDb hdb : unindexed){ - total+= "\n" + hdb.getName(); - } - if(plural){ - message = "The following databases are not indexed, would you like to index them now? \n " + total; - } - else{ - message = "The following database is not indexed, would you like to index it now? \n" + total; - } - int res = JOptionPane.showConfirmDialog(this, message, "Unindexed databases", JOptionPane.YES_NO_OPTION); - if(res == JOptionPane.YES_OPTION){ - ModalNoButtons indexingDialog = new ModalNoButtons(this, new Frame(),unindexed); - indexingDialog.setLocationRelativeTo(null); - indexingDialog.setVisible(true); - indexingDialog.setModal(true); - hashSetTableModel.resync(); - } - if(res == JOptionPane.NO_OPTION){ - JOptionPane.showMessageDialog(this, "All unindexed databases will be removed the list"); - removeThese(unindexed); - } - } - - boolean valid() { - // TODO check whether form is consistent and complete - return true; - } - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton deleteButton; - private javax.swing.JLabel hashDatabasesLabel; - private javax.swing.JLabel hashDbIndexStatusLabel; - private javax.swing.JLabel hashDbLocationLabel; - private javax.swing.JLabel hashDbNameLabel; - private javax.swing.JLabel hashDbTypeLabel; - private javax.swing.JTable hashSetTable; - private javax.swing.JButton importButton; - private javax.swing.JButton indexButton; - private javax.swing.JLabel indexLabel; - private javax.swing.JLabel informationLabel; - private javax.swing.JSeparator informationSeparator; - private javax.swing.JLabel ingestWarningLabel; - private javax.swing.JButton jButton3; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel4; - private javax.swing.JLabel jLabel6; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JLabel locationLabel; - private javax.swing.JLabel nameLabel; - private javax.swing.JLabel optionsLabel; - private javax.swing.JSeparator optionsSeparator; - private javax.swing.JCheckBox showInboxMessagesCheckBox; - private javax.swing.JLabel typeLabel; - private javax.swing.JCheckBox useForIngestCheckbox; - // End of variables declaration//GEN-END:variables - private void importHashSet(java.awt.event.ActionEvent evt) { - String name = new HashDbAddDatabaseDialog().display(); - if(name != null) { - hashSetTableModel.selectRowByName(name); - } - resync(); - } - - /** - * The visual display of hash databases loaded. - */ - private class HashSetTable extends JTable { - @Override - public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { - Component c = super.prepareRenderer(renderer, row, column); - JComponent jc = (JComponent) c; - String valueText = (String) getValueAt(row, column); - jc.setToolTipText(valueText); - - //Letting the user know which DBs need to be indexed - if(hashSetTableModel.indexExists(row)){ - c.setForeground(Color.black); - } - else{ - c.setForeground(Color.red); - } - return c; - } - } - - - private class HashSetTableModel extends AbstractTableModel { - - private HashDbXML xmlHandle = HashDbXML.getCurrent(); - - @Override - public int getColumnCount() { - return 1; - } - - @Override - public int getRowCount() { - return xmlHandle.getAllSets().size(); - } - - @Override - public String getColumnName(int column) { - return "Name"; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if(xmlHandle.getNSRLSet() == null) { - return getDBAt(rowIndex).getName(); - } else { - return rowIndex == 0 ? getDBAt(rowIndex).getName() + " (NSRL)" : getDBAt(rowIndex).getName(); - } - } - - //Internal function for determining whether a companion -md5.idx file exists - private boolean indexExists(int rowIndex){ - return getDBAt(rowIndex).indexExists(); - } - - - //Internal function for getting the DB at a certain index. Used as-is, as well as by dispatch from getValueAt() and indexExists() - private HashDb getDBAt(int rowIndex){ - if (xmlHandle.getNSRLSet() != null) { - if(rowIndex == 0) { - return xmlHandle.getNSRLSet(); - } else { - return xmlHandle.getKnownBadSets().get(rowIndex-1); - } - } else { - return xmlHandle.getKnownBadSets().get(rowIndex); - } - } - - // Selects the row with the given name - private void selectRowByName(String name) { - HashDb NSRL = xmlHandle.getNSRLSet(); - List bad = xmlHandle.getKnownBadSets(); - if(NSRL != null) { - if(NSRL.getName().equals(name)) { - setSelection(0); - } else { - for(int i=0; i getColumnClass(int c) { - return getValueAt(0, c).getClass(); - } - - void resync() { - fireTableDataChanged(); - } - } - - static void setButtonFromIndexStatus(JButton theButton, JLabel theLabel, IndexStatus status) { - theLabel.setText(status.message()); - switch (status) { - case INDEX_OUTDATED: - theButton.setText("Re-index"); - theLabel.setForeground(Color.black); - theButton.setEnabled(true); - break; - case INDEX_CURRENT: - theButton.setText("Re-index"); - theLabel.setForeground(Color.black); - theButton.setEnabled(true); - break; - case NO_INDEX: - theButton.setText("Index"); - theLabel.setForeground(Color.red); - theButton.setEnabled(true); - break; - case INDEXING: - theButton.setText("Indexing"); - theLabel.setForeground(Color.black); - theButton.setEnabled(false); - break; - default: - theButton.setText("Index"); - theLabel.setForeground(Color.black); - theButton.setEnabled(false); - } - if (IngestManager.getDefault().isIngestRunning()) { - theButton.setEnabled(false); - } - } - - private int getSelection() { - return hashSetTable.getSelectionModel().getMinSelectionIndex(); - } - - private void setSelection(int index) { - if(index >= 0 && index < hashSetTable.getRowCount()) { - hashSetTable.getSelectionModel().setSelectionInterval(index, index); - } - } - void resync() { - int index = getSelection(); - this.hashSetTableModel.resync(); - setSelection(index); - } - -} \ No newline at end of file diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManager.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManager.java new file mode 100755 index 0000000000..a799068425 --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbManager.java @@ -0,0 +1,859 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.hashdatabase; + +import java.beans.PropertyChangeEvent; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.XMLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.FileUtils; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.HashInfo; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This class implements a singleton that manages the set of hash databases + * used to classify files as unknown, known or known bad. + */ +public class HashDbManager implements PropertyChangeListener { + + private static final String ROOT_ELEMENT = "hash_sets"; + private static final String SET_ELEMENT = "hash_set"; + private static final String SET_NAME_ATTRIBUTE = "name"; + private static final String SET_TYPE_ATTRIBUTE = "type"; + private static final String SEARCH_DURING_INGEST_ATTRIBUTE = "use_for_ingest"; + private static final String SEND_INGEST_MESSAGES_ATTRIBUTE = "show_inbox_messages"; + private static final String PATH_ELEMENT = "hash_set_path"; + private static final String LEGACY_PATH_NUMBER_ATTRIBUTE = "number"; + private static final String CONFIG_FILE_NAME = "hashsets.xml"; + private static final String XSD_FILE_NAME = "HashsetsSchema.xsd"; + private static final String ENCODING = "UTF-8"; + private static final String ALWAYS_CALCULATE_HASHES_ELEMENT = "hash_calculate"; + private static final String VALUE_ATTRIBUTE = "value"; + private static final String HASH_DATABASE_FILE_EXTENSON = "kdb"; + private static final String LEGACY_INDEX_FILE_EXTENSION = "-md5.idx"; + private static HashDbManager instance = null; + private final String configFilePath = PlatformUtil.getUserConfigDirectory() + File.separator + CONFIG_FILE_NAME; + private List knownHashSets = new ArrayList<>(); + private List knownBadHashSets = new ArrayList<>(); + private Set hashSetNames = new HashSet<>(); + private Set hashSetPaths = new HashSet<>(); + private boolean alwaysCalculateHashes = true; + + /** + * Gets the singleton instance of this class. + */ + public static synchronized HashDbManager getInstance() { + if (instance == null) { + instance = new HashDbManager(); + } + return instance; + } + + private HashDbManager() { + if (hashSetsConfigurationFileExists()) { + readHashSetsConfigurationFromDisk(); + } + } + + /** + * Gets the extension, without the dot separator, that the SleuthKit requires + * for the hash database files that combine a database and an index and can + * therefore be updated. + */ + static String getHashDatabaseFileExtension() { + return HASH_DATABASE_FILE_EXTENSON; + } + + class DuplicateHashSetNameException extends Exception { + private DuplicateHashSetNameException(String hashSetName) { + super("The hash set name '"+ hashSetName +"' has already been used for another hash database."); + } + } + + class HashDatabaseDoesNotExistException extends Exception { + private HashDatabaseDoesNotExistException(String path) { + super("No hash database found at\n" + path); + } + } + + class HashDatabaseFileAlreadyExistsException extends Exception { + private HashDatabaseFileAlreadyExistsException(String path) { + super("A file already exists at\n" + path); + } + } + + class HashDatabaseAlreadyAddedException extends Exception { + private HashDatabaseAlreadyAddedException(String path) { + super("The hash database at\n" + path + "\nhas already been created or imported."); + } + } + + class IllegalHashDatabaseFileNameExtensionException extends Exception { + private IllegalHashDatabaseFileNameExtensionException() { + super("The hash database file name must have a ." + getHashDatabaseFileExtension() + " extension."); + } + } + + /** + * Adds an existing hash database to the set of hash databases used to classify files as known or known bad. + * Does not save the configuration - the configuration is only saved on demand to support cancellation of + * configuration panels. + * @param hashSetName Name used to represent the hash database in user interface components. + * @param path Full path to either a hash database file or a hash database index file. + * @param searchDuringIngest A flag indicating whether or not the hash database should be searched during ingest. + * @param sendIngestMessages A flag indicating whether hash set hit messages should be sent as ingest messages. + * @param knownFilesType The classification to apply to files whose hashes are found in the hash database. + * @return A HashDb representing the hash database. + * @throws HashDatabaseDoesNotExistException, DuplicateHashSetNameException, HashDatabaseAlreadyAddedException, TskCoreException + */ + synchronized HashDb addExistingHashDatabase(String hashSetName, String path, boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType) throws HashDatabaseDoesNotExistException, DuplicateHashSetNameException, HashDatabaseAlreadyAddedException, TskCoreException { + if (!new File(path).exists()) { + throw new HashDatabaseDoesNotExistException(path); + } + + if (hashSetPaths.contains(path)) { + throw new HashDatabaseAlreadyAddedException(path); + } + + if (hashSetNames.contains(hashSetName)) { + throw new DuplicateHashSetNameException(hashSetName); + } + + return addHashDatabase(SleuthkitJNI.openHashDatabase(path), hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType); + } + + /** + * Adds a new hash database to the set of hash databases used to classify files as known or known bad. + * Does not save the configuration - the configuration is only saved on demand to support cancellation of + * configuration panels. + * @param hashSetName Hash set name used to represent the hash database in user interface components. + * @param path Full path to the database file to be created. + * @param searchDuringIngest A flag indicating whether or not the hash database should be searched during ingest. + * @param sendIngestMessages A flag indicating whether hash set hit messages should be sent as ingest messages. + * @param knownFilesType The classification to apply to files whose hashes are found in the hash database. + * @return A HashDb representing the hash database. + * @throws TskCoreException + */ + synchronized HashDb addNewHashDatabase(String hashSetName, String path, boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType) throws HashDatabaseFileAlreadyExistsException, IllegalHashDatabaseFileNameExtensionException, DuplicateHashSetNameException, HashDatabaseAlreadyAddedException, TskCoreException { + File file = new File(path); + if (file.exists()) { + throw new HashDatabaseFileAlreadyExistsException(path); + } + if (!FilenameUtils.getExtension(file.getName()).equalsIgnoreCase(HASH_DATABASE_FILE_EXTENSON)) { + throw new IllegalHashDatabaseFileNameExtensionException(); + } + + if (hashSetPaths.contains(path)) { + throw new HashDatabaseAlreadyAddedException(path); + } + + if (hashSetNames.contains(hashSetName)) { + throw new DuplicateHashSetNameException(hashSetName); + } + + return addHashDatabase(SleuthkitJNI.createHashDatabase(path), hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType); + } + + private HashDb addHashDatabase(int handle, String hashSetName, boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType) throws TskCoreException { + // Wrap an object around the handle. + HashDb hashDb = new HashDb(handle, hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType); + + // Get the indentity data before updating the collections since the + // accessor methods may throw. + String databasePath = hashDb.getDatabasePath(); + String indexPath = hashDb.getIndexPath(); + + // Update the collections used to ensure that hash set names are unique + // and the same database is not added to the configuration more than once. + hashSetNames.add(hashDb.getHashSetName()); + if (!databasePath.equals("None")) { + hashSetPaths.add(databasePath); + } + if (!indexPath.equals("None")) { + hashSetPaths.add(indexPath); + } + + // Add the hash database to the appropriate collection for its type. + if (hashDb.getKnownFilesType() == HashDb.KnownFilesType.KNOWN) { + knownHashSets.add(hashDb); + } + else { + knownBadHashSets.add(hashDb); + } + + return hashDb; + } + + synchronized void indexHashDatabase(HashDb hashDb, boolean deleteIndexFile) { + hashDb.addPropertyChangeListener(this); + HashDbIndexer creator = new HashDbIndexer(hashDb, deleteIndexFile); + creator.execute(); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (event.getPropertyName().equals(HashDb.Event.INDEXING_DONE.name())) { + HashDb hashDb = (HashDb)event.getNewValue(); + if (null != hashDb) { + try { + String indexPath = hashDb.getIndexPath(); + if (!indexPath.equals("None")) { + hashSetPaths.add(indexPath); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDb.getHashSetName() + " hash database after indexing", ex); + } + } + } + } + + /** + * Removes a hash database from the set of hash databases used to classify + * files as known or known bad. Does not save the configuration - the + * configuration is only saved on demand to support cancellation of + * configuration panels. + * @throws TskCoreException + */ + synchronized void removeHashDatabase(HashDb hashDb) { + // Remove the database from whichever hash set list it occupies, + // and remove its hash set name from the hash set used to ensure unique + // hash set names are used, before undertaking These operations will succeed and constitute + // a mostly effective removal, even if the subsequent operations fail. + knownHashSets.remove(hashDb); + knownBadHashSets.remove(hashDb); + hashSetNames.remove(hashDb.getHashSetName()); + + // Now undertake the operations that could throw. + try { + hashSetPaths.remove(hashDb.getIndexPath()); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDb.getHashSetName() + " hash database when removing the database", ex); + } + try { + if (!hashDb.hasIndexOnly()) { + hashSetPaths.remove(hashDb.getDatabasePath()); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting database path of " + hashDb.getHashSetName() + " hash database when removing the database", ex); + } + try { + hashDb.close(); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + hashDb.getHashSetName() + " hash database when removing the database", ex); + } + } + + /** + * Gets all of the hash databases used to classify files as known or known bad. + * @return A list, possibly empty, of hash databases. + */ + public synchronized List getAllHashSets() { + List hashDbs = new ArrayList<>(); + hashDbs.addAll(knownHashSets); + hashDbs.addAll(knownBadHashSets); + return hashDbs; + } + + /** + * Gets all of the hash databases used to classify files as known. + * @return A list, possibly empty, of hash databases. + */ + public synchronized List getKnownFileHashSets() { + List hashDbs = new ArrayList<>(); + hashDbs.addAll(knownHashSets); + return hashDbs; + } + + /** + * Gets all of the hash databases used to classify files as known bad. + * @return A list, possibly empty, of hash databases. + */ + public synchronized List getKnownBadFileHashSets() { + List hashDbs = new ArrayList<>(); + hashDbs.addAll(knownBadHashSets); + return hashDbs; + } + + /** + * Gets all of the hash databases that accept updates. + * @return A list, possibly empty, of hash databases. + */ + public synchronized List getUpdateableHashSets() { + List updateableDbs = getUpdateableHashSets(knownHashSets); + updateableDbs.addAll(getUpdateableHashSets(knownBadHashSets)); + return updateableDbs; + } + + private List getUpdateableHashSets(List hashDbs) { + ArrayList updateableDbs = new ArrayList<>(); + for (HashDb db : hashDbs) { + try { + if (db.isUpdateable()) { + updateableDbs.add(db); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error checking updateable status of " + db.getHashSetName() + " hash database", ex); + } + } + return updateableDbs; + } + + /** + * Sets the value for the flag that indicates whether hashes should be calculated + * for content even if no hash databases are configured. + */ + synchronized void setAlwaysCalculateHashes(boolean alwaysCalculateHashes) { + this.alwaysCalculateHashes = alwaysCalculateHashes; + } + + /** + * Gets the flag that indicates whether hashes should be calculated + * for content even if no hash databases are configured. + */ + synchronized boolean getAlwaysCalculateHashes() { + return alwaysCalculateHashes; + } + + /** + * Saves the hash sets configuration. Note that the configuration is only + * saved on demand to support cancellation of configuration panels. + * @return True on success, false otherwise. + */ + public synchronized boolean save() { + return writeHashSetConfigurationToDisk(); + } + + /** + * Restores the last saved hash sets configuration. This supports + * cancellation of configuration panels. + */ + public synchronized void loadLastSavedConfiguration() { + closeHashDatabases(knownHashSets); + closeHashDatabases(knownBadHashSets); + hashSetNames.clear(); + hashSetPaths.clear(); + + if (hashSetsConfigurationFileExists()) { + readHashSetsConfigurationFromDisk(); + } + } + + private void closeHashDatabases(List hashDatabases) { + for (HashDb database : hashDatabases) { + try { + database.close(); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + database.getHashSetName() + " hash database", ex); + } + } + hashDatabases.clear(); + } + + private boolean writeHashSetConfigurationToDisk() { + boolean success = false; + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + Element rootEl = doc.createElement(ROOT_ELEMENT); + doc.appendChild(rootEl); + + writeHashDbsToDisk(doc, rootEl, knownHashSets); + writeHashDbsToDisk(doc, rootEl, knownBadHashSets); + + String calcValue = Boolean.toString(alwaysCalculateHashes); + Element setCalc = doc.createElement(ALWAYS_CALCULATE_HASHES_ELEMENT); + setCalc.setAttribute(VALUE_ATTRIBUTE, calcValue); + rootEl.appendChild(setCalc); + + success = XMLUtil.saveDoc(HashDbManager.class, configFilePath, ENCODING, doc); + } + catch (ParserConfigurationException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error saving hash databases", ex); + } + return success; + } + + private static void writeHashDbsToDisk(Document doc, Element rootEl, List hashDbs) { + for (HashDb db : hashDbs) { + // Get the path for the hash database before writing anything, in + // case an exception is thrown. + String path; + try { + if (db.hasIndexOnly()) { + path = db.getIndexPath(); + } + else { + path = db.getDatabasePath(); + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting path of hash database " + db.getHashSetName() + ", discarding from hash database configuration", ex); + continue; + } + + Element setElement = doc.createElement(SET_ELEMENT); + setElement.setAttribute(SET_NAME_ATTRIBUTE, db.getHashSetName()); + setElement.setAttribute(SET_TYPE_ATTRIBUTE, db.getKnownFilesType().toString()); + setElement.setAttribute(SEARCH_DURING_INGEST_ATTRIBUTE, Boolean.toString(db.getSearchDuringIngest())); + setElement.setAttribute(SEND_INGEST_MESSAGES_ATTRIBUTE, Boolean.toString(db.getSendIngestMessages())); + Element pathElement = doc.createElement(PATH_ELEMENT); + pathElement.setTextContent(path); + setElement.appendChild(pathElement); + rootEl.appendChild(setElement); + } + } + + private boolean hashSetsConfigurationFileExists() { + File f = new File(configFilePath); + return f.exists() && f.canRead() && f.canWrite(); + } + + private boolean readHashSetsConfigurationFromDisk() { + boolean updatedSchema = false; + + // Open the XML document that implements the configuration file. + final Document doc = XMLUtil.loadDoc(HashDbManager.class, configFilePath, XSD_FILE_NAME); + if (doc == null) { + return false; + } + + // Get the root element. + Element root = doc.getDocumentElement(); + if (root == null) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading hash sets: invalid file format."); + return false; + } + + // Get the hash set elements. + NodeList setsNList = root.getElementsByTagName(SET_ELEMENT); + int numSets = setsNList.getLength(); + if(numSets == 0) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.WARNING, "No element hash_set exists."); + } + + // Create HashDb objects for each hash set element. Skip to the next hash database if the definition of + // a particular hash database is not well-formed. + String attributeErrorMessage = " attribute was not set for hash_set at index {0}, cannot make instance of HashDb class"; + String elementErrorMessage = " element was not set for hash_set at index {0}, cannot make instance of HashDb class"; + for (int i = 0; i < numSets; ++i) { + Element setEl = (Element) setsNList.item(i); + + String hashSetName = setEl.getAttribute(SET_NAME_ATTRIBUTE); + if (hashSetName.isEmpty()) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, SET_NAME_ATTRIBUTE + attributeErrorMessage, i); + continue; + } + + // Handle configurations saved before duplicate hash set names were not permitted. + if (hashSetNames.contains(hashSetName)) { + int suffix = 0; + String newHashSetName; + do { + ++suffix; + newHashSetName = hashSetName + suffix; + } + while (hashSetNames.contains(newHashSetName)); + JOptionPane.showMessageDialog(null, "Duplicate hash set name " + hashSetName + " found.\nReplacing with " + newHashSetName + ".", "Open Hash Database Error", JOptionPane.ERROR_MESSAGE); + hashSetName = newHashSetName; + } + + String knownFilesType = setEl.getAttribute(SET_TYPE_ATTRIBUTE); + if(knownFilesType.isEmpty()) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, SET_TYPE_ATTRIBUTE + attributeErrorMessage, i); + continue; + } + + // Handle legacy known files types. + if (knownFilesType.equals("NSRL")) { + knownFilesType = HashDb.KnownFilesType.KNOWN.toString(); + updatedSchema = true; + } + + final String searchDuringIngest = setEl.getAttribute(SEARCH_DURING_INGEST_ATTRIBUTE); + if (searchDuringIngest.isEmpty()) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, SEARCH_DURING_INGEST_ATTRIBUTE + attributeErrorMessage, i); + continue; + } + Boolean seearchDuringIngestFlag = Boolean.parseBoolean(searchDuringIngest); + + final String sendIngestMessages = setEl.getAttribute(SEND_INGEST_MESSAGES_ATTRIBUTE); + if (searchDuringIngest.isEmpty()) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, SEND_INGEST_MESSAGES_ATTRIBUTE + attributeErrorMessage, i); + continue; + } + Boolean sendIngestMessagesFlag = Boolean.parseBoolean(sendIngestMessages); + + String dbPath; + NodeList pathsNList = setEl.getElementsByTagName(PATH_ELEMENT); + if (pathsNList.getLength() > 0) { + Element pathEl = (Element) pathsNList.item(0); // Shouldn't be more than one. + + // Check for legacy path number attribute. + String legacyPathNumber = pathEl.getAttribute(LEGACY_PATH_NUMBER_ATTRIBUTE); + if (null != legacyPathNumber && !legacyPathNumber.isEmpty()) { + updatedSchema = true; + } + + dbPath = pathEl.getTextContent(); + if (dbPath.isEmpty()) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, PATH_ELEMENT + elementErrorMessage, i); + continue; + } + } + else { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, PATH_ELEMENT + elementErrorMessage, i); + continue; + } + dbPath = getValidFilePath(hashSetName, dbPath); + + if (null != dbPath) { + try { + addExistingHashDatabase(hashSetName, dbPath, seearchDuringIngestFlag, sendIngestMessagesFlag, HashDb.KnownFilesType.valueOf(knownFilesType)); + } + catch (HashDatabaseDoesNotExistException | DuplicateHashSetNameException | HashDatabaseAlreadyAddedException | TskCoreException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash database", ex); + JOptionPane.showMessageDialog(null, "Unable to open " + dbPath + " hash database.", "Open Hash Database Error", JOptionPane.ERROR_MESSAGE); + } + } + else { + Logger.getLogger(HashDbManager.class.getName()).log(Level.WARNING, "No valid path for hash_set at index {0}, cannot make instance of HashDb class", i); + } + } + + // Get the element that stores the always calculate hashes flag. + NodeList calcList = root.getElementsByTagName(ALWAYS_CALCULATE_HASHES_ELEMENT); + if (calcList.getLength() > 0) { + Element calcEl = (Element) calcList.item(0); // Shouldn't be more than one. + final String value = calcEl.getAttribute(VALUE_ATTRIBUTE); + alwaysCalculateHashes = Boolean.parseBoolean(value); + } + else { + Logger.getLogger(HashDbManager.class.getName()).log(Level.WARNING, " element "); + alwaysCalculateHashes = true; + } + + if (updatedSchema) { + String backupFilePath = configFilePath + ".v1_backup"; + String messageBoxTitle = "Configuration File Format Changed"; + String baseMessage = "The format of the hash database configuration file has been updated."; + try { + FileUtils.copyFile(new File(configFilePath), new File (backupFilePath)); + JOptionPane.showMessageDialog(null, baseMessage + "\nA backup copy of the old configuration has been saved as\n" + backupFilePath, messageBoxTitle, JOptionPane.INFORMATION_MESSAGE); + } + catch (IOException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.WARNING, "Failed to save backup of old format configuration file to " + backupFilePath, ex); + JOptionPane.showMessageDialog(null, baseMessage, messageBoxTitle, JOptionPane.INFORMATION_MESSAGE); + } + + writeHashSetConfigurationToDisk(); + } + + return true; + } + + private String getValidFilePath(String hashSetName, String configuredPath) { + // Check the configured path. + File database = new File(configuredPath); + if (database.exists()) { + return configuredPath; + } + + // Try a path that could be in an older version of the configuration file. + String legacyPath = configuredPath + LEGACY_INDEX_FILE_EXTENSION; + database = new File(legacyPath); + if (database.exists()) { + return legacyPath; + } + + // Give the user an opportunity to find the desired file. + String newPath = null; + if (JOptionPane.showConfirmDialog(null, "Database " + hashSetName + " could not be found at location\n" + configuredPath + "\nWould you like to search for the file?", "Missing Database", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + newPath = searchForFile(); + if (null != newPath && !newPath.isEmpty()) { + database = new File(newPath); + if (!database.exists()) { + newPath = null; + } + } + } + return newPath; + } + + private String searchForFile() { + String filePath = null; + JFileChooser fc = new JFileChooser(); + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + String[] EXTENSION = new String[] { "txt", "idx", "hash", "Hash", "kdb" }; + FileNameExtensionFilter filter = new FileNameExtensionFilter("Hash Database File", EXTENSION); + fc.setFileFilter(filter); + fc.setMultiSelectionEnabled(false); + if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + File f = fc.getSelectedFile(); + try { + filePath = f.getCanonicalPath(); + } + catch (IOException ex) { + Logger.getLogger(HashDbManager.class.getName()).log(Level.WARNING, "Couldn't get selected file path", ex); + } + } + return filePath; + } + + /** + * Instances of this class represent hash databases used to classify files as known or know bad. + */ + public static class HashDb { + + /** + * Indicates how files with hashes stored in a particular hash database + * object should be classified. + */ + public enum KnownFilesType{ + KNOWN("Known"), + KNOWN_BAD("Known Bad"); + + private String displayName; + + private KnownFilesType(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return this.displayName; + } + } + + /** + * Property change events published by hash database objects. + */ + public enum Event { + INDEXING_DONE + } + + private int handle; + private String hashSetName; + private boolean searchDuringIngest; + private boolean sendIngestMessages; + private KnownFilesType knownFilesType; + private boolean indexing; + private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + private HashDb(int handle, String hashSetName, boolean useForIngest, boolean sendHitMessages, KnownFilesType knownFilesType) { + this.handle = handle; + this.hashSetName = hashSetName; + this.searchDuringIngest = useForIngest; + this.sendIngestMessages = sendHitMessages; + this.knownFilesType = knownFilesType; + this.indexing = false; + } + + /** + * Adds a listener for the events defined in HashDb.Event. + */ + public void addPropertyChangeListener(PropertyChangeListener pcl) { + propertyChangeSupport.addPropertyChangeListener(pcl); + } + + /** + * Removes a listener for the events defined in HashDb.Event. + */ + public void removePropertyChangeListener(PropertyChangeListener pcl) { + propertyChangeSupport.removePropertyChangeListener(pcl); + } + + public String getHashSetName() { + return hashSetName; + } + + public String getDatabasePath() throws TskCoreException { + return SleuthkitJNI.getHashDatabasePath(handle); + } + + public String getIndexPath() throws TskCoreException { + return SleuthkitJNI.getHashDatabaseIndexPath(handle); + } + + public KnownFilesType getKnownFilesType() { + return knownFilesType; + } + + public boolean getSearchDuringIngest() { + return searchDuringIngest; + } + + void setSearchDuringIngest(boolean useForIngest) { + this.searchDuringIngest = useForIngest; + } + + public boolean getSendIngestMessages() { + return sendIngestMessages; + } + + void setSendIngestMessages(boolean showInboxMessages) { + this.sendIngestMessages = showInboxMessages; + } + + /** + * Indicates whether the hash database accepts updates. + * @return True if the database accepts updates, false otherwise. + */ + public boolean isUpdateable() throws TskCoreException { + return SleuthkitJNI.isUpdateableHashDatabase(this.handle); + } + + /** + * Adds hashes of content (if calculated) to the hash database. + * @param content The content for which the calculated hashes, if any, are to be added to the hash database. + * @throws TskCoreException + */ + public void addHashes(Content content) throws TskCoreException { + addHashes(content, null); + } + + /** + * Adds hashes of content (if calculated) to the hash database. + * @param content The content for which the calculated hashes, if any, are to be added to the hash database. + * @param comment A comment to associate with the hashes, e.g., the name of the case in which the content was encountered. + * @throws TskCoreException + */ + public void addHashes(Content content, String comment) throws TskCoreException { + // TODO: This only works for AbstractFiles and MD5 hashes at present. + assert content instanceof AbstractFile; + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile)content; + if (null != file.getMd5Hash()) { + SleuthkitJNI.addToHashDatabase(null, file.getMd5Hash(), null, null, comment, handle); + } + } + } + + public boolean hasMd5HashOf(Content content) throws TskCoreException { + boolean result = false; + assert content instanceof AbstractFile; + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile)content; + if (null != file.getMd5Hash()) { + result = SleuthkitJNI.lookupInHashDatabase(file.getMd5Hash(), handle); + } + } + return result; + } + + public HashInfo lookUp(Content content) throws TskCoreException { + HashInfo result = null; + // TODO: This only works for AbstractFiles and MD5 hashes at present. + assert content instanceof AbstractFile; + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile)content; + if (null != file.getMd5Hash()) { + result = SleuthkitJNI.lookupInHashDatabaseVerbose(file.getMd5Hash(), handle); + } + } + return result; + } + + boolean hasLookupIndex() throws TskCoreException { + return SleuthkitJNI.hashDatabaseHasLookupIndex(handle); + } + + boolean hasIndexOnly() throws TskCoreException { + return SleuthkitJNI.hashDatabaseHasLegacyLookupIndexOnly(handle); + } + + boolean canBeReIndexed() throws TskCoreException { + return SleuthkitJNI.hashDatabaseCanBeReindexed(handle); + } + + boolean isIndexing() { + return indexing; + } + + private void close() throws TskCoreException { + SleuthkitJNI.closeHashDatabase(handle); + } + } + + private class HashDbIndexer extends SwingWorker { + private ProgressHandle progress = null; + private HashDb hashDb = null; + private boolean deleteIndexFile = false; + + HashDbIndexer(HashDb hashDb, boolean deleteIndexFile) { + this.hashDb = hashDb; + this.deleteIndexFile = deleteIndexFile; + }; + + @Override + protected Object doInBackground() { + hashDb.indexing = true; + progress = ProgressHandleFactory.createHandle("Indexing " + hashDb.hashSetName); + progress.start(); + progress.switchToIndeterminate(); + try { + SleuthkitJNI.createLookupIndexForHashDatabase(hashDb.handle, deleteIndexFile); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDb.class.getName()).log(Level.SEVERE, "Error indexing hash database", ex); + JOptionPane.showMessageDialog(null, "Error indexing " + hashDb.getHashSetName() + " hash database.", "Hash Database Indexing Error", JOptionPane.ERROR_MESSAGE); + } + return null; + } + + @Override + protected void done() { + hashDb.indexing = false; + progress.finish(); + hashDb.propertyChangeSupport.firePropertyChange(HashDb.Event.INDEXING_DONE.toString(), null, hashDb); + } + } +} \ No newline at end of file diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.form b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.form similarity index 52% rename from HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.form rename to HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.form index dc1fc4d1c0..75a4788a57 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.form +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.form @@ -19,19 +19,16 @@ + - - - - - - - + + + - - + + @@ -40,17 +37,16 @@ - - - - - + + - - - + - + + + + + @@ -71,7 +67,7 @@ - + @@ -82,33 +78,60 @@ - + - + - + - + - + - + - + - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.java new file mode 100644 index 0000000000..d6dbd0a8f7 --- /dev/null +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimpleConfigPanel.java @@ -0,0 +1,257 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 - 2013 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.hashdatabase; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; + +/** + * Instances of this class provide a simplified UI for managing the hash sets configuration. + */ +public class HashDbSimpleConfigPanel extends javax.swing.JPanel { + + private HashDatabasesTableModel knownTableModel; + private HashDatabasesTableModel knownBadTableModel; + + public HashDbSimpleConfigPanel() { + knownTableModel = new HashDatabasesTableModel(HashDbManager.HashDb.KnownFilesType.KNOWN); + knownBadTableModel = new HashDatabasesTableModel(HashDbManager.HashDb.KnownFilesType.KNOWN_BAD); + initComponents(); + customizeComponents(); + } + + private void customizeComponents() { + customizeHashDbsTable(jScrollPane1, knownHashTable, knownTableModel); + customizeHashDbsTable(jScrollPane2, knownBadHashTable, knownBadTableModel); + alwaysCalcHashesCheckbox.setSelected(HashDbManager.getInstance().getAlwaysCalculateHashes()); + + // Add a listener to the always calculate hashes checkbox component. + // The listener passes the user's selection on to the hash database manager. + alwaysCalcHashesCheckbox.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + HashDbManager.getInstance().setAlwaysCalculateHashes(alwaysCalcHashesCheckbox.isSelected()); + } + }); + + load(); + } + + private void customizeHashDbsTable(JScrollPane scrollPane, JTable table, HashDatabasesTableModel tableModel) { + table.setModel(tableModel); + table.setTableHeader(null); + table.setRowSelectionAllowed(false); + + final int width1 = scrollPane.getPreferredSize().width; + knownHashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + TableColumn column; + for (int i = 0; i < table.getColumnCount(); i++) { + column = table.getColumnModel().getColumn(i); + if (i == 0) { + column.setPreferredWidth(((int) (width1 * 0.07))); + } else { + column.setPreferredWidth(((int) (width1 * 0.92))); + } + } + } + + public void load() { + knownTableModel.load(); + knownBadTableModel.load(); + } + + public void store() { + HashDbManager.getInstance().save(); + } + + private class HashDatabasesTableModel extends AbstractTableModel { + private final HashDbManager.HashDb.KnownFilesType hashDatabasesType; + private List hashDatabases; + + HashDatabasesTableModel(HashDbManager.HashDb.KnownFilesType hashDatabasesType) { + this.hashDatabasesType = hashDatabasesType; + getHashDatabases(); + } + + private void getHashDatabases() { + if (HashDbManager.HashDb.KnownFilesType.KNOWN == hashDatabasesType) { + hashDatabases = HashDbManager.getInstance().getKnownFileHashSets(); + } + else { + hashDatabases = HashDbManager.getInstance().getKnownBadFileHashSets(); + } + } + + private void load() { + getHashDatabases(); + fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return hashDatabases.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + HashDb db = hashDatabases.get(rowIndex); + if (columnIndex == 0) { + return db.getSearchDuringIngest(); + } else { + return db.getHashSetName(); + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return !IngestManager.getDefault().isIngestRunning() && columnIndex == 0; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if(columnIndex == 0) { + HashDb db = hashDatabases.get(rowIndex); + boolean dbHasIndex = false; + try { + dbHasIndex = db.hasLookupIndex(); + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbSimpleConfigPanel.class.getName()).log(Level.SEVERE, "Error getting info for " + db.getHashSetName() + " hash database", ex); + } + if(((Boolean) getValueAt(rowIndex, columnIndex)) || dbHasIndex) { + db.setSearchDuringIngest((Boolean) aValue); + } + else { + JOptionPane.showMessageDialog(HashDbSimpleConfigPanel.this, "Hash databases must be indexed before they can be used for ingest"); + } + } + } + + @Override + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jScrollPane1 = new javax.swing.JScrollPane(); + knownHashTable = new javax.swing.JTable(); + knownBadHashDbsLabel = new javax.swing.JLabel(); + knownHashDbsLabel = new javax.swing.JLabel(); + alwaysCalcHashesCheckbox = new javax.swing.JCheckBox(); + jScrollPane2 = new javax.swing.JScrollPane(); + knownBadHashTable = new javax.swing.JTable(); + + jScrollPane1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + knownHashTable.setBackground(new java.awt.Color(240, 240, 240)); + knownHashTable.setShowHorizontalLines(false); + knownHashTable.setShowVerticalLines(false); + jScrollPane1.setViewportView(knownHashTable); + + knownBadHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashDbSimpleConfigPanel.class, "HashDbSimpleConfigPanel.knownBadHashDbsLabel.text")); // NOI18N + + knownHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashDbSimpleConfigPanel.class, "HashDbSimpleConfigPanel.knownHashDbsLabel.text")); // NOI18N + + alwaysCalcHashesCheckbox.setText(org.openide.util.NbBundle.getMessage(HashDbSimpleConfigPanel.class, "HashDbSimpleConfigPanel.alwaysCalcHashesCheckbox.text")); // NOI18N + + jScrollPane2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + knownBadHashTable.setBackground(new java.awt.Color(240, 240, 240)); + knownBadHashTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + + } + )); + knownBadHashTable.setShowHorizontalLines(false); + knownBadHashTable.setShowVerticalLines(false); + jScrollPane2.setViewportView(knownBadHashTable); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(knownHashDbsLabel) + .addComponent(knownBadHashDbsLabel)) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(alwaysCalcHashesCheckbox, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(knownHashDbsLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(knownBadHashDbsLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(alwaysCalcHashesCheckbox) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox alwaysCalcHashesCheckbox; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JLabel knownBadHashDbsLabel; + private javax.swing.JTable knownBadHashTable; + private javax.swing.JLabel knownHashDbsLabel; + private javax.swing.JTable knownHashTable; + // End of variables declaration//GEN-END:variables +} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.java deleted file mode 100644 index 9dfee56671..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSimplePanel.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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. - */ - -/* - * HashDbSimplePanel.java - * - * Created on May 7, 2012, 10:38:26 AM - */ -package org.sleuthkit.autopsy.hashdatabase; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.List; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JOptionPane; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableColumn; -import org.sleuthkit.autopsy.ingest.IngestManager; - -/** - * - * @author dfickling - */ -public class HashDbSimplePanel extends javax.swing.JPanel { - - private static final Logger logger = Logger.getLogger(HashDbSimplePanel.class.getName()); - private HashTableModel knownBadTableModel; - private HashDb nsrl; - - /** Creates new form HashDbSimplePanel */ - public HashDbSimplePanel() { - knownBadTableModel = new HashTableModel(); - initComponents(); - customizeComponents(); - } - - private void reloadCalc() { - final HashDbXML xmlHandle = HashDbXML.getCurrent(); - final HashDb nsrlDb = xmlHandle.getNSRLSet(); - final boolean nsrlUsed = - nsrlDb != null - && nsrlDb.getUseForIngest()== true - && nsrlDb.indexExists(); - final List knowns = xmlHandle.getKnownBadSets(); - final boolean knownExists = !knowns.isEmpty(); - boolean knownUsed = false; - if (knownExists) { - for (HashDb known : knowns) { - if (known.getUseForIngest() == true) { - knownUsed = true; - break; - } - } - } - - if(! nsrlUsed - && ! knownUsed ) { - calcHashesButton.setEnabled(true); - calcHashesButton.setSelected(true); - xmlHandle.setCalculate(true); - } else { - calcHashesButton.setEnabled(false); - calcHashesButton.setSelected(false); - xmlHandle.setCalculate(false); - } - } - - private void customizeComponents() { - final HashDbXML xmlHandle = HashDbXML.getCurrent(); - calcHashesButton.addActionListener( new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - if(calcHashesButton.isSelected()) { - xmlHandle.setCalculate(true); - } else { - xmlHandle.setCalculate(false); - } - } - - }); - - notableHashTable.setModel(knownBadTableModel); - - notableHashTable.setTableHeader(null); - notableHashTable.setRowSelectionAllowed(false); - //customize column witdhs - final int width1 = jScrollPane1.getPreferredSize().width; - notableHashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); - TableColumn column1 = null; - for (int i = 0; i < notableHashTable.getColumnCount(); i++) { - column1 = notableHashTable.getColumnModel().getColumn(i); - if (i == 0) { - column1.setPreferredWidth(((int) (width1 * 0.07))); - } else { - column1.setPreferredWidth(((int) (width1 * 0.92))); - } - } - - reloadSets(); - } - - /** This method is called from within the constructor to - * initialize the form. - * WARNING: Do NOT modify this code. The content of this method is - * always regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - jScrollPane1 = new javax.swing.JScrollPane(); - notableHashTable = new javax.swing.JTable(); - jLabel1 = new javax.swing.JLabel(); - nsrlDbLabel = new javax.swing.JLabel(); - calcHashesButton = new javax.swing.JCheckBox(); - nsrlDbLabelVal = new javax.swing.JLabel(); - - jScrollPane1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - - notableHashTable.setBackground(new java.awt.Color(240, 240, 240)); - notableHashTable.setShowHorizontalLines(false); - notableHashTable.setShowVerticalLines(false); - jScrollPane1.setViewportView(notableHashTable); - - jLabel1.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.jLabel1.text")); // NOI18N - - nsrlDbLabel.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.nsrlDbLabel.text")); // NOI18N - - calcHashesButton.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.calcHashesButton.text")); // NOI18N - - nsrlDbLabelVal.setText(org.openide.util.NbBundle.getMessage(HashDbSimplePanel.class, "HashDbSimplePanel.nsrlDbLabelVal.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(nsrlDbLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(nsrlDbLabelVal, javax.swing.GroupLayout.PREFERRED_SIZE, 105, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING)) - .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(calcHashesButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(7, 7, 7) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nsrlDbLabel) - .addComponent(nsrlDbLabelVal)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 73, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(calcHashesButton) - .addContainerGap()) - ); - }// //GEN-END:initComponents - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JCheckBox calcHashesButton; - private javax.swing.JLabel jLabel1; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JTable notableHashTable; - private javax.swing.JLabel nsrlDbLabel; - private javax.swing.JLabel nsrlDbLabelVal; - // End of variables declaration//GEN-END:variables - - private void reloadSets() { - nsrl = HashDbXML.getCurrent().getNSRLSet(); - - if (nsrl == null || nsrl.getUseForIngest() == false) { - nsrlDbLabelVal.setText("Disabled"); - } - else if (nsrl.indexExists() == false) { - nsrlDbLabelVal.setText("Disabled (No index)"); - } - else { - nsrlDbLabelVal.setText("Enabled"); - } - - reloadCalc(); - - knownBadTableModel.resync(); - } - - - - private class HashTableModel extends AbstractTableModel { - - private HashDbXML xmlHandle = HashDbXML.getCurrent(); - - private void resync() { - fireTableDataChanged(); - } - - @Override - public int getRowCount() { - int size = xmlHandle.getKnownBadSets().size(); - return size == 0 ? 1 : size; - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if (xmlHandle.getKnownBadSets().isEmpty()) { - if (columnIndex == 0) { - return ""; - } else { - return "Disabled"; - } - } else { - HashDb db = xmlHandle.getKnownBadSets().get(rowIndex); - if (columnIndex == 0) { - return db.getUseForIngest(); - } else { - return db.getName(); - } - } - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - return !IngestManager.getDefault().isIngestRunning() && columnIndex == 0; - } - - @Override - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - if(columnIndex == 0){ - HashDb db = xmlHandle.getKnownBadSets().get(rowIndex); - if(((Boolean) getValueAt(rowIndex, columnIndex)) || IndexStatus.isIngestible(db.status())) { - db.setUseForIngest((Boolean) aValue); - } else { - JOptionPane.showMessageDialog(HashDbSimplePanel.this, "Databases must be indexed before they can be used for ingest"); - } - reloadSets(); - } - } - - @Override - public Class getColumnClass(int c) { - return getValueAt(0, c).getClass(); - } - - } -} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbXML.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbXML.java deleted file mode 100644 index 9ddc638b9e..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbXML.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.hashdatabase; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileNameExtensionFilter; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.coreutils.XMLUtil; -import org.sleuthkit.autopsy.hashdatabase.HashDb.DBType; -import org.sleuthkit.datamodel.SleuthkitJNI; -import org.sleuthkit.datamodel.TskCoreException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -public class HashDbXML { - private static final String ROOT_EL = "hash_sets"; - private static final String SET_EL = "hash_set"; - private static final String SET_NAME_ATTR = "name"; - private static final String SET_TYPE_ATTR = "type"; - private static final String SET_USE_FOR_INGEST_ATTR = "use_for_ingest"; - private static final String SET_SHOW_INBOX_MESSAGES = "show_inbox_messages"; - private static final String PATH_EL = "hash_set_path"; - private static final String PATH_NUMBER_ATTR = "number"; - private static final String CUR_HASHSETS_FILE_NAME = "hashsets.xml"; - private static final String XSDFILE = "HashsetsSchema.xsd"; - private static final String ENCODING = "UTF-8"; - private static final String CUR_HASHSET_FILE = PlatformUtil.getUserConfigDirectory() + File.separator + CUR_HASHSETS_FILE_NAME; - private static final String SET_CALC = "hash_calculate"; - private static final String SET_VALUE = "value"; - private static final Logger logger = Logger.getLogger(HashDbXML.class.getName()); - private static HashDbXML currentInstance; - - private List knownBadSets; - private HashDb nsrlSet; - private String xmlFile; - private boolean calculate; - - private HashDbXML(String xmlFile) { - knownBadSets = new ArrayList(); - this.xmlFile = xmlFile; - } - - /** - * get instance for managing the current keyword list of the application - */ - static synchronized HashDbXML getCurrent() { - if (currentInstance == null) { - currentInstance = new HashDbXML(CUR_HASHSET_FILE); - currentInstance.reload(); - } - return currentInstance; - } - - /** - * Get the hash sets - */ - public List getAllSets() { - List ret = new ArrayList(); - if(nsrlSet != null) { - ret.add(nsrlSet); - } - ret.addAll(knownBadSets); - return ret; - } - - /** - * Get the Known Bad sets - */ - public List getKnownBadSets() { - return knownBadSets; - } - - /** - * Get the NSRL set - */ - public HashDb getNSRLSet() { - return nsrlSet; - } - - /** - * Add a known bad hash set - */ - public void addKnownBadSet(HashDb set) { - knownBadSets.add(set); - //save(); - } - - /** - * Add a known bad hash set - */ - public void addKnownBadSet(int index, HashDb set) { - knownBadSets.add(index, set); - //save(); - } - - /** - * Set the NSRL hash set (override old set) - */ - public void setNSRLSet(HashDb set) { - this.nsrlSet = set; - //save(); - } - - /** - * Remove a hash known bad set - */ - public void removeKnownBadSetAt(int index) { - knownBadSets.remove(index); - //save(); - } - - /** - * Remove the NSRL database - */ - public void removeNSRLSet() { - this.nsrlSet = null; - //save(); - } - - /** - * load the file or create new - */ - public void reload() { - boolean created = false; - - //TODO clearing the list causes a bug: we lose track of the state - //whether db is being indexed, we should somehow preserve the state when loading new HashDb objects - - knownBadSets.clear(); - nsrlSet = null; - - if (!this.setsFileExists()) { - //create new if it doesn't exist - save(); - created = true; - } - - //load, if fails to load create new; save regardless - load(); - if (!created) { - //create new if failed to load - save(); - } - } - - /** - * Sets the local variable calculate to the given boolean. - * @param set the state to make calculate - */ - public void setCalculate(boolean set) { - this.calculate = set; - //save(); - } - - /** - * Returns the value of the local boolean calculate. - * @return true if calculate is true, false otherwise - */ - public boolean getCalculate() { - return this.calculate; - } - - /** - * writes out current sets file replacing the last one - */ - public boolean save() { - boolean success = false; - - DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); - - try { - DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); - Document doc = docBuilder.newDocument(); - - Element rootEl = doc.createElement(ROOT_EL); - doc.appendChild(rootEl); - - for (HashDb set : knownBadSets) { - String useForIngest = Boolean.toString(set.getUseForIngest()); - String showInboxMessages = Boolean.toString(set.getShowInboxMessages()); - List paths = set.getDatabasePaths(); - String type = DBType.KNOWN_BAD.toString(); - - Element setEl = doc.createElement(SET_EL); - setEl.setAttribute(SET_NAME_ATTR, set.getName()); - setEl.setAttribute(SET_TYPE_ATTR, type); - setEl.setAttribute(SET_USE_FOR_INGEST_ATTR, useForIngest); - setEl.setAttribute(SET_SHOW_INBOX_MESSAGES, showInboxMessages); - - for (int i = 0; i < paths.size(); i++) { - String path = paths.get(i); - Element pathEl = doc.createElement(PATH_EL); - pathEl.setAttribute(PATH_NUMBER_ATTR, Integer.toString(i)); - pathEl.setTextContent(path); - setEl.appendChild(pathEl); - } - rootEl.appendChild(setEl); - } - - if(nsrlSet != null) { - String useForIngest = Boolean.toString(nsrlSet.getUseForIngest()); - String showInboxMessages = Boolean.toString(nsrlSet.getShowInboxMessages()); - List paths = nsrlSet.getDatabasePaths(); - String type = DBType.NSRL.toString(); - - Element setEl = doc.createElement(SET_EL); - setEl.setAttribute(SET_NAME_ATTR, nsrlSet.getName()); - setEl.setAttribute(SET_TYPE_ATTR, type); - setEl.setAttribute(SET_USE_FOR_INGEST_ATTR, useForIngest); - setEl.setAttribute(SET_SHOW_INBOX_MESSAGES, showInboxMessages); - - for (int i = 0; i < paths.size(); i++) { - String path = paths.get(i); - Element pathEl = doc.createElement(PATH_EL); - pathEl.setAttribute(PATH_NUMBER_ATTR, Integer.toString(i)); - pathEl.setTextContent(path); - setEl.appendChild(pathEl); - } - rootEl.appendChild(setEl); - } - - String calcValue = Boolean.toString(calculate); - Element setCalc = doc.createElement(SET_CALC); - setCalc.setAttribute(SET_VALUE, calcValue); - rootEl.appendChild(setCalc); - - success = XMLUtil.saveDoc(HashDbXML.class, xmlFile, ENCODING, doc); - } catch (ParserConfigurationException e) { - logger.log(Level.SEVERE, "Error saving hash sets: can't initialize parser.", e); - } - return success; - } - - /** - * load and parse XML, then dispose - */ - public boolean load() { - final Document doc = XMLUtil.loadDoc(HashDbXML.class, xmlFile, XSDFILE); - if (doc == null) { - return false; - } - - Element root = doc.getDocumentElement(); - if (root == null) { - logger.log(Level.SEVERE, "Error loading hash sets: invalid file format."); - return false; - } - NodeList setsNList = root.getElementsByTagName(SET_EL); - int numSets = setsNList.getLength(); - if(numSets==0) { - logger.log(Level.WARNING, "No element hash_set exists."); - } - for (int i = 0; i < numSets; ++i) { - Element setEl = (Element) setsNList.item(i); - final String name = setEl.getAttribute(SET_NAME_ATTR); - final String type = setEl.getAttribute(SET_TYPE_ATTR); - final String useForIngest = setEl.getAttribute(SET_USE_FOR_INGEST_ATTR); - final String showInboxMessages = setEl.getAttribute(SET_SHOW_INBOX_MESSAGES); - Boolean useForIngestBool = Boolean.parseBoolean(useForIngest); - Boolean showInboxMessagesBool = Boolean.parseBoolean(showInboxMessages); - List paths = new ArrayList(); - - // Parse all paths - NodeList pathsNList = setEl.getElementsByTagName(PATH_EL); - final int numPaths = pathsNList.getLength(); - for (int j = 0; j < numPaths; ++j) { - Element pathEl = (Element) pathsNList.item(j); - String number = pathEl.getAttribute(PATH_NUMBER_ATTR); - String path = pathEl.getTextContent(); - - // If either the database or it's index exist - File database = new File(path); - File index = new File(HashDb.toIndexPath(path)); - if(database.exists() || index.exists()) { - paths.add(path); - } else { - // Ask for new path - int ret = JOptionPane.showConfirmDialog(null, "Database " + name + " could not be found at location\n" - + path + "\n" - + " Would you like to search for the file?", "Missing Database", JOptionPane.YES_NO_OPTION); - if (ret == JOptionPane.YES_OPTION) { - String filePath = searchForFile(name); - if(filePath!=null) { - paths.add(filePath); - } - } - } - } - - // Check everything was properly set - if(name.isEmpty()) { - logger.log(Level.WARNING, "Name was not set for hash_set at index {0}.", i); - } - if(type.isEmpty()) { - logger.log(Level.SEVERE, "Type was not set for hash_set at index {0}, cannot make instance of HashDb class.", i); - return false; // exit because this causes a fatal error - } - if(useForIngest.isEmpty()) { - logger.log(Level.WARNING, "UseForIngest was not set for hash_set at index {0}.", i); - } - if(showInboxMessages.isEmpty()) { - logger.log(Level.WARNING, "ShowInboxMessages was not set for hash_set at index {0}.", i); - } - - if(paths.isEmpty()) { - logger.log(Level.WARNING, "No paths were set for hash_set at index {0}. Removing the database.", i); - } else { - // No paths for this entry, the user most likely declined to search for them - DBType typeDBType = DBType.valueOf(type); - HashDb set = new HashDb(name, paths, useForIngestBool, showInboxMessagesBool, typeDBType); - - if(typeDBType == DBType.KNOWN_BAD) { - knownBadSets.add(set); - } else if(typeDBType == DBType.NSRL) { - this.nsrlSet = set; - } - } - } - - NodeList calcList = root.getElementsByTagName(SET_CALC); - int numCalc = calcList.getLength(); // Shouldn't be more than 1 - if(numCalc==0) { - logger.log(Level.WARNING, "No element hash_calculate exists."); - } - for(int i=0; i - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/IndexStatus.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/IndexStatus.java deleted file mode 100644 index cf02a9f326..0000000000 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/IndexStatus.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.hashdatabase; - -/** - * The status of a HashDb as determined from its indexExists(), - * databaseExists(), and isOutdated() methods - * @author pmartel - */ -enum IndexStatus { - - /** - * The index and database both exist, and the index is older. - */ - INDEX_OUTDATED("WARNING: Index is older than database"), - /** - * The index and database both exist, and the index is not older. - */ - INDEX_CURRENT("Database and index exist"), - /** - * The index exists but the database does not. - */ - NO_DB("Index exists (no database)"), - /** - * The database exists but the index does not. - */ - NO_INDEX("ERROR: Index does not exist"), - /** - * Neither the index nor the database exists. - */ - NONE("ERROR: No index or database"), - /** - * The index is currently being generated - */ - INDEXING("Index is currently being generated"); - - private String message; - - /** - * @param message Short description of the state represented - */ - private IndexStatus(String message) { - this.message = message; - } - - /** - * Get status message - * @return a short description of the state represented - */ - String message() { - return this.message; - } - - public static boolean isIngestible(IndexStatus status) { - return status == NO_DB || status == INDEX_CURRENT || status == INDEX_OUTDATED; - } -} diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.form b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.form index df913c59e1..7ed52bedf9 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.form +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.form @@ -17,6 +17,7 @@ + diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.java index 4a7862bea1..9f1e10c7ba 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.java +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/ModalNoButtons.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,12 +21,14 @@ package org.sleuthkit.autopsy.hashdatabase; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.swing.JOptionPane; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.TskException; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.hashdatabase.HashDbManager.HashDb; /** * This class exists as a stop-gap measure to force users to have an indexed database. @@ -40,9 +42,10 @@ import org.sleuthkit.datamodel.TskException; */ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListener { + private static final String INDEX_FILE_EXTENSION = ".kdb"; List unindexed; HashDb toIndex; - HashDbManagementPanel hdbmp; + HashDbConfigPanel hdbmp; int length = 0; int currentcount = 1; String currentDb = ""; @@ -53,7 +56,7 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen * @param parent Swing parent frame. * @param unindexed the list of unindexed databases to index. */ - ModalNoButtons(HashDbManagementPanel hdbmp, java.awt.Frame parent, List unindexed) { + ModalNoButtons(HashDbConfigPanel hdbmp, java.awt.Frame parent, List unindexed) { super(parent, "Indexing databases", true); this.unindexed = unindexed; this.toIndex = null; @@ -68,7 +71,7 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen * @param parent Swing parent frame. * @param unindexed The unindexed database to index. */ - ModalNoButtons(HashDbManagementPanel hdbmp, java.awt.Frame parent, HashDb unindexed){ + ModalNoButtons(HashDbConfigPanel hdbmp, java.awt.Frame parent, HashDb unindexed){ super(parent, "Indexing database", true); this.unindexed = null; this.toIndex = unindexed; @@ -165,7 +168,6 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen * @param evt mouse click event */ private void CANCEL_BUTTONMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_CANCEL_BUTTONMouseClicked - // TODO add your handling code here: String message = "You are about to exit out of indexing your hash databases. \n" + "The generated index will be left unusable. If you choose to continue,\n " + "please delete the corresponding -md5.idx file in the hash folder.\n" @@ -173,7 +175,7 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen int res = JOptionPane.showConfirmDialog(this, message, "Unfinished Indexing", JOptionPane.YES_NO_OPTION); if(res == JOptionPane.YES_OPTION){ - List remove = new ArrayList(); + List remove = new ArrayList<>(); if(this.toIndex == null){ remove = this.unindexed; } @@ -203,17 +205,13 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen */ private void indexThis() { this.INDEXING_PROGBAR.setIndeterminate(true); - currentDb = this.toIndex.getName(); + currentDb = this.toIndex.getHashSetName(); this.CURRENTDB_LABEL.setText("(" + currentDb + ")"); this.length = 1; this.CURRENTLYON_LABEL.setText("Currently indexing 1 database"); if (!this.toIndex.isIndexing()) { this.toIndex.addPropertyChangeListener(this); - try { - this.toIndex.createIndex(); - } catch (TskException se) { - Logger.getLogger(ModalNoButtons.class.getName()).log(Level.WARNING, "Error making TSK index", se); - } + HashDbManager.getInstance().indexHashDatabase(toIndex, okToDeleteOldIndexFile(toIndex)); } } @@ -224,16 +222,12 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen length = this.unindexed.size(); this.INDEXING_PROGBAR.setIndeterminate(true); for (HashDb db : this.unindexed) { - currentDb = db.getName(); + currentDb = db.getHashSetName(); this.CURRENTDB_LABEL.setText("(" + currentDb + ")"); this.CURRENTLYON_LABEL.setText("Currently indexing 1 of " + length); if (!db.isIndexing()) { db.addPropertyChangeListener(this); - try { - db.createIndex(); - } catch (TskException e) { - Logger.getLogger(ModalNoButtons.class.getName()).log(Level.WARNING, "Error making TSK index", e); - } + HashDbManager.getInstance().indexHashDatabase(db, okToDeleteOldIndexFile(db)); } } } @@ -250,7 +244,7 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen * Displays the current count of indexing when one is completed, or kills this dialog if all indexing is complete. */ public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(HashDb.EVENT.INDEXING_DONE.name())) { + if (evt.getPropertyName().equals(HashDb.Event.INDEXING_DONE.name())) { if (currentcount >= length) { this.INDEXING_PROGBAR.setValue(100); this.setModal(false); @@ -258,9 +252,26 @@ class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListen this.dispose(); } else { currentcount++; - this.CURRENTLYON_LABEL.setText("Currently indexing " + currentcount + " of " + length); - + this.CURRENTLYON_LABEL.setText("Currently indexing " + currentcount + " of " + length); } } } + + private boolean okToDeleteOldIndexFile(HashDb hashDb) { + boolean deleteOldIndexFile = true; + try { + if (hashDb.hasLookupIndex()) { + String indexPath = hashDb.getIndexPath(); + File indexFile = new File(indexPath); + if (!indexPath.endsWith(INDEX_FILE_EXTENSION)) { + deleteOldIndexFile = JOptionPane.showConfirmDialog(this, "Updating index file format, delete " + indexFile.getName() + " file that uses the old file format?", "Delete Obsolete Index File", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; + } + } + } + catch (TskCoreException ex) { + Logger.getLogger(HashDbConfigPanel.class.getName()).log(Level.SEVERE, "Error getting index info for hash database", ex); + JOptionPane.showMessageDialog(null, "Error gettting index information for " + hashDb.getHashSetName() + " hash database. Cannot perform indexing operation.", "Hash Database Index Status Error", JOptionPane.ERROR_MESSAGE); + } + return deleteOldIndexFile; + } } diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/btn_icon_create_new_16.png b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/btn_icon_create_new_16.png new file mode 100644 index 0000000000..86c1018f39 Binary files /dev/null and b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/btn_icon_create_new_16.png differ diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/new16.png b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/new16.png new file mode 100644 index 0000000000..f286d2b6c0 Binary files /dev/null and b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/new16.png differ diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf index dd9e48a200..e309652025 100644 --- a/KeywordSearch/manifest.mf +++ b/KeywordSearch/manifest.mf @@ -1,9 +1,9 @@ -Manifest-Version: 1.0 -AutoUpdate-Show-In-Client: true -OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/5 -OpenIDE-Module-Implementation-Version: 9 -OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class -OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties -OpenIDE-Module-Requires: org.openide.windows.WindowManager - +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/5 +OpenIDE-Module-Implementation-Version: 9 +OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class +OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties +OpenIDE-Module-Requires: org.openide.windows.WindowManager + diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties index 140caac79c..4f3228693f 100644 --- a/KeywordSearch/nbproject/project.properties +++ b/KeywordSearch/nbproject/project.properties @@ -1,6 +1,6 @@ -javac.source=1.7 -javac.compilerargs=-Xlint -Xlint:-serial -license.file=../LICENSE-2.0.txt -nbm.homepage=http://www.sleuthkit.org/autopsy/ -nbm.needs.restart=true -spec.version.base=3.2 +javac.source=1.7 +javac.compilerargs=-Xlint -Xlint:-serial +license.file=../LICENSE-2.0.txt +nbm.homepage=http://www.sleuthkit.org/autopsy/ +nbm.needs.restart=true +spec.version.base=3.2 diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 1bfc26e95b..7b728c5005 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -38,7 +38,6 @@ KeywordSearchPanel.settingsLabel.text= KeywordSearchListsViewerPanel.searchAddButton.text=Search KeywordSearchListsViewerPanel.manageListsButton.text=Manage Lists KeywordSearchListsViewerPanel.ingestIndexLabel.text=Files Indexed: -KeywordSearchEditListPanel.selectorsCombo.toolTipText=Regular Expression selector type (optional) KeywordSearchPanel.searchButton.text= KeywordSearchPanel.cutMenuItem.text=Cut KeywordSearchPanel.copyMenuItem.text=Copy diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 66582a2852..77757cc5cd 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -432,7 +432,7 @@ public class ExtractedContentViewer implements DataContentViewer { } } if (msg == null) { - msg = "

" + name + "does not have text in the index.
It may have no text, not been analyzed yet, or keyword search was not enabled during ingest.

"; + msg = "

" + name + " does not have text in the index.
It may have no text, not been analyzed yet, or keyword search was not enabled during ingest.

"; } String htmlMsg = "" + msg + ""; return htmlMsg; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/JerichoParserWrapper.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/JerichoParserWrapper.java index aea91e3f5a..ba723ddff4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/JerichoParserWrapper.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/JerichoParserWrapper.java @@ -26,10 +26,10 @@ import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import net.htmlparser.jericho.Attributes; +import net.htmlparser.jericho.Renderer; import net.htmlparser.jericho.Source; import net.htmlparser.jericho.StartTag; import net.htmlparser.jericho.StartTagType; -import net.htmlparser.jericho.TextExtractor; /** * Uses Jericho HTML Parser to create a Reader for output, consisting of @@ -46,6 +46,15 @@ public class JerichoParserWrapper { this.in = in; } + /** + * Returns the reader, initialized in parse(), which will be + * null if parse() is not called or if parse() throws an error. + * @return Reader + */ + public Reader getReader() { + return reader; + } + /** * Initialize the reader by parsing the InputStream, adding it to StringBuilder, * and creating a StringReader from it. @@ -57,7 +66,7 @@ public class JerichoParserWrapper { Source source = new Source(in); source.fullSequentialParse(); - StringBuilder text = new StringBuilder(); + String text; StringBuilder scripts = new StringBuilder(); StringBuilder links = new StringBuilder(); StringBuilder images = new StringBuilder(); @@ -68,14 +77,8 @@ public class JerichoParserWrapper { int numImages = 1; int numComments = 1; int numOthers = 1; - - // Extract text from the source - TextExtractor extractor = new TextExtractor(source); - // Split it at every ". " but keep the . - String[] lines = extractor.toString().split("(?<=\\. )"); - for(String s : lines) { - text.append(s).append("\n"); - } + + text = renderHTMLAsPlainText(source); // Get all the tags in the source List tags = source.getAllStartTags(); @@ -113,7 +116,7 @@ public class JerichoParserWrapper { } } - out.append(text.toString()).append("\n"); + out.append(text).append("\n\n"); out.append("----------NONVISIBLE TEXT----------\n\n"); if(numScripts>1) { @@ -139,13 +142,14 @@ public class JerichoParserWrapper { } } - /** - * Returns the reader, initialized in parse(), which will be - * null if parse() is not called or if parse() throws an error. - * @return Reader - */ - public Reader getReader() { - return reader; + // Extract text from the source, nicely formatted with whitespace and + // newlines where appropriate. + private String renderHTMLAsPlainText(Source source) { + Renderer renderer = source.getRenderer(); + renderer.setNewLine("\n"); + renderer.setIncludeHyperlinkURLs(false); + renderer.setDecorateFontStyles(false); + renderer.setIncludeAlternateText(false); + return renderer.toString(); } - } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchConfigurationPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchConfigurationPanel.java index ccbd968384..6fa8618dfc 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchConfigurationPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchConfigurationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,21 +19,18 @@ package org.sleuthkit.autopsy.keywordsearch; -import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; /** * Container panel for keyword search advanced configuration options */ -final class KeywordSearchConfigurationPanel extends javax.swing.JPanel implements OptionsPanel { +public final class KeywordSearchConfigurationPanel extends javax.swing.JPanel implements OptionsPanel { - private static final Logger logger = Logger.getLogger(KeywordSearchConfigurationPanel.class.getName()); private KeywordSearchConfigurationPanel1 listsPanel; private KeywordSearchConfigurationPanel3 languagesPanel; private KeywordSearchConfigurationPanel2 generalPanel; - KeywordSearchConfigurationPanel() { + public KeywordSearchConfigurationPanel() { initComponents(); customizeComponents(); } @@ -45,8 +42,7 @@ final class KeywordSearchConfigurationPanel extends javax.swing.JPanel implement generalPanel = new KeywordSearchConfigurationPanel2(); tabbedPane.insertTab("Lists", null, listsPanel, "List configuration", 0); tabbedPane.insertTab("String Extraction", null, languagesPanel, "String extraction configuration for Keyword Search Ingest", 1); - tabbedPane.insertTab("General", null, generalPanel, "General configuration", 2); - + tabbedPane.insertTab("General", null, generalPanel, "General configuration", 2); } /** @@ -94,10 +90,15 @@ final class KeywordSearchConfigurationPanel extends javax.swing.JPanel implement generalPanel.store(); } + public void cancel() { + KeywordSearchListsXML.getCurrent().reload(); + } + boolean valid() { // TODO check whether form is consistent and complete return true; } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTabbedPane tabbedPane; // End of variables declaration//GEN-END:variables diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.form index 326354f950..8e9ae1651c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.form @@ -206,20 +206,16 @@ - + - + - - - - - + @@ -234,10 +230,7 @@ - - - - + @@ -276,19 +269,6 @@ - - - - - - - - - - - - - diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.java index a720dde3ba..ce91aefd16 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchEditListPanel.java @@ -126,18 +126,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec break; } } - if (selected > -1 && selected < keywords.size()) { - Keyword k = keywords.get(selected); - BlackboardAttribute.ATTRIBUTE_TYPE selType = k.getType(); - if (selType != null) { - selectorsCombo.setSelectedIndex(selType.ordinal()); - } else { - //set to none (last item) - selectorsCombo.setSelectedIndex(selectorsCombo.getItemCount() - 1); - } - } - - } } }); @@ -195,15 +183,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec } } }); - - //selectors - selectorsCombo.setEnabled(false); - for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) { - selectorsCombo.addItem(type.getDisplayName()); - } - selectorsCombo.addItem(""); - selectorsCombo.setSelectedIndex(selectorsCombo.getItemCount() - 1); - } /** @@ -251,7 +230,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec chRegex.setEnabled(listSet && (!ingestOngoing || !inIngest) && !isLocked); keywordOptionsLabel.setEnabled(addWordButton.isEnabled() || chRegex.isEnabled()); keywordOptionsSeparator.setEnabled(addWordButton.isEnabled() || chRegex.isEnabled()); - selectorsCombo.setEnabled(listSet && (!ingestOngoing || !inIngest) && !isLocked && chRegex.isSelected()); useForIngestCheckbox.setEnabled(listSet && (!ingestOngoing || !inIngest)); useForIngestCheckbox.setSelected(useForIngest); ingestMessagesCheckbox.setEnabled(useForIngestCheckbox.isEnabled() && useForIngestCheckbox.isSelected()); @@ -295,7 +273,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec addWordButton = new javax.swing.JButton(); addWordField = new javax.swing.JTextField(); chRegex = new javax.swing.JCheckBox(); - selectorsCombo = new javax.swing.JComboBox(); deleteWordButton = new javax.swing.JButton(); ingestMessagesCheckbox = new javax.swing.JCheckBox(); keywordsLabel = new javax.swing.JLabel(); @@ -359,8 +336,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec } }); - selectorsCombo.setToolTipText(org.openide.util.NbBundle.getMessage(KeywordSearchEditListPanel.class, "KeywordSearchEditListPanel.selectorsCombo.toolTipText")); // NOI18N - deleteWordButton.setText(org.openide.util.NbBundle.getMessage(KeywordSearchEditListPanel.class, "KeywordSearchEditListPanel.deleteWordButton.text")); // NOI18N deleteWordButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -373,17 +348,14 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec addKeywordPanelLayout.setHorizontalGroup( addKeywordPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(addKeywordPanelLayout.createSequentialGroup() - .addGroup(addKeywordPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(addKeywordPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(addKeywordPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(addKeywordPanelLayout.createSequentialGroup() - .addComponent(addWordField) + .addComponent(addWordField, javax.swing.GroupLayout.PREFERRED_SIZE, 216, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(addWordButton)) .addComponent(deleteWordButton)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, addKeywordPanelLayout.createSequentialGroup() - .addComponent(chRegex) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(selectorsCombo, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(chRegex, javax.swing.GroupLayout.Alignment.LEADING)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); addKeywordPanelLayout.setVerticalGroup( @@ -394,9 +366,7 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec .addComponent(addWordField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(addWordButton)) .addGap(7, 7, 7) - .addGroup(addKeywordPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(selectorsCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(chRegex)) + .addComponent(chRegex) .addGap(7, 7, 7) .addComponent(deleteWordButton) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) @@ -515,14 +485,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec String newWord = addWordField.getText().trim(); boolean isLiteral = !chRegex.isSelected(); final Keyword keyword = new Keyword(newWord, isLiteral); - if (!isLiteral) { - //get selector - int selI = this.selectorsCombo.getSelectedIndex(); - if (selI < this.selectorsCombo.getItemCount() - 1) { - BlackboardAttribute.ATTRIBUTE_TYPE selector = BlackboardAttribute.ATTRIBUTE_TYPE.values()[selI]; - keyword.setType(selector); - } - } if (newWord.equals("")) { return; @@ -620,7 +582,6 @@ class KeywordSearchEditListPanel extends javax.swing.JPanel implements ListSelec }//GEN-LAST:event_exportButtonActionPerformed private void chRegexActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chRegexActionPerformed - selectorsCombo.setEnabled(chRegex.isEnabled() && chRegex.isSelected()); }//GEN-LAST:event_chRegexActionPerformed private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useForIngestCheckboxActionPerformed @@ -659,7 +620,6 @@ private void useForIngestCheckboxActionPerformed(java.awt.event.ActionEvent evt) private javax.swing.JPopupMenu rightClickMenu; private javax.swing.JButton saveListButton; private javax.swing.JMenuItem selectAllMenuItem; - private javax.swing.JComboBox selectorsCombo; private javax.swing.JCheckBox useForIngestCheckbox; // End of variables declaration//GEN-END:variables diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java index 9cec5ce15d..d4d18d36a4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java @@ -28,9 +28,10 @@ import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; import org.openide.util.lookup.ProxyLookup; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.Content; @@ -132,7 +133,8 @@ class KeywordSearchFilterNode extends FilterNode { actions.add(ExtractAction.getInstance()); actions.add(new HashSearchAction("Search for files with the same MD5 hash", getOriginal())); actions.add(null); // creates a menu separator - actions.add(TagAbstractFileAction.getInstance()); + actions.add(AddContentTagAction.getInstance()); + actions.addAll(ContextMenuExtensionPoint.getActions()); return actions; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestSimplePanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestSimplePanel.java index 35b9459405..fb1bc964a8 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestSimplePanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestSimplePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011 - 2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.ListSelectionModel; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; /** @@ -71,6 +70,16 @@ public class KeywordSearchIngestSimplePanel extends javax.swing.JPanel { reloadEncodings(); } + public void load() { + reloadLists(); + reloadLangs(); + reloadEncodings(); + } + + public void store() { + KeywordSearchListsXML.getCurrent().save(); + } + /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchOptionsPanelController.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchOptionsPanelController.java index 4755b913f7..4efe38a276 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchOptionsPanelController.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchOptionsPanelController.java @@ -35,8 +35,7 @@ public final class KeywordSearchOptionsPanelController extends OptionsPanelContr } public void cancel() { - // Reload XML on cancel - KeywordSearchListsXML.getCurrent().reload(); + getPanel().cancel(); } public boolean isValid() { diff --git a/RecentActivity/manifest.mf b/RecentActivity/manifest.mf index 14b58804be..b6f0a41ec0 100644 --- a/RecentActivity/manifest.mf +++ b/RecentActivity/manifest.mf @@ -1,10 +1,10 @@ -Manifest-Version: 1.0 -OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/5 -OpenIDE-Module-Implementation-Version: 9 -OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties -OpenIDE-Module-Requires: - org.openide.modules.InstalledFileLocator, - org.openide.windows.TopComponent$Registry, - org.openide.windows.WindowManager - +Manifest-Version: 1.0 +OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/5 +OpenIDE-Module-Implementation-Version: 9 +OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties +OpenIDE-Module-Requires: + org.openide.modules.InstalledFileLocator, + org.openide.windows.TopComponent$Registry, + org.openide.windows.WindowManager + diff --git a/RecentActivity/nbproject/project.properties b/RecentActivity/nbproject/project.properties index 4ce77193f7..2cb871f415 100644 --- a/RecentActivity/nbproject/project.properties +++ b/RecentActivity/nbproject/project.properties @@ -1,7 +1,7 @@ -file.reference.gson-2.1.jar=release/modules/ext/gson-2.1.jar -javac.source=1.7 -javac.compilerargs=-Xlint -Xlint:-serial -license.file=../LICENSE-2.0.txt -nbm.homepage=http://www.sleuthkit.org/autopsy/ -nbm.needs.restart=true -spec.version.base=3.0 +file.reference.gson-2.1.jar=release/modules/ext/gson-2.1.jar +javac.source=1.7 +javac.compilerargs=-Xlint -Xlint:-serial +license.file=../LICENSE-2.0.txt +nbm.homepage=http://www.sleuthkit.org/autopsy/ +nbm.needs.restart=true +spec.version.base=3.0 diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java index 849aae992e..5868c44146 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java @@ -410,7 +410,10 @@ public class Chrome extends Extract { for (HashMap result : tempList) { Collection bbattributes = new ArrayList(); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "Recent Activity", (result.get("full_path").toString()))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "Recent Activity", Util.findID(dataSource, (result.get("full_path").toString())))); + long pathID = Util.findID(dataSource, (result.get("full_path").toString())); + if (pathID != -1) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "Recent Activity", pathID)); + } bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL.getTypeID(), "Recent Activity", ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "Recent Activity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : ""))); Long time = (Long.valueOf(result.get("start_time").toString())); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java index 980c48aba4..8edf90a654 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java @@ -23,12 +23,16 @@ package org.sleuthkit.autopsy.recentactivity; //IO imports +import java.io.BufferedReader; import org.sleuthkit.autopsy.coreutils.ExecUtil; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.io.Writer; //Util Imports @@ -122,35 +126,19 @@ public class ExtractIE extends Extract { } dataFound = true; - for (AbstractFile favoritesFile : favoritesFiles) { - if (favoritesFile.getSize() == 0) { + for (AbstractFile fav : favoritesFiles) { + if (fav.getSize() == 0) { continue; } - // @@@ WHY DON"T WE PARSE THIS FILE more intelligently. It's text-based if (controller.isCancelled()) { break; } - Content fav = favoritesFile; - byte[] t = new byte[(int) fav.getSize()]; - try { - final int bytesRead = fav.read(t, 0, fav.getSize()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error reading bytes of Internet Explorer favorite.", ex); - this.addErrorMessage(this.getName() + ": Error reading Internet Explorer Bookmark file " + favoritesFile.getName()); - continue; - } - String bookmarkString = new String(t); - String re1 = ".*?"; // Non-greedy match on filler - String re2 = "((?:http|https)(?::\\/{2}[\\w]+)(?:[\\/|\\.]?)(?:[^\\s\"]*))"; // HTTP URL 1 - String url = ""; - Pattern p = Pattern.compile(re1 + re2, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - Matcher m = p.matcher(bookmarkString); - if (m.find()) { - url = m.group(1); - } - String name = favoritesFile.getName(); - Long datetime = favoritesFile.getCrtime(); + + String url = getURLFromIEBookmarkFile(fav); + + String name = fav.getName(); + Long datetime = fav.getCrtime(); String Tempdate = datetime.toString(); datetime = Long.valueOf(Tempdate); String domain = Util.extractDomain(url); @@ -161,10 +149,39 @@ public class ExtractIE extends Extract { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), "RecentActivity", datetime)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), "RecentActivity", "Internet Explorer")); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), "RecentActivity", domain)); - this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, favoritesFile, bbattributes); + this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, fav, bbattributes); } services.fireModuleDataEvent(new ModuleDataEvent("Recent Activity", BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK)); } + + private String getURLFromIEBookmarkFile(AbstractFile fav) { + BufferedReader reader = new BufferedReader(new InputStreamReader(new ReadContentInputStream(fav))); + String line, url = ""; + try { + while ((line = reader.readLine()) != null) { + // The actual shortcut line we are interested in is of the + // form URL=http://path/to/website + if (line.startsWith("URL")) { + url = line.substring(line.indexOf("=") + 1); + break; + } + } + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to read from content: " + fav.getName(), ex); + this.addErrorMessage(this.getName() + ": Error parsing IE bookmark File " + fav.getName()); + } catch (IndexOutOfBoundsException ex) { + logger.log(Level.WARNING, "Failed while getting URL of IE bookmark. Unexpected format of the bookmark file: " + fav.getName(), ex); + this.addErrorMessage(this.getName() + ": Error parsing IE bookmark File " + fav.getName()); + } finally { + try { + reader.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to close reader.", ex); + } + } + + return url; + } /** * Finds files that store cookies and adds artifacts for them. diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 4abfcf0bec..90925e4f5a 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -357,15 +357,22 @@ public class Firefox extends Extract { //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : ""))); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), "RecentActivity", (Long.valueOf(result.get("startTime").toString())))); - try { - String urldecodedtarget = URLDecoder.decode(result.get("source").toString().replaceAll("file:///", ""), "UTF-8"); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", Util.findID(dataSource, urldecodedtarget))); - } catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Error decoding Firefox download URL in " + temps, ex); - errors++; + String target = result.get("target").toString(); + + if (target != null) { + try { + String decodedTarget = URLDecoder.decode(target.toString().replaceAll("file:///", ""), "UTF-8"); + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", decodedTarget)); + long pathID = Util.findID(dataSource, decodedTarget); + if (pathID != -1) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", pathID)); + } + } catch (UnsupportedEncodingException ex) { + logger.log(Level.SEVERE, "Error decoding Firefox download URL in " + temps, ex); + errors++; + } } - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", ((result.get("target").toString() != null) ? result.get("target").toString() : ""))); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), "RecentActivity", "FireFox")); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), "RecentActivity", (Util.extractDomain((result.get("source").toString() != null) ? result.get("source").toString() : "")))); this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes); @@ -440,8 +447,22 @@ public class Firefox extends Extract { //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : ""))); //TODO Revisit usage of deprecated constructor as per TSK-583 //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(); + if (target != null) { + try { + String decodedTarget = URLDecoder.decode(target.toString().replaceAll("file:///", ""), "UTF-8"); + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", decodedTarget)); + long pathID = Util.findID(dataSource, decodedTarget); + if (pathID != -1) { + bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", pathID)); + } + } catch (UnsupportedEncodingException ex) { + logger.log(Level.SEVERE, "Error decoding Firefox download URL in " + temps, ex); + errors++; + } + } bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), "RecentActivity", Long.valueOf(result.get("lastModified").toString()))); - bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", ((result.get("target").toString() != null) ? result.get("target").toString().replaceAll("file:///", "") : ""))); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), "RecentActivity", "FireFox")); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), "RecentActivity", (Util.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes); diff --git a/SevenZip/manifest.mf b/SevenZip/manifest.mf index 9989549bec..ca53e48be8 100644 --- a/SevenZip/manifest.mf +++ b/SevenZip/manifest.mf @@ -1,6 +1,6 @@ -Manifest-Version: 1.0 -OpenIDE-Module: org.sleuthkit.autopsy.sevenzip/1 -OpenIDE-Module-Implementation-Version: 3 -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/sevenzip/Bundle.properties - - +Manifest-Version: 1.0 +OpenIDE-Module: org.sleuthkit.autopsy.sevenzip/1 +OpenIDE-Module-Implementation-Version: 3 +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/sevenzip/Bundle.properties + + diff --git a/Testing/manifest.mf b/Testing/manifest.mf index 53c457afbb..381f4bb133 100644 --- a/Testing/manifest.mf +++ b/Testing/manifest.mf @@ -1,6 +1,6 @@ -Manifest-Version: 1.0 -AutoUpdate-Show-In-Client: false -OpenIDE-Module: org.sleuthkit.autopsy.testing/3 -OpenIDE-Module-Implementation-Version: 7 -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/testing/Bundle.properties - +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: false +OpenIDE-Module: org.sleuthkit.autopsy.testing/3 +OpenIDE-Module-Implementation-Version: 7 +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/testing/Bundle.properties + diff --git a/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties b/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties index 023a96d380..125ec1c485 100644 --- a/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties +++ b/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties @@ -1 +1 @@ -OpenIDE-Module-Name=Testing +OpenIDE-Module-Name=Testing diff --git a/Timeline/manifest.mf b/Timeline/manifest.mf index 3210242336..6cc867f901 100644 --- a/Timeline/manifest.mf +++ b/Timeline/manifest.mf @@ -1,7 +1,7 @@ -Manifest-Version: 1.0 -OpenIDE-Module: org.sleuthkit.autopsy.timeline/1 -OpenIDE-Module-Layer: org/sleuthkit/autopsy/timeline/layer.xml -OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/timeline/Bundle.properties -OpenIDE-Module-Requires: org.openide.windows.WindowManager -OpenIDE-Module-Implementation-Version: 3 - +Manifest-Version: 1.0 +OpenIDE-Module: org.sleuthkit.autopsy.timeline/1 +OpenIDE-Module-Layer: org/sleuthkit/autopsy/timeline/layer.xml +OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/timeline/Bundle.properties +OpenIDE-Module-Requires: org.openide.windows.WindowManager +OpenIDE-Module-Implementation-Version: 3 + diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java index 3468ab6fef..190aa2a7d5 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java @@ -885,10 +885,10 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, } @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.CONTENT; + public boolean isLeafTypeNode() { + return false; } - + @Override public T accept(DisplayableItemNodeVisitor v) { return null; diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index ea31e2447b..c79e98565e 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 18 Oct 2013 23:25:14 -0400 +#Fri, 08 Nov 2013 11:12:24 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=288 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 5b961ec43f..266f776266 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 18 Oct 2013 23:25:14 -0400 +#Fri, 08 Nov 2013 11:12:24 -0500 CTL_MainWindow_Title=Autopsy 3.0.8 CTL_MainWindow_Title_No_Project=Autopsy 3.0.8 diff --git a/build-windows.xml b/build-windows.xml index 09795298a4..06cba8fa66 100644 --- a/build-windows.xml +++ b/build-windows.xml @@ -3,7 +3,128 @@ Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.xml b/build.xml index 9712dd93fd..bab5fbdfd2 100644 --- a/build.xml +++ b/build.xml @@ -72,6 +72,12 @@ + + + + + + @@ -126,7 +132,16 @@ - + + + + + + + + + + diff --git a/docs/QuickStartGuide/index.html b/docs/QuickStartGuide/index.html index 7fe6d0867b..7bafa3b452 100644 --- a/docs/QuickStartGuide/index.html +++ b/docs/QuickStartGuide/index.html @@ -1,221 +1,221 @@ - - - - - - Autopsy 3 Quick Start Guide - - - -

Autopsy 3 Quick Start Guide

-

June 2013

-

www.sleuthkit.org/autopsy/

- - -

Installation

-

- The current version of Autopsy 3 runs only on Microsoft Windows. - We have gotten it to run on other platforms, such as Linux and OS X, but we do not have it in a state that makes it easy to distribute and find the needed libraries. -

-

- The Windows installer will make a directory for Autopsy and place all of the needed files inside of it. - The installer includes all dependencies, including Sleuth Kit and Java. -

-

Note that Autopsy 3 is a complete rewrite from Autopsy 2 and none of this document is relevant to Autopsy 2.

- -

Adding a Data Source (image, local disk, logical files)

-

- Data sources are added to a case. A case can have a single data source or it can have multiple data source if they are related. - Currently, a single report is generated for an entire case, so if you need to report on individual data sources, then you should use one data source per case. -

- -

Creating a Case

-

- To create a case, use either the "Create New Case" option on the Welcome screen or from the "File" menu. - This will start the New Case Wizard. You will need to supply it with the name of the case and a directory to store the case results into. - You can optionally provide case numbers and other details. -

- - -

Adding a Data Source

-

- The next step is to add input data source to the case. - The Add Data Source Wizard will start automatically after the case is created or you can manually start it from the "File" menu or toolbar. - You will need to choose the type of input data source to add (image, local disk or logical files and folders). - Next, supply it with the location of the source to add. -

-
    -
  • For a disk image, browse to the first file in the set (Autopsy will find the rest of the files). Autopsy currently supports E01 and raw (dd) files. -
  • -
  • - For local disk, select one of the detected disks. - Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). - However, the individual file content (not meta-data) does get updated with the changes made to the disk. - Note, you may need run Autopsy as an Administrator to detect all disks. -
  • -
  • For logical files (a single file or folder of files), use the "Add" button to add one or more files or folders on your system to the case. Folders will be recursively added to the case.
  • -
- - -

- There are a couple of options in the wizard that will allow you to make the ingest process faster. - These typically deal with deleted files. - It will take longer if unallocated space is analyzed and the entire drive is searched for deleted files. - In some scenarios, these recovery steps must be performed and in other scenarios these steps are not needed and instead fast results on the allocated files are needed. - Use these options to control how long the analysis will take. -

- -

- Autopsy will start to analyze these data sources and add them to the case and internal database. While it is doing that, it will prompt you to configure the Ingest Modules.

- - -

Ingest Modules

-

- You will next be prompted to configure the Ingest Modules. - Ingest modules will run in the background and perform specific tasks. - The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. - Ingest modules can be developed by third-parties and here are some of the standard ingest modules that come with Autopsy: -

-
    -
  • Recent Activity - extracts user activity as saved by web browsers and the OS. Also runs regripper on the registry hive. -
  • -
  • Hash Lookup - uses hash databases to ignore known files from the NIST NSRL and flag known bad files. - Use the "Advanced" button to add and configure the hash databases to use during this process. - You will get updates on known bad file hits as the ingest occurs. You can later add hash databases - via the Tools -> Options menu in the main UI. You can download an index of the NIST NSRL from - here. -
  • -
  • Keyword Search - uses keyword lists to identify files with specific words in them. - You can select the keyword lists to search for automatically and you can create new lists using the "Advanced" button. - Note that with keyword search, you can always conduct searches after ingest has finished. - The keyword lists that you select during ingest will be searched for at periodic intervals and you will get the results in real-time. - You do not need to wait for all files to be indexed. -
  • -
  • Archive Extractor opens ZIP, RAR, and other archive formats and sends the files from those archive files back - through the pipelines for analysis.
  • -
  • Exif Image Parser extracts EXIF information from JPEG files and posts the results into the tree in the main UI.
  • -
  • Thunderbird Parser Identifies Thunderbird MBOX files and extracts the e-mails from them.
  • -
-

- When you select a module, you will have the option to change its settings. - For example, you can configure which keyword search lists to use during ingest and which hash databases to use. - Refer to the help system inside of Autopsy for details on configuring each module. -

-

- While ingest modules are running in the background, you will see a progress bar in the lower right. - You can use the GUI to review incoming results and perform other tasks while ingest at that time. -

- - -

Analysis Basics

- Autopsy Screenshot -

You will start all of your analysis techniques from the tree on the left.

-
    -
  • The Data Sources root node shows all data in the case.
  • -
      -
    • The individual image nodes show the file system structure of the disk images or local disks in the case.
    • -
    • The LogicalFileSet nodes show the logical files in the case.
    • -
    -
  • The Views node shows the same data from a file type or timeline perspective.
  • -
  • The Results node shows the output from the ingest modules.
  • -
- -

- When you select a node from the tree on the left, a list of files will be shown in the upper right. - You can use the Thumbnail view in the upper right to view the pictures. - When you select a file from the upper right, its contents will be shown in the lower right. - You can use the tabs in the lower right to view the text of the file, an image, or the hex data. -

- -

- If you are viewing files from the Views and Results nodes, you can right-click on a file to go to its file system location. - This feature is useful to see what else the user stored in the same folder as the file that you are currently looking at. - You can also right click on a file to extract it to the local system. -

-

- If you want to search for single keywords, then you can use the search box in the upper right of the program. - The results will be shown in a table in the upper right. -

- -

You can tag (or bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report.

- -

Ingest Inbox

-

- As you are going through the results in the tree, the ingest modules are running in the background. - The results are shown in the tree as soon as the ingest modules find them and report them. -

-

- The Ingest Inbox receives messages from the ingest modules as they find results. - You can open the inbox to see what has been recently found. - It keeps track of what messages you have read. -

-

- The intended use of this inbox is that you can focus on some data for a while and then check back on the inbox at a time that is convenient for them. - You can then see what else was found while you were focused on the previous task. - You may learn that a known bad file was found or that a file was found with a relevant keyword and then decide to focus on that for a while. -

-

When you select a message, you can then jump to the Results tree where more details can be found or jump to the file's location in the filesystem.

- -

Timeline (Beta)

-

There is a basic timeline view that you can access via the Tools -> Make Timeline feature. This will take a few minutes to create the timeline for analysis. Its features are still in development.

- - -

Example Use Cases

-

In this section, we will provide examples of how to do common analysis tasks.

- -

Web Artifacts

-

- If you want to view the user's recent web activity, make sure that the Recent Activity ingest module was enabled. - You can then go to the "Results " node in the tree on the left and then into the "Extracted Data" node. - There, you can find bookmarks, cookies, downloads, and history. -

- -

Known Bad Hash Files

-

- If you want to see if the data source had known bad files, make sure that the Hash Lookup ingest module was enabled. - You can then view the "Hashset Hits" section in the "Results" area of the tree on the left. - Note that hash lookup can take a long time, so this section will be updated as long as the ingest process is occurring. - Use the Ingest Inbox to keep track of what known bad files were recently found. -

-

- When you find a known bad file in this interface, you may want to right click on the file to also view the file's original location. - You may find additional files that are relevant and stored in the same folder as this file. -

- -

Media: Images and Videos

-

- If you want to see all images and video on the disk image, then go to the "Views" section in the tree on the left and then "File Types". - Select either "Images" or "Videos". - You can use the thumbnail option in the upper right to view thumbnails of all images. -

-
    -
  • Note: - We are working on making this more efficient when there are lots of images and we are working on the feature to display video thumbnails. -
  • -
-

You can select an image or video from the upper right and view the video or image in the lower right. Video will be played with sound.

- - -

Reporting

-

- A final report can be generated that will include all analysis results. - Use the "Generate Report" button to create this. - It will create an HTML or XLS report in the Reports folder of the case folder. - If you forgot the location of your case folder, you can determine it using the "Case Properties" option in the "File" menu. - There is also an option to export report files to a separate folder outside of the case folder. -

- -
-

Copyright © 2012-2013 Basis Technology.

-

- This work is licensed under a - Creative Commons Attribution-Share Alike 3.0 United States License. -

- - + + + + + + Autopsy 3 Quick Start Guide + + + +

Autopsy 3 Quick Start Guide

+

June 2013

+

www.sleuthkit.org/autopsy/

+ + +

Installation

+

+ The current version of Autopsy 3 runs only on Microsoft Windows. + We have gotten it to run on other platforms, such as Linux and OS X, but we do not have it in a state that makes it easy to distribute and find the needed libraries. +

+

+ The Windows installer will make a directory for Autopsy and place all of the needed files inside of it. + The installer includes all dependencies, including Sleuth Kit and Java. +

+

Note that Autopsy 3 is a complete rewrite from Autopsy 2 and none of this document is relevant to Autopsy 2.

+ +

Adding a Data Source (image, local disk, logical files)

+

+ Data sources are added to a case. A case can have a single data source or it can have multiple data source if they are related. + Currently, a single report is generated for an entire case, so if you need to report on individual data sources, then you should use one data source per case. +

+ +

Creating a Case

+

+ To create a case, use either the "Create New Case" option on the Welcome screen or from the "File" menu. + This will start the New Case Wizard. You will need to supply it with the name of the case and a directory to store the case results into. + You can optionally provide case numbers and other details. +

+ + +

Adding a Data Source

+

+ The next step is to add input data source to the case. + The Add Data Source Wizard will start automatically after the case is created or you can manually start it from the "File" menu or toolbar. + You will need to choose the type of input data source to add (image, local disk or logical files and folders). + Next, supply it with the location of the source to add. +

+
    +
  • For a disk image, browse to the first file in the set (Autopsy will find the rest of the files). Autopsy currently supports E01 and raw (dd) files. +
  • +
  • + For local disk, select one of the detected disks. + Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). + However, the individual file content (not meta-data) does get updated with the changes made to the disk. + Note, you may need run Autopsy as an Administrator to detect all disks. +
  • +
  • For logical files (a single file or folder of files), use the "Add" button to add one or more files or folders on your system to the case. Folders will be recursively added to the case.
  • +
+ + +

+ There are a couple of options in the wizard that will allow you to make the ingest process faster. + These typically deal with deleted files. + It will take longer if unallocated space is analyzed and the entire drive is searched for deleted files. + In some scenarios, these recovery steps must be performed and in other scenarios these steps are not needed and instead fast results on the allocated files are needed. + Use these options to control how long the analysis will take. +

+ +

+ Autopsy will start to analyze these data sources and add them to the case and internal database. While it is doing that, it will prompt you to configure the Ingest Modules.

+ + +

Ingest Modules

+

+ You will next be prompted to configure the Ingest Modules. + Ingest modules will run in the background and perform specific tasks. + The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. + Ingest modules can be developed by third-parties and here are some of the standard ingest modules that come with Autopsy: +

+
    +
  • Recent Activity + extracts user activity as saved by web browsers and the OS. Also runs regripper on the registry hive. +
  • +
  • Hash Lookup + uses hash databases to ignore known files from the NIST NSRL and flag known bad files. + Use the "Advanced" button to add and configure the hash databases to use during this process. + You will get updates on known bad file hits as the ingest occurs. You can later add hash databases + via the Tools -> Options menu in the main UI. You can download an index of the NIST NSRL from + here. +
  • +
  • Keyword Search + uses keyword lists to identify files with specific words in them. + You can select the keyword lists to search for automatically and you can create new lists using the "Advanced" button. + Note that with keyword search, you can always conduct searches after ingest has finished. + The keyword lists that you select during ingest will be searched for at periodic intervals and you will get the results in real-time. + You do not need to wait for all files to be indexed. +
  • +
  • Archive Extractor opens ZIP, RAR, and other archive formats and sends the files from those archive files back + through the pipelines for analysis.
  • +
  • Exif Image Parser extracts EXIF information from JPEG files and posts the results into the tree in the main UI.
  • +
  • Thunderbird Parser Identifies Thunderbird MBOX files and extracts the e-mails from them.
  • +
+

+ When you select a module, you will have the option to change its settings. + For example, you can configure which keyword search lists to use during ingest and which hash databases to use. + Refer to the help system inside of Autopsy for details on configuring each module. +

+

+ While ingest modules are running in the background, you will see a progress bar in the lower right. + You can use the GUI to review incoming results and perform other tasks while ingest at that time. +

+ + +

Analysis Basics

+ Autopsy Screenshot +

You will start all of your analysis techniques from the tree on the left.

+
    +
  • The Data Sources root node shows all data in the case.
  • +
      +
    • The individual image nodes show the file system structure of the disk images or local disks in the case.
    • +
    • The LogicalFileSet nodes show the logical files in the case.
    • +
    +
  • The Views node shows the same data from a file type or timeline perspective.
  • +
  • The Results node shows the output from the ingest modules.
  • +
+ +

+ When you select a node from the tree on the left, a list of files will be shown in the upper right. + You can use the Thumbnail view in the upper right to view the pictures. + When you select a file from the upper right, its contents will be shown in the lower right. + You can use the tabs in the lower right to view the text of the file, an image, or the hex data. +

+ +

+ If you are viewing files from the Views and Results nodes, you can right-click on a file to go to its file system location. + This feature is useful to see what else the user stored in the same folder as the file that you are currently looking at. + You can also right click on a file to extract it to the local system. +

+

+ If you want to search for single keywords, then you can use the search box in the upper right of the program. + The results will be shown in a table in the upper right. +

+ +

You can tag (or bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report.

+ +

Ingest Inbox

+

+ As you are going through the results in the tree, the ingest modules are running in the background. + The results are shown in the tree as soon as the ingest modules find them and report them. +

+

+ The Ingest Inbox receives messages from the ingest modules as they find results. + You can open the inbox to see what has been recently found. + It keeps track of what messages you have read. +

+

+ The intended use of this inbox is that you can focus on some data for a while and then check back on the inbox at a time that is convenient for them. + You can then see what else was found while you were focused on the previous task. + You may learn that a known bad file was found or that a file was found with a relevant keyword and then decide to focus on that for a while. +

+

When you select a message, you can then jump to the Results tree where more details can be found or jump to the file's location in the filesystem.

+ +

Timeline (Beta)

+

There is a basic timeline view that you can access via the Tools -> Make Timeline feature. This will take a few minutes to create the timeline for analysis. Its features are still in development.

+ + +

Example Use Cases

+

In this section, we will provide examples of how to do common analysis tasks.

+ +

Web Artifacts

+

+ If you want to view the user's recent web activity, make sure that the Recent Activity ingest module was enabled. + You can then go to the "Results " node in the tree on the left and then into the "Extracted Data" node. + There, you can find bookmarks, cookies, downloads, and history. +

+ +

Known Bad Hash Files

+

+ If you want to see if the data source had known bad files, make sure that the Hash Lookup ingest module was enabled. + You can then view the "Hashset Hits" section in the "Results" area of the tree on the left. + Note that hash lookup can take a long time, so this section will be updated as long as the ingest process is occurring. + Use the Ingest Inbox to keep track of what known bad files were recently found. +

+

+ When you find a known bad file in this interface, you may want to right click on the file to also view the file's original location. + You may find additional files that are relevant and stored in the same folder as this file. +

+ +

Media: Images and Videos

+

+ If you want to see all images and video on the disk image, then go to the "Views" section in the tree on the left and then "File Types". + Select either "Images" or "Videos". + You can use the thumbnail option in the upper right to view thumbnails of all images. +

+
    +
  • Note: + We are working on making this more efficient when there are lots of images and we are working on the feature to display video thumbnails. +
  • +
+

You can select an image or video from the upper right and view the video or image in the lower right. Video will be played with sound.

+ + +

Reporting

+

+ A final report can be generated that will include all analysis results. + Use the "Generate Report" button to create this. + It will create an HTML or XLS report in the Reports folder of the case folder. + If you forgot the location of your case folder, you can determine it using the "Case Properties" option in the "File" menu. + There is also an option to export report files to a separate folder outside of the case folder. +

+ +
+

Copyright © 2012-2013 Basis Technology.

+

+ This work is licensed under a + Creative Commons Attribution-Share Alike 3.0 United States License. +

+ + diff --git a/docs/doxygen/modAdvanced.dox b/docs/doxygen/modAdvanced.dox index 5a2416f1a2..13fcc87319 100644 --- a/docs/doxygen/modAdvanced.dox +++ b/docs/doxygen/modAdvanced.dox @@ -1,4 +1,4 @@ -/*! \page adv_dev_page Advanced Develpment Concepts +/*! \page adv_dev_page Advanced Development Concepts \section mod_dev_adv Advanced Concepts diff --git a/docs/doxygen/needs_a_home.dox b/docs/doxygen/needs_a_home.dox index c6badf6b36..b0a2b42d4f 100755 --- a/docs/doxygen/needs_a_home.dox +++ b/docs/doxygen/needs_a_home.dox @@ -1,30 +1,30 @@ - - - -The component is by default registered with the ingest manager as an ingest event listener. -The viewer first loads all the viewer-supported data currently in the blackboard when Autopsy starts. -During the ingest process the viewer receives events from ingest modules -(relayed by ingest manager) and it selectively refreshes parts of the tree providing real-time updates to the user. -When ingest is completed, the viewer responds to the final ingest data event generated by the ingest manager, -and performs a final refresh of all viewer-supported data in the blackboard. - - -Node content support capabilities are registered in the node's Lookup. - - - - -\section design_data_flow Data Flow - -\subsection design_data_flow_create Creating Nodes in DataExplorer - -Data flows between the UI zones using a NetBeans node. The DataExplorer modules create the NetBeans nodes. They query the SQLite database or do whatever they want to identify the set of files that are of interest. They create the NetBeans nodes based on Sleuthkit data model objects. See the org.sleuthkit.autopsy.datamodel package for more details on this. - -\subsection design_data_flow_toResult Getting Nodes to DataResult - -Each DataExplorer TopComponent is responsible for creating its own DataResult TopComponent to display its results. It can choose to re-use the same TopComponent for multiple searches (as DirectoryTree does) or it can choose to make a new one each time (as FileSearch does). The setNode() method on the DataResult object is used to set the root node to display. A dummy root node must be created as the parent if a parent does not already exist. - -The DataExplorer is responsible for setting the double-click and right-click actions associated with the node. The default single click action is to pass data to DataContent. To override this, you must create a new DataResultViewer instance that overrides the propertyChange() method. The DataExplorer adds actions to wrapping the node in a FilterNode variant. The FilterNode then defines the actions for the node by overriding the getPreferredAction() and getActions() methods. As an example, org.sleuthkit.autopsy.directorytree.DataResultFilterNode and org.sleuthkit.autopsy.directorytree.DataResultFilterChildren wraps the nodes that are passed over by the DirectoryTree DataExplorer. - -DataResult can send data back to its DataExplorer by making a custom action that looks up it's instance (DataExplorer.getInstance()). + + + +The component is by default registered with the ingest manager as an ingest event listener. +The viewer first loads all the viewer-supported data currently in the blackboard when Autopsy starts. +During the ingest process the viewer receives events from ingest modules +(relayed by ingest manager) and it selectively refreshes parts of the tree providing real-time updates to the user. +When ingest is completed, the viewer responds to the final ingest data event generated by the ingest manager, +and performs a final refresh of all viewer-supported data in the blackboard. + + +Node content support capabilities are registered in the node's Lookup. + + + + +\section design_data_flow Data Flow + +\subsection design_data_flow_create Creating Nodes in DataExplorer + +Data flows between the UI zones using a NetBeans node. The DataExplorer modules create the NetBeans nodes. They query the SQLite database or do whatever they want to identify the set of files that are of interest. They create the NetBeans nodes based on Sleuthkit data model objects. See the org.sleuthkit.autopsy.datamodel package for more details on this. + +\subsection design_data_flow_toResult Getting Nodes to DataResult + +Each DataExplorer TopComponent is responsible for creating its own DataResult TopComponent to display its results. It can choose to re-use the same TopComponent for multiple searches (as DirectoryTree does) or it can choose to make a new one each time (as FileSearch does). The setNode() method on the DataResult object is used to set the root node to display. A dummy root node must be created as the parent if a parent does not already exist. + +The DataExplorer is responsible for setting the double-click and right-click actions associated with the node. The default single click action is to pass data to DataContent. To override this, you must create a new DataResultViewer instance that overrides the propertyChange() method. The DataExplorer adds actions to wrapping the node in a FilterNode variant. The FilterNode then defines the actions for the node by overriding the getPreferredAction() and getActions() methods. As an example, org.sleuthkit.autopsy.directorytree.DataResultFilterNode and org.sleuthkit.autopsy.directorytree.DataResultFilterChildren wraps the nodes that are passed over by the DirectoryTree DataExplorer. + +DataResult can send data back to its DataExplorer by making a custom action that looks up it's instance (DataExplorer.getInstance()). diff --git a/docs/doxygen/workflow.dox b/docs/doxygen/workflow.dox index e7e3b9c882..c9bdf78486 100644 --- a/docs/doxygen/workflow.dox +++ b/docs/doxygen/workflow.dox @@ -1,53 +1,53 @@ -/*! \page workflow_page General Workflow and Design - -\section design_overview Overview -This section outlines the internal Autopsy design from the typical analysis work flow perspective. -This page is organized based on these phases: -- A Case is created. -- Images are added to the case and ingest modules are run. -- Results are manually reviewed and searched. -- Reports are generated. - -\section design_case Creating a Case -The first step in Autopsy work flow is creating a case. This is done in the org.sleuthkit.autopsy.casemodule package (see \ref casemodule_overview for details). This module contains the wizards needed and deals with how to store the information. You should not need to do much modifications in this package. But, you will want to use the org.sleuthkit.autopsy.casemodule.Case object to access all data related to this case. - - -\section design_image Adding an Image and Running Ingest Modules - -After case is created, one or more disk images can be added to the case. There is a wizard to guide that process and it is located in the org.sleuthkit.autopsy.casemodule package. Refer to the package section \ref casemodule_add_image for more details on the wizard. Most developers will not need to touch this code though. An important concept though is that adding an image to a case means that Autopsy uses The Sleuth Kit to enumerate all of the files in the file system and make a database entry for them in the embedded SQLite database that was created for the case. The database will be used for all further analysis. - -After image has been added to the case, the user can select one or more ingest modules to be executed on the image. Ingest modules focus on a specific type of analysis task and run in the background. They either analyze the entire disk image or individual files. The user will see the results from the modules in the result tree and in the ingest inbox. - -The org.sleuthkit.autopsy.ingest package provides the basic infrastructure for the ingest module management. - -If you want to develop a module that analyzes drive data, then this is probably the type of module that you want to build. See \ref mod_ingest_page for more details on making an ingest module. - - -\section design_view Viewing Results - -The UI has three main areas. The tree on the left-hand side, the result viewers in the upper right, and the content viewers in the lower right. Data passes between these areas by encapsulating them in Netbeans Node objects (see org.openide.nodes.Node). These allow Autopsy to generically handle all types of data. The org.sleuthkit.autopsy.datamodel package wraps the generic org.sleuthkit.datamodel Sleuth Kit objects as Netbeans Nodes. - -Nodes are modeled in a parent-child hierarchy with other nodes. All data within a Case is represented in a hierarchy with the disk images being one level below the case and volumes and such below the image. - -The tree on the left hand-side shows the analysis results. -Its contents are populated from the central database. -This is where you can browse the file system contents and see the results from the blackboard. - -The tree is implemented in the org.sleuthkit.autopsy.directorytree package. - -The area in the upper right is the result viewer area. When a node is selected from the tree, the node and its children are sent to this area. This area is used to view a set of nodes. The viewer is itself a framework with modules that display the data in different layouts. For example, the standard version comes with a table viewer and a thumbnail viewer. Refer to \ref mod_result_page for details on building a data result module. - -When an item is selected from the result viewer area, it is passed to the bottom right content viewers. It too is a framework with many modules that know how to show information about a specific file in different ways. For example, there are viewers that show the data in a hex dump format, extract the strings, and display pictures and movies. -See \ref mod_content_page for details on building new content viewers. - -\section design_report Report generation - -When ingest is complete, the user can generate reports. -There is a reporting framework to enable many different formats. Autopsy currently comes with generic html, xml and Excel reports. See the org.sleuthkit.autopsy.report package for details on the framework and -\ref mod_report_page for details on building a new report module. - - - - - -*/ +/*! \page workflow_page General Workflow and Design + +\section design_overview Overview +This section outlines the internal Autopsy design from the typical analysis work flow perspective. +This page is organized based on these phases: +- A Case is created. +- Images are added to the case and ingest modules are run. +- Results are manually reviewed and searched. +- Reports are generated. + +\section design_case Creating a Case +The first step in Autopsy work flow is creating a case. This is done in the org.sleuthkit.autopsy.casemodule package (see \ref casemodule_overview for details). This module contains the wizards needed and deals with how to store the information. You should not need to do much modifications in this package. But, you will want to use the org.sleuthkit.autopsy.casemodule.Case object to access all data related to this case. + + +\section design_image Adding an Image and Running Ingest Modules + +After case is created, one or more disk images can be added to the case. There is a wizard to guide that process and it is located in the org.sleuthkit.autopsy.casemodule package. Refer to the package section \ref casemodule_add_image for more details on the wizard. Most developers will not need to touch this code though. An important concept though is that adding an image to a case means that Autopsy uses The Sleuth Kit to enumerate all of the files in the file system and make a database entry for them in the embedded SQLite database that was created for the case. The database will be used for all further analysis. + +After image has been added to the case, the user can select one or more ingest modules to be executed on the image. Ingest modules focus on a specific type of analysis task and run in the background. They either analyze the entire disk image or individual files. The user will see the results from the modules in the result tree and in the ingest inbox. + +The org.sleuthkit.autopsy.ingest package provides the basic infrastructure for the ingest module management. + +If you want to develop a module that analyzes drive data, then this is probably the type of module that you want to build. See \ref mod_ingest_page for more details on making an ingest module. + + +\section design_view Viewing Results + +The UI has three main areas. The tree on the left-hand side, the result viewers in the upper right, and the content viewers in the lower right. Data passes between these areas by encapsulating them in Netbeans Node objects (see org.openide.nodes.Node). These allow Autopsy to generically handle all types of data. The org.sleuthkit.autopsy.datamodel package wraps the generic org.sleuthkit.datamodel Sleuth Kit objects as Netbeans Nodes. + +Nodes are modeled in a parent-child hierarchy with other nodes. All data within a Case is represented in a hierarchy with the disk images being one level below the case and volumes and such below the image. + +The tree on the left hand-side shows the analysis results. +Its contents are populated from the central database. +This is where you can browse the file system contents and see the results from the blackboard. + +The tree is implemented in the org.sleuthkit.autopsy.directorytree package. + +The area in the upper right is the result viewer area. When a node is selected from the tree, the node and its children are sent to this area. This area is used to view a set of nodes. The viewer is itself a framework with modules that display the data in different layouts. For example, the standard version comes with a table viewer and a thumbnail viewer. Refer to \ref mod_result_page for details on building a data result module. + +When an item is selected from the result viewer area, it is passed to the bottom right content viewers. It too is a framework with many modules that know how to show information about a specific file in different ways. For example, there are viewers that show the data in a hex dump format, extract the strings, and display pictures and movies. +See \ref mod_content_page for details on building new content viewers. + +\section design_report Report generation + +When ingest is complete, the user can generate reports. +There is a reporting framework to enable many different formats. Autopsy currently comes with generic html, xml and Excel reports. See the org.sleuthkit.autopsy.report package for details on the framework and +\ref mod_report_page for details on building a new report module. + + + + + +*/ diff --git a/nbproject/platform.properties b/nbproject/platform.properties index e0bdd68b73..a9fa87f749 100644 --- a/nbproject/platform.properties +++ b/nbproject/platform.properties @@ -1,120 +1,120 @@ -branding.token=autopsy -netbeans-plat-version=7.3.1 -suite.dir=${basedir} -nbplatform.active.dir=${suite.dir}/netbeans-plat/${netbeans-plat-version} -harness.dir=${nbplatform.active.dir}/harness -bootstrap.url=http://deadlock.netbeans.org/hudson/job/nbms-and-javadoc/lastStableBuild/artifact/nbbuild/netbeans/harness/tasks.jar -autoupdate.catalog.url=http://dlc.sun.com.edgesuite.net/netbeans/updates/${netbeans-plat-version}/uc/final/distribution/catalog.xml.gz -cluster.path=\ - ${nbplatform.active.dir}/harness:\ - ${nbplatform.active.dir}/java:\ - ${nbplatform.active.dir}/platform -disabled.modules=\ - org.apache.tools.ant.module,\ - org.netbeans.api.debugger.jpda,\ - org.netbeans.api.java,\ - org.netbeans.lib.nbjavac,\ - org.netbeans.libs.cglib,\ - org.netbeans.libs.javacapi,\ - org.netbeans.libs.javacimpl,\ - org.netbeans.libs.springframework,\ - org.netbeans.modules.ant.browsetask,\ - org.netbeans.modules.ant.debugger,\ - org.netbeans.modules.ant.freeform,\ - org.netbeans.modules.ant.grammar,\ - org.netbeans.modules.ant.kit,\ - org.netbeans.modules.beans,\ - org.netbeans.modules.classfile,\ - org.netbeans.modules.dbschema,\ - org.netbeans.modules.debugger.jpda,\ - org.netbeans.modules.debugger.jpda.ant,\ - org.netbeans.modules.debugger.jpda.kit,\ - org.netbeans.modules.debugger.jpda.projects,\ - org.netbeans.modules.debugger.jpda.ui,\ - org.netbeans.modules.debugger.jpda.visual,\ - org.netbeans.modules.findbugs.installer,\ - org.netbeans.modules.form,\ - org.netbeans.modules.form.binding,\ - org.netbeans.modules.form.j2ee,\ - org.netbeans.modules.form.kit,\ - org.netbeans.modules.form.nb,\ - org.netbeans.modules.form.refactoring,\ - org.netbeans.modules.hibernate,\ - org.netbeans.modules.hibernatelib,\ - org.netbeans.modules.hudson.ant,\ - org.netbeans.modules.hudson.maven,\ - org.netbeans.modules.i18n,\ - org.netbeans.modules.i18n.form,\ - org.netbeans.modules.j2ee.core.utilities,\ - org.netbeans.modules.j2ee.eclipselink,\ - org.netbeans.modules.j2ee.eclipselinkmodelgen,\ - org.netbeans.modules.j2ee.jpa.refactoring,\ - org.netbeans.modules.j2ee.jpa.verification,\ - org.netbeans.modules.j2ee.metadata,\ - org.netbeans.modules.j2ee.metadata.model.support,\ - org.netbeans.modules.j2ee.persistence,\ - org.netbeans.modules.j2ee.persistence.kit,\ - org.netbeans.modules.j2ee.persistenceapi,\ - org.netbeans.modules.java.api.common,\ - org.netbeans.modules.java.debug,\ - org.netbeans.modules.java.editor,\ - org.netbeans.modules.java.editor.lib,\ - org.netbeans.modules.java.examples,\ - org.netbeans.modules.java.freeform,\ - org.netbeans.modules.java.guards,\ - org.netbeans.modules.java.helpset,\ - org.netbeans.modules.java.hints,\ - org.netbeans.modules.java.hints.declarative,\ - org.netbeans.modules.java.hints.declarative.test,\ - org.netbeans.modules.java.hints.legacy.spi,\ - org.netbeans.modules.java.hints.test,\ - org.netbeans.modules.java.hints.ui,\ - org.netbeans.modules.java.j2seplatform,\ - org.netbeans.modules.java.j2seproject,\ - org.netbeans.modules.java.kit,\ - org.netbeans.modules.java.lexer,\ - org.netbeans.modules.java.navigation,\ - org.netbeans.modules.java.platform,\ - org.netbeans.modules.java.preprocessorbridge,\ - org.netbeans.modules.java.project,\ - org.netbeans.modules.java.source,\ - org.netbeans.modules.java.source.ant,\ - org.netbeans.modules.java.source.queries,\ - org.netbeans.modules.java.source.queriesimpl,\ - org.netbeans.modules.java.sourceui,\ - org.netbeans.modules.java.testrunner,\ - org.netbeans.modules.javadoc,\ - org.netbeans.modules.javawebstart,\ - org.netbeans.modules.junit,\ - org.netbeans.modules.maven,\ - org.netbeans.modules.maven.checkstyle,\ - org.netbeans.modules.maven.coverage,\ - org.netbeans.modules.maven.embedder,\ - org.netbeans.modules.maven.grammar,\ - org.netbeans.modules.maven.graph,\ - org.netbeans.modules.maven.hints,\ - org.netbeans.modules.maven.indexer,\ - org.netbeans.modules.maven.junit,\ - org.netbeans.modules.maven.kit,\ - org.netbeans.modules.maven.model,\ - org.netbeans.modules.maven.osgi,\ - org.netbeans.modules.maven.persistence,\ - org.netbeans.modules.maven.refactoring,\ - org.netbeans.modules.maven.repository,\ - org.netbeans.modules.maven.search,\ - org.netbeans.modules.maven.spring,\ - org.netbeans.modules.projectimport.eclipse.core,\ - org.netbeans.modules.projectimport.eclipse.j2se,\ - org.netbeans.modules.refactoring.java,\ - org.netbeans.modules.spellchecker.bindings.java,\ - org.netbeans.modules.spring.beans,\ - org.netbeans.modules.testng,\ - org.netbeans.modules.testng.ant,\ - org.netbeans.modules.testng.maven,\ - org.netbeans.modules.websvc.jaxws21,\ - org.netbeans.modules.websvc.jaxws21api,\ - org.netbeans.modules.websvc.saas.codegen.java,\ - org.netbeans.modules.xml.jaxb,\ - org.netbeans.modules.xml.tools.java,\ - org.netbeans.spi.java.hints - +branding.token=autopsy +netbeans-plat-version=7.3.1 +suite.dir=${basedir} +nbplatform.active.dir=${suite.dir}/netbeans-plat/${netbeans-plat-version} +harness.dir=${nbplatform.active.dir}/harness +bootstrap.url=http://deadlock.netbeans.org/hudson/job/nbms-and-javadoc/lastStableBuild/artifact/nbbuild/netbeans/harness/tasks.jar +autoupdate.catalog.url=http://dlc.sun.com.edgesuite.net/netbeans/updates/${netbeans-plat-version}/uc/final/distribution/catalog.xml.gz +cluster.path=\ + ${nbplatform.active.dir}/harness:\ + ${nbplatform.active.dir}/java:\ + ${nbplatform.active.dir}/platform +disabled.modules=\ + org.apache.tools.ant.module,\ + org.netbeans.api.debugger.jpda,\ + org.netbeans.api.java,\ + org.netbeans.lib.nbjavac,\ + org.netbeans.libs.cglib,\ + org.netbeans.libs.javacapi,\ + org.netbeans.libs.javacimpl,\ + org.netbeans.libs.springframework,\ + org.netbeans.modules.ant.browsetask,\ + org.netbeans.modules.ant.debugger,\ + org.netbeans.modules.ant.freeform,\ + org.netbeans.modules.ant.grammar,\ + org.netbeans.modules.ant.kit,\ + org.netbeans.modules.beans,\ + org.netbeans.modules.classfile,\ + org.netbeans.modules.dbschema,\ + org.netbeans.modules.debugger.jpda,\ + org.netbeans.modules.debugger.jpda.ant,\ + org.netbeans.modules.debugger.jpda.kit,\ + org.netbeans.modules.debugger.jpda.projects,\ + org.netbeans.modules.debugger.jpda.ui,\ + org.netbeans.modules.debugger.jpda.visual,\ + org.netbeans.modules.findbugs.installer,\ + org.netbeans.modules.form,\ + org.netbeans.modules.form.binding,\ + org.netbeans.modules.form.j2ee,\ + org.netbeans.modules.form.kit,\ + org.netbeans.modules.form.nb,\ + org.netbeans.modules.form.refactoring,\ + org.netbeans.modules.hibernate,\ + org.netbeans.modules.hibernatelib,\ + org.netbeans.modules.hudson.ant,\ + org.netbeans.modules.hudson.maven,\ + org.netbeans.modules.i18n,\ + org.netbeans.modules.i18n.form,\ + org.netbeans.modules.j2ee.core.utilities,\ + org.netbeans.modules.j2ee.eclipselink,\ + org.netbeans.modules.j2ee.eclipselinkmodelgen,\ + org.netbeans.modules.j2ee.jpa.refactoring,\ + org.netbeans.modules.j2ee.jpa.verification,\ + org.netbeans.modules.j2ee.metadata,\ + org.netbeans.modules.j2ee.metadata.model.support,\ + org.netbeans.modules.j2ee.persistence,\ + org.netbeans.modules.j2ee.persistence.kit,\ + org.netbeans.modules.j2ee.persistenceapi,\ + org.netbeans.modules.java.api.common,\ + org.netbeans.modules.java.debug,\ + org.netbeans.modules.java.editor,\ + org.netbeans.modules.java.editor.lib,\ + org.netbeans.modules.java.examples,\ + org.netbeans.modules.java.freeform,\ + org.netbeans.modules.java.guards,\ + org.netbeans.modules.java.helpset,\ + org.netbeans.modules.java.hints,\ + org.netbeans.modules.java.hints.declarative,\ + org.netbeans.modules.java.hints.declarative.test,\ + org.netbeans.modules.java.hints.legacy.spi,\ + org.netbeans.modules.java.hints.test,\ + org.netbeans.modules.java.hints.ui,\ + org.netbeans.modules.java.j2seplatform,\ + org.netbeans.modules.java.j2seproject,\ + org.netbeans.modules.java.kit,\ + org.netbeans.modules.java.lexer,\ + org.netbeans.modules.java.navigation,\ + org.netbeans.modules.java.platform,\ + org.netbeans.modules.java.preprocessorbridge,\ + org.netbeans.modules.java.project,\ + org.netbeans.modules.java.source,\ + org.netbeans.modules.java.source.ant,\ + org.netbeans.modules.java.source.queries,\ + org.netbeans.modules.java.source.queriesimpl,\ + org.netbeans.modules.java.sourceui,\ + org.netbeans.modules.java.testrunner,\ + org.netbeans.modules.javadoc,\ + org.netbeans.modules.javawebstart,\ + org.netbeans.modules.junit,\ + org.netbeans.modules.maven,\ + org.netbeans.modules.maven.checkstyle,\ + org.netbeans.modules.maven.coverage,\ + org.netbeans.modules.maven.embedder,\ + org.netbeans.modules.maven.grammar,\ + org.netbeans.modules.maven.graph,\ + org.netbeans.modules.maven.hints,\ + org.netbeans.modules.maven.indexer,\ + org.netbeans.modules.maven.junit,\ + org.netbeans.modules.maven.kit,\ + org.netbeans.modules.maven.model,\ + org.netbeans.modules.maven.osgi,\ + org.netbeans.modules.maven.persistence,\ + org.netbeans.modules.maven.refactoring,\ + org.netbeans.modules.maven.repository,\ + org.netbeans.modules.maven.search,\ + org.netbeans.modules.maven.spring,\ + org.netbeans.modules.projectimport.eclipse.core,\ + org.netbeans.modules.projectimport.eclipse.j2se,\ + org.netbeans.modules.refactoring.java,\ + org.netbeans.modules.spellchecker.bindings.java,\ + org.netbeans.modules.spring.beans,\ + org.netbeans.modules.testng,\ + org.netbeans.modules.testng.ant,\ + org.netbeans.modules.testng.maven,\ + org.netbeans.modules.websvc.jaxws21,\ + org.netbeans.modules.websvc.jaxws21api,\ + org.netbeans.modules.websvc.saas.codegen.java,\ + org.netbeans.modules.xml.jaxb,\ + org.netbeans.modules.xml.tools.java,\ + org.netbeans.spi.java.hints + diff --git a/nbproject/project.properties b/nbproject/project.properties index 6446b47619..7e397a51ff 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -8,6 +8,7 @@ app.version=3.0.8 ### Build type isn't used at this point, but it may be useful ### Must be one of: DEVELOPMENT, RELEASE build.type=RELEASE +project.org.sleuthkit.autopsy.filetypeid=FileTypeId #build.type=DEVELOPMENT update_versions=false #custom JVM options @@ -31,7 +32,8 @@ modules=\ ${project.org.sleuthkit.autopsy.corelibs}:\ ${project.org.sleuthkit.autopsy.sevenzip}:\ ${project.org.sleuthkit.autopsy.scalpel}:\ - ${project.org.sleuthkit.autopsy.timeline} + ${project.org.sleuthkit.autopsy.timeline}:\ + ${project.org.sleuthkit.autopsy.filetypeid} project.org.sleuthkit.autopsy.core=Core project.org.sleuthkit.autopsy.corelibs=CoreLibs project.org.sleuthkit.autopsy.hashdatabase=HashDatabase diff --git a/thirdparty/crt/win32/crt.zip b/thirdparty/crt/win32/crt.zip new file mode 100755 index 0000000000..a771033d47 Binary files /dev/null and b/thirdparty/crt/win32/crt.zip differ diff --git a/thirdparty/crt/win64/crt.zip b/thirdparty/crt/win64/crt.zip new file mode 100755 index 0000000000..a22a29a142 Binary files /dev/null and b/thirdparty/crt/win64/crt.zip differ diff --git a/thirdparty/crt/x86-32/10.0.30319.1/crt.zip b/thirdparty/crt/x86-32/10.0.30319.1/crt.zip deleted file mode 100644 index cb961707a6..0000000000 Binary files a/thirdparty/crt/x86-32/10.0.30319.1/crt.zip and /dev/null differ diff --git a/thirdparty/crt/x86-32/10.0.40219.1/crt.zip b/thirdparty/crt/x86-32/10.0.40219.1/crt.zip deleted file mode 100644 index 450e13b8c7..0000000000 Binary files a/thirdparty/crt/x86-32/10.0.40219.1/crt.zip and /dev/null differ diff --git a/thirdparty/crt/x86-32/10.0.40219.325/crt.zip b/thirdparty/crt/x86-32/10.0.40219.325/crt.zip deleted file mode 100644 index 9df507dd3e..0000000000 Binary files a/thirdparty/crt/x86-32/10.0.40219.325/crt.zip and /dev/null differ diff --git a/thirdparty/crt/x86-32/9.0.21022.8/crt.zip b/thirdparty/crt/x86-32/9.0.21022.8/crt.zip deleted file mode 100644 index 3b9b047315..0000000000 Binary files a/thirdparty/crt/x86-32/9.0.21022.8/crt.zip and /dev/null differ diff --git a/thirdparty/crt/x86-32/9.0.30729.1/crt.zip b/thirdparty/crt/x86-32/9.0.30729.1/crt.zip deleted file mode 100644 index 794f597b4e..0000000000 Binary files a/thirdparty/crt/x86-32/9.0.30729.1/crt.zip and /dev/null differ