diff --git a/Core/build.xml b/Core/build.xml index 98644b8cb3..009e2012cf 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -21,11 +21,6 @@ - - - - - @@ -89,17 +84,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 7ae5484bc9..93d0e96015 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -3,7 +3,7 @@ file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar file.reference.commons-compress-1.14.jar=release/modules/ext/commons-compress-1.14.jar file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.1.jar file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar -file.reference.dd-plist-1.20.jar=release\\modules\\ext\\dd-plist-1.20.jar +file.reference.dd-plist-1.20.jar=release/modules/ext/dd-plist-1.20.jar file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar file.reference.jgraphx-v3.8.0.jar=release/modules/ext/jgraphx-v3.8.0.jar @@ -12,7 +12,6 @@ file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.metadata-extractor-2.10.1.jar=release/modules/ext/metadata-extractor-2.10.1.jar file.reference.postgresql-9.4.1211.jre7.jar=release/modules/ext/postgresql-9.4.1211.jre7.jar -file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index ac3ec60d44..f6a841482c 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -339,6 +339,10 @@ org.sleuthkit.autopsy.report org.sleuthkit.datamodel + + ext/jackcess-2.1.8.jar + release/modules/ext/jackcess-2.1.8.jar + ext/zookeeper-3.4.6.jar release/modules/ext/zookeeper-3.4.6.jar @@ -348,33 +352,33 @@ release/modules/ext/jdom-2.0.5.jar - ext/sleuthkit-postgresql-4.6.1.jar - release/modules/ext/sleuthkit-postgresql-4.6.1.jar - - - ext/opencv-248.jar - release/modules/ext/opencv-248.jar + ext/cxf-rt-transports-http-3.0.16.jar + release/modules/ext/cxf-rt-transports-http-3.0.16.jar ext/curator-framework-2.8.0.jar release/modules/ext/curator-framework-2.8.0.jar - ext/commons-dbcp2-2.1.1.jar - release/modules/ext/commons-dbcp2-2.1.1.jar - - - ext/jgraphx-v3.8.0.jar - release/modules/ext/jgraphx-v3.8.0.jar + ext/bcprov-jdk15on-1.54.jar + release/modules/ext/bcprov-jdk15on-1.54.jar ext/commons-compress-1.14.jar release/modules/ext/commons-compress-1.14.jar + + ext/fontbox-2.0.8.jar + release/modules/ext/fontbox-2.0.8.jar + ext/commons-dbcp2-2.1.1.jar release\modules\ext\commons-dbcp2-2.1.1.jar + + ext/jgraphx-v3.8.0.jar + release/modules/ext/jgraphx-v3.8.0.jar + ext/jython-standalone-2.7.0.jar release/modules/ext/jython-standalone-2.7.0.jar @@ -387,6 +391,14 @@ ext/mchange-commons-java-0.2.9.jar release/modules/ext/mchange-commons-java-0.2.9.jar + + ext/cxf-core-3.0.16.jar + release/modules/ext/cxf-core-3.0.16.jar + + + ext/javax.ws.rs-api-2.0.1.jar + release/modules/ext/javax.ws.rs-api-2.0.1.jar + ext/postgresql-9.4.1211.jre7.jar release/modules/ext/postgresql-9.4.1211.jre7.jar @@ -399,6 +411,10 @@ ext/metadata-extractor-2.10.1.jar release/modules/ext/metadata-extractor-2.10.1.jar + + ext/sleuthkit-postgresql-4.6.1.jar + release/modules/ext/sleuthkit-postgresql-4.6.1.jar + ext/tika-core-1.17.jar release/modules/ext/tika-core-1.17.jar @@ -411,58 +427,18 @@ ext/curator-client-2.8.0.jar release/modules/ext/curator-client-2.8.0.jar - - ext/bcprov-jdk15on-1.54.jar - release/modules/ext/bcprov-jdk15on-1.54.jar - - - ext/jackcess-2.1.8.jar - release/modules/ext/jackcess-2.1.8.jar - - - ext/jackcess-encrypt-2.1.2.jar - release/modules/ext/jackcess-encrypt-2.1.2.jar - - - ext/jempbox-1.8.13.jar - release/modules/ext/jempbox-1.8.13.jar - - - ext/javax.ws.rs-api-2.0.1.jar - release/modules/ext/javax.ws.rs-api-2.0.1.jar - - - ext/cxf-rt-rs-client-3.0.16.jar - release/modules/ext/cxf-rt-rs-client-3.0.16.jar - - - ext/cxf-rt-transports-http-3.0.16.jar - release/modules/ext/cxf-rt-transports-http-3.0.16.jar - - - ext/cxf-core-3.0.16.jar - release/modules/ext/cxf-core-3.0.16.jar - ext/cxf-rt-frontend-jaxrs-3.0.16.jar release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar - - ext/tika-parsers-1.17.jar - release/modules/ext/tika-parsers-1.17.jar - - - ext/fontbox-2.0.8.jar - release/modules/ext/fontbox-2.0.8.jar - - - ext/pdfbox-2.0.8.jar - release/modules/ext/pdfbox-2.0.8.jar - ext/pdfbox-tools-2.0.8.jar release/modules/ext/pdfbox-tools-2.0.8.jar + + ext/tika-parsers-1.17.jar + release/modules/ext/tika-parsers-1.17.jar + ext/sqlite-jdbc-3.8.11.jar release/modules/ext/sqlite-jdbc-3.8.11.jar @@ -483,6 +459,14 @@ ext/dd-plist-1.20.jar release/modules/ext/dd-plist-1.20.jar + + ext/jempbox-1.8.13.jar + release/modules/ext/jempbox-1.8.13.jar + + + ext/cxf-rt-rs-client-3.0.16.jar + release/modules/ext/cxf-rt-rs-client-3.0.16.jar + ext/sevenzipjbinding-AllPlatforms.jar release/modules/ext/sevenzipjbinding-AllPlatforms.jar @@ -491,6 +475,10 @@ ext/commons-pool2-2.4.2.jar release\modules\ext\commons-pool2-2.4.2.jar + + ext/jackcess-encrypt-2.1.2.jar + release/modules/ext/jackcess-encrypt-2.1.2.jar + ext/jsoup-1.10.3.jar release/modules/ext/jsoup-1.10.3.jar @@ -499,6 +487,10 @@ ext/jdom-2.0.5-contrib.jar release/modules/ext/jdom-2.0.5-contrib.jar + + ext/pdfbox-2.0.8.jar + release/modules/ext/pdfbox-2.0.8.jar + ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 7ad185ac8e..9015d9a6ad 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -37,8 +37,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to blackboard artifacts. */ @NbBundle.Messages({ - "AddBlackboardArtifactTagAction.singularTagResult=Tag Result", - "AddBlackboardArtifactTagAction.pluralTagResult=Tag Results", + "AddBlackboardArtifactTagAction.singularTagResult=Add Result Tag", + "AddBlackboardArtifactTagAction.pluralTagResult=Add Result Tags", "# {0} - artifactName", "AddBlackboardArtifactTagAction.unableToTag.msg=Unable to tag {0}.", "AddBlackboardArtifactTagAction.taggingErr=Tagging Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index 602789735d..34e7b2a110 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -38,8 +38,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to content. */ @NbBundle.Messages({ - "AddContentTagAction.singularTagFile=Tag File", - "AddContentTagAction.pluralTagFile=Tag Files", + "AddContentTagAction.singularTagFile=Add File Tag", + "AddContentTagAction.pluralTagFile=Add File Tags", "# {0} - fileName", "AddContentTagAction.unableToTag.msg=Unable to tag {0}, not a regular file.", "AddContentTagAction.cannotApplyTagErr=Cannot Apply Tag", diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 6efb30e6a2..6606185e24 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; @@ -92,6 +94,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Get the current set of tag names. Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); @@ -101,6 +104,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Create a menu item for each of the existing and visible tags. // Selecting one of these menu items adds a tag with the associated tag name. + List standardTagMenuitems = new ArrayList<>(); if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -114,15 +118,26 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagNameItem.addActionListener((ActionEvent e) -> { getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); - - add(tagNameItem); + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } if (getItemCount() > 0) { addSeparator(); } - + + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + + addSeparator(); + // 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. diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 1cbc4b70b6..c83c8c2782 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -20,7 +20,7 @@ AddTagAction.newTag=New Tag... AddTagAction.tagAndComment=Tag and Comment... AddBookmarkTagAction.bookmark.text=Bookmark GetTagNameAndCommentDialog.noTags=No Tags -GetTagNameAndCommentDialog.createTag=Create Tag +GetTagNameAndCommentDialog.selectTag=Select Tag GetTagNameAndCommentDialog.cancelName=cancel GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties index d6f865ecf0..10bd2ad0df 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties @@ -23,7 +23,7 @@ AddTagAction.noTags=\u30bf\u30b0\u7121\u3057 AddTagAction.newTag=\u65b0\u898f\u30bf\u30b0\u2026 AddTagAction.tagAndComment=\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8\u3092\u8ffd\u52a0\u2026 GetTagNameAndCommentDialog.noTags=\u30bf\u30b0\u7121\u3057 -GetTagNameAndCommentDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 +GetTagNameAndCommentDialog.selectTag=\u30bf\u30b0\u3092\u9078\u629e GetTagNameAndCommentDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb GetTagNameDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 GetTagNameDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java index 18ae9ac7e5..c3878e6b59 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java @@ -38,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException; * artifacts. */ @NbBundle.Messages({ - "DeleteBlackboardArtifactTagAction.deleteTag=Delete Tag", + "DeleteBlackboardArtifactTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteBlackboardArtifactTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java index 07cce02bfc..b7ff3a73a0 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to delete tags applied to content. */ @NbBundle.Messages({ - "DeleteContentTagAction.deleteTag=Delete Tag", + "DeleteContentTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteContentTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 57c43e964b..818d660ff3 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -24,11 +24,11 @@ - + - + @@ -38,14 +38,14 @@ - + - + - + @@ -58,11 +58,11 @@ - - - + + + - + @@ -125,16 +125,6 @@ - - - - - - - - - - @@ -145,5 +135,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index d2c66f410b..ef7e853d68 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -22,9 +22,13 @@ import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.util.ArrayList; import java.util.logging.Level; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.DefaultListCellRenderer; @@ -50,7 +54,8 @@ import org.sleuthkit.datamodel.TskData; public class GetTagNameAndCommentDialog extends JDialog { private static final long serialVersionUID = 1L; - private final Set tagNamesSet = new HashSet<>(); + private final List tagNamesList = new ArrayList<>(); + private final List standardTagNamesList = new ArrayList<>(); private TagNameAndComment tagNameAndComment = null; public static class TagNameAndComment { @@ -105,7 +110,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private GetTagNameAndCommentDialog(Window owner) { super(owner, - NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.createTag"), + NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.selectTag"), ModalityType.APPLICATION_MODAL); } @@ -144,16 +149,29 @@ public class GetTagNameAndCommentDialog extends JDialog { // not exist in the database). try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagNamesSet.addAll(tagsManager.getAllTagNames()); + List standardTagNames = TagsManager.getStandardTagNames(); + Map tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + + tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> { + if (standardTagNames.contains(tagName.getDisplayName())) { + standardTagNamesList.add(tagName); + } else { + tagNamesList.add(tagName); + } + }); + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class .getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - for (TagName tag : tagNamesSet) { - + tagNamesList.forEach((tag) -> { tagCombo.addItem(tag); - } + }); + + standardTagNamesList.forEach((tag) -> { + tagCombo.addItem(tag); + }); // Center and show the dialog box. this.setLocationRelativeTo(this.getOwner()); @@ -174,8 +192,9 @@ public class GetTagNameAndCommentDialog extends JDialog { tagCombo = new javax.swing.JComboBox(); tagLabel = new javax.swing.JLabel(); commentLabel = new javax.swing.JLabel(); - commentText = new javax.swing.JTextField(); newTagButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + commentText = new javax.swing.JTextArea(); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { @@ -203,9 +222,6 @@ public class GetTagNameAndCommentDialog extends JDialog { org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentLabel.text")); // 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(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.newTagButton.text")); // NOI18N newTagButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -213,6 +229,12 @@ public class GetTagNameAndCommentDialog extends JDialog { } }); + commentText.setColumns(20); + commentText.setRows(5); + 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 + jScrollPane1.setViewportView(commentText); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -222,7 +244,7 @@ public class GetTagNameAndCommentDialog extends JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newTagButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 165, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) @@ -232,8 +254,8 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(commentText) - .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 318, Short.MAX_VALUE)))) .addContainerGap()) ); @@ -247,10 +269,10 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(commentLabel) - .addComponent(commentText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 37, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 22, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton) @@ -282,7 +304,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed TagName newTagName = GetTagNameDialog.doDialog(this); if (newTagName != null) { - tagNamesSet.add(newTagName); + tagNamesList.add(newTagName); tagCombo.addItem(newTagName); tagCombo.setSelectedItem(newTagName); } @@ -291,7 +313,8 @@ public class GetTagNameAndCommentDialog extends JDialog { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JLabel commentLabel; - private javax.swing.JTextField commentText; + private javax.swing.JTextArea commentText; + private javax.swing.JScrollPane jScrollPane1; private javax.swing.JButton newTagButton; private javax.swing.JButton okButton; private javax.swing.JComboBox tagCombo; diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java new file mode 100644 index 0000000000..a014257daf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -0,0 +1,126 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This Action allows users to replace a tag applied to blackboard + * artifacts, with another tag + */ +public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction { + + private static final Logger logger = Logger.getLogger(ReplaceBlackboardArtifactTagAction.class.getName()); + private static final long serialVersionUID = 1L; + + // 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 ReplaceBlackboardArtifactTagAction instance; + + public static synchronized ReplaceBlackboardArtifactTagAction getInstance() { + if (null == instance) { + instance = new ReplaceBlackboardArtifactTagAction(); + } + return instance; + } + + private ReplaceBlackboardArtifactTagAction() { + super(MENU_TEXT); + } + + /** + * Replaces the specified tag on the given artifact with the new one + * + * @param oldArtifactTag tag to be replaced + * @param newTagName name of the tag to replace with + */ + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - artifactID", + "ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."}) + @Override + protected void replaceTag( BlackboardArtifactTag oldArtifactTag, TagName newTagName) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldArtifactTag.getName().getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); + tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName); + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing artifact tag", ex); //NON-NLS + } + } + }.execute(); + } + + /** + * Returns list of tags selected by user to replace + * + * @return a list of tags + */ + @Override + Collection getTagsToReplace() { + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java new file mode 100644 index 0000000000..484a30178c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -0,0 +1,120 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This Action allow users to replace a content tag with another tag + */ +public final class ReplaceContentTagAction extends ReplaceTagAction { + + private static final Logger logger = Logger.getLogger(ReplaceContentTagAction.class.getName()); + + private static final long serialVersionUID = 1L; + + // 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 ReplaceContentTagAction instance; + + public static synchronized ReplaceContentTagAction getInstance() { + if (null == instance) { + instance = new ReplaceContentTagAction(); + } + return instance; + } + + private ReplaceContentTagAction() { + super(MENU_TEXT); + } + + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - content obj id", + "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) + @Override + protected void replaceTag(ContentTag oldTag, TagName newTagName) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteContentTag(oldTag); + tagsManager.addContentTag(oldTag.getContent(), newTagName); + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing content tag", ex); //NON-NLS + } + } + }.execute(); + } + + /** + * Returns list of content tags selected by user to replace + * + * @return a list of tags + */ + @Override + Collection getTagsToReplace() { + return Utilities.actionsGlobalContext().lookupAll(ContentTag.class); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java new file mode 100644 index 0000000000..a5a0843965 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -0,0 +1,188 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.util.NbBundle; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Abstract class to define context action to replace a tag with another + * + * @param tag type + */ +@NbBundle.Messages({ + "ReplaceTagAction.replaceTag=Replace Selected Tag(s) With" +}) +abstract class ReplaceTagAction extends AbstractAction implements Presenter.Popup { + + private static final long serialVersionUID = 1L; + protected static final String MENU_TEXT = NbBundle.getMessage(ReplaceTagAction.class, + "ReplaceTagAction.replaceTag"); + + ReplaceTagAction(String menuText) { + super(menuText); + } + + /** + * Subclasses of replaceTagAction should not override actionPerformed, + * but instead override replaceTag. + * + * @param event + */ + @Override + @SuppressWarnings("NoopMethodInAbstractClass") + public void actionPerformed(ActionEvent event) { + } + + protected String getActionDisplayName() { + return MENU_TEXT; + } + + /** + * Method to actually replace the selected tag with the given new tag + * + * @param oldTag + * @param newTagName + */ + abstract protected void replaceTag(T oldTag, TagName newTagName); + + /** + * Returns elected tags which are to be replaced + * + * @return + */ + abstract Collection getTagsToReplace(); + + + @Override + public JMenuItem getPopupPresenter() { + return new ReplaceTagMenu(); + } + + /** + * Instances of this class implement a context menu user interface for + * selecting a tag name to replace the tag with + */ + private final class ReplaceTagMenu extends JMenu { + + private static final long serialVersionUID = 1L; + + ReplaceTagMenu() { + super(getActionDisplayName()); + + final Collection selectedTags = getTagsToReplace(); + + // Get the current set of tag names. + Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + + List standardTagMenuitems = new ArrayList<>(); + // Ideally we should'nt allow user to pick a replacement tag that's already been applied to an item + // In the very least we don't allow them to pick the same tag as the one they are trying to replace + Set existingTagNames = new HashSet<>(); + if (!selectedTags.isEmpty()) { + T firstTag = selectedTags.iterator().next(); + existingTagNames.add(firstTag.getName().getDisplayName()); + } + + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + String tagDisplayName = entry.getKey(); + String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); + // for the bookmark tag name only, added shortcut label + if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { + tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); + } + + // Add action to replace the tag + tagNameItem.addActionListener((ActionEvent event) -> { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, entry.getValue()); + }); + }); + + // Don't allow replacing a tag with same tag. + if (existingTagNames.contains(tagDisplayName)) { + tagNameItem.setEnabled(false); + } + + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } + } + } else { + JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); + empty.setEnabled(false); + add(empty); + } + + // + if (this.getItemCount() > 0) { + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + + addSeparator(); + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent event) -> { + TagName newTagName = GetTagNameDialog.doDialog(); + if (null != newTagName) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, newTagName); + }); + } + }); + add(newTagMenuItem); + + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java index 742cb1750c..a469a36584 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,15 +18,22 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Frame; +import javax.swing.JDialog; + /** * Dialog to show add image error messages */ -public class AddImageErrorsDialog extends javax.swing.JDialog { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class AddImageErrorsDialog extends JDialog { /** * Creates new form AddImageErrorsDialog + * + * @param parent The parent frame. + * @param modal Does this dialog act as a modal? */ - public AddImageErrorsDialog(java.awt.Frame parent, boolean modal) { + public AddImageErrorsDialog(Frame parent, boolean modal) { super(parent, modal); initComponents(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 6c6097717e..15eb0c3c67 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -57,6 +57,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * {@link AddImageWizardIngestConfigPanel} (which is a bit weird if you ask m * -jm) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { private boolean readyToIngest = false; @@ -79,7 +80,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { */ private AddImageWizardAddingProgressVisual component; private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 - private final List newContents = Collections.synchronizedList(new ArrayList()); + private final List newContents = Collections.synchronizedList(new ArrayList<>()); private final DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl(); private IngestJobSettings ingestJobSettings; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java index 3b6772637f..63f4cae069 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +19,14 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.JProgressBar; -import org.openide.WizardDescriptor; import org.openide.util.NbBundle; /** * visual component to display progress bar and status updates while adding an * image in the wizard */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { private static final String ADDING_DATA_SOURCE_COMPLETE = NbBundle diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java index e4d6d09984..15f1ae86a5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * The "Add Image" wizard panel1 handling the logic of selecting image file(s) * to add to Case, and pick the time zone. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardDataSourceSettingsPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java index a5671ce0ee..d44b3ed522 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,8 +37,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * 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 - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardDataSourceSettingsVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardDataSourceSettingsVisual.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java index 4678e136a7..cfcbdd015c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java @@ -40,10 +40,11 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * TODO: review this for dead code. think about moving logic of adding image to * 3rd panel( {@link AddImageWizardAddingProgressPanel}) separate class -jm */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel { @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules") - private IngestJobSettingsPanel ingestJobSettingsPanel; + private final IngestJobSettingsPanel ingestJobSettingsPanel; /** * The visual component that displays this panel. If you need to access the * component from this class, just use getComponent(). diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java index f1a0211425..3d730e6b0e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,8 +25,8 @@ import javax.swing.JPanel; /** * UI panel for the ingest job configuration wizard panel of the add data source * wizard. - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigVisual extends JPanel { private final JPanel ingestPanel; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java index 86981e9d5d..5db401fd0f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * Create a wizard panel which contains a panel allowing the selection of the * DataSourceProcessor */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardSelectDspPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { @NbBundle.Messages("SelectDataSourceProcessorPanel.name.text=Select Type of Data Source To Add") diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index 43bfed1caa..ee14581bc7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -46,6 +46,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Panel which displays the available DataSourceProcessors and allows selection * of one */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardSelectDspVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardSelectDspVisual.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 4f25030fd3..402b64d78f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -49,8 +49,8 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; * * Used to display a list of multi user cases and allow the user to open one of * them. - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index b8542552c4..252a44e577 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.openide.windows.WindowManager; * Panel for displaying the case information, including both case details and * ingest job history. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class CaseInformationPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java index 478820353f..3ddb97fcfd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /* * The panel in the default Autopsy startup window. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class CueBannerPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java index d1c726d3eb..907c2fd0f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,9 @@ import java.awt.event.ActionListener; import org.openide.util.NbBundle.Messages; /** - * - * @author wschaefer + * Panel to allow examiner to edit option case properties. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class EditOptionalCasePropertiesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 56d5e67839..1d2b1ce1e2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -31,7 +31,6 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import static org.sleuthkit.autopsy.casemodule.Bundle.*; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; @@ -46,6 +45,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; * to select a file as well as choose the timezone and whether to ignore orphan * files in FAT32. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ImageFilePanel extends JPanel implements DocumentListener { private static final Logger logger = Logger.getLogger(ImageFilePanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index fc702076a0..cb1ec5ddfd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -34,6 +34,10 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; +/** + * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or + * C:. + */ @NbBundle.Messages({ "LocalDiskPanel.errorMessage.noOpenCaseTitle=No open case available", "LocalDiskPanel.errorMessage.noOpenCaseBody=LocalDiskPanel listener couldn't get the open case.", @@ -45,10 +49,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; "LocalDiskPanel.moduleErrorMessage.body=A module caused an error listening to LocalDiskPanel updates. See log to determine which module. Some data could be incomplete.", "LocalDiskPanel.localDiskMessage.unspecified=Unspecified" }) -/** - * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or - * C:. - */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LocalDiskPanel extends JPanel { private static final Logger logger = Logger.getLogger(LocalDiskPanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 3ab4088094..ef985db2dd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; /** * A panel which allows the user to select local files and/or directories. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LocalFilesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -278,7 +279,10 @@ final class LocalFilesPanel extends javax.swing.JPanel { * * @param paths Absolute paths to the selected data source */ - @NbBundle.Messages("LocalFilesPanel.pathValidation.error=WARNING: Exception while gettting opon case.") + @NbBundle.Messages({ + "LocalFilesPanel.pathValidation.dataSourceOnCDriveError=Warning: Path to multi-user data source is on \"C:\" drive", + "LocalFilesPanel.pathValidation.getOpenCase=WARNING: Exception while gettting open case." + }) private void warnIfPathIsInvalid(final List pathsList) { errorLabel.setVisible(false); @@ -288,13 +292,13 @@ final class LocalFilesPanel extends javax.swing.JPanel { for (String currentPath : pathsList) { if (!PathValidator.isValid(currentPath, currentCaseType)) { errorLabel.setVisible(true); - errorLabel.setText(NbBundle.getMessage(this.getClass(), "DataSourceOnCDriveError.text")); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_dataSourceOnCDriveError()); return; } } } catch (NoCurrentCaseException ex) { errorLabel.setVisible(true); - errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_error()); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_getOpenCase()); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java index 0298c98d69..5f96422a9e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java @@ -28,13 +28,14 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +/** + * Add input wizard subpanel for adding local files / dirs to the case + */ @Messages({ "LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders", "LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01)" }) -/** - * Add input wizard subpanel for adding local files / dirs to the case - */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LogicalFilesDspPanel extends JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java index 8c90aeccb4..af95b0c5a0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import org.openide.windows.WindowManager; /** * This class extends a JDialog and maintains the MultiUserCasesPanel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MultiUserCasesDialog extends JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index 7a1ca81862..07d863bc2b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,9 +36,10 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * A panel that allows a user to open cases created by auto ingest. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MultiUserCasesPanel extends JPanel{ - private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); + private static final Logger logger = Logger.getLogger(MultiUserCasesPanel.class.getName()); private static final long serialVersionUID = 1L; private final JDialog parentDialog; private final CaseBrowser caseBrowserPanel; @@ -98,7 +99,7 @@ final class MultiUserCasesPanel extends JPanel{ Case.openAsCurrentCase(caseMetadataFilePath); } catch (CaseActionException ex) { if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); } SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index cd1179b52b..6f365af86e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,9 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Component; import org.openide.util.NbBundle; -import java.awt.*; import java.io.File; import javax.swing.JFileChooser; import javax.swing.JPanel; @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; /** * The JPanel for the first page of the new case wizard. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { private final JFileChooser fileChooser = new JFileChooser(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java index 2098fc91b3..ec06648248 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * NewCaseVisualPanel2.java - * - * Created on Mar 7, 2012, 11:01:48 AM - */ package org.sleuthkit.autopsy.casemodule; import org.openide.util.NbBundle; /** - * - * @author dfickling + * The JPanel for the second page of the new case wizard. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel2 extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f113a622fe..d80feed87a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -121,6 +121,15 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * Returns a list of names of standard/predefined tags + * + * @return list of predefined tag names + */ + public static List getStandardTagNames() { + return TagNameDefinition.getStandardTagNames(); + } + /** * Constructs a per case Autopsy service that manages the addition of * content and artifact tags to the case database. @@ -157,6 +166,21 @@ public class TagsManager implements Closeable { return caseDb.getTagNamesInUse(); } + /** + * Selects all of the rows from the tag_names table in the case database for + * which there is at least one matching row in the content_tags or + * blackboard_artifact_tags tables, for the given data source object id. + * + * @param dsObjId data source object id + * + * @return A list, possibly empty, of TagName data transfer objects (DTOs) + * for the rows. + * + * @throws TskCoreException + */ + public List getTagNamesInUse(long dsObjId) throws TskCoreException { + return caseDb.getTagNamesInUse(dsObjId); + } /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current @@ -392,6 +416,24 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsCountByTagName(tagName); } + /** + * Gets content tags count by tag name, for the given data source + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames and/or addTagName. + * + * @param dsObjId data source object id + * + * @return A count of the content tags with the specified tag name, and for + * the given data source + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsCountByTagName(tagName, dsObjId); + } + /** * Gets a content tag by tag id. * @@ -421,6 +463,23 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsByTagName(tagName); } + /** + * Gets content tags by tag name, for the given data source. + * + * @param tagName The tag name of interest. + * + * @param dsObjId data source object id + * + * @return A list, possibly empty, of the content tags with the specified + * tag name, and for the given data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsByTagName(tagName, dsObjId); + } + /** * Gets content tags count by content. * @@ -522,6 +581,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } + /** + * Gets an artifact tags count by tag name, for the given data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId data source object id + * + * @return A count of the artifact tags with the specified tag name, + * for the given data source. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagsCountByTagName(tagName, dsObjId); + } + /** * Gets an artifact tag by tag id. * @@ -553,6 +630,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsByTagName(tagName); } + /** + * Gets artifact tags by tag name, for specified data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId data source object id + * + * @return A list, possibly empty, of the artifact tags with the specified + * tag name, for the specified data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagsByTagName(tagName, dsObjId); + } + /** * Gets artifact tags for a particular artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java new file mode 100755 index 0000000000..dfeb4fbdcd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -0,0 +1,148 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.centralrepository; + +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An AbstractAction to manage adding and modifying a Central Repository file + * instance comment. + */ +public final class AddEditCentralRepoCommentAction extends AbstractAction { + + private static final Logger logger = Logger.getLogger(AddEditCentralRepoCommentAction.class.getName()); + + private boolean addToDatabase; + private CorrelationAttribute correlationAttribute; + String title; + + /** + * Private constructor to create an instance given a CorrelationAttribute. + * + * @param correlationAttribute The correlation attribute to modify. + * @param title The text for the menu item. + */ + private AddEditCentralRepoCommentAction(CorrelationAttribute correlationAttribute, String title) { + super(title); + this.title = title; + this.correlationAttribute = correlationAttribute; + } + + /** + * Private constructor to create an instance given an AbstractFile. + * + * @param file The file from which a correlation attribute to modify is + * derived. + * @param title The text for the menu item. + */ + private AddEditCentralRepoCommentAction(AbstractFile file, String title) { + + super(title); + this.title = title; + correlationAttribute = EamArtifactUtil.getCorrelationAttributeFromContent(file); + if (correlationAttribute == null) { + addToDatabase = true; + correlationAttribute = EamArtifactUtil.makeCorrelationAttributeFromContent(file); + } + } + + @Override + public void actionPerformed(ActionEvent event) { + addEditCentralRepoComment(); + } + + /** + * Create a Add/Edit dialog for the correlation attribute file instance + * comment. The comment will be updated in the database if the file instance + * exists there, or a new file instance will be added to the database with + * the comment attached otherwise. + * + * The current comment for this instance is returned in case it is needed to + * update the display. + * + * @return the current comment for this instance + */ + public String addEditCentralRepoComment() { + CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); + centralRepoCommentDialog.display(); + + if (centralRepoCommentDialog.isCommentUpdated()) { + EamDb dbManager; + + try { + dbManager = EamDb.getInstance(); + + if (addToDatabase) { + dbManager.addArtifact(correlationAttribute); + } else { + dbManager.updateAttributeInstanceComment(correlationAttribute); + } + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); + } + } + return centralRepoCommentDialog.getComment(); + } + + /** + * Create an instance labeled "Add/Edit Central Repository Comment" given an + * AbstractFile. This is intended for the result view. + * + * @param file The file from which a correlation attribute to modify is + * derived. + * + * @return The instance. + * + * @throws EamDbException + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=Add/Edit Central Repository Comment"}) + public static AddEditCentralRepoCommentAction createAddEditCentralRepoCommentAction(AbstractFile file) { + + return new AddEditCentralRepoCommentAction(file, + Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment()); + } + + /** + * Create an instance labeled "Add/Edit Comment" given a + * CorrelationAttribute. This is intended for the content view. + * + * @param correlationAttribute The correlation attribute to modify. + * + * @return The instance. + */ + @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditComment=Add/Edit Comment"}) + public static AddEditCentralRepoCommentAction createAddEditCommentAction(CorrelationAttribute correlationAttribute) { + + return new AddEditCentralRepoCommentAction(correlationAttribute, + Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditComment()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties index 9e873a514b..3223583037 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties @@ -5,3 +5,8 @@ OpenIDE-Module-Long-Description=\ Correlation Engine ingest module and central database. \n\n\ The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\ Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest. +CentralRepoCommentDialog.fileLabel.text=File: +CentralRepoCommentDialog.commentLabel.text=Comment: +CentralRepoCommentDialog.pathLabel.text= +CentralRepoCommentDialog.okButton.text=&OK +CentralRepoCommentDialog.cancelButton.text=C&ancel diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form new file mode 100755 index 0000000000..5a9882d4cf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form @@ -0,0 +1,138 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java new file mode 100755 index 0000000000..c95b0bfde7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -0,0 +1,203 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.centralrepository; + +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; + +/** + * Dialog to allow Central Repository file instance comments to be added and + * modified. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +final class CentralRepoCommentDialog extends javax.swing.JDialog { + + private final CorrelationAttribute correlationAttribute; + private boolean commentUpdated = false; + private String currentComment = ""; + + /** + * Create an instance. + * + * @param correlationAttribute The correlation attribute to be modified. + * @param title The title to assign the dialog. + */ + CentralRepoCommentDialog(CorrelationAttribute correlationAttribute, String title) { + super(WindowManager.getDefault().getMainWindow(), title); + + initComponents(); + + CorrelationAttributeInstance instance = correlationAttribute.getInstances().get(0); + + // Store the original comment + if (instance.getComment() != null) { + currentComment = instance.getComment(); + } + + pathLabel.setText(instance.getFilePath()); + commentTextArea.setText(instance.getComment()); + + this.correlationAttribute = correlationAttribute; + } + + /** + * Display the dialog. + */ + void display() { + setModal(true); + setSize(getPreferredSize()); + setLocationRelativeTo(this.getParent()); + setAlwaysOnTop(false); + pack(); + setVisible(true); + } + + /** + * Has the comment been updated? + * + * @return True if the comment has been updated; otherwise false. + */ + boolean isCommentUpdated() { + return commentUpdated; + } + + /** + * Get the current comment. + * If the user hit OK, this will be the new comment. + * If the user canceled, this will be the original comment. + * @return the comment + */ + String getComment() { + return currentComment; + } + + /** + * 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(); + commentTextArea = new javax.swing.JTextArea(); + okButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + fileLabel = new javax.swing.JLabel(); + pathLabel = new javax.swing.JLabel(); + commentLabel = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setSize(getPreferredSize()); + + commentTextArea.setColumns(20); + commentTextArea.setLineWrap(true); + commentTextArea.setRows(5); + commentTextArea.setTabSize(4); + commentTextArea.setWrapStyleWord(true); + jScrollPane1.setViewportView(commentTextArea); + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.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(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.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(fileLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.fileLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.pathLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.commentLabel.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) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(fileLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pathLabel)) + .addComponent(commentLabel)) + .addGap(0, 451, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fileLabel) + .addComponent(pathLabel)) + .addGap(19, 19, 19) + .addComponent(commentLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(okButton) + .addComponent(cancelButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + currentComment = commentTextArea.getText(); + correlationAttribute.getInstances().get(0).setComment(currentComment); + commentUpdated = true; + + dispose(); + }//GEN-LAST:event_okButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelButton; + private javax.swing.JLabel commentLabel; + private javax.swing.JTextArea commentTextArea; + private javax.swing.JLabel fileLabel; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JButton okButton; + private javax.swing.JLabel pathLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java new file mode 100755 index 0000000000..5a6e8fa652 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -0,0 +1,56 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository; + +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.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * This creates a single context menu item for adding or editing a Central + * Repository comment. + */ +@ServiceProvider(service = ContextMenuActionsProvider.class) +public class CentralRepoContextMenuActionsProvider implements ContextMenuActionsProvider { + + @Override + public List getActions() { + ArrayList actions = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + + if (selectedFiles.size() != 1) { + return actions; + } + + for (AbstractFile file : selectedFiles) { + if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { + actions.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); + } + } + + return actions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index 8b18108d52..39759e3eb7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -3,3 +3,4 @@ DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency +DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Comment diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 2d26ab3bc8..0097313943 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -3,6 +3,9 @@
+ + + @@ -36,6 +39,13 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 2a9fd30a49..15ed21d753 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; -import java.awt.Color; import java.awt.Component; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedWriter; @@ -37,36 +35,27 @@ import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import java.util.stream.Collectors; -import javax.swing.GroupLayout; import javax.swing.JFileChooser; -import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import static javax.swing.JOptionPane.DEFAULT_OPTION; import static javax.swing.JOptionPane.PLAIN_MESSAGE; import static javax.swing.JOptionPane.ERROR_MESSAGE; import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.LayoutStyle; -import javax.swing.ListSelectionModel; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; -import org.openide.awt.Mnemonics; import org.openide.nodes.Node; -import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -76,9 +65,9 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; /** * View correlation results from other cases @@ -88,8 +77,8 @@ import org.sleuthkit.datamodel.TskDataException; @Messages({"DataContentViewerOtherCases.title=Other Occurrences", "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",}) public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { - - private final static Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + + private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private final DataContentViewerOtherCasesTableModel tableModel; private final Collection correlationAttributes; @@ -123,10 +112,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { saveToCSV(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); + } else if (jmi.equals(addCommentMenuItem)) { + try { + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); + AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedNode.createCorrelationAttribute()); + String currentComment = action.addEditCentralRepoComment(); + selectedNode.updateComment(currentComment); + otherCasesTable.repaint(); + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error performing Add/Edit Comment action", ex); + } } } }; @@ -135,6 +134,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi selectAllMenuItem.addActionListener(actList); showCaseDetailsMenuItem.addActionListener(actList); showCommonalityMenuItem.addActionListener(actList); + addCommentMenuItem.addActionListener(actList); // Set background of every nth row as light grey. TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer(); @@ -150,7 +150,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi "DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency", "DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details."}) /** - * Show how common the selected correlationAttributes are with details dialog. + * Show how common the selected correlationAttributes are with details + * dialog. */ private void showCommonalityDetails() { if (correlationAttributes.isEmpty()) { @@ -174,7 +175,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex); + logger.log(Level.SEVERE, "Error getting commonality details.", ex); JOptionPane.showConfirmDialog(showCommonalityMenuItem, Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(), Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), @@ -204,8 +205,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (-1 != selectedRowViewIdx) { EamDb dbManager = EamDb.getInstance(); int selectedRowModelIdx = otherCasesTable.convertRowIndexToModel(selectedRowViewIdx); - CorrelationAttribute eamArtifact = (CorrelationAttribute) tableModel.getRow(selectedRowModelIdx); - CorrelationCase eamCasePartial = eamArtifact.getInstances().get(0).getCorrelationCase(); + OtherOccurrenceNodeData nodeData = (OtherOccurrenceNodeData) tableModel.getRow(selectedRowModelIdx); + CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); if (eamCasePartial == null) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(), @@ -298,7 +299,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); + logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); } } @@ -389,7 +390,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID()); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS + logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS return null; } @@ -435,20 +436,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } else { try { // If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled. - if(this.file != null) { + if (this.file != null) { String md5 = this.file.getMd5Hash(); - if(md5 != null && !md5.isEmpty()) { - ret.add(new CorrelationAttribute(CorrelationAttribute.getDefaultCorrelationTypes().get(0),md5)); + if (md5 != null && !md5.isEmpty()) { + ret.add(new CorrelationAttribute(CorrelationAttribute.getDefaultCorrelationTypes().get(0), md5)); } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } @@ -456,55 +457,80 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Query the db for artifact instances from other cases correlated to the - * given central repository artifact. Will not show instances from the same - * datasource / device + * Query the central repo database (if enabled) and the case database to find all + * artifact instances correlated to the given central repository artifact. If the + * central repo is not enabled, this will only return files from the current case + * with matching MD5 hashes. * - * @param corAttr CorrelationAttribute to query for + * @param corAttr CorrelationAttribute to query for * @param dataSourceName Data source to filter results - * @param deviceId Device Id to filter results + * @param deviceId Device Id to filter results * - * @return A collection of correlated artifact instances from other cases + * @return A collection of correlated artifact instances */ - private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { + private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { final Case openCase = Case.getCurrentCase(); String caseUUID = openCase.getName(); - HashMap artifactInstances = new HashMap<>(); + + HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { - EamDb dbManager = EamDb.getInstance(); - artifactInstances.putAll(dbManager.getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).stream() - .filter(artifactInstance -> !artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) - || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) - || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) - .collect(Collectors.toMap(correlationAttr -> new UniquePathKey(correlationAttr.getCorrelationDataSource().getDeviceID(), correlationAttr.getFilePath()), - correlationAttr -> correlationAttr))); - } + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = addCaseDbMatches(corAttr, openCase); - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateAttributeInstance(openCase, artifactInstances, caseDbFile); + for (CorrelationAttributeInstance artifactInstance:instances) { + + // Only add the attribute if it isn't the object the user selected. + // We consider it to be a different object if at least one of the following is true: + // - the case UUID is different + // - the data source name is different + // - the data source device ID is different + // - the file path is different + if (!artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) + || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId) + || !artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName())) { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } } } - return artifactInstances; + if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { + List caseDbFiles = getCaseDbMatches(corAttr, openCase); + + for (AbstractFile caseDbFile : caseDbFiles) { + addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); + } + } + + return nodeDataMap; } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { // do nothing. // @@@ Review this behavior - LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS } return new HashMap<>(0); } - private List addCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { + /** + * Get all other abstract files in the current case with the same MD5 as the selected node. + * @param corAttr The CorrelationAttribute containing the MD5 to search for + * @param openCase The current case + * @return List of matching AbstractFile objects + * @throws NoCurrentCaseException + * @throws TskCoreException + * @throws EamDbException + */ + private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); SleuthkitCase tsk = openCase.getSleuthkitCase(); @@ -522,75 +548,64 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Adds the file to the artifactInstances map if it does not already exist + * Adds the file to the nodeDataMap map if it does not already exist * * @param autopsyCase - * @param artifactInstances + * @param nodeDataMap * @param newFile + * * @throws TskCoreException - * @throws EamDbException + * @throws EamDbException */ - private void addOrUpdateAttributeInstance(final Case autopsyCase, Map artifactInstances, AbstractFile newFile) throws TskCoreException, EamDbException { + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { - // figure out if the casedb file is known via either hash or tags - TskData.FileKnown localKnown = newFile.getKnown(); - - if (localKnown != TskData.FileKnown.BAD) { + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(newFile, autopsyCase); + + // If the caseDB object has a notable tag associated with it, update + // the known status to BAD + if (newNode.getKnown() != TskData.FileKnown.BAD) { List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); for (ContentTag tag : fileMatchTags) { TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { - localKnown = TskData.FileKnown.BAD; + newNode.updateKnown(TskData.FileKnown.BAD); break; } } } - // make a key to see if the file is already in the map - String filePath = newFile.getParentPath() + newFile.getName(); - String deviceId; - try { - deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); - } catch (TskDataException | TskCoreException ex) { - LOGGER.log(Level.WARNING, "Error getting data source info: " + ex); - return; - } - UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); + // Make a key to see if the file is already in the map + UniquePathKey uniquePathKey = new UniquePathKey(newNode); - // double check that the CR version is BAD if the caseDB version is BAD. - if (artifactInstances.containsKey(uniquePathKey)) { - if (localKnown == TskData.FileKnown.BAD) { - CorrelationAttributeInstance prevInstance = artifactInstances.get(uniquePathKey); - prevInstance.setKnownStatus(localKnown); + // If this node is already in the list, the only thing we need to do is + // update the known status to BAD if the caseDB version had known status BAD. + // Otherwise this is a new node so add the new node to the map. + if (nodeDataMap.containsKey(uniquePathKey)) { + if (newNode.getKnown() == TskData.FileKnown.BAD) { + OtherOccurrenceNodeData prevInstance = nodeDataMap.get(uniquePathKey); + prevInstance.updateKnown(newNode.getKnown()); } - } - // add the data from the case DB by pushing data into CorrelationAttributeInstance class - else { - // NOTE: If we are in here, it is likely because CR is not enabled. So, we cannot rely - // on any of the methods that query the DB. - CorrelationCase correlationCase = new CorrelationCase(autopsyCase.getName(), autopsyCase.getDisplayName()); - - CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, newFile.getDataSource()); - - CorrelationAttributeInstance caseDbInstance = new CorrelationAttributeInstance(correlationCase, correlationDataSource, filePath, "", localKnown); - artifactInstances.put(uniquePathKey, caseDbInstance); + } else { + nodeDataMap.put(uniquePathKey, newNode); } } @Override public boolean isSupported(Node node) { + + // Is supported if one of the following is true: + // - The central repo is enabled and the node has correlatable content + // (either through the MD5 hash of the associated file or through a BlackboardArtifact) + // - The central repo is disabled and the backing file has a valid MD5 hash this.file = this.getAbstractFileFromNode(node); - // Is supported if this node - // has correlatable content (File, BlackboardArtifact) OR - // other common files across datasources. - - if(EamDb.isEnabled()){ + if (EamDb.isEnabled()) { return this.file != null - && this.file.getSize() > 0 - && !getCorrelationAttributesFromNode(node).isEmpty(); - } else{ + && this.file.getSize() > 0 + && !getCorrelationAttributesFromNode(node).isEmpty(); + } else { return this.file != null - && this.file.getSize() > 0; + && this.file.getSize() > 0 + && ((this.file.getMd5Hash() != null) && ( ! this.file.getMd5Hash().isEmpty())); } } @@ -632,22 +647,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get the attributes we can correlate on correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); for (CorrelationAttribute corAttr : correlationAttributes) { - Map corAttrInstances = new HashMap<>(0); + Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB - corAttrInstances.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + + correlatedNodeDataMap.values().forEach((nodeData) -> { + tableModel.addNodeData(nodeData); - corAttrInstances.values().forEach((corAttrInstance) -> { - try { - CorrelationAttribute newCeArtifact = new CorrelationAttribute( - corAttr.getCorrelationType(), - corAttr.getCorrelationValue() - ); - newCeArtifact.addInstance(corAttrInstance); - tableModel.addEamArtifact(newCeArtifact); - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex); - } }); } @@ -690,159 +697,191 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // //GEN-BEGIN:initComponents private void initComponents() { - rightClickPopupMenu = new JPopupMenu(); - selectAllMenuItem = new JMenuItem(); - exportToCSVMenuItem = new JMenuItem(); - showCaseDetailsMenuItem = new JMenuItem(); - showCommonalityMenuItem = new JMenuItem(); - CSVFileChooser = new JFileChooser(); - otherCasesPanel = new JPanel(); - tableContainerPanel = new JPanel(); - tableScrollPane = new JScrollPane(); - otherCasesTable = new JTable(); - tableStatusPanel = new JPanel(); - tableStatusPanelLabel = new JLabel(); + rightClickPopupMenu = new javax.swing.JPopupMenu(); + selectAllMenuItem = new javax.swing.JMenuItem(); + exportToCSVMenuItem = new javax.swing.JMenuItem(); + showCaseDetailsMenuItem = new javax.swing.JMenuItem(); + showCommonalityMenuItem = new javax.swing.JMenuItem(); + addCommentMenuItem = new javax.swing.JMenuItem(); + CSVFileChooser = new javax.swing.JFileChooser(); + otherCasesPanel = new javax.swing.JPanel(); + tableContainerPanel = new javax.swing.JPanel(); + tableScrollPane = new javax.swing.JScrollPane(); + otherCasesTable = new javax.swing.JTable(); + tableStatusPanel = new javax.swing.JPanel(); + tableStatusPanelLabel = new javax.swing.JLabel(); - Mnemonics.setLocalizedText(selectAllMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.selectAllMenuItem.text")); // NOI18N + rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { + public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { + rightClickPopupMenuPopupMenuWillBecomeVisible(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(selectAllMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.selectAllMenuItem.text")); // NOI18N rightClickPopupMenu.add(selectAllMenuItem); - Mnemonics.setLocalizedText(exportToCSVMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.exportToCSVMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(exportToCSVMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.exportToCSVMenuItem.text")); // NOI18N rightClickPopupMenu.add(exportToCSVMenuItem); - Mnemonics.setLocalizedText(showCaseDetailsMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCaseDetailsMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(showCaseDetailsMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCaseDetailsMenuItem.text")); // NOI18N rightClickPopupMenu.add(showCaseDetailsMenuItem); - Mnemonics.setLocalizedText(showCommonalityMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N rightClickPopupMenu.add(showCommonalityMenuItem); - setMinimumSize(new Dimension(1500, 10)); + org.openide.awt.Mnemonics.setLocalizedText(addCommentMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.addCommentMenuItem.text")); // NOI18N + rightClickPopupMenu.add(addCommentMenuItem); + + setMinimumSize(new java.awt.Dimension(1500, 10)); setOpaque(false); - setPreferredSize(new Dimension(1500, 44)); + setPreferredSize(new java.awt.Dimension(1500, 44)); - otherCasesPanel.setPreferredSize(new Dimension(1500, 144)); + otherCasesPanel.setPreferredSize(new java.awt.Dimension(1500, 144)); - tableContainerPanel.setPreferredSize(new Dimension(1500, 63)); + tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63)); - tableScrollPane.setPreferredSize(new Dimension(1500, 30)); + tableScrollPane.setPreferredSize(new java.awt.Dimension(1500, 30)); otherCasesTable.setAutoCreateRowSorter(true); otherCasesTable.setModel(tableModel); - otherCasesTable.setToolTipText(NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N + otherCasesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N otherCasesTable.setComponentPopupMenu(rightClickPopupMenu); - otherCasesTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + otherCasesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); tableScrollPane.setViewportView(otherCasesTable); - tableStatusPanel.setPreferredSize(new Dimension(1500, 16)); + tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); - tableStatusPanelLabel.setForeground(new Color(255, 0, 51)); + tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); - GroupLayout tableStatusPanelLayout = new GroupLayout(tableStatusPanel); + javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel); tableStatusPanel.setLayout(tableStatusPanelLayout); - tableStatusPanelLayout.setHorizontalGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableStatusPanelLayout.setHorizontalGroup( + tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 0, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableStatusPanelLayout.createSequentialGroup() .addContainerGap() - .addComponent(tableStatusPanelLabel, GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) .addContainerGap())) ); - tableStatusPanelLayout.setVerticalGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableStatusPanelLayout.setVerticalGroup( + tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 16, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanelLabel, GroupLayout.PREFERRED_SIZE, 16, GroupLayout.PREFERRED_SIZE) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE))) ); - GroupLayout tableContainerPanelLayout = new GroupLayout(tableContainerPanel); + javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); - tableContainerPanelLayout.setHorizontalGroup(tableContainerPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(tableStatusPanel, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + tableContainerPanelLayout.setHorizontalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(tableStatusPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); - tableContainerPanelLayout.setVerticalGroup(tableContainerPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableContainerPanelLayout.setVerticalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableScrollPane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(tableStatusPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); - GroupLayout otherCasesPanelLayout = new GroupLayout(otherCasesPanel); + javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); otherCasesPanel.setLayout(otherCasesPanelLayout); - otherCasesPanelLayout.setHorizontalGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + otherCasesPanelLayout.setHorizontalGroup( + otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 1500, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(tableContainerPanel, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - otherCasesPanelLayout.setVerticalGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + otherCasesPanelLayout.setVerticalGroup( + otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 60, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, GroupLayout.DEFAULT_SIZE, 60, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 52, Short.MAX_VALUE) .addGap(0, 0, 0))) ); - GroupLayout layout = new GroupLayout(this); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); - layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); - layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, GroupLayout.DEFAULT_SIZE, 60, Short.MAX_VALUE) + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 52, Short.MAX_VALUE) ); }// //GEN-END:initComponents + private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible + boolean enableCentralRepoActions = false; + + if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { + int rowIndex = otherCasesTable.getSelectedRow(); + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); + if (selectedNode.isCentralRepoNode()) { + enableCentralRepoActions = true; + } + } + + addCommentMenuItem.setVisible(enableCentralRepoActions); + showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); + showCommonalityMenuItem.setVisible(enableCentralRepoActions); + }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible // Variables declaration - do not modify//GEN-BEGIN:variables - private JFileChooser CSVFileChooser; - private JMenuItem exportToCSVMenuItem; - private JPanel otherCasesPanel; - private JTable otherCasesTable; - private JPopupMenu rightClickPopupMenu; - private JMenuItem selectAllMenuItem; - private JMenuItem showCaseDetailsMenuItem; - private JMenuItem showCommonalityMenuItem; - private JPanel tableContainerPanel; - private JScrollPane tableScrollPane; - private JPanel tableStatusPanel; - private JLabel tableStatusPanelLabel; + private javax.swing.JFileChooser CSVFileChooser; + private javax.swing.JMenuItem addCommentMenuItem; + private javax.swing.JMenuItem exportToCSVMenuItem; + private javax.swing.JPanel otherCasesPanel; + private javax.swing.JTable otherCasesTable; + private javax.swing.JPopupMenu rightClickPopupMenu; + private javax.swing.JMenuItem selectAllMenuItem; + private javax.swing.JMenuItem showCaseDetailsMenuItem; + private javax.swing.JMenuItem showCommonalityMenuItem; + private javax.swing.JPanel tableContainerPanel; + private javax.swing.JScrollPane tableScrollPane; + private javax.swing.JPanel tableStatusPanel; + private javax.swing.JLabel tableStatusPanelLabel; // End of variables declaration//GEN-END:variables /** - * Used as a key to ensure we eliminate duplicates from the result set by not overwriting CR correlation instances. + * Used as a key to ensure we eliminate duplicates from the result set by + * not overwriting CR correlation instances. */ static final class UniquePathKey { private final String dataSourceID; private final String filePath; + private final String type; - UniquePathKey(String theDataSource, String theFilePath) { + UniquePathKey(OtherOccurrenceNodeData nodeData) { super(); - dataSourceID = theDataSource; - filePath = theFilePath.toLowerCase(); - } - - /** - * - * @return the dataSourceID device ID - */ - String getDataSourceID() { - return dataSourceID; - } - - /** - * - * @return the filPath including the filename and extension. - */ - String getFilePath() { - return filePath; + dataSourceID = nodeData.getDeviceID(); + if (nodeData.getFilePath() != null) { + filePath = nodeData.getFilePath().toLowerCase(); + } else { + filePath = null; + } + type = nodeData.getType(); } @Override public boolean equals(Object other) { if (other instanceof UniquePathKey) { - return ((UniquePathKey) other).getDataSourceID().equals(dataSourceID) && ((UniquePathKey) other).getFilePath().equals(filePath); + UniquePathKey otherKey = (UniquePathKey)(other); + return ( Objects.equals(otherKey.dataSourceID, this.dataSourceID) + && Objects.equals(otherKey.filePath, this.filePath) + && Objects.equals(otherKey.type, this.type)); } return false; } @@ -852,7 +891,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi //int hash = 7; //hash = 67 * hash + this.dataSourceID.hashCode(); //hash = 67 * hash + this.filePath.hashCode(); - return Objects.hash(dataSourceID, filePath); + return Objects.hash(dataSourceID, filePath, type); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java index f0ecb31e72..5febf88dc3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java @@ -68,10 +68,10 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } }; - List eamArtifacts; + List nodeDataList; DataContentViewerOtherCasesTableModel() { - eamArtifacts = new ArrayList<>(); + nodeDataList = new ArrayList<>(); } @Override @@ -95,7 +95,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public int getRowCount() { - return eamArtifacts.size(); + return nodeDataList.size(); } @Override @@ -105,15 +105,15 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == eamArtifacts.size()) { + if (0 == nodeDataList.size()) { return Bundle.DataContentViewerOtherCasesTableModel_noData(); } return mapValueById(rowIdx, TableColumns.values()[colIdx]); } - public Object getRow(int rowIdx) { - return eamArtifacts.get(rowIdx); + Object getRow(int rowIdx) { + return nodeDataList.get(rowIdx); } /** @@ -125,40 +125,39 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { * @return value in the cell */ private Object mapValueById(int rowIdx, TableColumns colId) { - CorrelationAttribute eamArtifact = eamArtifacts.get(rowIdx); - CorrelationAttributeInstance eamArtifactInstance = eamArtifact.getInstances().get(0); + OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); String value = Bundle.DataContentViewerOtherCasesTableModel_noData(); switch (colId) { case CASE_NAME: - if (null != eamArtifactInstance.getCorrelationCase()) { - value = eamArtifactInstance.getCorrelationCase().getDisplayName(); + if (null != nodeData.getCaseName()) { + value = nodeData.getCaseName(); } break; case DEVICE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getDeviceID(); + if (null != nodeData.getDeviceID()) { + value = nodeData.getDeviceID(); } break; case DATA_SOURCE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getName(); + if (null != nodeData.getDataSourceName()) { + value = nodeData.getDataSourceName(); } break; case FILE_PATH: - value = eamArtifactInstance.getFilePath(); + value = nodeData.getFilePath(); break; case TYPE: - value = eamArtifact.getCorrelationType().getDisplayName(); + value = nodeData.getType(); break; case VALUE: - value = eamArtifact.getCorrelationValue(); + value = nodeData.getValue(); break; case KNOWN: - value = eamArtifactInstance.getKnownStatus().getName(); + value = nodeData.getKnown().getName(); break; case COMMENT: - value = eamArtifactInstance.getComment(); + value = nodeData.getComment(); break; } return value; @@ -170,18 +169,17 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } /** - * Add one local central repository artifact to the table. + * Add one correlated instance object to the table * - * @param eamArtifact central repository artifact to add to the - * table + * @param newNodeData data to add to the table */ - public void addEamArtifact(CorrelationAttribute eamArtifact) { - eamArtifacts.add(eamArtifact); + void addNodeData(OtherOccurrenceNodeData newNodeData) { + nodeDataList.add(newNodeData); fireTableDataChanged(); } - public void clearTable() { - eamArtifacts.clear(); + void clearTable() { + nodeDataList.clear(); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java new file mode 100644 index 0000000000..958068fb14 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -0,0 +1,233 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository.contentviewer; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; + +/** + * Class for populating the Other Occurrences tab + */ +class OtherOccurrenceNodeData { + + // For now hard code the string for the central repo files type, since + // getting it dynamically can fail. + private static final String FILE_TYPE_STR = "Files"; + + private final String caseName; + private String deviceID; + private String dataSourceName; + private final String filePath; + private final String typeStr; + private final CorrelationAttribute.Type type; + private final String value; + private TskData.FileKnown known; + private String comment; + + private AbstractFile originalAbstractFile = null; + private CorrelationAttributeInstance originalCorrelationInstance = null; + + /** + * Create a node from a central repo instance. + * @param instance The central repo instance + * @param type The type of the instance + * @param value The value of the instance + */ + OtherOccurrenceNodeData(CorrelationAttributeInstance instance, CorrelationAttribute.Type type, String value) { + caseName = instance.getCorrelationCase().getDisplayName(); + deviceID = instance.getCorrelationDataSource().getDeviceID(); + dataSourceName = instance.getCorrelationDataSource().getName(); + filePath = instance.getFilePath(); + this.typeStr = type.getDisplayName(); + this.type = type; + this.value = value; + known = instance.getKnownStatus(); + comment = instance.getComment(); + + originalCorrelationInstance = instance; + } + + /** + * Create a node from an abstract file. + * @param newFile The abstract file + * @param autopsyCase The current case + * @throws EamDbException + */ + OtherOccurrenceNodeData(AbstractFile newFile, Case autopsyCase) throws EamDbException { + caseName = autopsyCase.getDisplayName(); + try { + DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); + deviceID = dataSource.getDeviceId(); + dataSourceName = dataSource.getName(); + } catch (TskDataException | TskCoreException ex) { + throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); + } + + filePath = newFile.getParentPath() + newFile.getName(); + typeStr = FILE_TYPE_STR; + this.type = null; + value = newFile.getMd5Hash(); + known = newFile.getKnown(); + comment = ""; + + originalAbstractFile = newFile; + } + + /** + * Check if this node is a "file" type + * @return true if it is a file type + */ + boolean isFileType() { + return FILE_TYPE_STR.equals(typeStr); + } + + /** + * Update the known status for this node + * @param newKnownStatus The new known status + */ + void updateKnown(TskData.FileKnown newKnownStatus) { + known = newKnownStatus; + } + + /** + * Update the comment for this node + * @param newComment The new comment + */ + void updateComment(String newComment) { + comment = newComment; + } + + /** + * Check if this is a central repo node. + * @return true if this node was created from a central repo instance, false otherwise + */ + boolean isCentralRepoNode() { + return (originalCorrelationInstance != null); + } + + /** + * Uses the saved instance plus type and value to make a new CorrelationAttribute. + * Should only be called if isCentralRepoNode() is true. + * @return the newly created CorrelationAttribute + */ + CorrelationAttribute createCorrelationAttribute() throws EamDbException { + if (! isCentralRepoNode() ) { + throw new EamDbException("Can not create CorrelationAttribute for non central repo node"); + } + CorrelationAttribute attr = new CorrelationAttribute(type, value); + attr.addInstance(originalCorrelationInstance); + return attr; + } + + /** + * Get the case name + * @return the case name + */ + String getCaseName() { + return caseName; + } + + /** + * Get the device ID + * @return the device ID + */ + String getDeviceID() { + return deviceID; + } + + /** + * Get the data source name + * @return the data source name + */ + String getDataSourceName() { + return dataSourceName; + } + + /** + * Get the file path + * @return the file path + */ + String getFilePath() { + return filePath; + } + + /** + * Get the type (as a string) + * @return the type + */ + String getType() { + return typeStr; + } + + /** + * Get the value (MD5 hash for files) + * @return the value + */ + String getValue() { + return value; + } + + /** + * Get the known status + * @return the known status + */ + TskData.FileKnown getKnown() { + return known; + } + + /** + * Get the comment + * @return the comment + */ + String getComment() { + return comment; + } + + /** + * Get the backing abstract file. + * Should only be called if isCentralRepoNode() is false + * @return the original abstract file + */ + AbstractFile getAbstractFile() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("AbstractFile is null"); + } + return originalAbstractFile; + } + + /** + * Get the backing CorrelationAttributeInstance. + * Should only be called if isCentralRepoNode() is true + * @return the original CorrelationAttributeInstance + * @throws EamDbException + */ + CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("CorrelationAttributeInstance is null"); + } + return originalCorrelationInstance; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index d2dccc0937..59df6b7bbc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion; import org.sleuthkit.autopsy.coreutils.Logger; @@ -46,14 +45,14 @@ import org.sleuthkit.datamodel.TskData; /** * - * Generic JDBC methods + * Generic JDBC methods * */ abstract class AbstractSqlEamDb implements EamDb { - private final static Logger LOGGER = Logger.getLogger(AbstractSqlEamDb.class.getName()); + private final static Logger logger = Logger.getLogger(AbstractSqlEamDb.class.getName()); - protected final List DEFAULT_CORRELATION_TYPES; + protected final List defaultCorrelationTypes; private int bulkArtifactsCount; protected int bulkArtifactsThreshold; @@ -62,8 +61,7 @@ abstract class AbstractSqlEamDb implements EamDb { // number of instances to keep in bulk queue before doing an insert. // Update Test code if this changes. It's hard coded there. static final int DEFAULT_BULK_THRESHHOLD = 1000; - - + /** * Connect to the DB and initialize it. * @@ -73,8 +71,8 @@ abstract class AbstractSqlEamDb implements EamDb { bulkArtifactsCount = 0; bulkArtifacts = new HashMap<>(); - DEFAULT_CORRELATION_TYPES = CorrelationAttribute.getDefaultCorrelationTypes(); - DEFAULT_CORRELATION_TYPES.forEach((type) -> { + defaultCorrelationTypes = CorrelationAttribute.getDefaultCorrelationTypes(); + defaultCorrelationTypes.forEach((type) -> { bulkArtifacts.put(type.getDbTableName(), new ArrayList<>()); }); } @@ -87,7 +85,7 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Add a new name/value pair in the db_info table. * - * @param name Key to set + * @param name Key to set * @param value Value to set * * @throws EamDbException @@ -107,7 +105,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error adding new name/value pair to db_info.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -140,7 +138,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting value for name.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -151,7 +149,7 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Update the value for a name in the name/value db_info table. * - * @param name Name to find + * @param name Name to find * @param value Value to assign to name. * * @throws EamDbException @@ -170,7 +168,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating value for name.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -181,11 +179,12 @@ abstract class AbstractSqlEamDb implements EamDb { * Expects the Organization for this case to already exist in the database. * * @param eamCase The case to add + * * @returns New Case class with populated database ID */ @Override public synchronized CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException { - + // check if there is already an existing CorrelationCase for this Case CorrelationCase cRCase = getCaseByUUID(eamCase.getCaseUUID()); if (cRCase != null) { @@ -241,7 +240,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new case.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -286,10 +285,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public void updateCase(CorrelationCase eamCase) throws EamDbException { - if(eamCase == null) { - throw new EamDbException("CorrelationCase argument is null"); + if (eamCase == null) { + throw new EamDbException("Correlation case is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -340,7 +339,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating case.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -378,7 +377,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting case details.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -415,7 +414,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all cases.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -432,8 +431,7 @@ abstract class AbstractSqlEamDb implements EamDb { public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException { if (eamDataSource.getCaseID() == -1) { throw new EamDbException("Case ID is -1"); - } - else if (eamDataSource.getID() != -1) { + } else if (eamDataSource.getID() != -1) { throw new EamDbException("Database ID is already set in object"); } Connection conn = connect(); @@ -454,7 +452,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new data source.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -462,18 +460,18 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Retrieves Data Source details based on data source device ID * - * @param correlationCase the current CorrelationCase used for ensuring - * uniqueness of DataSource + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource * @param dataSourceDeviceId the data source device ID number * * @return The data source */ @Override public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException { - if(correlationCase == null) { - throw new EamDbException("CorrelationCase argument is null"); + if (correlationCase == null) { + throw new EamDbException("Correlation case is null"); } - + Connection conn = connect(); CorrelationDataSource eamDataSourceResult = null; @@ -493,7 +491,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting data source.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -527,7 +525,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all data sources.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -543,16 +541,16 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException { - if(eamArtifact == null) { + if (eamArtifact == null) { throw new EamDbException("CorrelationAttribute is null"); } - if(eamArtifact.getCorrelationType() == null) { + if (eamArtifact.getCorrelationType() == null) { throw new EamDbException("Correlation type is null"); } - if(eamArtifact.getCorrelationValue() == null) { + if (eamArtifact.getCorrelationValue() == null) { throw new EamDbException("Correlation value is null"); } - + Connection conn = connect(); List eamInstances = eamArtifact.getInstances(); @@ -560,28 +558,28 @@ abstract class AbstractSqlEamDb implements EamDb { // @@@ We should cache the case and data source IDs in memory String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); - StringBuilder sql = new StringBuilder(); - sql.append("INSERT INTO "); - sql.append(tableName); - sql.append("(case_id, data_source_id, value, file_path, known_status, comment) "); - sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "); - sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "); - sql.append(getConflictClause()); - + String sql + = "INSERT INTO " + + tableName + + "(case_id, data_source_id, value, file_path, known_status, comment) " + + "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), " + + "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) " + + getConflictClause(); + try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { - if(eamInstance.getCorrelationCase() == null) { - throw new EamDbException("CorrelationAttributeInstance has null case"); + if (eamInstance.getCorrelationCase() == null) { + throw new EamDbException("CorrelationAttributeInstance case is null"); } - if(eamInstance.getCorrelationDataSource() == null) { - throw new EamDbException("CorrelationAttributeInstance has null data source"); + if (eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("CorrelationAttributeInstance data source is null"); } - if(eamInstance.getKnownStatus() == null) { - throw new EamDbException("CorrelationAttributeInstance has null known status"); + if (eamInstance.getKnownStatus() == null) { + throw new EamDbException("CorrelationAttributeInstance known status is null"); } - + preparedStatement.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); preparedStatement.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); preparedStatement.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); @@ -600,7 +598,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new artifact into artifacts table.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -618,7 +616,7 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } Connection conn = connect(); @@ -630,19 +628,21 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE value=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE value=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { @@ -652,7 +652,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -664,7 +664,7 @@ abstract class AbstractSqlEamDb implements EamDb { * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath * - * @param aType EamArtifact.Type to search for + * @param aType EamArtifact.Type to search for * @param filePath File path to search for * * @return List of 0 or more EamArtifactInstances @@ -673,10 +673,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - if(filePath == null) { + if (filePath == null) { throw new EamDbException("Correlation value is null"); } Connection conn = connect(); @@ -688,19 +688,21 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE file_path=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE file_path=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, filePath.toLowerCase()); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { @@ -710,7 +712,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -726,17 +728,17 @@ abstract class AbstractSqlEamDb implements EamDb { * @param value The correlation value * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ @Override public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - if(value == null) { + if (value == null) { throw new EamDbException("Correlation value is null"); } - + Connection conn = connect(); Long instanceCount = 0L; @@ -744,13 +746,13 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM "); - sql.append(tableName); - sql.append(" WHERE value=?"); + String sql + = "SELECT count(*) FROM " + + tableName + + " WHERE value=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value.toLowerCase()); resultSet = preparedStatement.executeQuery(); resultSet.next(); @@ -758,7 +760,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting count of artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -769,7 +771,7 @@ abstract class AbstractSqlEamDb implements EamDb { @Override public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException { if (corAttr == null) { - throw new EamDbException("Correlation attribute is null"); + throw new EamDbException("CorrelationAttribute is null"); } Double uniqueTypeValueTuples = getCountUniqueCaseDataSourceTuplesHavingTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).doubleValue(); Double uniqueCaseDataSourceTuples = getCountUniqueDataSources().doubleValue(); @@ -789,10 +791,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Long instanceCount = 0L; @@ -800,15 +802,15 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM (SELECT DISTINCT case_id, data_source_id FROM "); - sql.append(tableName); - sql.append(" WHERE value=?) AS "); - sql.append(tableName); - sql.append("_distinct_case_data_source_tuple"); + String sql + = "SELECT count(*) FROM (SELECT DISTINCT case_id, data_source_id FROM " + + tableName + + " WHERE value=?) AS " + + tableName + + "_distinct_case_data_source_tuple"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); resultSet = preparedStatement.executeQuery(); resultSet.next(); @@ -816,7 +818,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting unique caseDisplayName/dataSource tuples having artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -842,7 +844,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting data sources.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -855,11 +857,11 @@ abstract class AbstractSqlEamDb implements EamDb { * associated with the caseDisplayName and dataSource of the given * eamArtifact instance. * - * @param caseUUID Case ID to search for + * @param caseUUID Case ID to search for * @param dataSourceID Data source ID to search for * * @return Number of artifact instances having caseDisplayName and - * dataSource + * dataSource */ @Override public Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException { @@ -871,19 +873,19 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; // Figure out sql variables or subqueries - StringBuilder sql = new StringBuilder(); - sql.append("SELECT 0 "); + String sql = "SELECT 0 "; for (CorrelationAttribute.Type type : artifactTypes) { String table_name = EamDbUtil.correlationTypeToInstanceTableName(type); - sql.append("+ (SELECT count(*) FROM "); - sql.append(table_name); - sql.append(" WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) and data_source_id=(SELECT id FROM data_sources WHERE device_id=?))"); + sql + += "+ (SELECT count(*) FROM " + + table_name + + " WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) and data_source_id=(SELECT id FROM data_sources WHERE device_id=?))"; } try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); for (int i = 0; i < artifactTypes.size(); ++i) { preparedStatement.setString(2 * i + 1, caseUUID); @@ -896,7 +898,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting artifact instances by caseName/dataSource.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -914,10 +916,10 @@ abstract class AbstractSqlEamDb implements EamDb { @Override public void prepareBulkArtifact(CorrelationAttribute eamArtifact) throws EamDbException { - if(eamArtifact.getCorrelationType() == null) { + if (eamArtifact.getCorrelationType() == null) { throw new EamDbException("Correlation type is null"); } - + synchronized (bulkArtifacts) { bulkArtifacts.get(eamArtifact.getCorrelationType().getDbTableName()).add(eamArtifact); bulkArtifactsCount++; @@ -951,21 +953,19 @@ abstract class AbstractSqlEamDb implements EamDb { if (bulkArtifactsCount == 0) { return; } - - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); for (CorrelationAttribute.Type type : artifactTypes) { String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); - StringBuilder sql = new StringBuilder(); - sql.append("INSERT INTO "); - sql.append(tableName); - sql.append(" (case_id, data_source_id, value, file_path, known_status, comment) "); - sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "); - sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "); - sql.append(getConflictClause()); + String sql + = "INSERT INTO " + + tableName + + " (case_id, data_source_id, value, file_path, known_status, comment) " + + "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), " + + "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) " + + getConflictClause(); - bulkPs = conn.prepareStatement(sql.toString()); + bulkPs = conn.prepareStatement(sql); Collection eamArtifacts = bulkArtifacts.get(type.getDbTableName()); for (CorrelationAttribute eamArtifact : eamArtifacts) { @@ -973,17 +973,17 @@ abstract class AbstractSqlEamDb implements EamDb { for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { - - if(eamInstance.getCorrelationCase() == null) { - throw new EamDbException("Correlation attribute instance has null case"); + + if (eamInstance.getCorrelationCase() == null) { + throw new EamDbException("CorrelationAttributeInstance case is null"); } - if(eamInstance.getCorrelationDataSource() == null) { - throw new EamDbException("Correlation attribute instance has null data source"); + if (eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("CorrelationAttributeInstance data source is null"); } - if(eamInstance.getKnownStatus()== null) { - throw new EamDbException("Correlation attribute instance has null known known status"); + if (eamInstance.getKnownStatus() == null) { + throw new EamDbException("CorrelationAttributeInstance known status is null"); } - + bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); @@ -1003,7 +1003,8 @@ abstract class AbstractSqlEamDb implements EamDb { bulkPs.executeBatch(); bulkArtifacts.get(type.getDbTableName()).clear(); } - + + TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); EnterpriseHealthMonitor.submitTimingMetric(timingMetric); // Reset state @@ -1012,7 +1013,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting bulk artifacts.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } @@ -1022,16 +1023,16 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertCases(List cases) throws EamDbException { - if(cases == null) { + if (cases == null) { throw new EamDbException("cases argument is null"); } - + if (cases.isEmpty()) { return; } Connection conn = connect(); - + int counter = 0; PreparedStatement bulkPs = null; try { @@ -1092,44 +1093,177 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting bulk cases.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } /** - * Sets an eamArtifact instance to the given knownStatus. - * knownStatus should be BAD if the file has been tagged with a notable tag and - * UNKNOWN otherwise. If eamArtifact - * exists, it is updated. If eamArtifact does not exist it is added with the - * given status. + * Update a correlation attribute instance in the database with that in the + * associated CorrelationAttribute object. + * + * @param eamArtifact The correlation attribute whose database instance will + * be updated. + * + * @throws EamDbException + */ + @Override + public void updateAttributeInstanceComment(CorrelationAttribute eamArtifact) throws EamDbException { + if (eamArtifact == null) { + throw new EamDbException("CorrelationAttribute is null"); + } + + CorrelationAttributeInstance eamInstance = eamArtifact.getInstances().get(0); + + if (eamInstance == null) { + throw new EamDbException("CorrelationAttributeInstance is null"); + } + if (eamInstance.getCorrelationCase() == null) { + throw new EamDbException("Correlation case is null"); + } + if (eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("Correlation data source is null"); + } + + Connection conn = connect(); + PreparedStatement preparedQuery = null; + String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); + + String sqlUpdate + = "UPDATE " + + tableName + + " SET comment=? " + + "WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) " + + "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) " + + "AND value=? " + + "AND file_path=?"; + + try { + preparedQuery = conn.prepareStatement(sqlUpdate); + preparedQuery.setString(1, eamInstance.getComment()); + preparedQuery.setString(2, eamInstance.getCorrelationCase().getCaseUUID()); + preparedQuery.setString(3, eamInstance.getCorrelationDataSource().getDeviceID()); + preparedQuery.setString(4, eamArtifact.getCorrelationValue()); + preparedQuery.setString(5, eamInstance.getFilePath()); + preparedQuery.executeUpdate(); + } catch (SQLException ex) { + throw new EamDbException("Error getting/setting artifact instance comment=" + eamInstance.getComment(), ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedQuery); + EamDbUtil.closeConnection(conn); + } + } + + /** + * Find a correlation attribute in the Central Repository database given the + * instance type, case, data source, value, and file path. + * + * @param type The type of instance. + * @param correlationCase The case tied to the instance. + * @param correlationDataSource The data source tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. + * + * @return The correlation attribute if it exists; otherwise null. + * + * @throws EamDbException + */ + @Override + public CorrelationAttribute getCorrelationAttribute(CorrelationAttribute.Type type, CorrelationCase correlationCase, + CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException { + + if (type == null) { + throw new EamDbException("Correlation type is null"); + } + if (correlationCase == null) { + throw new EamDbException("Correlation case is null"); + } + if (correlationDataSource == null) { + throw new EamDbException("Correlation data source is null"); + } + if (value == null) { + throw new EamDbException("Correlation value is null"); + } + if (filePath == null) { + throw new EamDbException("Correlation file path is null"); + } + + Connection conn = connect(); + + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + CorrelationAttribute correlationAttribute = null; + + try { + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); + String sql + = "SELECT id, known_status, comment FROM " + + tableName + + " WHERE case_id=?" + + " AND data_source_id=?" + + " AND value=?" + + " AND file_path=?"; + + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setInt(1, correlationCase.getID()); + preparedStatement.setInt(2, correlationDataSource.getID()); + preparedStatement.setString(3, value.toLowerCase()); + preparedStatement.setString(4, filePath.toLowerCase()); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + int instanceId = resultSet.getInt(1); + int knownStatus = resultSet.getInt(2); + String comment = resultSet.getString(3); + + correlationAttribute = new CorrelationAttribute(type, value); + CorrelationAttributeInstance artifactInstance = new CorrelationAttributeInstance( + instanceId, correlationCase, correlationDataSource, filePath, comment, TskData.FileKnown.valueOf((byte) knownStatus)); + correlationAttribute.addInstance(artifactInstance); + } + } catch (SQLException ex) { + throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + + return correlationAttribute; + } + + /** + * Sets an eamArtifact instance to the given knownStatus. knownStatus should + * be BAD if the file has been tagged with a notable tag and UNKNOWN + * otherwise. If eamArtifact exists, it is updated. If eamArtifact does not + * exist it is added with the given status. * * @param eamArtifact Artifact containing exactly one (1) ArtifactInstance. - * @param knownStatus The status to change the artifact to. Should never be KNOWN + * @param knownStatus The status to change the artifact to. Should never be + * KNOWN */ @Override public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException { - if(eamArtifact == null) { - throw new EamDbException("Correlation attribute is null"); + if (eamArtifact == null) { + throw new EamDbException("CorrelationAttribute is null"); } - if(knownStatus == null) { + if (knownStatus == null) { throw new EamDbException("Known status is null"); } if (1 != eamArtifact.getInstances().size()) { throw new EamDbException("Error: Artifact must have exactly one (1) Artifact Instance to set as notable."); // NON-NLS } - + List eamInstances = eamArtifact.getInstances(); CorrelationAttributeInstance eamInstance = eamInstances.get(0); - if(eamInstance.getCorrelationCase() == null) { + if (eamInstance.getCorrelationCase() == null) { throw new EamDbException("Correlation case is null"); } - if(eamInstance.getCorrelationDataSource() == null) { + if (eamInstance.getCorrelationDataSource() == null) { throw new EamDbException("Correlation data source is null"); } - - Connection conn = connect(); + + Connection conn = connect(); PreparedStatement preparedUpdate = null; PreparedStatement preparedQuery = null; @@ -1137,22 +1271,22 @@ abstract class AbstractSqlEamDb implements EamDb { String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); - StringBuilder sqlQuery = new StringBuilder(); - sqlQuery.append("SELECT id FROM "); - sqlQuery.append(tableName); - sqlQuery.append(" WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) "); - sqlQuery.append("AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) "); - sqlQuery.append("AND value=? "); - sqlQuery.append("AND file_path=?"); + String sqlQuery + = "SELECT id FROM " + + tableName + + " WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) " + + "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) " + + "AND value=? " + + "AND file_path=?"; - StringBuilder sqlUpdate = new StringBuilder(); - sqlUpdate.append("UPDATE "); - sqlUpdate.append(tableName); - sqlUpdate.append(" SET known_status=?, comment=? "); - sqlUpdate.append("WHERE id=?"); + String sqlUpdate + = "UPDATE " + + tableName + + " SET known_status=?, comment=? " + + "WHERE id=?"; try { - preparedQuery = conn.prepareStatement(sqlQuery.toString()); + preparedQuery = conn.prepareStatement(sqlQuery); preparedQuery.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); preparedQuery.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); preparedQuery.setString(3, eamArtifact.getCorrelationValue()); @@ -1160,7 +1294,7 @@ abstract class AbstractSqlEamDb implements EamDb { resultSet = preparedQuery.executeQuery(); if (resultSet.next()) { int instance_id = resultSet.getInt("id"); - preparedUpdate = conn.prepareStatement(sqlUpdate.toString()); + preparedUpdate = conn.prepareStatement(sqlUpdate); preparedUpdate.setByte(1, knownStatus.getFileKnownValue()); // NOTE: if the user tags the same instance as BAD multiple times, @@ -1195,8 +1329,8 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting/setting artifact instance knownStatus=" + knownStatus.getName(), ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedUpdate); - EamDbUtil.closePreparedStatement(preparedQuery); + EamDbUtil.closeStatement(preparedUpdate); + EamDbUtil.closeStatement(preparedQuery); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1213,10 +1347,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -1226,19 +1360,21 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE value=? AND known_status=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE value=? AND known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1249,20 +1385,23 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } return artifactInstances; } - + /** * * Gets list of matching eamArtifact instances that have knownStatus = * "Bad". + * * @param aType EamArtifact.Type to search for + * * @return List with 0 or more matching eamArtifact instances. + * * @throws EamDbException */ @Override @@ -1280,22 +1419,22 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE known_status=?"); - sql.append(" GROUP BY "); - sql.append(tableName); - sql.append(".value"); + String sql + = "SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE known_status=?" + + " GROUP BY " + + tableName + + ".value"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setByte(1, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { @@ -1305,7 +1444,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1323,10 +1462,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Long badInstances = 0L; @@ -1334,13 +1473,13 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM "); - sql.append(tableName); - sql.append(" WHERE value=? AND known_status=?"); + String sql + = "SELECT count(*) FROM " + + tableName + + " WHERE value=? AND known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1349,7 +1488,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting count of notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1365,16 +1504,16 @@ abstract class AbstractSqlEamDb implements EamDb { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws EamDbException */ @Override public List getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Collection caseNames = new LinkedHashSet<>(); @@ -1383,19 +1522,19 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT DISTINCT case_name FROM "); - sql.append(tableName); - sql.append(" INNER JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id WHERE "); - sql.append(tableName); - sql.append(".value=? AND "); - sql.append(tableName); - sql.append(".known_status=?"); + String sql + = "SELECT DISTINCT case_name FROM " + + tableName + + " INNER JOIN cases ON " + + tableName + + ".case_id=cases.id WHERE " + + tableName + + ".value=? AND " + + tableName + + ".known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1405,7 +1544,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1417,6 +1556,7 @@ abstract class AbstractSqlEamDb implements EamDb { * Remove a reference set and all entries contained in it. * * @param referenceSetID + * * @throws EamDbException */ @Override @@ -1429,6 +1569,7 @@ abstract class AbstractSqlEamDb implements EamDb { * Remove the entry for this set from the reference_sets table * * @param referenceSetID + * * @throws EamDbException */ private void deleteReferenceSetEntry(int referenceSetID) throws EamDbException { @@ -1444,7 +1585,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error deleting reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1454,6 +1595,7 @@ abstract class AbstractSqlEamDb implements EamDb { * (Currently only removes entries from the reference_file table) * * @param referenceSetID + * * @throws EamDbException */ private void deleteReferenceSetEntries(int referenceSetID) throws EamDbException { @@ -1472,7 +1614,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error deleting files from reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1485,13 +1627,15 @@ abstract class AbstractSqlEamDb implements EamDb { * @param referenceSetID * @param setName * @param version + * * @return true if a matching entry exists in the central repository + * * @throws EamDbException */ @Override public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException { EamGlobalSet refSet = this.getReferenceSetByID(referenceSetID); - if(refSet == null) { + if (refSet == null) { return false; } @@ -1504,7 +1648,9 @@ abstract class AbstractSqlEamDb implements EamDb { * * @param hash * @param referenceSetID + * * @return true if the hash is found in the reference set + * * @throws EamDbException */ @Override @@ -1518,6 +1664,7 @@ abstract class AbstractSqlEamDb implements EamDb { * @param value * @param referenceSetID * @param correlationTypeID + * * @return true if the value is found in the reference set */ @Override @@ -1542,7 +1689,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error determining if value (" + value + ") is in reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1560,8 +1707,8 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { - throw new EamDbException("null correlation type"); + if (aType == null) { + throw new EamDbException("Correlation type is null"); } // TEMP: Only support file correlation type @@ -1586,24 +1733,59 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error determining if artifact is notable by reference.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } return 0 < badInstances; } + /** + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @throws EamDbException + */ + @Override + public void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException { + if (type == null) { + throw new EamDbException("Correlation type is null"); + } + if (instanceTableCallback == null) { + throw new EamDbException("Callback interface is null"); + } + + Connection conn = connect(); + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); + StringBuilder sql = new StringBuilder(); + sql.append("select * from "); + sql.append(tableName); + + try { + preparedStatement = conn.prepareStatement(sql.toString()); + resultSet = preparedStatement.executeQuery(); + instanceTableCallback.process(resultSet); + } catch (SQLException ex) { + throw new EamDbException("Error getting all artifact instances from instances table", ex); + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + } @Override public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException { if (eamOrg == null) { throw new EamDbException("EamOrganization is null"); - } - else if (eamOrg.getOrgID() != -1) { + } else if (eamOrg.getOrgID() != -1) { throw new EamDbException("EamOrganization already has an ID"); } - + Connection conn = connect(); ResultSet generatedKeys = null; PreparedStatement preparedStatement = null; @@ -1620,7 +1802,7 @@ abstract class AbstractSqlEamDb implements EamDb { preparedStatement.executeUpdate(); generatedKeys = preparedStatement.getGeneratedKeys(); if (generatedKeys.next()) { - eamOrg.setOrgID((int)generatedKeys.getLong(1)); + eamOrg.setOrgID((int) generatedKeys.getLong(1)); return eamOrg; } else { throw new SQLException("Creating user failed, no ID obtained."); @@ -1628,7 +1810,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new organization.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(generatedKeys); EamDbUtil.closeConnection(conn); } @@ -1661,7 +1843,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all organizations.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1694,7 +1876,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting organization by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1704,47 +1886,48 @@ abstract class AbstractSqlEamDb implements EamDb { * Get the organization associated with the given reference set. * * @param referenceSetID ID of the reference set + * * @return The organization object + * * @throws EamDbException */ @Override public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException { EamGlobalSet globalSet = getReferenceSetByID(referenceSetID); - if(globalSet == null) { + if (globalSet == null) { throw new EamDbException("Reference set with ID " + referenceSetID + " not found"); } return (getOrganizationByID(globalSet.getOrgID())); } - /** + /** * Tests that an organization passed in as an argument is valid - * + * * @param org + * * @throws EamDbException if invalid */ private void testArgument(EamOrganization org) throws EamDbException { - if(org == null) { - throw new EamDbException("Organization is null"); - } - else if (org.getOrgID() == -1) { + if (org == null) { + throw new EamDbException("EamOrganization is null"); + } else if (org.getOrgID() == -1) { throw new EamDbException("Organization has -1 row ID"); } } - - + /** * Update an existing organization. * * @param updatedOrganization the values the Organization with the same ID - * will be updated to in the database. + * will be updated to in the database. * * @throws EamDbException */ @Override public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException { testArgument(updatedOrganization); - + Connection conn = connect(); PreparedStatement preparedStatement = null; String sql = "UPDATE organizations SET org_name = ?, poc_name = ?, poc_email = ?, poc_phone = ? WHERE id = ?"; @@ -1759,18 +1942,15 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating organization.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } - @Messages({"AbstractSqlEamDb.deleteOrganization.inUseException.message=Can not delete organization " - + "which is currently in use by a case or reference set in the central repository.", - "AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."}) @Override public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException { testArgument(organizationToDelete); - + Connection conn = connect(); PreparedStatement checkIfUsedStatement = null; ResultSet resultSet = null; @@ -1784,16 +1964,16 @@ abstract class AbstractSqlEamDb implements EamDb { resultSet = checkIfUsedStatement.executeQuery(); resultSet.next(); if (resultSet.getLong(1) > 0) { - throw new EamDbException(Bundle.AbstractSqlEamDb_deleteOrganization_inUseException_message()); + throw new EamDbException("Can not delete organization which is currently in use by a case or reference set in the central repository."); } deleteOrgStatement = conn.prepareStatement(deleteOrgSql); deleteOrgStatement.setInt(1, organizationToDelete.getOrgID()); deleteOrgStatement.executeUpdate(); } catch (SQLException ex) { - throw new EamDbException(Bundle.AbstractSqlEamDb_deleteOrganization_errorDeleting_message(), ex); // NON-NLS + throw new EamDbException("Error executing query when attempting to delete organization by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(checkIfUsedStatement); - EamDbUtil.closePreparedStatement(deleteOrgStatement); + EamDbUtil.closeStatement(checkIfUsedStatement); + EamDbUtil.closeStatement(deleteOrgStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1810,18 +1990,18 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException { - if(eamGlobalSet == null){ - throw new EamDbException("EamGlobalSet argument is null"); + if (eamGlobalSet == null) { + throw new EamDbException("EamGlobalSet is null"); } - - if(eamGlobalSet.getFileKnownStatus() == null){ + + if (eamGlobalSet.getFileKnownStatus() == null) { throw new EamDbException("File known status on the EamGlobalSet is null"); } - - if(eamGlobalSet.getType() == null){ + + if (eamGlobalSet.getType() == null) { throw new EamDbException("Type on the EamGlobalSet is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement1 = null; @@ -1856,8 +2036,8 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new global set.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); - EamDbUtil.closePreparedStatement(preparedStatement2); + EamDbUtil.closeStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement2); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1884,7 +2064,7 @@ abstract class AbstractSqlEamDb implements EamDb { preparedStatement1 = conn.prepareStatement(sql1); preparedStatement1.setInt(1, referenceSetID); resultSet = preparedStatement1.executeQuery(); - if(resultSet.next()) { + if (resultSet.next()) { return getEamGlobalSetFromResultSet(resultSet); } else { return null; @@ -1893,7 +2073,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference set by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1903,18 +2083,18 @@ abstract class AbstractSqlEamDb implements EamDb { * Get all reference sets * * @param correlationType Type of sets to return - * + * * @return List of all reference sets in the central repository * * @throws EamDbException */ @Override public List getAllReferenceSets(CorrelationAttribute.Type correlationType) throws EamDbException { - - if(correlationType == null){ + + if (correlationType == null) { throw new EamDbException("Correlation type is null"); } - + List results = new ArrayList<>(); Connection conn = connect(); @@ -1932,7 +2112,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference sets.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1943,19 +2123,20 @@ abstract class AbstractSqlEamDb implements EamDb { * Add a new reference instance * * @param eamGlobalFileInstance The reference instance to add - * @param correlationType Correlation Type that this Reference Instance is + * @param correlationType Correlation Type that this Reference + * Instance is * * @throws EamDbException */ @Override public void addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) throws EamDbException { - if(eamGlobalFileInstance.getKnownStatus() == null){ - throw new EamDbException("known status of EamGlobalFileInstance is null"); + if (eamGlobalFileInstance.getKnownStatus() == null) { + throw new EamDbException("Known status of EamGlobalFileInstance is null"); } - if(correlationType == null){ + if (correlationType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -1973,7 +2154,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new reference instance into reference_ table.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1985,7 +2166,9 @@ abstract class AbstractSqlEamDb implements EamDb { * * @param referenceSetName * @param version + * * @return true if a matching set is found + * * @throws EamDbException */ @Override @@ -2007,7 +2190,7 @@ abstract class AbstractSqlEamDb implements EamDb { throw new EamDbException("Error testing whether reference set exists (name: " + referenceSetName + " version: " + version, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2020,10 +2203,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) throws EamDbException { - if(contentType == null) { - throw new EamDbException("Null correlation type"); + if (contentType == null) { + throw new EamDbException("Correlation type is null"); } - if(globalInstances == null) { + if (globalInstances == null) { throw new EamDbException("Null set of EamGlobalFileInstance"); } @@ -2040,10 +2223,10 @@ abstract class AbstractSqlEamDb implements EamDb { bulkPs = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(contentType))); for (EamGlobalFileInstance globalInstance : globalInstances) { - if(globalInstance.getKnownStatus() == null){ + if (globalInstance.getKnownStatus() == null) { throw new EamDbException("EamGlobalFileInstance with value " + globalInstance.getMD5Hash() + " has null known status"); } - + bulkPs.setInt(1, globalInstance.getGlobalSetID()); bulkPs.setString(2, globalInstance.getMD5Hash()); bulkPs.setByte(3, globalInstance.getKnownStatus().getFileKnownValue()); @@ -2061,7 +2244,7 @@ abstract class AbstractSqlEamDb implements EamDb { } throw new EamDbException("Error inserting bulk artifacts.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } @@ -2069,7 +2252,7 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Get all reference entries having a given correlation type and value * - * @param aType Type to use for matching + * @param aType Type to use for matching * @param aValue Value to use for matching * * @return List of all global file instances with a type and value @@ -2078,10 +2261,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException { - if(aType == null) { - throw new EamDbException("correlation type is null"); + if (aType == null) { + throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); List globalFileInstances = new ArrayList<>(); @@ -2101,7 +2284,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference instances by type and value.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2119,9 +2302,9 @@ abstract class AbstractSqlEamDb implements EamDb { @Override public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException { if (newType == null) { - throw new EamDbException("null correlation type"); + throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -2168,8 +2351,8 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new correlation type.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); - EamDbUtil.closePreparedStatement(preparedStatementQuery); + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatementQuery); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2196,7 +2379,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2207,7 +2390,7 @@ abstract class AbstractSqlEamDb implements EamDb { * artifacts. * * @return List of enabled EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @@ -2231,7 +2414,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting enabled correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2242,7 +2425,7 @@ abstract class AbstractSqlEamDb implements EamDb { * correlate artifacts. * * @return List of supported EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @@ -2266,7 +2449,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting supported correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2298,7 +2481,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating correlation type.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -2326,7 +2509,7 @@ abstract class AbstractSqlEamDb implements EamDb { preparedStatement = conn.prepareStatement(sql); preparedStatement.setInt(1, typeId); resultSet = preparedStatement.executeQuery(); - if(resultSet.next()) { + if (resultSet.next()) { aType = getCorrelationTypeFromResultSet(resultSet); return aType; } else { @@ -2336,7 +2519,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting correlation type by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2346,7 +2529,7 @@ abstract class AbstractSqlEamDb implements EamDb { * Convert a ResultSet to a EamCase object * * @param resultSet A resultSet with a set of values to create a EamCase - * object. + * object. * * @return fully populated EamCase object, or null * @@ -2416,7 +2599,7 @@ abstract class AbstractSqlEamDb implements EamDb { * Convert a ResultSet to a EamArtifactInstance object * * @param resultSet A resultSet with a set of values to create a - * EamArtifactInstance object. + * EamArtifactInstance object. * * @return fully populated EamArtifactInstance, or null * @@ -2427,15 +2610,14 @@ abstract class AbstractSqlEamDb implements EamDb { return null; } // @@@ We should have data source ID in the previous query instead of passing -1 into the below constructor - CorrelationAttributeInstance eamArtifactInstance = new CorrelationAttributeInstance( + return new CorrelationAttributeInstance( + resultSet.getInt("id"), new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")), - new CorrelationDataSource(-1, resultSet.getInt("case_id"), resultSet.getString("device_id"), resultSet.getString("name")), + new CorrelationDataSource(resultSet.getInt("case_id"), resultSet.getInt("data_source_id"), resultSet.getString("device_id"), resultSet.getString("name")), resultSet.getString("file_path"), resultSet.getString("comment"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")) ); - - return eamArtifactInstance; } private EamOrganization getEamOrganizationFromResultSet(ResultSet resultSet) throws SQLException { @@ -2443,15 +2625,13 @@ abstract class AbstractSqlEamDb implements EamDb { return null; } - EamOrganization eamOrganization = new EamOrganization( + return new EamOrganization( resultSet.getInt("id"), resultSet.getString("org_name"), resultSet.getString("poc_name"), resultSet.getString("poc_email"), resultSet.getString("poc_phone") ); - - return eamOrganization; } private EamGlobalSet getEamGlobalSetFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { @@ -2459,7 +2639,7 @@ abstract class AbstractSqlEamDb implements EamDb { return null; } - EamGlobalSet eamGlobalSet = new EamGlobalSet( + return new EamGlobalSet( resultSet.getInt("id"), resultSet.getInt("org_id"), resultSet.getString("set_name"), @@ -2469,8 +2649,6 @@ abstract class AbstractSqlEamDb implements EamDb { EamDb.getInstance().getCorrelationTypeById(resultSet.getInt("type")), LocalDate.parse(resultSet.getString("import_date")) ); - - return eamGlobalSet; } private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { @@ -2478,15 +2656,13 @@ abstract class AbstractSqlEamDb implements EamDb { return null; } - EamGlobalFileInstance eamGlobalFileInstance = new EamGlobalFileInstance( + return new EamGlobalFileInstance( resultSet.getInt("id"), resultSet.getInt("reference_set_id"), resultSet.getString("value"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")), resultSet.getString("comment") ); - - return eamGlobalFileInstance; } /** @@ -2498,7 +2674,7 @@ abstract class AbstractSqlEamDb implements EamDb { public void upgradeSchema() throws EamDbException, SQLException { ResultSet resultSet = null; - Statement statement; + Statement statement = null; Connection conn = null; try { @@ -2507,30 +2683,30 @@ abstract class AbstractSqlEamDb implements EamDb { statement = conn.createStatement(); int minorVersion = 0; - int majorVersion = 0; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'"); if (resultSet.next()) { String minorVersionStr = resultSet.getString("value"); try { minorVersion = Integer.parseInt(minorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt"); + throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt", ex); } } + int majorVersion = 0; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); if (resultSet.next()) { String majorVersionStr = resultSet.getString("value"); try { majorVersion = Integer.parseInt(majorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt"); + throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt", ex); } } CaseDbSchemaVersionNumber dbSchemaVersion = new CaseDbSchemaVersionNumber(majorVersion, minorVersion); if (dbSchemaVersion.equals(CURRENT_DB_SCHEMA_VERSION)) { - LOGGER.log(Level.INFO, "Central Repository is up to date"); + logger.log(Level.INFO, "Central Repository is up to date"); return; } @@ -2551,18 +2727,19 @@ abstract class AbstractSqlEamDb implements EamDb { } conn.commit(); - LOGGER.log(Level.INFO, "Central Repository upgraded to version " + CURRENT_DB_SCHEMA_VERSION); + logger.log(Level.INFO, "Central Repository upgraded to version " + CURRENT_DB_SCHEMA_VERSION); } catch (SQLException | EamDbException ex) { try { if (conn != null) { conn.rollback(); } } catch (SQLException ex2) { - LOGGER.log(Level.SEVERE, "Database rollback failed", ex2); + logger.log(Level.SEVERE, "Database rollback failed", ex2); } throw ex; } finally { EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeStatement(statement); EamDbUtil.closeConnection(conn); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index d20fbc3b29..3ccca9f293 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,8 +24,8 @@ import org.sleuthkit.datamodel.TskData; /** * - * Used to store details about a specific instance of a - * CorrelationAttribute. Includes its data source, path, etc. + * Used to store details about a specific instance of a CorrelationAttribute. + * Includes its data source, path, etc. * */ @Messages({ @@ -70,10 +70,10 @@ public class CorrelationAttributeInstance implements Serializable { String comment, TskData.FileKnown knownStatus ) throws EamDbException { - if(filePath == null) { + if (filePath == null) { throw new EamDbException("file path is null"); } - + this.ID = ID; this.correlationCase = eamCase; this.correlationDataSource = eamDataSource; @@ -102,6 +102,16 @@ public class CorrelationAttributeInstance implements Serializable { + this.getComment(); } + /** + * Is this a database instance? + * + * @return True if the instance ID is greater or equal to zero; otherwise + * false. + */ + public boolean isDatabaseInstance() { + return (ID >= 0); + } + /** * @return the database ID */ @@ -145,9 +155,9 @@ public class CorrelationAttributeInstance implements Serializable { } /** - * Get this knownStatus. This only indicates whether an item has been - * tagged as notable and should never return KNOWN. - * + * Get this knownStatus. This only indicates whether an item has been tagged + * as notable and should never return KNOWN. + * * @return BAD if the item has been tagged as notable, UNKNOWN otherwise */ public TskData.FileKnown getKnownStatus() { @@ -155,10 +165,11 @@ public class CorrelationAttributeInstance implements Serializable { } /** - * Set the knownStatus. This only indicates whether an item has been - * tagged as notable and should never be set to KNOWN. - * - * @param knownStatus Should be BAD if the item is tagged as notable, UNKNOWN otherwise + * Set the knownStatus. This only indicates whether an item has been tagged + * as notable and should never be set to KNOWN. + * + * @param knownStatus Should be BAD if the item is tagged as notable, + * UNKNOWN otherwise */ public void setKnownStatus(TskData.FileKnown knownStatus) { this.knownStatus = knownStatus; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 264c70fc66..8cf07a42e9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -90,7 +90,18 @@ public class CorrelationDataSource implements Serializable { } catch (TskDataException | TskCoreException ex) { throw new EamDbException("Error getting data source info: " + ex.getMessage()); } - return new CorrelationDataSource(correlationCase.getID(), -1, deviceId, dataSource.getName()); + + CorrelationDataSource correlationDataSource = null; + if (EamDbUtil.useCentralRepo()) { + correlationDataSource = EamDb.getInstance().getDataSource(correlationCase, deviceId); + } + if (correlationDataSource == null) { + correlationDataSource = new CorrelationDataSource(correlationCase, deviceId, dataSource.getName()); + if (EamDbUtil.useCentralRepo()) { + EamDb.getInstance().newDataSource(correlationDataSource); + } + } + return correlationDataSource; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index c5319dd044..6aad75d38e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -39,7 +39,7 @@ import org.sleuthkit.datamodel.TskData; public class EamArtifactUtil { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(EamArtifactUtil.class.getName()); + private static final Logger logger = Logger.getLogger(EamArtifactUtil.class.getName()); public EamArtifactUtil() { } @@ -83,7 +83,7 @@ public class EamArtifactUtil { } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS return eamArtifacts; } @@ -115,10 +115,10 @@ public class EamArtifactUtil { eamArtifact.addInstance(eamInstance); } } catch (TskCoreException | EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS return eamArtifacts; } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Case is closed.", ex); // NON-NLS + logger.log(Level.SEVERE, "Case is closed.", ex); // NON-NLS return eamArtifacts; } } @@ -136,7 +136,7 @@ public class EamArtifactUtil { * @return the new EamArtifact, or null if one was not created because * bbArtifact did not contain the needed data */ - private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, + private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, BlackboardArtifact bbArtifact) throws EamDbException { String value = null; int artifactTypeID = bbArtifact.getArtifactTypeID(); @@ -202,10 +202,10 @@ public class EamArtifactUtil { } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS return null; - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS return null; } @@ -216,6 +216,45 @@ public class EamArtifactUtil { } } + /** + * Retrieve CorrelationAttribute from the given Content. + * + * @param content The content object + * + * @return The new CorrelationAttribute, or null if retrieval failed. + */ + public static CorrelationAttribute getCorrelationAttributeFromContent(Content content) { + + if (!(content instanceof AbstractFile)) { + return null; + } + + final AbstractFile file = (AbstractFile) content; + + if (!isSupportedAbstractFileType(file)) { + return null; + } + + CorrelationAttribute correlationAttribute = null; + + try { + CorrelationAttribute.Type type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); + if (null == correlationCase) { + correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows()); + } + CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource()); + String value = file.getMd5Hash(); + String filePath = (file.getParentPath() + file.getName()).toLowerCase(); + + correlationAttribute = EamDb.getInstance().getCorrelationAttribute(type, correlationCase, correlationDataSource, value, filePath); + } catch (TskCoreException | EamDbException | NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error retrieving correlation attribute.", ex); + } + + return correlationAttribute; + } + /** * Create an EamArtifact from the given Content. Will return null if an * artifact can not be created - this is not necessarily an error case, it @@ -225,7 +264,7 @@ public class EamArtifactUtil { * * Does not add the artifact to the database. * - * @param content The content object + * @param content The content object * * @return The new EamArtifact or null if creation failed */ @@ -262,7 +301,7 @@ public class EamArtifactUtil { eamArtifact.addInstance(cei); return eamArtifact; } catch (TskCoreException | EamDbException | NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Error making correlation attribute.", ex); + logger.log(Level.SEVERE, "Error making correlation attribute.", ex); return null; } } @@ -271,17 +310,17 @@ public class EamArtifactUtil { * Check whether the given abstract file should be processed for the central * repository. * - * @param af The file to test + * @param file The file to test * * @return true if the file should be added to the central repo, false * otherwise */ - public static boolean isSupportedAbstractFileType(AbstractFile af) { - if (af == null) { + public static boolean isSupportedAbstractFileType(AbstractFile file) { + if (file == null) { return false; } - switch (af.getType()) { + switch (file.getType()) { case UNALLOC_BLOCKS: case UNUSED_BLOCKS: case SLACK: @@ -293,9 +332,9 @@ public class EamArtifactUtil { case LOCAL: return true; case FS: - return af.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC); + return file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC); default: - LOGGER.log(Level.WARNING, "Unexpected file type {0}", af.getType().getName()); + logger.log(Level.WARNING, "Unexpected file type {0}", file.getType().getName()); return false; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java index 131331ab60..7ced4a1d7d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -104,7 +104,7 @@ public interface EamDb { /** * Add a new name/value pair in the db_info table. * - * @param name Key to set + * @param name Key to set * @param value Value to set * * @throws EamDbException @@ -125,7 +125,7 @@ public interface EamDb { /** * Update the value for a name in the name/value db_info table. * - * @param name Name to find + * @param name Name to find * @param value Value to assign to name. * * @throws EamDbException @@ -159,7 +159,9 @@ public interface EamDb { * Retrieves Central Repo case based on an Autopsy Case * * @param autopsyCase Autopsy case to find corresponding CR case for + * * @return CR Case + * * @throws EamDbException */ CorrelationCase getCase(Case autopsyCase) throws EamDbException; @@ -190,8 +192,8 @@ public interface EamDb { /** * Retrieves Data Source details based on data source device ID * - * @param correlationCase the current CorrelationCase used for ensuring - * uniqueness of DataSource + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource * @param dataSourceDeviceId the data source device ID number * * @return The data source @@ -228,7 +230,7 @@ public interface EamDb { * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath * - * @param aType EamArtifact.Type to search for + * @param aType EamArtifact.Type to search for * @param filePath File path to search for * * @return List of 0 or more EamArtifactInstances @@ -245,7 +247,7 @@ public interface EamDb { * @param value Value to search for * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException; @@ -282,11 +284,11 @@ public interface EamDb { * associated with the caseDisplayName and dataSource of the given * eamArtifact instance. * - * @param caseUUID Case ID to search for + * @param caseUUID Case ID to search for * @param dataSourceID Data source ID to search for * * @return Number of artifact instances having caseDisplayName and - * dataSource + * dataSource */ Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException; @@ -310,6 +312,34 @@ public interface EamDb { */ void bulkInsertCases(List cases) throws EamDbException; + /** + * Update a correlation attribute instance comment in the database with that + * in the associated CorrelationAttribute object. + * + * @param eamArtifact The correlation attribute whose database instance will + * be updated. + * + * @throws EamDbException + */ + void updateAttributeInstanceComment(CorrelationAttribute eamArtifact) throws EamDbException; + + /** + * Find a correlation attribute in the Central Repository database given the + * instance type, case, data source, value, and file path. + * + * @param type The type of instance. + * @param correlationCase The case tied to the instance. + * @param correlationDataSource The data source tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. + * + * @return The correlation attribute if it exists; otherwise null. + * + * @throws EamDbException + */ + CorrelationAttribute getCorrelationAttribute(CorrelationAttribute.Type type, CorrelationCase correlationCase, + CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException; + /** * Sets an eamArtifact instance to the given known status. If eamArtifact * exists, it is updated. If eamArtifact does not exist nothing happens @@ -357,7 +387,7 @@ public interface EamDb { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws EamDbException */ @@ -367,6 +397,7 @@ public interface EamDb { * Remove a reference set and all values contained in it. * * @param referenceSetID + * * @throws EamDbException */ public void deleteReferenceSet(int referenceSetID) throws EamDbException; @@ -379,7 +410,9 @@ public interface EamDb { * @param referenceSetID * @param referenceSetName * @param version + * * @return true if a matching entry exists in the central repository + * * @throws EamDbException */ public boolean referenceSetIsValid(int referenceSetID, String referenceSetName, String version) throws EamDbException; @@ -391,7 +424,9 @@ public interface EamDb { * * @param referenceSetName * @param version + * * @return true if a matching set is found + * * @throws EamDbException */ public boolean referenceSetExists(String referenceSetName, String version) throws EamDbException; @@ -402,7 +437,9 @@ public interface EamDb { * * @param hash * @param referenceSetID + * * @return true if the hash is found in the reference set + * * @throws EamDbException */ public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException; @@ -413,6 +450,7 @@ public interface EamDb { * @param value * @param referenceSetID * @param correlationTypeID + * * @return true if the hash is found in the reference set */ public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException; @@ -462,7 +500,9 @@ public interface EamDb { * Get the organization associated with the given reference set. * * @param referenceSetID ID of the reference set + * * @return The organization object + * * @throws EamDbException */ EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException; @@ -471,7 +511,7 @@ public interface EamDb { * Update an existing organization. * * @param updatedOrganization the values the Organization with the same ID - * will be updated to in the database. + * will be updated to in the database. * * @throws EamDbException */ @@ -512,7 +552,7 @@ public interface EamDb { * Get all reference sets * * @param correlationType Type of sets to return - * + * * @return List of all reference sets in the central repository * * @throws EamDbException @@ -523,7 +563,8 @@ public interface EamDb { * Add a new reference instance * * @param eamGlobalFileInstance The reference instance to add - * @param correlationType Correlation Type that this Reference Instance is + * @param correlationType Correlation Type that this Reference + * Instance is * * @throws EamDbException */ @@ -533,8 +574,8 @@ public interface EamDb { * Insert the bulk collection of Global File Instances * * @param globalInstances a Set of EamGlobalFileInstances to insert into the - * db. - * @param contentType the Type of the global instances + * db. + * @param contentType the Type of the global instances * * @throws EamDbException */ @@ -543,7 +584,7 @@ public interface EamDb { /** * Get all reference entries having a given correlation type and value * - * @param aType Type to use for matching + * @param aType Type to use for matching * @param aValue Value to use for matching * * @return List of all global file instances with a type and value @@ -568,7 +609,7 @@ public interface EamDb { * used to correlate artifacts. * * @return List of EamArtifact.Type's. If none are defined in the database, - * the default list will be returned. + * the default list will be returned. * * @throws EamDbException */ @@ -579,7 +620,7 @@ public interface EamDb { * artifacts. * * @return List of enabled EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @@ -590,7 +631,7 @@ public interface EamDb { * correlate artifacts. * * @return List of supported EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @@ -630,8 +671,18 @@ public interface EamDb { * (meaning the database is in use). * * @return the lock, or null if locking is not supported + * * @throws EamDbException if the coordination service is running but we fail - * to get the lock + * to get the lock */ public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException; + + /** + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @throws EamDbException + */ + void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java index e2fc4bf371..9b8a4e1ed5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java @@ -42,18 +42,18 @@ public class EamDbUtil { private static final String DEFAULT_ORG_NAME = "Not Specified"; /** - * Close the prepared statement. + * Close the statement. * - * @param preparedStatement + * @param statement The statement to be closed. * * @throws EamDbException */ - public static void closePreparedStatement(PreparedStatement preparedStatement) { - if (null != preparedStatement) { + public static void closeStatement(Statement statement) { + if (null != statement) { try { - preparedStatement.close(); + statement.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing PreparedStatement.", ex); + LOGGER.log(Level.SEVERE, "Error closing Statement.", ex); } } } @@ -361,4 +361,18 @@ public class EamDbUtil { return "reference_" + type.getDbTableName(); } + /** + * Close the prepared statement. + * + * @param preparedStatement The prepared statement to be closed. + * + * @deprecated Use closeStatement() instead. + * + * @throws EamDbException + */ + @Deprecated + public static void closePreparedStatement(PreparedStatement preparedStatement) { + closeStatement(preparedStatement); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java new file mode 100644 index 0000000000..d7da43bbf4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java @@ -0,0 +1,109 @@ +/* + * Central Repository + * + * Copyright 2015-2017 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.centralrepository.datamodel; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * CallBack Interface to process attribute instance Table. Used in EamDb.processInstanceTable + * is called only once. The implementation of this method needs to call resultset.next to + * loop through each row of attribute instance table. + */ +public interface InstanceTableCallback { + + /** + * Process the attribute instance + * + * @param resultSet attribute instance table. + */ + void process(ResultSet resultSet); + + + /** + * + * @param resultSet attribute instance table + * @return ID of the instance + * @throws SQLException + */ + static int getId(ResultSet resultSet) throws SQLException{ + return resultSet.getInt("id"); + } + + /** + * + * @param resultSet attribute instance table + * @return Case ID of a given instance + * @throws SQLException + */ + static int getCaseId(ResultSet resultSet) throws SQLException { + return resultSet.getInt("case_id"); + } + + /** + * + * @param resultSet attribute instance table + * @return Data source id of a particular instance + * @throws SQLException + */ + static int getDataSourceId(ResultSet resultSet) throws SQLException { + return resultSet.getInt("data_source_id"); + } + + /** + * + * @param resultSet attribute instance table + * @return md5 hash value of the instance + * @throws SQLException + */ + static String getValue(ResultSet resultSet) throws SQLException { + return resultSet.getString("value"); + } + + /** + * + * @param resultSet attribute instance table + * @return file path of the instance + * @throws SQLException + */ + static String getFilePath(ResultSet resultSet) throws SQLException { + return resultSet.getString("file_path"); + } + + /** + * + * @param resultSet attribute instance table + * @return status integer based on whether instance is marked notable or not + * @throws SQLException + */ + static int getKnownStatus(ResultSet resultSet) throws SQLException { + return resultSet.getInt("known_status"); + } + + /** + * + * @param resultSet attribute instance table + * @return previous comment made for the instance + * @throws SQLException + */ + static String getComment(ResultSet resultSet) throws SQLException { + return resultSet.getString("comment"); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java index 8852a05d45..f008070cea 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java @@ -114,7 +114,7 @@ final class PostgresEamDb extends AbstractSqlEamDb { String instancesTemplate = "TRUNCATE TABLE %s_instances RESTART IDENTITY CASCADE"; String referencesTemplate = "TRUNCATE TABLE reference_%s RESTART IDENTITY CASCADE"; - for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) { + for (CorrelationAttribute.Type type : defaultCorrelationTypes) { dropContent.executeUpdate(String.format(instancesTemplate, type.getDbTableName())); // FUTURE: support other reference types if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java index 1154da273d..32de4e7646 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java @@ -221,7 +221,7 @@ public final class PostgresEamDbSettings { LOGGER.log(Level.SEVERE, "Failed to execute database existance query.", ex); // NON-NLS return false; } finally { - EamDbUtil.closePreparedStatement(ps); + EamDbUtil.closeStatement(ps); EamDbUtil.closeResultSet(rs); EamDbUtil.closeConnection(conn); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index bfa69546aa..4a3ef36530 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -124,7 +124,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { String instancesTemplate = "DELETE FROM %s_instances"; String referencesTemplate = "DELETE FROM global_files"; - for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) { + for (CorrelationAttribute.Type type : defaultCorrelationTypes) { dropContent.executeUpdate(String.format(instancesTemplate, type.getDbTableName())); // FUTURE: support other reference types if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) { @@ -416,8 +416,8 @@ final class SqliteEamDb extends AbstractSqlEamDb { } finally { releaseSharedLock(); } - } - + } + /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath @@ -679,6 +679,22 @@ final class SqliteEamDb extends AbstractSqlEamDb { } } + /** + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @throws EamDbException + */ + @Override + public void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException { + try { + acquireSharedLock(); + super.processInstanceTable(type, instanceTableCallback); + } finally { + releaseSharedLock(); + } + } /** * Check whether a reference set with the given name/version is in the central repo. * Used to check for name collisions when creating reference sets. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java index a7c3016d96..3fa46b3306 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.centralrepository.optionspanel; -import java.awt.Dimension; -import java.awt.Toolkit; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -38,8 +36,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; /** * Dialog to handle management of artifact types handled by the Central - * Repository + * Repository */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ManageCorrelationPropertiesDialog extends javax.swing.JDialog { private static final Logger LOGGER = Logger.getLogger(ManageCorrelationPropertiesDialog.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index a0417855fe..006fbd846f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,6 +48,7 @@ import org.sleuthkit.datamodel.TskCoreException; * CVTTopComponent when this tab is active allowing for context sensitive * actions to work correctly. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AccountsBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index 09052d9d9e..1e1004de25 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; @TopComponent.Registration(mode = "cvt", openAtStartup = false) @RetainLocation("cvt") @NbBundle.Messages("CVTTopComponent.name= Communications Visualization") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class CVTTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 266c0d91f2..81ffcb4805 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +57,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Panel that holds the Filter control widgets and triggers queries against the * CommunicationsManager on user filtering changes. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final public class FiltersPanel extends JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index dca1770463..339e842280 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; * messages and other account details, and a ContentViewer to show individual * messages. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class MessageBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java index cc5e2008b5..07736120c7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java @@ -76,6 +76,7 @@ import org.sleuthkit.datamodel.TskData; @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class FXVideoPanel extends MediaViewVideoPanel { // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index 53be8ddbcd..c33a653292 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -36,6 +36,7 @@ import org.sleuthkit.datamodel.AbstractFile; * Generic Application content viewer */ @ServiceProvider(service = DataContentViewer.class, position = 3) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class FileViewer extends javax.swing.JPanel implements DataContentViewer { private static final int CONFIDENCE_LEVEL = 5; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 92da0733d8..963e3f8b39 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -30,6 +30,7 @@ import org.sleuthkit.datamodel.AbstractFile; /** * Media content viewer for videos, sounds and images. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index b04473990c..da526803a8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -48,7 +48,6 @@ import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.ImageUtils; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.datamodel.AbstractFile; @@ -60,12 +59,11 @@ import org.sleuthkit.datamodel.AbstractFile; @NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer", "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.", "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel { private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm()); - private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName()); - private final boolean fxInited; private JFXPanel fxPanel; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index 77c0814c55..7861a7e05d 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -49,6 +49,7 @@ import org.openide.util.actions.Presenter; /** * Panel to display a SQLite table */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SQLiteTableView extends JPanel implements ExplorerManager.Provider { private final org.openide.explorer.view.OutlineView outlineView; diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 63ac880171..e45055b077 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -206,7 +206,7 @@ public class Installer extends ModuleInstall { // Prevent the Autopsy UI from shrinking on high DPI displays System.setProperty("sun.java2d.dpiaware", "false"); System.setProperty("prism.allowhidpi", "false"); - + // Update existing configuration in case of unsupported settings updateConfig(); @@ -218,16 +218,16 @@ public class Installer extends ModuleInstall { packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault()); } - + /** * If the mode in the configuration file is 'REVIEW' (2, now invalid), this * method will set it to 'STANDALONE' (0) and disable auto ingest. */ private void updateConfig() { String mode = ModuleSettings.getConfigSetting(SETTINGS_PROPERTIES, "AutopsyMode"); - if(mode != null) { + if (mode != null) { int ordinal = Integer.parseInt(mode); - if(ordinal > 1) { + if (ordinal > 1) { UserPreferences.setMode(UserPreferences.SelectedMode.STANDALONE); ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, "JoinAutoModeCluster", Boolean.toString(false)); } @@ -268,6 +268,19 @@ public class Installer extends ModuleInstall { } } + /** + * Make a folder in the config directory for object detection classifiers if one does not + * exist. + */ + private static void ensureClassifierFolderExists() { + File objectDetectionClassifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath()); + objectDetectionClassifierDir.mkdir(); + } + + /** + * Make a folder in the config directory for Python Modules if one does not + * exist. + */ private static void ensurePythonModulesFolderExists() { File pythonModulesDir = new File(PlatformUtil.getUserPythonModulesPath()); pythonModulesDir.mkdir(); @@ -277,6 +290,7 @@ public class Installer extends ModuleInstall { public void restored() { super.restored(); ensurePythonModulesFolderExists(); + ensureClassifierFolderExists(); initJavaFx(); for (ModuleInstall mi : packageInstallers) { try { diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 904ab285c8..22fa2b7135 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -69,6 +69,7 @@ public final class UserPreferences { private static final String MODE = "AutopsyMode"; // NON-NLS private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; private static final int LOG_FILE_NUM_INT = 10; + public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS // Prevent instantiation. private UserPreferences() { @@ -187,6 +188,14 @@ public final class UserPreferences { preferences.putInt(NUMBER_OF_FILE_INGEST_THREADS, value); } + public static boolean groupItemsInTreeByDatasource() { + return preferences.getBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, false); + } + + public static void setGroupItemsInTreeByDatasource(boolean value) { + preferences.putBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, value); + } + /** * Reads persisted case database connection info. * diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java index cccb6e1a46..4589c43755 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java @@ -1,12 +1,24 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2011-2018 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.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; @@ -16,6 +28,7 @@ import org.openide.windows.WindowManager; * the panel given to it. No additional buttons or features, except the default * close operation, which is set to dispose. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** @@ -27,6 +40,8 @@ public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationCleanDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java index 35a26344ea..a62d3a1459 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,26 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * AdvancedConfigurationDialog.java - * - * Created on Feb 28, 2012, 4:47:31 PM - */ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; /** - * - * @author dfickling + * Advanced configuration dialog. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** @@ -47,6 +39,8 @@ public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index bae8218148..bb98cc90f6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -26,7 +26,7 @@ LBL_Description=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.7.0/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.8.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html LBL_Close=Close diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java index 9b94c9a7b5..642ba5f4b0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,8 +38,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Data content panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentPanel extends javax.swing.JPanel implements DataContent, ChangeListener { private static Logger logger = Logger.getLogger(DataContentPanel.class.getName()); @@ -241,7 +242,7 @@ public class DataContentPanel extends javax.swing.JPanel implements DataContent, private static class UpdateWrapper { - private DataContentViewer wrapped; + private final DataContentViewer wrapped; private boolean outdated; UpdateWrapper(DataContentViewer wrapped) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index 6331d67023..af015d0b4a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; //@TopComponent.Description(preferredID = "DataContentTopComponent") //@TopComponent.Registration(mode = "output", openAtStartup = true) //@TopComponent.OpenActionRegistration(displayName = "#CTL_DataContentAction", preferredID = "DataContentTopComponent") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataContentTopComponent extends TopComponent implements DataContent, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataContentTopComponent.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index e2775df40e..a5760a26aa 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2013 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,6 +63,7 @@ import org.netbeans.swing.etable.ETable; * in a JTable representation of its BlackboardAttributes. */ @ServiceProvider(service = DataContentViewer.class, position = 7) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentViewerArtifact extends javax.swing.JPanel implements DataContentViewer { @NbBundle.Messages({ @@ -484,7 +485,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat if ((artifact == null) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) - || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) { + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()) + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID())) { return 3; } else { return 6; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java index 2b75990d68..6639856099 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,6 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -import javax.swing.JTextPane; import javax.swing.text.BadLocationException; import javax.swing.text.Utilities; import org.openide.nodes.Node; @@ -40,6 +39,7 @@ import org.sleuthkit.datamodel.TskException; /** * Hex view of file contents. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @ServiceProvider(service = DataContentViewer.class, position = 1) public class DataContentViewerHex extends javax.swing.JPanel implements DataContentViewer { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 4aa7a63117..ced154edf7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -72,6 +72,7 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; * (DataContentTopComponent) that is normally docked into the lower right hand * side of the main application window, or it could be a custom content view. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { private static final long serialVersionUID = 1L; @@ -451,7 +452,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } } } - }; + } if (tabToSelect == NO_TAB_SELECTED) { tabToSelect = resultViewerTabs.getSelectedIndex(); if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index dc88be9fee..29e289d67e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -68,6 +68,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * viewers to the actions global context. */ @RetainLocation("editor") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 1089bc62f9..0e36b8fc00 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -77,6 +77,7 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; * ancestor top component's explorer manager at runtime. */ @ServiceProvider(service = DataResultViewer.class) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java b/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java index e06d61d778..3981ac63b9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,8 +52,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; @Messages("CTL_OfflineHelpAction=Offline Autopsy Documentation") public final class OfflineHelpAction implements ActionListener { - private URI uri; - private static final Logger Logger + private static final Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(AboutWindowPanel.class.getName()); @Override @@ -71,7 +70,8 @@ public final class OfflineHelpAction implements ActionListener { String fileForHelp = ""; String indexForHelp = ""; String currentDirectory = ""; - + URI uri = null; + try { // Match the form: file:///C:/some/directory/AutopsyXYZ/docs/index.html fileForHelp = NbBundle.getMessage(OfflineHelpAction.class, "FILE_FOR_LOCAL_HELP"); @@ -79,7 +79,7 @@ public final class OfflineHelpAction implements ActionListener { currentDirectory = System.getProperty("user.dir").replace("\\", "/").replace(" ", "%20"); //NON-NLS uri = new URI(fileForHelp + currentDirectory + indexForHelp); } catch (Exception ex) { - Logger.log(Level.SEVERE, "Unable to load Offline Documentation: " + logger.log(Level.SEVERE, "Unable to load Offline Documentation: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } if (uri != null) { @@ -89,7 +89,7 @@ public final class OfflineHelpAction implements ActionListener { try { desktop.browse(uri); } catch (IOException ex) { - Logger.log(Level.SEVERE, "Unable to launch the system browser: " + logger.log(Level.SEVERE, "Unable to launch the system browser: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } } else { @@ -98,7 +98,7 @@ public final class OfflineHelpAction implements ActionListener { try { HtmlBrowser.URLDisplayer.getDefault().showURL(uri.toURL()); } catch (MalformedURLException ex) { - Logger.log(Level.SEVERE, "Unable to launch the built-in browser: " + logger.log(Level.SEVERE, "Unable to launch the built-in browser: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java b/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java index 2c73a50a67..8c7bda3e6e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; * A dialog that allows the user to choose sort criteria for the thumbnail * viewer. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class SortChooser extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 11e92836ac..5089ed41e2 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -59,10 +59,11 @@ import javax.imageio.stream.ImageInputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.opencv.core.Core; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.corelibs.OpenCvLoader; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException; @@ -96,7 +97,7 @@ public class ImageUtils { private static final List SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>(); private static final SortedSet SUPPORTED_IMAGE_MIME_TYPES; - private static final boolean OPEN_CV_LOADED; + private static final boolean FFMPEG_LOADED; /** * Map from tsk object id to Java File object. Used to get the same File for @@ -105,6 +106,8 @@ public class ImageUtils { * * NOTE: Must be cleared when the case is changed. */ + @Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg", + "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"}) private static final ConcurrentHashMap cacheFileMap = new ConcurrentHashMap<>(); static { @@ -117,25 +120,23 @@ public class ImageUtils { tempImage = null; } DEFAULT_THUMBNAIL = tempImage; - - //load opencv libraries - boolean openCVLoadedTemp; - try { - System.loadLibrary(Core.NATIVE_LIBRARY_NAME); - if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS - System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS - } else { - System.loadLibrary("opencv_ffmpeg248"); //NON-NLS + boolean tempFfmpegLoaded = false; + if (OpenCvLoader.isOpenCvLoaded()) { + try { + if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS + System.loadLibrary("opencv_ffmpeg2413_64"); //NON-NLS + } else { + System.loadLibrary("opencv_ffmpeg2413"); //NON-NLS + } + tempFfmpegLoaded = true; + } catch (UnsatisfiedLinkError e) { + tempFfmpegLoaded = false; + LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); //NON-NLS + MessageNotifyUtil.Notify.show(Bundle.ImageUtils_ffmpegLoadedError_title(), Bundle.ImageUtils_ffmpegLoadedError_msg(), MessageNotifyUtil.MessageType.WARNING); } - - openCVLoadedTemp = true; - } catch (UnsatisfiedLinkError e) { - openCVLoadedTemp = false; - LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e); //NON-NLS - MessageNotifyUtil.Notify.show("Open CV", "OpenCV native library failed to load, see log for more details", MessageNotifyUtil.MessageType.WARNING); } + FFMPEG_LOADED = tempFfmpegLoaded; - OPEN_CV_LOADED = openCVLoadedTemp; SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes())); SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files SUPPORTED_IMAGE_EXTENSIONS.removeIf("db"::equals); // remove db files @@ -165,8 +166,8 @@ public class ImageUtils { /** * Thread/Executor that saves generated thumbnails to disk in the background */ - private static final Executor imageSaver = - Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() + private static final Executor imageSaver + = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() .namingPattern("thumbnail-saver-%d").build()); //NON-NLS public static List getSupportedImageExtensions() { @@ -687,7 +688,7 @@ public class ImageUtils { //There was no correctly-sized cached thumbnail so make one. BufferedImage thumbnail = null; if (VideoUtils.isVideoThumbnailSupported(file)) { - if (OPEN_CV_LOADED) { + if (FFMPEG_LOADED) { updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); if (isCancelled()) { return null; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index 7d66dc4e3c..f0a3086588 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2014 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,6 +51,7 @@ import org.sleuthkit.datamodel.TskCoreException; public class PlatformUtil { private static final String PYTHON_MODULES_SUBDIRECTORY = "python_modules"; //NON-NLS + private static final String CLASSIFIERS_SUBDIRECTORY = "object_detection_classifiers"; //NON-NLS private static String javaPath = null; public static final String OS_NAME_UNKNOWN = NbBundle.getMessage(PlatformUtil.class, "PlatformUtil.nameUnknown"); public static final String OS_VERSION_UNKNOWN = NbBundle.getMessage(PlatformUtil.class, "PlatformUtil.verUnknown"); @@ -116,6 +117,15 @@ public class PlatformUtil { return getUserDirectory().getAbsolutePath() + File.separator + PYTHON_MODULES_SUBDIRECTORY; } + /** + * Get root path where the user's object detection classifiers are stored. + * + * @return Absolute path to the object detection classifiers root directory. + */ + public static String getObjectDetectionClassifierPath() { + return getUserDirectory().getAbsolutePath() + File.separator + CLASSIFIERS_SUBDIRECTORY; + } + /** * get file path to the java executable binary use embedded java if * available, otherwise use system java in PATH no validation is done if @@ -310,19 +320,17 @@ public class PlatformUtil { return (System.getProperty("os.arch").contains("64")); //NON-NLS } } - - + /** - * Attempts to determine whether the JVM is 64-bit or 32-bit. - * May not be completely reliable for non-Windows operating systems. + * Attempts to determine whether the JVM is 64-bit or 32-bit. May not be + * completely reliable for non-Windows operating systems. * * @return True if the JVM is 64-bit. False otherwise. */ public static boolean is64BitJVM() { return (System.getProperty("sun.arch.data.model").equals("64")); } - - + /** * Get a list of all physical drives attached to the client's machine. Error * threshold of 4 non-existent physical drives before giving up. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 3f1ca56e47..28329f17c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,12 +21,14 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; +import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Children; import org.openide.nodes.Sheet; @@ -36,6 +38,9 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bf139f9a47..71705d2725 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -162,12 +162,12 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(DeletedContent dc) { - return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase()); + return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override public AbstractNode visit(FileSize dc) { - return new FileSize.FileSizeRootNode(dc.getSleuthkitCase()); + return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override @@ -192,22 +192,27 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { - return tagsNodeKey.new RootNode(); + return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); } @Override public AbstractNode visit(DataSources i) { - return new DataSourcesNode(); + return new DataSourcesNode(i.filteringDataSourceObjId()); } + @Override + public AbstractNode visit(DataSourceGrouping datasourceGrouping) { + return new DataSourceGroupingNode(datasourceGrouping.getDataSource()); + } + @Override public AbstractNode visit(Views v) { - return new ViewsNode(v.getSleuthkitCase()); + return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId()); } @Override - public AbstractNode visit(Results r) { - return new ResultsNode(r.getSleuthkitCase()); + public AbstractNode visit(Results results) { + return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() ); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index 2dcc5942db..29843bccb7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.datamodel.accounts.Accounts; public interface AutopsyItemVisitor { T visit(DataSources i); + + T visit(DataSourceGrouping datasourceGrouping); T visit(Views v); @@ -173,6 +175,11 @@ public interface AutopsyItemVisitor { return defaultVisit(v); } + @Override + public T visit(DataSourceGrouping datasourceGrouping) { + return defaultVisit(datasourceGrouping); + } + @Override public T visit(Results r) { return defaultVisit(r); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java new file mode 100644 index 0000000000..cc3b8cb8ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java @@ -0,0 +1,146 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Child factory to create the top level children of the autopsy tree + * + */ +public class AutopsyTreeChildrenFactory extends ChildFactory.Detachable { + + private static final Logger logger = Logger.getLogger(AutopsyTreeChildrenFactory.class.getName()); + private final SleuthkitCase tskCase; + + /** + * Constructs the child factory + * @param tskCase + */ + public AutopsyTreeChildrenFactory(SleuthkitCase tskCase) { + this.tskCase = tskCase; + + } + + /** + * Listener for handling DATA_SOURCE_ADDED events. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + refreshChildren(); + } + } + }; + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + protected void removeNotify() { + super.removeNotify(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + /** + * Creates keys for the top level children. + * + * @param list list of keys created + * @return true, indicating that the key list is complete + */ + @Override + protected boolean createKeys(List list) { + + try { + if (UserPreferences.groupItemsInTreeByDatasource()) { + List dataSources = tskCase.getDataSources(); + List keys = new ArrayList<>(); + dataSources.forEach((datasource) -> { + keys.add(new DataSourceGrouping(datasource)); + }); + list.addAll(keys); + + list.add(new Reports()); + } else { + + List keys = new ArrayList<>(Arrays.asList( + new DataSources(), + new Views(tskCase), + new Results(tskCase), + new Tags(), + new Reports())); + + list.addAll(keys); + } + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error getting datas sources list from the database.", tskCoreException); + } + return true; + } + + /** + * Creates nodes for the top level Key + * + * @param key + * + * @return Node for the key, null if key is unknown. + */ + @Override + protected Node createNodeForKey(Object key) { + + if (key instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); + } else if (key instanceof AutopsyVisitableItem) { + return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); + } + else { + logger.log(Level.SEVERE, "Unknown key type ", key.getClass().getName()); + return null; + } + } + + /** + * Refresh the children + */ + public void refreshChildren() { + refresh(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index ab64cead5e..235f00cb1b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -48,6 +48,8 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; @@ -68,7 +70,7 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class BlackboardArtifactNode extends AbstractContentNode { - private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName()); + private static final Logger logger = Logger.getLogger(BlackboardArtifactNode.class.getName()); private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.CONTENT_TAG_ADDED, @@ -217,6 +219,7 @@ public class BlackboardArtifactNode extends AbstractContentNode actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(context))); + AbstractFile file = getLookup().lookup(AbstractFile.class); //if this artifact has a time stamp add the action to view it in the timeline try { @@ -224,7 +227,7 @@ public class BlackboardArtifactNode extends AbstractContentNode("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); @@ -490,7 +492,7 @@ public class BlackboardArtifactNode extends AbstractContentNode sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; @@ -132,8 +131,8 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); } actions.add(new ViewTaggedArtifactAction(BlackboardArtifactTagNode_viewSourceArtifact_text(), artifact)); - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), true)); - actions.add(DeleteBlackboardArtifactTagAction.getInstance()); + actions.addAll(DataModelActionsFactory.getActions(tag, true)); + return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 733c49e8b7..374c96069b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -56,6 +56,7 @@ DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash DataSourcesNode.name=Data Sources +DataSourcesNode.group_by_datasource.name=Data Source Files DataSourcesNode.createSheet.name.name=Name DataSourcesNode.createSheet.name.displayName=Name DataSourcesNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index 3d20fded35..979c4d50cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -57,6 +57,7 @@ DataModelActionsFactory.viewNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30 DataModelActionsFactory.openExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u306b\u8868\u793a DataModelActionsFactory.srfFileSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 DataSourcesNode.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 +DataSourcesNode.group_by_datasource.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb DataSourcesNode.createSheet.name.name=\u540d\u524d DataSourcesNode.createSheet.name.displayName=\u540d\u524d DataSourcesNode.createSheet.name.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 59d7d21015..547303878b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -29,7 +29,6 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -128,8 +127,9 @@ class ContentTagNode extends DisplayableItemNode { if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), false)); - actions.add(DeleteContentTagAction.getInstance()); + + actions.addAll(DataModelActionsFactory.getActions(tag, false)); + return actions.toArray(new Action[actions.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index b4b88463c4..6b4b1c8faf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -28,8 +28,12 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; @@ -39,7 +43,9 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.File; @@ -351,6 +357,80 @@ public class DataModelActionsFactory { return actionsList; } + public static List getActions(ContentTag contentTag, boolean isArtifactSource) { + + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), contentTag.getContent())); + final ContentTagNode tagNode = new ContentTagNode(contentTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteContentTagAction.getInstance()); + actionsList.add(ReplaceContentTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + + public static List getActions(BlackboardArtifactTag artifactTag, boolean isArtifactSource) { + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), artifactTag.getContent())); + final BlackboardArtifactTagNode tagNode = new BlackboardArtifactTagNode(artifactTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteBlackboardArtifactTagAction.getInstance()); + actionsList.add(ReplaceBlackboardArtifactTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + public static List getActions(Content content, boolean isArtifactSource) { if (content instanceof File) { return getActions((File) content, isArtifactSource); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java new file mode 100644 index 0000000000..91ae285c81 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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 org.sleuthkit.datamodel.DataSource; + +/** + * A top level UI grouping of Files, Views, Results, Tags + * for 'Group by Data Source' view of the tree. + * + */ +public class DataSourceGrouping implements AutopsyVisitableItem { + + private final DataSource dataSource; + + public DataSourceGrouping(DataSource dataSource) { + this.dataSource = dataSource; + } + + DataSource getDataSource() { + return this.dataSource; + } + + @Override + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java new file mode 100644 index 0000000000..4c8ee90e7c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; + + +/** + * Data source grouping node - an optional grouping node in the data tree view + * + */ +class DataSourceGroupingNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName()); + + /** + * Creates a data source grouping node for the given data source. + * + * @param dataSource specifies the data source + */ + DataSourceGroupingNode(DataSource dataSource) { + + super (Optional.ofNullable(createDSGroupingNodeChildren(dataSource)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); + + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) { + + long dsObjId = dataSource.getId(); + try { + return new RootContentChildren(Arrays.asList( + new DataSources(dsObjId), + new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Tags(dsObjId) ) + + ); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index 5f8cbfff54..52ca52e89f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -23,9 +23,20 @@ package org.sleuthkit.autopsy.datamodel; */ public class DataSources implements AutopsyVisitableItem { + private final long datasourceObjId; + public DataSources() { + this(0); } + public DataSources(long datasourceObjId) { + this.datasourceObjId = datasourceObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 6e8374252d..d4e0ebf261 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -33,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; /** * Nodes for the images @@ -40,22 +42,27 @@ import org.sleuthkit.datamodel.TskCoreException; public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); + private final String displayName; // NOTE: The images passed in via argument will be ignored. @Deprecated public DataSourcesNode(List images) { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } public DataSourcesNode() { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } + public DataSourcesNode(long dsObjId) { + super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); + displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; + init(); + } + private void init() { setName(NAME); - setDisplayName(NAME); + setDisplayName(displayName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS } @@ -70,14 +77,20 @@ public class DataSourcesNode extends DisplayableItemNode { public static class DataSourcesNodeChildren extends AbstractContentChildren { private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName()); - + private final long datasourceObjId; + List currentKeys; public DataSourcesNodeChildren() { - super(); - this.currentKeys = new ArrayList<>(); + this(0); } + public DataSourcesNodeChildren(long dsObjId) { + super(); + this.currentKeys = new ArrayList<>(); + this.datasourceObjId = dsObjId; + } + private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -103,9 +116,15 @@ public class DataSourcesNode extends DisplayableItemNode { private void reloadKeys() { try { - currentKeys = Case.getCurrentCaseThrows().getDataSources(); + if (datasourceObjId == 0) { + currentKeys = Case.getCurrentCaseThrows().getDataSources(); + } + else { + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); + currentKeys = new ArrayList<>(Arrays.asList(content)); + } setKeys(currentKeys); - } catch (TskCoreException | NoCurrentCaseException ex) { + } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS setKeys(Collections.emptySet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 812eaa065b..370cc5cec7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskData; public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", "DeletedContent.allDelFilter.text=All"}) @@ -101,9 +102,18 @@ public class DeletedContent implements AutopsyVisitableItem { } public DeletedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public DeletedContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -118,8 +128,8 @@ public class DeletedContent implements AutopsyVisitableItem { @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files") private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name(); - DeletedContentsNode(SleuthkitCase skCase) { - super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME)); + DeletedContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new DeletedContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS @@ -164,11 +174,13 @@ public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; private Observable notifier; + private final long datasourceObjId; // true if we have already told user that not all files will be shown private static volatile boolean maxFilesDialogShown = false; - public DeletedContentsChildren(SleuthkitCase skCase) { + public DeletedContentsChildren(SleuthkitCase skCase, long dsObjId) { this.skCase = skCase; + this.datasourceObjId = dsObjId; this.notifier = new DeletedContentsChildrenObservable(); } @@ -257,24 +269,27 @@ public class DeletedContent implements AutopsyVisitableItem { @Override protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) { - return new DeletedContentNode(skCase, key, notifier); + return new DeletedContentNode(skCase, key, notifier, datasourceObjId); } public class DeletedContentNode extends DisplayableItemNode { private final DeletedContent.DeletedContentFilter filter; + private final long datasourceObjId; // Use version that has observer for updates @Deprecated - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter) { - super(Children.create(new DeletedContentChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId ), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); } - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o) { - super(Children.create(new DeletedContentChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); o.addObserver(new DeletedContentNodeObserver()); } @@ -299,7 +314,7 @@ public class DeletedContent implements AutopsyVisitableItem { private void updateDisplayName() { //get count of children without preloading all children nodes - final long count = DeletedContentChildren.calculateItems(skCase, filter); + final long count = DeletedContentChildren.calculateItems(skCase, filter, datasourceObjId); //final long count = getChildren().getNodesCount(true); super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); } @@ -351,11 +366,13 @@ public class DeletedContent implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); private static final int MAX_OBJECTS = 10001; private final Observable notifier; + private final long datasourceObjId; - DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o) { + DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = datasourceObjId; } private final Observer observer = new DeletedContentChildrenObserver(); @@ -366,7 +383,7 @@ public class DeletedContent implements AutopsyVisitableItem { @Override public void update(Observable o, Object arg) { refresh(true); - } + } } @Override @@ -405,7 +422,7 @@ public class DeletedContent implements AutopsyVisitableItem { return true; } - static private String makeQuery(DeletedContent.DeletedContentFilter filter) { + static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { String query = ""; switch (filter) { case FS_DELETED_FILTER: @@ -443,6 +460,10 @@ public class DeletedContent implements AutopsyVisitableItem { + " OR known IS NULL)"; //NON-NLS } + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + query += " LIMIT " + MAX_OBJECTS; //NON-NLS return query; } @@ -450,7 +471,7 @@ public class DeletedContent implements AutopsyVisitableItem { private List runFsQuery() { List ret = new ArrayList<>(); - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); try { ret = skCase.findAllFilesWhere(query); } catch (TskCoreException e) { @@ -469,9 +490,9 @@ public class DeletedContent implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 6d90aa9b0e..0392614fe3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -61,6 +61,8 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); + T visit(DataSourceGroupingNode dataSourceGroupingNode); + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); T visit(DeletedContentNode dcn); @@ -336,6 +338,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(vn); } + @Override + public T visit(DataSourceGroupingNode dataSourceGroupingNode) { + return defaultVisit(dataSourceGroupingNode); + } + @Override public T visit(ResultsNode rn) { return defaultVisit(rn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 409c3165e8..d9df849d86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -40,6 +40,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -86,10 +87,29 @@ public class EmailExtracted implements AutopsyVisitableItem { } private SleuthkitCase skCase; private final EmailResults emailResults; + private final long datasourceObjId; + + /** + * Constructor + * + * @param skCase Case DB + */ public EmailExtracted(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public EmailExtracted(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; emailResults = new EmailResults(); } @@ -141,6 +161,9 @@ public class EmailExtracted implements AutopsyVisitableItem { + "attribute_type_id=" + pathAttrId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 304073c9b6..366aa12979 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -35,9 +35,11 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; @@ -57,12 +59,31 @@ import org.sleuthkit.datamodel.TskException; public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed + private Blackboard blackboard; public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); + private final long datasourceObjId; + /** + * Constructs extracted content object + * + * @param skCase Case DB + */ public ExtractedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + /** + * Constructs extracted content object + * + * @param skCase Case DB + * @param objId Object id of the parent datasource + */ + public ExtractedContent(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.datasourceObjId = objId; + this.blackboard = skCase.getBlackboard(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -270,7 +291,10 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { - List types = skCase.getArtifactTypesInUse(); + List types = (UserPreferences.groupItemsInTreeByDatasource()) ? + blackboard.getArtifactTypesInUse(datasourceObjId) : + skCase.getArtifactTypesInUse() ; + types.removeAll(doNotShow); Collections.sort(types, new Comparator() { @@ -332,7 +356,9 @@ public class ExtractedContent implements AutopsyVisitableItem { // a performance increase might be had by adding a // "getBlackboardArtifactCount()" method to skCase try { - this.childCount = skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); + this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifactsCount(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) .log(Level.WARNING, "Error getting child count", ex); //NON-NLS @@ -454,7 +480,10 @@ public class ExtractedContent implements AutopsyVisitableItem { protected boolean createKeys(List list) { if (skCase != null) { try { - List arts = skCase.getBlackboardArtifacts(type.getTypeID()); + List arts = + UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifacts(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 4bf994fb0c..51ece423da 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.VirtualDirectory; public class FileSize implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public enum FileSizeFilter implements AutopsyVisitableItem { @@ -97,9 +98,14 @@ public class FileSize implements AutopsyVisitableItem { } public FileSize(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public FileSize(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -109,6 +115,9 @@ public class FileSize implements AutopsyVisitableItem { return this.skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /* * Root node. Children are nodes for specific sizes. */ @@ -116,8 +125,8 @@ public class FileSize implements AutopsyVisitableItem { private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name"); - FileSizeRootNode(SleuthkitCase skCase) { - super(Children.create(new FileSizeRootChildren(skCase), true), Lookups.singleton(NAME)); + FileSizeRootNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new FileSizeRootChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS @@ -161,10 +170,12 @@ public class FileSize implements AutopsyVisitableItem { public static class FileSizeRootChildren extends ChildFactory { private SleuthkitCase skCase; + private final long datasourceObjId; private Observable notifier; - public FileSizeRootChildren(SleuthkitCase skCase) { + public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { this.skCase = skCase; + this.datasourceObjId = datasourceObjId; notifier = new FileSizeRootChildrenObservable(); } @@ -248,7 +259,7 @@ public class FileSize implements AutopsyVisitableItem { @Override protected Node createNodeForKey(FileSizeFilter key) { - return new FileSizeNode(skCase, key, notifier); + return new FileSizeNode(skCase, key, notifier, datasourceObjId); } /* @@ -257,12 +268,14 @@ public class FileSize implements AutopsyVisitableItem { public class FileSizeNode extends DisplayableItemNode { private FileSizeFilter filter; + private final long datasourceObjId; // use version with observer instead so that it updates @Deprecated - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter) { - super(Children.create(new FileSizeChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, null, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); } @@ -272,10 +285,12 @@ public class FileSize implements AutopsyVisitableItem { * @param filter * @param o Observable that provides updates when events are * fired + * @param datasourceObjId filter by data source, if configured in user preferences */ - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o) { - super(Children.create(new FileSizeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, o, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); o.addObserver(new FileSizeNodeObserver()); } @@ -309,7 +324,7 @@ public class FileSize implements AutopsyVisitableItem { } private void updateDisplayName() { - final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter); + final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter, datasourceObjId); super.setDisplayName(filter.getDisplayName() + " (" + numVisibleChildren + ")"); } @@ -349,6 +364,7 @@ public class FileSize implements AutopsyVisitableItem { private final SleuthkitCase skCase; private final FileSizeFilter filter; private final Observable notifier; + private final long datasourceObjId; private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName()); /** @@ -358,10 +374,12 @@ public class FileSize implements AutopsyVisitableItem { * @param o Observable that provides updates when new files are * added to case */ - FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o) { + FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = dsObjId; + } @Override @@ -395,7 +413,7 @@ public class FileSize implements AutopsyVisitableItem { return true; } - private static String makeQuery(FileSizeFilter filter) { + private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { String query; switch (filter) { case SIZE_50_200: @@ -427,6 +445,11 @@ public class FileSize implements AutopsyVisitableItem { query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS } + // filter by datasource if indicated in user preferences + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + return query; } @@ -434,7 +457,7 @@ public class FileSize implements AutopsyVisitableItem { List ret = new ArrayList<>(); try { - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); ret = skCase.findAllFilesWhere(query); } catch (Exception e) { @@ -449,9 +472,9 @@ public class FileSize implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index e4dca77d01..6a304d7b86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -72,11 +72,18 @@ public final class FileTypes implements AutopsyVisitableItem { private final SleuthkitCase skCase; + private final long datasourceObjId; + FileTypes(SleuthkitCase skCase) { - this.skCase = skCase; - updateShowCounts(); + this(skCase, 0); } + FileTypes(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + updateShowCounts(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -86,6 +93,9 @@ public final class FileTypes implements AutopsyVisitableItem { return skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /** * Check the db to determine if the nodes should show child counts. */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 2d04fe9733..1d24f081ab 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -69,6 +69,10 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. @@ -359,6 +363,9 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { + (UserPreferences.hideKnownFilesInViewsTree() ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ") + + (UserPreferences.groupItemsInTreeByDatasource() + ? " AND data_source_obj_id = " + filteringDataSourceObjId() + : " ") + " AND (extension IN (" + filter.getFilter().stream() .map(String::toLowerCase) .map(s -> "'"+StringUtils.substringAfter(s, ".")+"'") diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e41477d96e..90cb90ae77 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; import org.sleuthkit.autopsy.coreutils.Logger; @@ -91,15 +92,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * @return The base expression to be used in the where clause of queries for * files by mime type. */ - static private String createBaseWhereExpr() { + private String createBaseWhereExpr() { return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + " AND (type IN (" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() - + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + "))" + + ( UserPreferences.groupItemsInTreeByDatasource() ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ") + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : ""); } @@ -188,6 +190,10 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Method to check if the node in question is a ByMimeTypeNode which is * empty. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index a720409489..24bae3d368 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -63,9 +64,29 @@ public class HashsetHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(HashsetHits.class.getName()); private SleuthkitCase skCase; private final HashsetResults hashsetResults; - + private final long datasourceObjId; + + + /** + * Constructor + * + * @param skCase Case DB + * + */ public HashsetHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public HashsetHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; hashsetResults = new HashsetResults(); } @@ -120,7 +141,10 @@ public class HashsetHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS - + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); synchronized (hashSetHitsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 7c3249f990..67622d180c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,9 +60,28 @@ public class InterestingHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(InterestingHits.class.getName()); private SleuthkitCase skCase; private final InterestingResults interestingResults = new InterestingResults(); + private final long datasourceObjId; + /** + * Constructor + * + * @param skCase Case DB + * + */ public InterestingHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public InterestingHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; interestingResults.update(); } @@ -112,6 +132,9 @@ public class InterestingHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { synchronized (interestingItemsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c35d9bdda0..51367297b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -44,6 +44,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -73,6 +74,7 @@ public class KeywordHits implements AutopsyVisitableItem { private SleuthkitCase skCase; private final KeywordResults keywordResults; + private final long datasourceObjId; /** * String used in the instance MAP so that exact matches and substring can @@ -81,6 +83,7 @@ public class KeywordHits implements AutopsyVisitableItem { */ private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; + /** * query attributes table for the ones that we need for the tree */ @@ -101,8 +104,25 @@ public class KeywordHits implements AutopsyVisitableItem { return (instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME)); } - public KeywordHits(SleuthkitCase skCase) { + /** + * Constructor + * + * @param skCase Case DB + */ + KeywordHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public KeywordHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; keywordResults = new KeywordResults(); } @@ -300,7 +320,12 @@ public class KeywordHits implements AutopsyVisitableItem { return; } - try (CaseDbQuery dbQuery = skCase.executeQuery(KEYWORD_HIT_ATTRIBUTES_QUERY)) { + String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; + if (UserPreferences.groupItemsInTreeByDatasource()) { + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { long artifactId = resultSet.getLong("artifact_id"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index c723506363..133f90c291 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -96,8 +96,8 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); - actionsList.addAll(Arrays.asList(super.getActions(true))); + actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "LocalFileNode.viewFileInDir.text"), this.content)); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction( diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 7f1cd1cea7..b2d9f4799b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -26,11 +26,17 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Results implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Results(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Results(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -39,4 +45,8 @@ public class Results implements AutopsyVisitableItem { public SleuthkitCase getSleuthkitCase() { return skCase; } + + long filteringDataSourceObjId() { + return datasourceObjId; + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 87a3d5f903..91d18bbcc6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,17 +33,22 @@ public class ResultsNode extends DisplayableItemNode { @NbBundle.Messages("ResultsNode.name.text=Results") public static final String NAME = Bundle.ResultsNode_name_text(); - - public ResultsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new ExtractedContent(sleuthkitCase), - new KeywordHits(sleuthkitCase), - new HashsetHits(sleuthkitCase), - new EmailExtracted(sleuthkitCase), - new InterestingHits(sleuthkitCase), - new Accounts(sleuthkitCase) - )), Lookups.singleton(NAME)); + this(sleuthkitCase, 0); + } + + public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + super( + + new RootContentChildren(Arrays.asList( + new ExtractedContent(sleuthkitCase, dsObjId ), + new KeywordHits(sleuthkitCase, dsObjId), + new HashsetHits(sleuthkitCase, dsObjId), + new EmailExtracted(sleuthkitCase, dsObjId), + new InterestingHits(sleuthkitCase, dsObjId ), + new Accounts(sleuthkitCase, dsObjId) ) + ), + Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 9a01fa608f..c41a750f7e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -36,6 +36,7 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -57,6 +58,20 @@ public class Tags implements AutopsyVisitableItem { private final String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final long datasourceObjId; + + Tags() { + this(0); + } + + Tags(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -83,11 +98,13 @@ public class Tags implements AutopsyVisitableItem { */ public class RootNode extends DisplayableItemNode { - public RootNode() { - super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME)); + + public RootNode(long objId) { + super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); + } @Override @@ -121,6 +138,8 @@ public class Tags implements AutopsyVisitableItem { private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { + private final long datasourceObjId; + private final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.CONTENT_TAG_ADDED, @@ -176,6 +195,15 @@ public class Tags implements AutopsyVisitableItem { } }; + /** + * Constructor + * @param objId data source object id + */ + TagNameNodeFactory(long objId) { + this.datasourceObjId = objId; + + } + @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); @@ -196,7 +224,11 @@ public class Tags implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - List tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + + List tagNamesInUse = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse() + ; Collections.sort(tagNamesInUse); keys.addAll(tagNamesInUse); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -244,8 +276,15 @@ public class Tags implements AutopsyVisitableItem { long tagsCount = 0; try { TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + if (UserPreferences.groupItemsInTreeByDatasource()) { + tagsCount = tm.getContentTagsCountByTagName(tagName, datasourceObjId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId); + } + else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -348,7 +387,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -403,7 +444,11 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { // Use the content tags bearing the specified tag name as the keys. try { - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName)); + List contentTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + + keys.addAll(contentTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -447,7 +492,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -502,7 +549,10 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { try { // Use the blackboard artifact tags bearing the specified tag name as the keys. - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + List artifactTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + keys.addAll(artifactTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index b31cfda543..d2a8671678 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -26,11 +26,21 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Views implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Views(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Views(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 08f2369a61..a1114761bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,19 +34,28 @@ public class ViewsNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); public ViewsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new FileTypes(sleuthkitCase), - // June '15: Recent Files was removed because it was not useful w/out filtering - // add it back in if we can filter the results to a more managable size. - // new RecentFiles(sleuthkitCase), - new DeletedContent(sleuthkitCase), - new FileSize(sleuthkitCase))), - Lookups.singleton(NAME)); + this(sleuthkitCase, 0); + } + + public ViewsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + + super( + new RootContentChildren(Arrays.asList( + new FileTypes(sleuthkitCase, dsObjId), + // June '15: Recent Files was removed because it was not useful w/out filtering + // add it back in if we can filter the results to a more managable size. + // new RecentFiles(sleuthkitCase), + new DeletedContent(sleuthkitCase, dsObjId), + new FileSize(sleuthkitCase, dsObjId)) + ), + Lookups.singleton(NAME) + ); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 8301928528..557e6582ca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -58,6 +58,7 @@ import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; @@ -94,6 +95,8 @@ final public class Accounts implements AutopsyVisitableItem { final public static String NAME = Bundle.AccountsRootNode_name(); private SleuthkitCase skCase; + private final long datasourceObjId; + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); /* Should rejected accounts be shown in the accounts section of the tree. */ @@ -108,12 +111,24 @@ final public class Accounts implements AutopsyVisitableItem { * @param skCase The SleuthkitCase object to use for db queries. */ public Accounts(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + * @param objId Object id of the data source + */ + public Accounts(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; this.rejectActionInstance = new RejectAccounts(); this.approveActionInstance = new ApproveAccounts(); } - + + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -130,6 +145,18 @@ final public class Accounts implements AutopsyVisitableItem { return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS } + /** + * Returns the clause to filter artifacts by data source. + * + * @return A clause that will or will not filter artifacts by datasource + * based on the UserPreferences groupItemsInTreeByDatasource setting + */ + private String getFilterByDataSourceClause() { + return (UserPreferences.groupItemsInTreeByDatasource()) ? + " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId + " " + : " "; + } + /** * Gets a new Action that when invoked toggles showing rejected artifacts on * or off. @@ -291,10 +318,14 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( - "SELECT DISTINCT blackboard_attributes.value_text as account_type " - + " FROM blackboard_attributes " - + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + String accountTypesInUseQuery = + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() + + getFilterByDataSourceClause(); + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery ); ResultSet resultSet = executeQuery.getResultSet()) { while (resultSet.next()) { String accountType = resultSet.getString("account_type"); @@ -429,6 +460,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet rs = results.getResultSet();) { @@ -739,6 +771,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + " ORDER BY hits DESC "; //NON-NLS @@ -807,6 +840,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -943,6 +977,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY BIN " //NON-NLS + " ORDER BY BIN "; //NON-NLS @@ -1009,6 +1044,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { @@ -1304,6 +1340,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " ORDER BY blackboard_attributes.value_text"; //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -1375,6 +1412,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java index d50f31e383..00cd992eb1 100644 --- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java +++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java @@ -42,6 +42,10 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +/** + * Display statistics on system performance. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class PerformancePanel extends javax.swing.JDialog { /** diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java index 073f7b4eee..fad5d90b61 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog for adding or editing an external viewer rule */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddExternalViewerRuleDialog extends JDialog { private ExternalViewerRule rule; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 647c6f70d3..0e7f3cf315 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -119,3 +119,4 @@ AddExternalViewerRulePanel.browseButton.text=Browse AddExternalViewerRulePanel.exePathTextField.text= AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension AddExternalViewerRulePanel.extRadioButton.text=Extension +DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 90c6ac00a6..48dfad0bb6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -16,14 +16,17 @@ - - - + + + - - + + + + + @@ -31,14 +34,23 @@ - - - - - + + + + + + + + + + + + + + - - + + @@ -130,5 +142,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 24edc21909..33977d5904 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -30,6 +30,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; @@ -64,8 +65,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.CreditCards; -import org.sleuthkit.autopsy.datamodel.DataSources; -import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmailExtracted; import org.sleuthkit.autopsy.datamodel.EmptyNode; @@ -73,12 +72,8 @@ import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; -import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.Results; import org.sleuthkit.autopsy.datamodel.ResultsNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildren; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Views; +import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildrenFactory; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; @@ -97,6 +92,7 @@ import org.sleuthkit.datamodel.TskCoreException; @Messages({ "DirectoryTreeTopComponent.resultsView.title=Listing" }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DirectoryTreeTopComponent extends TopComponent implements DataExplorer, ExplorerManager.Provider { private final transient ExplorerManager em = new ExplorerManager(); @@ -106,7 +102,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); - private RootContentChildren contentChildren; + private AutopsyTreeChildrenFactory autopsyTreeChildrenFactory; + private Children autopsyTreeChildren; /** * the constructor @@ -129,6 +126,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.forwardList = new LinkedList<>(); backButton.setEnabled(false); forwardButton.setEnabled(false); + + groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); } /** @@ -141,6 +140,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat switch (evt.getKey()) { case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: + case UserPreferences.GROUP_ITEMS_IN_TREE_BY_DATASOURCE: refreshContentTreeSafe(); break; case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE: @@ -181,6 +181,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); showRejectedCheckBox = new javax.swing.JCheckBox(); + groupByDatasourceCheckBox = new javax.swing.JCheckBox(); treeView.setBorder(null); @@ -218,30 +219,45 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(groupByDatasourceCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.groupByDatasourceCheckBox.text")); // NOI18N + groupByDatasourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + groupByDatasourceCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) + .addComponent(treeView) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) + .addContainerGap() .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) - .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 65, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showRejectedCheckBox) + .addComponent(groupByDatasourceCheckBox)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(showRejectedCheckBox)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(groupByDatasourceCheckBox)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 854, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents @@ -295,9 +311,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed + private void groupByDatasourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_groupByDatasourceCheckBoxActionPerformed + UserPreferences.setGroupItemsInTreeByDatasource(this.groupByDatasourceCheckBox.isSelected()); + }//GEN-LAST:event_groupByDatasourceCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; + private javax.swing.JCheckBox groupByDatasourceCheckBox; private javax.swing.JCheckBox showRejectedCheckBox; private javax.swing.JScrollPane treeView; // End of variables declaration//GEN-END:variables @@ -375,13 +396,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } else { // if there's at least one image, load the image and open the top component final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - contentChildren = new RootContentChildren(Arrays.asList( - new DataSources(), - new Views(tskCase), - new Results(tskCase), - new Tags(), - new Reports())); - Node root = new AbstractNode(contentChildren) { + + autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory(tskCase); + autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, true); + Node root = new AbstractNode(autopsyTreeChildren) { //JIRA-2807: What is the point of these overrides? /** * to override the right click action in the white blank space @@ -421,17 +439,23 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat TreeView tree = getTree(); Node results = rootChildren.findChild(ResultsNode.NAME); - tree.expandNode(results); - Children resultsChildren = results.getChildren(); - Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); + if (!Objects.isNull(results)) { + tree.expandNode(results); + Children resultsChildren = results.getChildren(); + Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); - Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); + Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + if (!Objects.isNull(accounts)) { + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + } + } Node views = rootChildren.findChild(ViewsNode.NAME); - Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); - tree.collapseNode(views); + if (!Objects.isNull(views)) { + Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); + tree.collapseNode(views); + } /* * JIRA-2806: What is this supposed to do? Right now it selects * the data sources node, but the comment seems to indicate @@ -463,7 +487,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // of changing the selected node fires a handler that tries to make // dataResult active) try { - em.setSelectedNodes(get()); + Node[] selections = get(); + if (selections != null && selections.length > 0){ + em.setSelectedNodes(selections); + } } catch (PropertyVetoException ex) { LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS } catch (InterruptedException | ExecutionException ex) { @@ -485,7 +512,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public void componentClosed() { //@@@ push the selection node to null? - contentChildren = null; + autopsyTreeChildren = null; } void writeProperties(java.util.Properties p) { @@ -728,7 +755,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ String[] currentLast = backList.peekLast(); String lastNodeName = null; - if (currentLast != null) { + if (currentLast != null && currentLast.length > 0) { lastNodeName = currentLast[currentLast.length - 1]; } @@ -772,27 +799,56 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { - SwingUtilities.invokeLater(this::refreshDataSourceTree); + SwingUtilities.invokeLater(this::rebuildTree); } /** - * Refreshes changed content nodes + * Rebuilds the directory tree */ - private void refreshDataSourceTree() { - Node selectedNode = getSelectedNode(); - final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); - Children rootChildren = em.getRootContext().getChildren(); - Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); - if (dataSourcesFilterNode == null) { - LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS - return; - } - Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal(); - DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren(); - contentRootChildren.refreshContentKeys(); - setSelectedNode(selectedPath, DataSourcesNode.NAME); - } + private void rebuildTree() { + // refresh all children of the root. + autopsyTreeChildrenFactory.refreshChildren(); + + // Select the first node and reset the selection history + // This should happen on the EDT once the tree has been rebuilt. + // hence the SwingWorker that does this in the done() method + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + selectFirstChildNode(); + resetHistory(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error selecting tree node.", ex); //NON-NLS + } //NON-NLS + } + }.execute(); + } + + /** + * Selects the first node in the tree. + * + */ + private void selectFirstChildNode () { + Children rootChildren = em.getRootContext().getChildren(); + + if (rootChildren.getNodesCount() > 0) { + Node firstNode = rootChildren.getNodeAt(0); + if (firstNode != null) { + final String[] selectedPath = NodeOp.createPath(firstNode, em.getRootContext()); + setSelectedNode(selectedPath, null); + } + } + } /** * Set the selected node using a path to a previously selected node. * diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java index ce5b915b41..29fd7d3bf4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java @@ -27,9 +27,8 @@ import org.sleuthkit.datamodel.TskCoreException; /** * This is the form / panel to show the File System Details. - * - * @author jantonius */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FileSystemDetailsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(FileSystemDetailsPanel.class.getName()); private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java index b551c11c69..e26c0605ab 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,21 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * ImageDetailsPanel.java - * - * Created on May 2, 2011, 3:53:49 PM - */ package org.sleuthkit.autopsy.directorytree; -import java.awt.*; import java.awt.event.ActionListener; /** - * - * @author jantonius + * Image details panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ImageDetailsPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 792252605a..f4da3a3edf 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -24,6 +24,7 @@ import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; @@ -33,6 +34,8 @@ import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; @@ -44,9 +47,12 @@ import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.VolumeSystem; /** @@ -122,7 +128,9 @@ public class ViewContextAction extends AbstractAction { @Override @Messages({ "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.", - "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.",}) + "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.", + "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree." + }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { /* @@ -130,7 +138,40 @@ public class ViewContextAction extends AbstractAction { */ DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + + Node parentTreeViewNode; + if (UserPreferences.groupItemsInTreeByDatasource()) { // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = content.getDataSource().getId(); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node datasourceGroupingNode = rootChildren.findChild(dsname); + if (! Objects.isNull(datasourceGroupingNode) ) { + Children dsChildren = datasourceGroupingNode.getChildren(); + parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + } + else { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; + } + } catch (NoCurrentCaseException| TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + return; + } + } else { // Classic view + // Start the search at the DataSourcesNode + parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + } + /* * Get the parent content for the content to be selected in the * results view. If the parent content is null, then the specified diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java index cea09df745..825de50796 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,12 @@ */ package org.sleuthkit.autopsy.directorytree; -import java.awt.*; import java.awt.event.ActionListener; /** * This is the form / panel to show the Volume Details. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class VolumeDetailsPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java b/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java index 17c73388aa..f8f2e14902 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java @@ -31,7 +31,6 @@ package org.sleuthkit.autopsy.examples; import java.awt.Component; import org.openide.nodes.Node; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -45,6 +44,7 @@ import org.sleuthkit.datamodel.TskCoreException; * compiled each time to ensure that it is compliant with the API. */ // @ServiceProvider(service = DataContentViewer.class) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SampleContentViewer extends javax.swing.JPanel implements DataContentViewer { /** diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java index 2d0f511963..22a7cb3e2f 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * UI component used to make per ingest job settings for sample ingest modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class SampleIngestModuleIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java index 9171104238..845c39f06c 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java @@ -16,24 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * FileSearchDialog.java - * - * Created on Mar 5, 2012, 1:57:33 PM - */ package org.sleuthkit.autopsy.filesearch; -import org.openide.util.NbBundle; - import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; +import org.openide.util.NbBundle; import org.openide.windows.WindowManager; /** * File search dialog */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchDialog extends javax.swing.JDialog { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 92c9683d71..a81afa8dca 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * FileSearchPanel.java - * - * Created on Mar 5, 2012, 1:51:50 PM - */ package org.sleuthkit.autopsy.filesearch; import java.awt.Component; @@ -57,6 +51,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * FileSearchPanel that present search options */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchPanel extends javax.swing.JPanel { private final List filters = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java index 3f0d5176ac..6a5491ea08 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * + * Panel to allow examiner to search for a hash value. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class HashSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java index 57d968ed23..b1a68652b2 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * KnownStatusSearchPanel.java - * - * Created on Oct 19, 2011, 11:45:44 AM - */ package org.sleuthkit.autopsy.filesearch; import javax.swing.JCheckBox; /** - * - * @author pmartel + * Search for known, unknown, and bad files. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class KnownStatusSearchPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java index 74467703c1..4dda56ce21 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,10 @@ import javax.swing.event.ListSelectionEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +/** + * Enter MIME types for search. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class MimeTypePanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(MimeTypePanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java index 2e0c6e7192..dce02e5d90 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * NameSearchPanel.java - * - * Created on Oct 19, 2011, 11:58:53 AM - */ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; @@ -33,9 +27,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * - * @author pmartel + * Provide a name for search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class NameSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java index 089b83530a..6a69ba36f9 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,9 +29,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * - * @author pmartel + * Provide file size for search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SizeSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index b15e547896..57a5ae452d 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -65,7 +65,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; - private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1); @@ -962,7 +962,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error getting database lock"); } - String[] metricNames = {"Disk Reads: Hash calculation", "Database: getImages query", "Solr: Index chunk", "Solr: Connectivity check"}; // NON-NLS + String[] metricNames = {"Disk Reads: Hash calculation", "Database: getImages query", "Solr: Index chunk", "Solr: Connectivity check", + "Correlation Engine: Notable artifact query", "Correlation Engine: Bulk insert"}; // NON-NLS Random rand = new Random(); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java index 2d13e3fadb..318dc6e05a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ package org.sleuthkit.autopsy.ingest; * A UI panel that allows a user to make data source ingest cancellation * requests. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class DataSourceIngestCancellationPanel extends javax.swing.JPanel { private boolean cancelAllIngestModules; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java index 1fa70462cc..d2dd0c2ad2 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2013 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * the main layered pane container for messages table (IngestMessagePanel) and * details view (IngestMessageDetailsPanel) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessageMainPanel extends javax.swing.JPanel { private IngestMessagePanel messagePanel; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index f9063b289c..a8f4d39dea 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import javax.swing.DefaultComboBoxModel; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; @@ -51,14 +52,13 @@ import javax.swing.table.TableCellRenderer; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.ingest.IngestMessage.*; import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType; import org.sleuthkit.datamodel.BlackboardArtifact; /** * Notification window showing messages from modules to user - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessagePanel extends JPanel implements TableModelListener { private final MessageTableModel tableModel; @@ -245,7 +245,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { * It is not possible to internationalize the list of options in a ComboBox * inside of the generated form code. So, it is done here. */ - sortByComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { + sortByComboBox.setModel(new DefaultComboBoxModel<>(new String[] { NbBundle.getMessage(this.getClass(), "IngestMessagePanel.sortByComboBox.model.time"), NbBundle.getMessage(this.getClass(), "IngestMessagePanel.sortByComboBox.model.priority")})); @@ -519,7 +519,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { if (moduleName != null && m.getMessageType() == IngestMessage.MessageType.DATA) { //not a manager message, a data message, then group if (!groupings.containsKey(moduleName)) { - groupings.put(moduleName, new HashMap>()); + groupings.put(moduleName, new HashMap<>()); } final Map> groups = groupings.get(moduleName); //groups for this uniqueness @@ -564,7 +564,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { messageGroup = first; //move to bottom of table //remove from existing position - int toRemove = 0; + int toRemove; while ((toRemove = getTableEntryIndex(uniqueness)) != -1) { messageData.remove(toRemove); //remove the row, will be added to the bottom diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java index 04052b1ce6..767c19b52f 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +40,7 @@ import org.sleuthkit.datamodel.Content; /** * Top component which displays something. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestMessageTopComponent extends TopComponent { private static IngestMessageTopComponent instance; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index 432cb48810..261ef90352 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; * Tool bar for an ingest messages button that allows a user to open the ingest * messages inbox top component. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessagesToolbar extends javax.swing.JPanel { private IngestMessagesButton ingestMessagesButton = new IngestMessagesButton(); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java index 40f3d1c298..cbfd996473 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY /** * Global options panel for keyword searching. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { @NbBundle.Messages({"IngestOptionsPanel.settingsTab.text=Settings", diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java index eddebd59c4..c86f90ad4b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog that displays ingest task progress snapshots. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestProgressSnapshotDialog extends JDialog { private static final String TITLE = NbBundle.getMessage(IngestProgressSnapshotDialog.class, "IngestProgressSnapshotDialog.title.text"); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java index ed02db96cb..11b3d1f349 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; /** * Options panel that allow users to set application preferences. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestSettingsPanel extends IngestModuleGlobalSettingsPanel { IngestSettingsPanel() { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java index e16fc98aa0..12cc78ecd5 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.ingest.IngestProfiles.IngestProfile; * Visual panel for the choosing of ingest profiles by the user when running * ingest. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestProfileSelectionPanel extends JPanel { @Messages({"IngestProfileSelectionPanel.customSettings.name=Custom Settings", diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java index add27f8146..451b0e4bc7 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,6 @@ import org.sleuthkit.datamodel.Content; final class RunIngestModulesWizardIterator implements WizardDescriptor.Iterator { private final static String PROP_LASTPROFILE_NAME = "RIMW_LASTPROFILE_NAME"; //NON-NLS - private final IngestJobSettings.IngestType ingestType; private final List panels; private int currentPanelIndex; @@ -47,14 +46,13 @@ final class RunIngestModulesWizardIterator implements WizardDescriptor.Iterator< * @param ingestType The type of ingest to be configured. */ RunIngestModulesWizardIterator(String executionContext, IngestJobSettings.IngestType ingestType, List dataSources) { - this.ingestType = ingestType; panels = new ArrayList<>(); List profiles = IngestProfiles.getIngestProfiles(); - if (!profiles.isEmpty() && IngestJobSettings.IngestType.FILES_ONLY != this.ingestType) { + if (!profiles.isEmpty() && IngestJobSettings.IngestType.FILES_ONLY != ingestType) { panels.add(new IngestProfileSelectionWizardPanel(executionContext, PROP_LASTPROFILE_NAME)); } - panels.add(new IngestModulesConfigWizardPanel(executionContext, this.ingestType, dataSources)); + panels.add(new IngestModulesConfigWizardPanel(executionContext, ingestType, dataSources)); String[] steps = new String[panels.size()]; for (int i = 0; i < panels.size(); i++) { Component c = panels.get(i).getComponent(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties index dfda2e6061..89cf1636b7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties @@ -13,7 +13,7 @@ EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=Content-o EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull=Full Encryption (Archive File) EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir\: {0}\: {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive\: {0}, item\: {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping item in {1}. +EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping items in {1}. EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected\: {0} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive\: {0}, will use\: {1} diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 5e67e9da8b..66c7f7030d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.io.File; import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.AbstractFile; @@ -30,6 +31,8 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Archive; /** * A file level ingest module that extracts embedded files from supported @@ -44,12 +47,13 @@ import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; }) public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAdapter { - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // "iso"}; NON-NLS - private String moduleDirRelative; - private String moduleDirAbsolute; + //Outer concurrent hashmap with keys of JobID, inner concurrentHashmap with keys of objectID + private static final ConcurrentHashMap> mapOfDepthTrees = new ConcurrentHashMap<>(); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private MSOfficeEmbeddedContentExtractor officeExtractor; private SevenZipExtractor archiveExtractor; private FileTypeDetector fileTypeDetector; + private long jobId; /** * Constructs a file level ingest module that extracts embedded files from @@ -66,10 +70,14 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * case database for extracted (derived) file paths. The absolute path * is used to write the extracted (derived) files to local storage. */ + jobId = context.getJobId(); + String moduleDirRelative = null; + String moduleDirAbsolute = null; + try { - final Case currentCase = Case.getCurrentCaseThrows(); - moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); - moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + final Case currentCase = Case.getCurrentCaseThrows(); + moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_NoOpenCase_errMsg(), ex); } @@ -93,16 +101,18 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } - - /* - * Construct a 7Zip file extractor for processing archive files. - */ try { this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); } catch (SevenZipNativeInitializationException ex) { throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); } - + if (refCounter.incrementAndGet(jobId) == 1) { + /* + * Construct a concurrentHashmap to keep track of depth in archives + * while processing archive files. + */ + mapOfDepthTrees.put(jobId, new ConcurrentHashMap<>()); + } /* * Construct an embedded content extractor for processing Microsoft * Office documents. @@ -112,6 +122,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex); } + } @Override @@ -143,13 +154,20 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * type/format. */ if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) { - archiveExtractor.unpack(abstractFile); + archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId)); } else if (officeExtractor.isContentExtractionSupported(abstractFile)) { officeExtractor.extractEmbeddedContent(abstractFile); } return ProcessResult.OK; } + @Override + public void shutDown() { + if (refCounter.decrementAndGet(jobId) == 0) { + mapOfDepthTrees.remove(jobId); + } + } + /** * Creates a unique name for a file by concatentating the file name and the * file object id. diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java index 722f211951..b122dbd4fb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.awt.event.ActionEvent; import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -125,7 +126,7 @@ public class ExtractArchiveWithPasswordAction extends AbstractAction { } try { SevenZipExtractor extractor = new SevenZipExtractor(null, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); - done = extractor.unpack(archive, password); + done = extractor.unpack(archive, new ConcurrentHashMap<>(), password); } catch (SevenZipNativeInitializationException ex) { IngestServices.getInstance().postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Unable to extract file with password", password)); logger.log(Level.INFO, "Unable to extract file with password", ex); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 9e78e0d58e..ee9415ddca 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import net.sf.sevenzipjbinding.ArchiveFormat; import static net.sf.sevenzipjbinding.ArchiveFormat.RAR; @@ -73,7 +74,6 @@ class SevenZipExtractor { private IngestServices services = IngestServices.getInstance(); private final IngestJobContext context; private final FileTypeDetector fileTypeDetector; - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS //encryption type strings private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel"); @@ -84,8 +84,6 @@ class SevenZipExtractor { private static final int MAX_COMPRESSION_RATIO = 600; private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L; private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB - //counts archive depth - private ArchiveDepthCountTree archiveDepthCountTree; private String moduleDirRelative; private String moduleDirAbsolute; @@ -131,7 +129,6 @@ class SevenZipExtractor { this.fileTypeDetector = fileTypeDetector; this.moduleDirRelative = moduleDirRelative; this.moduleDirAbsolute = moduleDirAbsolute; - this.archiveDepthCountTree = new ArchiveDepthCountTree(); } /** @@ -150,8 +147,8 @@ class SevenZipExtractor { } } return false; - } - + } + /** * Check if the item inside archive is a potential zipbomb * @@ -159,12 +156,18 @@ class SevenZipExtractor { * * More heuristics to be added here * - * @param archiveName the parent archive - * @param archiveFileItem the archive item + * @param archiveFile the AbstractFile for the parent archive which + * which we are checking + * @param archiveFileItem the current item being extracted from the parent + * archive + * @param depthMap a concurrent hashmap which keeps track of the + * depth of all nested archives, key of objectID + * @param escapedFilePath the path to the archiveFileItem which has been + * escaped * * @return true if potential zip bomb, false otherwise */ - private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) { + private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem, ConcurrentHashMap depthMap, String escapedFilePath) { try { final Long archiveItemSize = archiveFileItem.getSize(); @@ -183,20 +186,12 @@ class SevenZipExtractor { int cRatio = (int) (archiveItemSize / archiveItemPackedSize); if (cRatio >= MAX_COMPRESSION_RATIO) { - String itemName = archiveFileItem.getPath(); - logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName); - String path; - try { - path = archiveFile.getUniquePath(); - } catch (TskCoreException ex) { - path = archiveFile.getParentPath() + archiveFile.getName(); - } + Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId()); String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", + cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath); return true; } else { return false; @@ -208,6 +203,47 @@ class SevenZipExtractor { } } + /** + * Flag the root archive archive as a zipbomb by creating an interesting + * file artifact and posting a message to the inbox for the user. + * + * @param rootArchive - the Archive which the artifact is to be for + * @param archiveFile - the AbstractFile which for the file which + * triggered the potential zip bomb to be detected + * @param details - the String which contains the details about how + * the potential zip bomb was detected + * @param escapedFilePath - the escaped file path for the archiveFile + */ + private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) { + rootArchive.flagAsZipBomb(); + logger.log(Level.INFO, details); //NON-NLS + String msg = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath); + try { + BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), + "Possible Zip Bomb")); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, + EmbeddedFileExtractorModuleFactory.getModuleName(), + Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName()))); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + EmbeddedFileExtractorModuleFactory.getModuleName(), + details)); + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error( + Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName()); + } + services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS + } + services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + } + /** * Check file extension and return appropriate input options for * SevenZip.openInArchive() @@ -459,11 +495,13 @@ class SevenZipExtractor { * Unpack the file to local folder and return a list of derived files * * @param archiveFile file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives, key of objectID * - * @return list of unpacked derived files + * @return true if unpacking is complete */ - void unpack(AbstractFile archiveFile) { - unpack(archiveFile, null); + void unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap) { + unpack(archiveFile, depthMap, null); } /** @@ -471,12 +509,16 @@ class SevenZipExtractor { * the password if specified. * * @param archiveFile - file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives, key of objectID * @param password - the password to use, null for no password * - * @return list of unpacked derived files + * @return true if unpacking is complete */ - @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) - boolean unpack(AbstractFile archiveFile, String password) { + @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.", + "# {0} - rootArchive", + "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"}) + boolean unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap, String password) { boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and boolean hasEncrypted = false; boolean fullEncryption = true; @@ -491,8 +533,7 @@ class SevenZipExtractor { SevenZipContentReadStream stream = null; final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName()); //recursion depth check for zip bomb - final long archiveId = archiveFile.getId(); - SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr; + Archive parentAr; try { blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { @@ -515,20 +556,24 @@ class SevenZipExtractor { unpackSuccessful = false; return unpackSuccessful; } - parentAr = archiveDepthCountTree.findArchive(archiveId); + parentAr = depthMap.get(archiveFile.getId()); if (parentAr == null) { - parentAr = archiveDepthCountTree.addArchive(null, archiveId); - - } else if (parentAr.getDepth() == MAX_DEPTH) { - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); - String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", - parentAr.getDepth(), escapedArchiveFilePath); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); - unpackSuccessful = false; - return unpackSuccessful; + parentAr = new Archive(0, archiveFile.getId(), archiveFile); + depthMap.put(archiveFile.getId(), parentAr); + } else { + Archive rootArchive = depthMap.get(parentAr.getRootArchiveId()); + if (rootArchive.isFlaggedAsZipBomb()) { + //skip this archive as the root archive has already been determined to contain a zip bomb + unpackSuccessful = false; + return unpackSuccessful; + } else if (parentAr.getDepth() == MAX_DEPTH) { + String details = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", + parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath); + unpackSuccessful = false; + return unpackSuccessful; + } } try { stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile)); @@ -579,8 +624,9 @@ class SevenZipExtractor { ++itemNumber; //check if possible zip bomb - if (isZipBombArchiveItemCheck(archiveFile, item)) { - continue; //skip the item + if (isZipBombArchiveItemCheck(archiveFile, item, depthMap, escapedArchiveFilePath)) { + unpackSuccessful = false; + return unpackSuccessful; } SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive); //update progress bar @@ -608,7 +654,6 @@ class SevenZipExtractor { escapedArchiveFilePath, item.getPath()); String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details"); - //MessageNotifyUtil.Notify.error(msg, details); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, item.getPath()}); //NON-NLS logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS @@ -666,7 +711,9 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId()); + Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); + parentAr.addChild(child); + depthMap.put(unpackedFile.getId(), child); } } @@ -908,7 +955,7 @@ class SevenZipExtractor { this.rootNode = new UnpackedNode(); this.rootNode.setFile(archiveFile); this.rootNode.setFileName(archiveFile.getName()); - this.rootNode.localRelPath = localPathRoot; + this.rootNode.setLocalRelPath(localPathRoot); } /** @@ -951,6 +998,7 @@ class SevenZipExtractor { // create new node if (child == null) { child = new UnpackedNode(childName, parent); + parent.addChild(child); } // go down one more level @@ -965,7 +1013,7 @@ class SevenZipExtractor { */ List getRootFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { ret.add(child.getFile()); } return ret; @@ -979,7 +1027,7 @@ class SevenZipExtractor { */ List getAllFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { getAllFileObjectsRec(ret, child); } return ret; @@ -987,7 +1035,7 @@ class SevenZipExtractor { private void getAllFileObjectsRec(List list, UnpackedNode parent) { list.add(parent.getFile()); - for (UnpackedNode child : parent.children) { + for (UnpackedNode child : parent.getChildren()) { getAllFileObjectsRec(list, child); } } @@ -998,7 +1046,7 @@ class SevenZipExtractor { */ void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException { final FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath); } } @@ -1055,7 +1103,7 @@ class SevenZipExtractor { node.getFileName()), ex); } //recurse adding the children if this file was incomplete the children presumably need to be added - for (UnpackedNode child : node.children) { + for (UnpackedNode child : node.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath)); } } @@ -1067,7 +1115,7 @@ class SevenZipExtractor { private String fileName; private AbstractFile file; - private List children = new ArrayList<>(); + private final List children = new ArrayList<>(); private String localRelPath = ""; private long size; private long ctime, crtime, atime, mtime; @@ -1082,31 +1130,53 @@ class SevenZipExtractor { UnpackedNode(String fileName, UnpackedNode parent) { this.fileName = fileName; this.parent = parent; - this.localRelPath = parent.localRelPath + File.separator + fileName; - //new child derived file will be set by unpack() method - parent.children.add(this); + this.localRelPath = parent.getLocalRelPath() + File.separator + fileName; } - public long getCtime() { + long getCtime() { return ctime; } - public long getCrtime() { + long getCrtime() { return crtime; } - public long getAtime() { + long getAtime() { return atime; } - public long getMtime() { + long getMtime() { return mtime; } - public void setFileName(String fileName) { + void setFileName(String fileName) { this.fileName = fileName; } + /** + * Add a child to the list of child nodes associated with this node. + * + * @param child - the node which is a child node of this node + */ + void addChild(UnpackedNode child) { + children.add(child); + } + + /** + * Get this nodes list of child UnpackedNode + * + * @return children - the UnpackedNodes which are children of this + * node. + */ + List getChildren() { + return children; + } + + /** + * Gets the parent node of this node. + * + * @return - the parent UnpackedNode + */ UnpackedNode getParent() { return parent; } @@ -1137,7 +1207,7 @@ class SevenZipExtractor { UnpackedNode getChild(String childFileName) { UnpackedNode ret = null; for (UnpackedNode child : children) { - if (child.fileName.equals(childFileName)) { + if (child.getFileName().equals(childFileName)) { ret = child; break; } @@ -1145,94 +1215,132 @@ class SevenZipExtractor { return ret; } - public String getFileName() { + String getFileName() { return fileName; } - public AbstractFile getFile() { + AbstractFile getFile() { return file; } - public String getLocalRelPath() { + String getLocalRelPath() { return localRelPath; } - public long getSize() { + /** + * Set the local relative path associated with this UnpackedNode + * + * @param localRelativePath - the local relative path to be + * associated with this node. + */ + void setLocalRelPath(String localRelativePath) { + localRelPath = localRelativePath; + } + + long getSize() { return size; } - public boolean isIsFile() { + boolean isIsFile() { return isFile; } } } /** - * Tracks archive hierarchy and archive depth + * Class to keep track of an objects id and its depth in the archive + * structure. */ - private static class ArchiveDepthCountTree { + static class Archive { - //keeps all nodes refs for easy search - private final List archives = new ArrayList<>(); + //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within + private final int depth; + private final List children; + private final long rootArchiveId; + private boolean flaggedAsZipBomb = false; + private final AbstractFile archiveFile; /** - * Search for previously added parent archive by id + * Create a new Archive object. * - * @param objectId parent archive object id - * - * @return the archive node or null if not found + * @param depth the depth in the archive structure - 0 will be + * the root archive unpack was called on, and it + * will increase as unpack recurses down through + * archives contained within + * @param rootArchiveId the unique object id of the root parent archive + * of this archive + * @param archiveFile the AbstractFile which this Archive object + * represents */ - Archive findArchive(long objectId) { - for (Archive ar : archives) { - if (ar.objectId == objectId) { - return ar; - } - } - - return null; + Archive(int depth, long rootArchiveId, AbstractFile archiveFile) { + this.children = new ArrayList<>(); + this.depth = depth; + this.rootArchiveId = rootArchiveId; + this.archiveFile = archiveFile; } /** - * Add a new archive to track of depth + * Add a child to the list of child archives associated with this + * archive. * - * @param parent parent archive or null - * @param objectId object id of the new archive - * - * @return the archive added + * @param child - the archive which is a child archive of this archive */ - Archive addArchive(Archive parent, long objectId) { - Archive child = new Archive(parent, objectId); - archives.add(child); - return child; + void addChild(Archive child) { + children.add(child); } - private static class Archive { + /** + * Set the flag which identifies whether this file has been determined to be a zip bomb to true. + */ + synchronized void flagAsZipBomb() { + flaggedAsZipBomb = true; + } - int depth; - long objectId; - Archive parent; - List children; + /** + * Gets whether or not this archive has been flagged as a zip bomb. + * + * @return True when flagged as a zip bomb, false if it is not flagged + */ + synchronized boolean isFlaggedAsZipBomb() { + return flaggedAsZipBomb; + } - Archive(Archive parent, long objectId) { - this.parent = parent; - this.objectId = objectId; - children = new ArrayList<>(); - if (parent != null) { - parent.children.add(this); - this.depth = parent.depth + 1; - } else { - this.depth = 0; - } - } + /** + * Get the AbstractFile which this Archive object represents. + * + * @return archiveFile - the AbstractFile which this Archive represents. + */ + AbstractFile getArchiveFile() { + return archiveFile; + } - /** - * get archive depth of this archive - * - * @return - */ - int getDepth() { - return depth; - } + /** + * Get the object id of the root archive which contained this archive. + * + * @return rootArchiveId - the objectID of the root archive + */ + long getRootArchiveId() { + return rootArchiveId; + } + + /** + * Get the object id of this archive. + * + * @return the unique objectId of this archive from its AbstractFile + */ + long getObjectId() { + return archiveFile.getId(); + } + + /** + * Get archive depth of this archive + * + * @return depth - an integer representing that represents how many + * times the upack method has been recursed from the root + * archive unpack was called on + */ + int getDepth() { + return depth; } } @@ -1260,7 +1368,7 @@ class SevenZipExtractor { /** * Get the AbstractFile contained in this object * - * @return abstractFile - The abstractFile this object wraps + * @return archiveFile - The archiveFile this object wraps */ private AbstractFile getFile() { return abstractFile; diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 1d5d5d05d1..ea1f4042bc 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -75,6 +75,8 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter private static final String MIME_TYPE_MSACCESS = "application/x-msaccess"; private static final String MIME_TYPE_PDF = "application/pdf"; + private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"}; + private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; @@ -122,32 +124,40 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter try { /* - * Qualify the file type. + * Qualify the file type, qualify it against hash databases, and + * verify the file hasn't been deleted. */ if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) - && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { + && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed) + && !file.getKnown().equals(TskData.FileKnown.KNOWN) + && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) { /* - * Qualify the file against hash databases. + * Is the file in FILE_IGNORE_LIST? */ - if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { - /* - * Qualify the MIME type. - */ - String mimeType = fileTypeDetector.getMIMEType(file); - if (mimeType.equals("application/octet-stream")) { - if (isFileEncryptionSuspected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, - String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy)); - } - } else { - if (isFilePasswordProtected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password()); + String filePath = file.getParentPath(); + if (filePath.equals("/")) { + String fileName = file.getName(); + for (String listEntry : FILE_IGNORE_LIST) { + if (fileName.equalsIgnoreCase(listEntry)) { + // Skip this file. + return IngestModule.ProcessResult.OK; } } } + + /* + * Qualify the MIME type. + */ + String mimeType = fileTypeDetector.getMIMEType(file); + if (mimeType.equals("application/octet-stream") && isFileEncryptionSuspected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, + String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy)); + } else if (isFilePasswordProtected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password()); + } } } catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) { logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex); @@ -376,7 +386,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter fileSizeQualified = true; } } - + if (fileSizeQualified) { /* * Qualify the entropy. @@ -386,7 +396,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter possiblyEncrypted = true; } } - + return possiblyEncrypted; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java index 372c62769a..e5373bc555 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.awt.BorderLayout; import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; @@ -40,6 +39,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; * A dialog box that allows a user to create a file type signature, to be added * to a selected file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddFileTypeSignatureDialog extends JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java index c22ecd94a7..bd047b86e0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * Panel for creating a file type signature to be added to a file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddFileTypeSignaturePanel extends javax.swing.JPanel { private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem"); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java index a75b47646e..93ee3ce836 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** * Searches for files by md5 hash, based off the hash given in this panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class HashDbSearchPanel extends javax.swing.JPanel implements ActionListener { private static final Logger logger = Logger.getLogger(HashDbSearchPanel.class.getName()); @@ -329,7 +330,7 @@ class HashDbSearchPanel extends javax.swing.JPanel implements ActionListener { errorField.setVisible(false); // Get all the rows in the table int numRows = hashTable.getRowCount(); - ArrayList hashes = new ArrayList(); + ArrayList hashes = new ArrayList<>(); for (int i = 0; i < numRows; i++) { hashes.add((String) hashTable.getValueAt(i, 0)); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java index 2d0d4f9bf4..874a5c136b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 - 2013 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.modules.hashdatabase; -import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -41,6 +40,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SleuthkitHashSet * completion. Furthermore, it does not delete any files left over from a * half-indexed state, forcing the user to perform cleanup. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListener { List unindexed; diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java index ec8c84dc0b..0c36938eb6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java @@ -50,7 +50,6 @@ final class CallLogAnalyzer { private Connection connection = null; private ResultSet resultSet = null; private Statement statement = null; - private String dbPath = ""; private long fileId = 0; private java.io.File jFile = null; private final String moduleName = iOSModuleFactory.getModuleName(); @@ -79,6 +78,7 @@ final class CallLogAnalyzer { return; } for (AbstractFile file : absFiles) { + String dbPath = ""; try { jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); dbPath = jFile.toString(); //path of file as string diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index 1adb60d4dd..85fa762300 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -54,8 +54,6 @@ import org.sleuthkit.datamodel.TskCoreException; final class ContactAnalyzer { private Connection connection = null; - private ResultSet resultSet = null; - private Statement statement = null; private String dbPath = ""; private long fileId = 0; private java.io.File jFile = null; @@ -114,13 +112,16 @@ final class ContactAnalyzer { if (DatabasePath == null || DatabasePath.isEmpty()) { return; } + Case currentCase; try { currentCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; - } + } + + Statement statement = null; try { Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS @@ -137,6 +138,7 @@ final class ContactAnalyzer { return; } + ResultSet resultSet = null; try { // get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype) //sorted by name, so phonenumber/email would be consecutive for a person if they exist. diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java index 4965652c5f..36888279a9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY /** * A panel that allows a user to create and edit files set membership rules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FilesSetRulePanel extends javax.swing.JPanel { @Messages({ diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java index c24cba6447..b8d03d7cb4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java @@ -23,8 +23,9 @@ import javax.swing.JFileChooser; import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** - * + * Configuration panel for STIX report generation. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class STIXReportModuleConfigPanel extends javax.swing.JPanel { String stixFile = null; diff --git a/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java index b918d1f9f0..f6794e1637 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.progress; /** * A progress panel consisting of a message label and a progress bar. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ProgressPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java index 50e9715a16..b0655dfa2b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java @@ -41,6 +41,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Allow examiner to select artifacts on which to report. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ArtifactSelectionDialog extends javax.swing.JDialog { private ArtifactModel model; diff --git a/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java b/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java index ad1ce529dc..2f18ddf3d2 100644 --- a/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,10 @@ */ package org.sleuthkit.autopsy.report; -import java.awt.*; - /** * The panel shown for all TableReportModules when configuring report modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DefaultReportConfigurationPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java index d08acea3a6..c9bf291314 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; * A panel that displays a panel used by a report generation module to show * progress. It provides OK and Cancel buttons. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ReportGenerationPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java index c6fd374c36..9d3fd1bf41 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,10 @@ package org.sleuthkit.autopsy.report; import org.openide.util.NbBundle; -import java.awt.*; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.EventQueue; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; @@ -30,6 +33,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel used by a report generation module to show progress. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ReportProgressPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java index 5dceb24650..eb844b2bba 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,15 +35,14 @@ import org.openide.util.NbBundle; /** * Visual component of the File Report Configuration panel of the Report Wizard. - * - * @author jwallace */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ReportWizardFileOptionsVisualPanel extends javax.swing.JPanel { private List options; - private Map optionStates = new EnumMap<>(FileReportDataTypes.class); + private final Map optionStates = new EnumMap<>(FileReportDataTypes.class); private ListModel model; - private ReportWizardFileOptionsPanel wizPanel; + private final ReportWizardFileOptionsPanel wizPanel; public ReportWizardFileOptionsVisualPanel(ReportWizardFileOptionsPanel wizPanel) { this.wizPanel = wizPanel; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java index 6861dac340..d54ce01120 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016 Basis Technology Corp. + * Copyright 2016-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,13 +37,11 @@ class PerCaseTimelineProperties { private static final String STALE_KEY = "stale"; //NON-NLS private static final String WAS_INGEST_RUNNING_KEY = "was_ingest_running"; // NON-NLS - private final Case autoCase; private final Path propertiesPath; - PerCaseTimelineProperties(Case c) { - Objects.requireNonNull(c, "Case must not be null"); - this.autoCase = c; - propertiesPath = Paths.get(autoCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS + PerCaseTimelineProperties(Case autopsyCase) { + Objects.requireNonNull(autopsyCase, "Case must not be null"); + propertiesPath = Paths.get(autopsyCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 82e479c4ab..dede3d537a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,6 +72,7 @@ import org.sleuthkit.datamodel.TskCoreException; * to choose a specific event and a time range around it to show in the Timeline * List View. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ShowInTimelineDialog extends Dialog { private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName()); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 0f0e950ec6..ce96fc0efe 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -27,6 +27,8 @@ import java.util.Set; import java.util.HashSet; import java.nio.file.Path; import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.stream.Collectors; import junit.framework.Test; import junit.framework.TestCase; @@ -44,7 +46,7 @@ import static junit.framework.Assert.assertTrue; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /** - * + * Functional tests for the Central Repository data model. */ public class CentralRepoDatamodelTest extends TestCase { @@ -63,6 +65,7 @@ public class CentralRepoDatamodelTest extends TestCase { private EamOrganization org1; private EamOrganization org2; CorrelationAttribute.Type fileType; + CorrelationAttribute.Type usbDeviceType; private Map propertiesMap = null; @@ -112,7 +115,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDbUtil.setUseCentralRepo(true); EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); EamDbPlatformEnum.saveSelectedPlatform(); - } catch (Exception ex) { + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -154,6 +157,8 @@ public class CentralRepoDatamodelTest extends TestCase { // Store the file type object for later use fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); assertTrue("getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); + usbDeviceType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.USBID_TYPE_ID); + assertTrue("getCorrelationTypeById(USBID_TYPE_ID) returned null", usbDeviceType != null); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -631,6 +636,9 @@ public class CentralRepoDatamodelTest extends TestCase { String inDataSource1twicePath2 = "C:\\files\\path2.txt"; String onlyInDataSource3Hash = "2af54305f183778d87de0c70c591fae4"; String onlyInDataSource3Path = "C:\\files\\path3.txt"; + String callbackTestFilePath1 = "C:\\files\\processinstancecallback\\path1.txt"; + String callbackTestFilePath2 = "C:\\files\\processinstancecallback\\path2.txt"; + String callbackTestFileHash = "fb9dd8f04dacd3e82f4917f1a002223c"; // These will all go in dataSource1fromCase1 String emailValue = "test@gmail.com"; @@ -997,6 +1005,25 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } + + // Test updating a correlation attribute instance comment + try { + CorrelationAttribute correlationAttribute = EamDb.getInstance().getCorrelationAttribute( + usbDeviceType, case1, dataSource1fromCase1, devIdValue, devIdPath); + assertNotNull("getCorrelationAttribute returned null", correlationAttribute); + + correlationAttribute.getInstances().get(0).setComment("new comment"); + EamDb.getInstance().updateAttributeInstanceComment(correlationAttribute); + + // Get a fresh copy to verify the update. + correlationAttribute = EamDb.getInstance().getCorrelationAttribute( + usbDeviceType, case1, dataSource1fromCase1, devIdValue, devIdPath); + assertEquals("updateAttributeInstanceComment did not set comment to \"new comment\".", + "new comment", correlationAttribute.getInstances().get(0).getComment()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } // Test getting count for dataSource1fromCase1 (includes all types) try { @@ -1068,6 +1095,34 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } + + // Test running processinstance which queries all rows from instances table + try { + // Add two instances to the central repository and use the callback query to verify we can see them + CorrelationAttribute attr = new CorrelationAttribute(fileType, callbackTestFileHash); + CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, callbackTestFilePath1); + CorrelationAttributeInstance inst2 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, callbackTestFilePath2); + attr.addInstance(inst1); + attr.addInstance(inst2); + EamDb DbManager = EamDb.getInstance(); + DbManager.addArtifact(attr); + AttributeInstanceTableCallback instancetableCallback = new AttributeInstanceTableCallback(); + DbManager.processInstanceTable(fileType, instancetableCallback); + int count1 = instancetableCallback.getCounter(); + int count2 = instancetableCallback.getCounterNamingConvention(); + assertTrue("Process Instance count with filepath naming convention: " + count2 + "-expected 2", count2 == 2); + assertTrue("Process Instance count with filepath without naming convention: " + count1 + "-expected greater than 0", count1 > 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + } + + try { + //test null inputs + EamDb.getInstance().processInstanceTable(null, null); + Assert.fail("processinstance method failed to throw exception for null type value"); + } catch (EamDbException ex) { + // This is the expected + } } /** @@ -2499,7 +2554,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This seems to help in allowing the Autopsy case to be deleted try { Thread.sleep(2000); - } catch (Exception ex) { + } catch (InterruptedException ex) { } } catch (CaseActionException ex) { @@ -2618,4 +2673,34 @@ public class CentralRepoDatamodelTest extends TestCase { } } + public class AttributeInstanceTableCallback implements InstanceTableCallback { + + int counterNamingConvention = 0; + int counter = 0; + + @Override + public void process(ResultSet resultSet) { + try { + while(resultSet.next()){ + if(InstanceTableCallback.getFilePath(resultSet).contains("processinstancecallback")){ + counterNamingConvention++; + }else{ + counter++; + } + } + } catch (SQLException ex) { + Exceptions.printStackTrace(ex); + } + } + + public int getCounter() { + return counter; + } + + public int getCounterNamingConvention(){ + return counterNamingConvention; + } + + } + } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java index 66a68f3a38..39f18e0696 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2018 Basis Technology Corp. @@ -84,20 +83,20 @@ class IntraCaseUtils { static final String PDF = "adsf.pdf"; //not a typo - it appears this way in the test image static final String EMPTY = "file.dat"; - static final String SET1 = "commonfiles_image1_v1.vhd"; - static final String SET2 = "commonfiles_image2_v1.vhd"; - static final String SET3 = "commonfiles_image3_v1.vhd"; - static final String SET4 = "commonfiles_image4_v1.vhd"; + static final String SET1 = "CommonFiles_img1_v1.vhd"; + static final String SET2 = "CommonFiles_img2_v1.vhd"; + static final String SET3 = "CommonFiles_img3_v1.vhd"; + static final String SET4 = "CommonFiles_img4_v1.vhd"; private final DataSourceLoader dataSourceLoader; private final String caseName; IntraCaseUtils(NbTestCase nbTestCase, String caseName){ - imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image1_v1.vhd"); - imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image2_v1.vhd"); - imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image3_v1.vhd"); - imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image4_v1.vhd"); + imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), SET1); + imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); + imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); + imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); this.dataSourceLoader = new DataSourceLoader(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 813c09334a..8f562d698b 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -39,11 +39,13 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Functional tests for embedded files. + */ public class EmbeddedFileTest extends NbTestCase { private static final String CASE_NAME = "EmbeddedFileTest"; - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "EmbeddedIM_img1_v1.vhd"); public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; private static final int DEEP_FOLDER_COUNT = 25; private Case openCase; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index fcb2b8a4ca..16b0f2f4a4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -50,10 +50,13 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +/** + * Functional tests for ingest file filters. + */ public class IngestFileFiltersTest extends NbTestCase { - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); - private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "local_files_test.zip"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"IngestFilters_img1_v1.img"); + private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "IngestFilters_local1_v1.zip"); private boolean testSucceeded; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index ef639bec5f..dc1f85afcd 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -47,6 +47,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; +/** + * Functional tests for Encryption Detection. + */ public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_DETECTION_CASE_NAME = "testBitlockerEncryption"; @@ -54,10 +57,10 @@ public class EncryptionDetectionTest extends NbTestCase { private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; private static final String SQLCIPHER_DETECTION_CASE_NAME = "SQLCipherDetectionTest"; - private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); - private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); - private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_sqlcipher_test.vhd"); + private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "BitlockerDetection_img1_v1.vhd"); + private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "PasswordDetection_img1_v1.img"); + private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "VeracryptDetection_img1_v1.vhd"); + private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "SqlCipherDetection_img1_v1.vhd"); private boolean testSucceeded; diff --git a/CoreLibs/build.xml b/CoreLibs/build.xml index f4c8cf0844..0e4a3701f8 100644 --- a/CoreLibs/build.xml +++ b/CoreLibs/build.xml @@ -20,8 +20,16 @@ - - + + + + + + + + + + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 4031d14fc3..77a432d10c 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -56,6 +56,7 @@ file.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10 file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar +file.reference.opencv-2413.jar=release/modules/ext/opencv-2413.jar file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.3.jar file.reference.platform-3.4.0.jar=release/modules/ext/platform-3.4.0.jar file.reference.poi-3.17.jar=release/modules/ext/poi-3.17.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index ca89b8c1c2..8ed00c1826 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -619,6 +619,18 @@ org.joda.time.field org.joda.time.format org.joda.time.tz + org.opencv.calib3d + org.opencv.contrib + org.opencv.core + org.opencv.features2d + org.opencv.gpu + org.opencv.highgui + org.opencv.imgproc + org.opencv.ml + org.opencv.objdetect + org.opencv.photo + org.opencv.utils + org.opencv.video org.openxmlformats.schemas.drawingml.x2006.chart org.openxmlformats.schemas.drawingml.x2006.chart.impl org.openxmlformats.schemas.drawingml.x2006.main @@ -733,6 +745,10 @@ ext/jfxtras-common-8.0-r4.jar release/modules/ext/jfxtras-common-8.0-r4.jar + + ext/opencv-2413.jar + release/modules/ext/opencv-2413.jar + ext/jsr305-1.3.9.jar release/modules/ext/jsr305-1.3.9.jar diff --git a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java new file mode 100644 index 0000000000..7cfad05bc8 --- /dev/null +++ b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java @@ -0,0 +1,57 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.corelibs; + +import org.opencv.core.Core; + +public final class OpenCvLoader { + + private static final boolean OPEN_CV_LOADED; + private static UnsatisfiedLinkError exception = null; + + static { + boolean tempOpenCvLoaded = false; + try { + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + tempOpenCvLoaded = true; + } catch (UnsatisfiedLinkError e) { + tempOpenCvLoaded = false; + exception = e; //save relevant error for throwing at appropriate time + } + OPEN_CV_LOADED = tempOpenCvLoaded; + } + + /** + * Return whether or not the OpenCV library has been loaded. + * + * @return - true if the opencv library is loaded or false if it is not + */ + public static boolean isOpenCvLoaded() throws UnsatisfiedLinkError { + if (!OPEN_CV_LOADED) { + //exception should never be null if the open cv isn't loaded but just in case + if (exception != null) { + throw exception; + } else { + throw new UnsatisfiedLinkError("OpenCV native library failed to load"); + } + + } + return OPEN_CV_LOADED; + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java index 6ea478699e..090932e154 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; @Messages({ "CTL_AinStatusDashboardAction=Auto Ingest Nodes", "CTL_AinStatusDashboardTopComponent=Auto Ingest Nodes"}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AinStatusDashboardTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java index 8516151027..1cc10fab76 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; * A panel which displays an outline view with all auto ingest nodes and their * status. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java index e0a5378444..3a8e844812 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; * "zip", "rar", "arj", "7z", "7zip", "gzip, etc). Allows the user to select a * file. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ArchiveFilePanel extends JPanel implements DocumentListener { private static final Logger logger = Logger.getLogger(ArchiveFilePanel.class.getName()); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 195df4e4e7..4ce309b6ab 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; @Messages({ "CTL_AutoIngestDashboardAction=Auto Ingest Jobs", "CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs"}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AutoIngestDashboardTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 25a3b3d495..4083d448f7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -87,7 +87,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn private int numberOfCrashes; @GuardedBy("this") private StageDetails stageDetails; - + /* * Version 2 fields. */ @@ -100,7 +100,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn private List ingestThreadsSnapshot; private List ingestJobsSnapshot; private Map moduleRunTimesSnapshot; - + /** * Constructs a new automated ingest job. All job state not specified in the * job manifest is set to the default state for a new job. @@ -132,14 +132,14 @@ final class AutoIngestJob implements Comparable, IngestProgressSn this.processingStatus = ProcessingStatus.PENDING; this.numberOfCrashes = 0; this.stageDetails = this.getProcessingStageDetails(); - + /* * Version 2 fields. */ this.dataSourceSize = 0; - + /* - * Version 3 fields. + * Version 3 fields. */ this.ingestThreadsSnapshot = Collections.emptyList(); this.ingestJobsSnapshot = Collections.emptyList(); @@ -181,12 +181,12 @@ final class AutoIngestJob implements Comparable, IngestProgressSn this.processingStatus = nodeData.getProcessingStatus(); this.numberOfCrashes = nodeData.getNumberOfCrashes(); this.stageDetails = this.getProcessingStageDetails(); - + /* * Version 2 fields. */ this.dataSourceSize = nodeData.getDataSourceSize(); - + /* * Version 3 fields */ @@ -257,7 +257,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn * Sets the processing stage of the job. The start date/time for the stage * is set when the stage is set. * - * @param newStage The processing stage. + * @param newStage The processing stage. * @param stageStartDate The date and time this stage started. */ synchronized void setProcessingStage(Stage newStage, Date stageStartDate) { @@ -377,20 +377,22 @@ final class AutoIngestJob implements Comparable, IngestProgressSn /** * Sets the ingest job snapshot for the auto ingest job. - * @param snapshot + * + * @param snapshot */ synchronized void setIngestJobsSnapshot(List snapshot) { this.ingestJobsSnapshot = snapshot; } - + /** * Sets the module run times snapshot for the auto ingest job. - * @param snapshot + * + * @param snapshot */ synchronized void setModuleRuntimesSnapshot(Map snapshot) { this.moduleRunTimesSnapshot = snapshot; } - + /** * Cancels the job. */ @@ -589,7 +591,21 @@ final class AutoIngestJob implements Comparable, IngestProgressSn */ @Override public int compareTo(AutoIngestJob otherJob) { - return -this.getManifest().getDateFileCreated().compareTo(otherJob.getManifest().getDateFileCreated()); + int comparisonResult = -(this.getPriority().compareTo(otherJob.getPriority())); + if (comparisonResult == 0) { + //if the priority is the same compare with the jobs manifest creation date + comparisonResult = this.getManifest().getDateFileCreated().compareTo(otherJob.getManifest().getDateFileCreated()); + if (comparisonResult == 0) { + //if the manifest files were created at the same time compare with the jobs case name + comparisonResult = -this.getManifest().getCaseName().compareTo(otherJob.getManifest().getCaseName()); + if (comparisonResult == 0) { + //if the case name is the same compare with the jobs datasource file name + comparisonResult = -this.getManifest().getDataSourcePath().getFileName().toString().compareTo(otherJob.getManifest().getDataSourcePath().getFileName().toString()); + //if they are still the same at this point they may be ordered inconsistently + } + } + } + return comparisonResult; } @Override @@ -607,31 +623,6 @@ final class AutoIngestJob implements Comparable, IngestProgressSn return this.moduleRunTimesSnapshot; } - /** - * Comparator that supports doing a descending sort of jobs based on job - * completion date. - */ - static class CompletedDateDescendingComparator implements Comparator { - - @Override - public int compare(AutoIngestJob o1, AutoIngestJob o2) { - return -o1.getCompletedDate().compareTo(o2.getCompletedDate()); - } - - } - - /** - * Comparator that orders jobs in descending order by job priority. - */ - public static class PriorityComparator implements Comparator { - - @Override - public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { - return -(job.getPriority().compareTo(anotherJob.getPriority())); - } - - } - /** * Comparator that orders jobs such that those running on the local host * appear first, then the remaining jobs are sorted alphabetically by case @@ -653,18 +644,6 @@ final class AutoIngestJob implements Comparable, IngestProgressSn } - /** - * Comparator that orders jobs by data source name. - */ - static class DataSourceFileNameComparator implements Comparator { - - @Override - public int compare(AutoIngestJob aJob, AutoIngestJob anotherJob) { - return aJob.getManifest().getDataSourceFileName().compareToIgnoreCase(anotherJob.getManifest().getDataSourceFileName()); - } - - } - /** * Processing statuses for an auto ingest job. */ @@ -730,7 +709,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn } } - + /** * Exception thrown when there is a problem creating auto ingest job. */ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 2746e48056..a7b2ad6116 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -23,6 +23,7 @@ import com.google.common.eventbus.Subscribe; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import org.openide.nodes.AbstractNode; @@ -109,7 +110,7 @@ final class AutoIngestJobsNode extends AbstractNode { switch (autoIngestJobStatus) { case PENDING_JOB: jobs = jobsSnapshot.getPendingJobs(); - jobs.sort(new AutoIngestJob.PriorityComparator()); + Collections.sort(jobs); break; case RUNNING_JOB: jobs = jobsSnapshot.getRunningJobs(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index b9d356f6e8..a3cdc1c51d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -30,12 +30,12 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent; /** * A panel which displays an outline view with all jobs for a specified status. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 9040c63709..e30c795013 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -408,7 +408,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen completedJobs.remove(job); // Add to pending jobs table and re-sort. pendingJobs.add(job); - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); setChanged(); notifyObservers(Event.REPROCESS_JOB); @@ -454,7 +454,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteNodeControlEvent(AutoIngestNodeControlEvent event) { if (event.getTargetNodeName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { - sysLogger.log(Level.INFO, "Received {0} event from user {1} on machine {2}", new Object[] {event.getControlEventType().toString(), event.getUserName(), event.getOriginatingNodeName()}); + sysLogger.log(Level.INFO, "Received {0} event from user {1} on machine {2}", new Object[]{event.getControlEventType().toString(), event.getUserName(), event.getOriginatingNodeName()}); switch (event.getControlEventType()) { case PAUSE: pause(); @@ -656,7 +656,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } if (!jobsToDeprioritize.isEmpty()) { @@ -705,7 +705,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } if (!jobsToPrioritize.isEmpty()) { @@ -755,7 +755,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } if (null != jobToDeprioritize) { @@ -811,7 +811,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } if (null != jobToPrioritize) { @@ -868,7 +868,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } } @@ -1168,7 +1168,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen newPendingJobsList.clear(); newCompletedJobsList.clear(); Files.walkFileTree(rootInputDirectory, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, this); - Collections.sort(newPendingJobsList, new AutoIngestJob.PriorityComparator()); + Collections.sort(newPendingJobsList); AutoIngestManager.this.pendingJobs = newPendingJobsList; AutoIngestManager.this.completedJobs = newCompletedJobsList; @@ -2028,21 +2028,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while reading the lock data */ private Lock dequeueAndLockNextJob() throws CoordinationServiceException, InterruptedException { - sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job, enforcing max jobs per case"); + sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job"); Lock manifestLock; synchronized (jobsLock) { - manifestLock = dequeueAndLockNextJob(true); + manifestLock = dequeueAndLockNextJobHelper(); if (null != manifestLock) { sysLogger.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); } else { sysLogger.log(Level.INFO, "No ready job"); - sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job, not enforcing max jobs per case"); - manifestLock = dequeueAndLockNextJob(false); - if (null != manifestLock) { - sysLogger.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); - } else { - sysLogger.log(Level.INFO, "No ready job"); - } } } return manifestLock; @@ -2054,8 +2047,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * queue, made the current job, and a coordination service lock on the * manifest for the job is returned. * - * @param enforceMaxJobsPerCase Whether or not to enforce the maximum - * concurrent jobs per case setting. * * @return A manifest file lock if a ready job was found, null * otherwise. @@ -2066,7 +2057,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @throws InterruptedException if the thread is interrupted * while reading the lock data */ - private Lock dequeueAndLockNextJob(boolean enforceMaxJobsPerCase) throws CoordinationServiceException, InterruptedException { + private Lock dequeueAndLockNextJobHelper() throws CoordinationServiceException, InterruptedException { Lock manifestLock = null; synchronized (jobsLock) { Iterator iterator = pendingJobs.iterator(); @@ -2096,19 +2087,6 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen continue; } - if (enforceMaxJobsPerCase) { - int currentJobsForCase = 0; - for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { - if (0 == job.getManifest().getCaseName().compareTo(runningJob.getManifest().getCaseName())) { - ++currentJobsForCase; - } - } - if (currentJobsForCase >= AutoIngestUserPreferences.getMaxConcurrentJobsForOneCase()) { - manifestLock.release(); - manifestLock = null; - continue; - } - } iterator.remove(); currentJob = job; break; @@ -2412,7 +2390,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen throw new CaseManagementException(String.format("Error creating solr settings file for case %s for %s", caseName, manifest.getFilePath()), ex); } catch (CaseActionException ex) { throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); - } + } } else { throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", caseName, manifest.getFilePath())); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java index 31ecd5f4ca..4f90a12f17 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java @@ -37,21 +37,25 @@ class AutoIngestRowSorter extends TableRowSorter @Override public void toggleSortOrder(int column) { - if (!this.getModel().getColumnClass(column).equals(Date.class) && !this.getModel().getColumnClass(column).equals(Integer.class)) { - //currently the only Integer column this sorter is being applied to is the Priority column - super.toggleSortOrder(column); //if it isn't a date or Integer column perform the regular sorting - } else { - ArrayList sortKeys = new ArrayList<>(getSortKeys()); - if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); - } - setSortKeys(sortKeys); + SortOrder firstSort = SortOrder.ASCENDING; + SortOrder secondSort = SortOrder.DESCENDING; + + if (this.getModel().getColumnClass(column).equals(Date.class) || this.getModel().getColumnClass(column).equals(Integer.class)) { + firstSort = SortOrder.DESCENDING; + secondSort = SortOrder.ASCENDING; } + + ArrayList sortKeys = new ArrayList<>(getSortKeys()); + if (sortKeys.isEmpty() || sortKeys.get(0).getSortOrder() == SortOrder.UNSORTED) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, firstSort)); + } else if (sortKeys.get(0).getSortOrder() == firstSort) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, secondSort)); + } else { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.UNSORTED)); + } + setSortKeys(sortKeys); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form index 5e1c5c0a41..60674ab755 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form @@ -37,8 +37,8 @@ - - + + @@ -82,103 +82,95 @@ - + - - - - - - - - - - - - + + + + + + + - + + + + + + + + + + + - + - + + + + + + - - - - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -237,16 +229,6 @@ - - - - - - - - - - @@ -287,16 +269,6 @@ - - - - - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java index 96d5009df2..042ad04f41 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java @@ -63,8 +63,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { initThreadCount(); spSecondsBetweenJobs.setValue(AutoIngestUserPreferences.getSecondsToSleepBetweenCases()); spMaximumRetryAttempts.setValue(AutoIngestUserPreferences.getMaxNumTimesToProcessImage()); - int maxJobsPerCase = AutoIngestUserPreferences.getMaxConcurrentJobsForOneCase(); - spConcurrentJobsPerCase.setValue(maxJobsPerCase); spInputScanInterval.setValue(AutoIngestUserPreferences.getMinutesOfInputScanInterval()); spInputScanInterval.setEnabled(mode == AutoIngestSettingsPanel.OptionsUiMode.AIM); spSecondsBetweenJobs.setEnabled(mode == AutoIngestSettingsPanel.OptionsUiMode.AIM); @@ -83,7 +81,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { void store() { AutoIngestUserPreferences.setSecondsToSleepBetweenCases((int) spSecondsBetweenJobs.getValue()); AutoIngestUserPreferences.setMaxNumTimesToProcessImage((int) spMaximumRetryAttempts.getValue()); - AutoIngestUserPreferences.setMaxConcurrentIngestNodesForOneCase((int) spConcurrentJobsPerCase.getValue()); AutoIngestUserPreferences.setMinutesOfInputScanInterval((int) spInputScanInterval.getValue()); UserPreferences.setNumberOfFileIngestThreads((Integer) numberOfFileIngestThreadsComboBox.getSelectedItem()); boolean isChecked = cbTimeoutEnabled.isSelected(); @@ -123,11 +120,9 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { lbInputScanInterval = new javax.swing.JLabel(); lbRetriesAllowed = new javax.swing.JLabel(); lbNumberOfThreads = new javax.swing.JLabel(); - lbConcurrentJobsPerCase = new javax.swing.JLabel(); cbTimeoutEnabled = new javax.swing.JCheckBox(); numberOfFileIngestThreadsComboBox = new javax.swing.JComboBox<>(); lbRestartRequired = new javax.swing.JLabel(); - spConcurrentJobsPerCase = new javax.swing.JSpinner(); spMaximumRetryAttempts = new javax.swing.JSpinner(); spInputScanInterval = new javax.swing.JSpinner(); spTimeoutHours = new javax.swing.JSpinner(); @@ -162,9 +157,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(lbNumberOfThreads, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.text")); // NOI18N lbNumberOfThreads.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.toolTipText_1")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(lbConcurrentJobsPerCase, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.text")); // NOI18N - lbConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText_1")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(cbTimeoutEnabled, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.text")); // NOI18N cbTimeoutEnabled.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.toolTipText")); // NOI18N cbTimeoutEnabled.addItemListener(new java.awt.event.ItemListener() { @@ -188,9 +180,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { lbRestartRequired.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbRestartRequired, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbRestartRequired.text")); // NOI18N - spConcurrentJobsPerCase.setModel(new javax.swing.SpinnerNumberModel(3, 1, 100, 1)); - spConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText")); // NOI18N - spMaximumRetryAttempts.setModel(new javax.swing.SpinnerNumberModel(2, 0, 9999999, 1)); spMaximumRetryAttempts.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_2")); // NOI18N @@ -221,76 +210,70 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { .addGap(5, 5, 5) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbInputScanInterval) - .addGap(49, 49, 49)) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbRetriesAllowed) - .addGap(54, 54, 54)) - .addComponent(lbConcurrentJobsPerCase, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbNumberOfThreads, javax.swing.GroupLayout.Alignment.LEADING)) + .addComponent(lbNumberOfThreads) + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lbRestartRequired) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addGap(0, 41, Short.MAX_VALUE) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbInputScanInterval) + .addGap(49, 49, 49)) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbRetriesAllowed) + .addGap(54, 54, 54))) + .addGap(0, 21, Short.MAX_VALUE) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbSecondsBetweenJobs) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbSecondsBetweenJobs) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbTimeoutText) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbRestartRequired) - .addComponent(lbSecondsBetweenJobsSeconds) - .addComponent(lbTimeoutHours) - .addComponent(lbInputScanIntervalMinutes)) - .addContainerGap(50, Short.MAX_VALUE)) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbTimeoutText) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbSecondsBetweenJobsSeconds) + .addComponent(lbTimeoutHours) + .addComponent(lbInputScanIntervalMinutes)) + .addContainerGap(255, Short.MAX_VALUE)))) ); jPanelAutoIngestJobSettingsLayout.setVerticalGroup( jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() .addContainerGap() + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbSecondsBetweenJobs) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbSecondsBetweenJobsSeconds)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbTimeoutText) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbTimeoutHours)) + .addComponent(cbTimeoutEnabled)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbInputScanInterval) + .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbInputScanIntervalMinutes)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbRetriesAllowed) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(lbRestartRequired) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbSecondsBetweenJobs) - .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbSecondsBetweenJobsSeconds)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbTimeoutText) - .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbTimeoutHours)) - .addComponent(cbTimeoutEnabled)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbInputScanInterval) - .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbInputScanIntervalMinutes)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbRetriesAllowed) - .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbConcurrentJobsPerCase) - .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbNumberOfThreads) - .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbNumberOfThreads) + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); @@ -316,8 +299,8 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { .addGap(20, 20, 20) .addComponent(spMainScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanelAutoIngestJobSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(26, 26, 26)) + .addComponent(jPanelAutoIngestJobSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -336,7 +319,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox cbTimeoutEnabled; private javax.swing.JPanel jPanelAutoIngestJobSettings; - private javax.swing.JLabel lbConcurrentJobsPerCase; private javax.swing.JLabel lbInputScanInterval; private javax.swing.JLabel lbInputScanIntervalMinutes; private javax.swing.JLabel lbNumberOfThreads; @@ -347,7 +329,6 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { private javax.swing.JLabel lbTimeoutHours; private javax.swing.JLabel lbTimeoutText; private javax.swing.JComboBox numberOfFileIngestThreadsComboBox; - private javax.swing.JSpinner spConcurrentJobsPerCase; private javax.swing.JSpinner spInputScanInterval; private javax.swing.JScrollPane spMainScrollPane; private javax.swing.JSpinner spMaximumRetryAttempts; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties index 97a82988f2..eaae72eb66 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties @@ -89,7 +89,6 @@ AdvancedAutoIngestSettingsPanel.spMaximumRetryAttempts.toolTipText=The maximum n AdvancedAutoIngestSettingsPanel.lbRestartRequired.text=Application restart required to take effect. AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.toolTipText=Components that spawn potentially long-running processes optionally terminate those processes if the specified time out period has elapsed. AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.text= -AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.text=Target concurrent jobs per case: AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.text=Number of threads to use for file ingest: AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.text=Maximum job retries allowed: AdvancedAutoIngestSettingsPanel.lbInputScanInterval.text=Interval between input scans: @@ -103,7 +102,6 @@ AdvancedAutoIngestSettingsPanel.lbInputScanIntervalMinutes.toolTipText= AdvancedAutoIngestSettingsPanel.lbTimeoutHours.toolTipText= AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_1=The maximum number of retries for crashed jobs. AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_2=The maximum number of retries for crashed jobs. -AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText_1=A soft limit on the number of concurrent jobs per case when multiple cases are processed simultaneously. AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.toolTipText_1=The number of threads running file level ingest modules. AdvancedAutoIngestSettingsPanel.numberOfFileIngestThreadsComboBox.toolTipText=The number of threads running file level ingest modules. NodeStatusLogPanel.tbDbName.toolTipText_1=Database name diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java new file mode 100644 index 0000000000..fe86fb9f0b --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java @@ -0,0 +1,164 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.experimental.objectdetection; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.opencv.core.CvException; +import org.opencv.core.Mat; +import org.opencv.core.MatOfByte; +import org.opencv.core.MatOfRect; +import org.opencv.highgui.Highgui; +import org.opencv.objdetect.CascadeClassifier; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.corelibs.OpenCvLoader; +import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Data source module to detect objects in images. + */ +public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter { + + private final static Logger logger = Logger.getLogger(ObjectDetectectionFileIngestModule.class.getName()); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private long jobId; + private Map classifiers; + private final IngestServices services = IngestServices.getInstance(); + private Blackboard blackboard; + + @Messages({"ObjectDetectionFileIngestModule.noClassifiersFound.subject=No classifiers found.", + "# {0} - classifierDir", "ObjectDetectionFileIngestModule.noClassifiersFound.message=No classifiers were found in {0}, object detection will not be executed."}) + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + jobId = context.getJobId(); + File classifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath()); + classifiers = new HashMap<>(); + //Load all classifiers found in PlatformUtil.getObjectDetectionClassifierPath() + if (OpenCvLoader.isOpenCvLoaded() && classifierDir.exists() && classifierDir.isDirectory()) { + for (File classifier : classifierDir.listFiles()) { + if (classifier.isFile() && FilenameUtils.getExtension(classifier.getName()).equalsIgnoreCase("xml")) { + classifiers.put(classifier.getName(), new CascadeClassifier(classifier.getAbsolutePath())); + } + } + } else { + throw new IngestModule.IngestModuleException("Unable to load classifiers for object detection module."); + } + if (refCounter.incrementAndGet(jobId) == 1 && classifiers.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage(ObjectDetectionModuleFactory.getModuleName(), + Bundle.ObjectDetectionFileIngestModule_noClassifiersFound_subject(), + Bundle.ObjectDetectionFileIngestModule_noClassifiersFound_message(PlatformUtil.getObjectDetectionClassifierPath()))); + } + try { + blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); + } catch (NoCurrentCaseException ex) { + throw new IngestModule.IngestModuleException("Exception while getting open case.", ex); + } + } + + @Messages({"# {0} - detectionCount", "ObjectDetectionFileIngestModule.classifierDetection.text=Classifier detected {0} object(s)"}) + @Override + public ProcessResult process(AbstractFile file) { + if (!classifiers.isEmpty() && ImageUtils.isImageThumbnailSupported(file)) { + //Any image we can create a thumbnail for is one we should apply the classifiers to + InputStream inputStream = new ReadContentInputStream(file); + byte[] imageInMemory; + try { + imageInMemory = IOUtils.toByteArray(inputStream); + } catch (IOException ex) { + logger.log(Level.WARNING, "Unable to perform object detection on " + file.getName(), ex); + return IngestModule.ProcessResult.ERROR; + } + Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE); + MatOfRect detectionRectangles = new MatOfRect(); //the rectangles which reprent the coordinates on the image for where objects were detected + for (String classifierKey : classifiers.keySet()) { + //apply each classifier to the file + try { + classifiers.get(classifierKey).detectMultiScale(originalImage, detectionRectangles); + } catch (CvException ignored) { + //The image was likely an image which we are unable to generate a thumbnail for, and the classifier was likely one where that is not acceptable + logger.log(Level.INFO, String.format("Classifier '%s' could not be applied to file '%s'.", classifierKey, file.getParentPath() + file.getName())); //NON-NLS + continue; + } + + if (!detectionRectangles.empty()) { + //if any detections occurred create an artifact for this classifier and file combination + try { + BlackboardArtifact artifact = file.newArtifact(TSK_OBJECT_DETECTED); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, + ObjectDetectionModuleFactory.getModuleName(), + classifierKey)); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + ObjectDetectionModuleFactory.getModuleName(), + Bundle.ObjectDetectionFileIngestModule_classifierDetection_text((int) detectionRectangles.size().height))); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + /* + * Send an event to update the view with the new result. + */ + services.fireModuleDataEvent(new ModuleDataEvent(ObjectDetectionModuleFactory.getModuleName(), TSK_OBJECT_DETECTED, Collections.singletonList(artifact))); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + } + } + + return IngestModule.ProcessResult.OK; + } + + @Override + public void shutDown() { + refCounter.decrementAndGet(jobId); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java new file mode 100644 index 0000000000..829b4e5c02 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.experimental.objectdetection; + +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * A factory that creates ingest modules which uses classifiers to detect + * objects in pictures. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class ObjectDetectionModuleFactory extends IngestModuleFactoryAdapter { + + + /** + * Get the name of the Object Detection module + * + * @return the name of the Object Detection module + */ + static String getModuleName() { + return Bundle.ObjectDetectionModuleFactory_moduleName_text(); + } + + @Messages({"ObjectDetectionModuleFactory.moduleName.text=Object Detection"}) + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @Messages({"ObjectDetectionModuleFactory.moduleDescription.text=Use object classifiers to identify objects in pictures."}) + @Override + public String getModuleDescription() { + return Bundle.ObjectDetectionModuleFactory_moduleDescription_text(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + return new ObjectDetectectionFileIngestModule(); + } + +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index ec5b511b7f..a6ce70c3ac 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,6 +65,7 @@ import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList; "CTL_ImageGalleryAction=Image/Video Gallery", "CTL_ImageGalleryTopComponent=Image/Video Gallery" }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider { public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index ced485d9c6..accc19cb9a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -121,6 +121,12 @@ class AdHocSearchChildFactory extends ChildFactory { createFlatKeys(queryRequest.getQuery(), toPopulate); } + + // If there were no hits, make a single Node that will display that + // no results were found. + if (toPopulate.isEmpty()) { + toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); + } return true; } @@ -207,9 +213,7 @@ class AdHocSearchChildFactory extends ChildFactory { } - if (hitNumber == 0) { - toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); - } else { + if (hitNumber != 0) { // Add all the nodes to toPopulate at once. Minimizes node creation // EDT threads, which can slow and/or hang the UI on large queries. toPopulate.addAll(tempList); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 89bfbab816..c06394bf23 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -37,6 +37,7 @@ import javax.swing.DefaultListModel; * class and extended classes model the user's intentions, not necessarily how * the search manager and 3rd party tools actually perform the search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); @@ -53,18 +54,18 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { private void initListeners() { KeywordSearch.addNumIndexedFilesChangeListener( new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String changed = evt.getPropertyName(); - Object newValue = evt.getNewValue(); + @Override + public void propertyChange(PropertyChangeEvent evt) { + String changed = evt.getPropertyName(); + Object newValue = evt.getNewValue(); - if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { - int newFilesIndexed = ((Integer) newValue).intValue(); - filesIndexed = newFilesIndexed; - postFilesIndexedChange(); - } - } - }); + if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { + int newFilesIndexed = ((Integer) newValue); + filesIndexed = newFilesIndexed; + postFilesIndexedChange(); + } + } + }); } /** @@ -128,8 +129,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { } } - AdHocSearchDelegator man = null; - final List keywordLists = getKeywordLists(); if (keywordLists.isEmpty()) { KeywordSearchUtil.displayDialog(keywordSearchErrorDialogHeader, NbBundle.getMessage(this.getClass(), @@ -137,8 +136,8 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new AdHocSearchDelegator(keywordLists, getDataSourcesSelected()); - + + AdHocSearchDelegator man = new AdHocSearchDelegator(keywordLists, getDataSourcesSelected()); if (man.validate()) { man.execute(); } else { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index 06df441bf3..82494f2f0d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import java.io.PushbackReader; import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.text.Normalizer; import java.util.Iterator; import java.util.NoSuchElementException; import javax.annotation.concurrent.NotThreadSafe; @@ -148,7 +149,9 @@ class Chunker implements Iterator, Iterable { } private static StringBuilder sanitize(String s) { - return sanitizeToUTF8(replaceInvalidUTF16(s)); + String normStr = Normalizer.normalize(s, Normalizer.Form.NFKC); + return sanitizeToUTF8(replaceInvalidUTF16(normStr)); + } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index f7463bbccb..5061a2e2f0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JTextPane; import javax.swing.SizeRequirements; import javax.swing.SwingWorker; import javax.swing.text.Element; @@ -48,6 +47,7 @@ import org.sleuthkit.autopsy.coreutils.TextUtil; * Panel displays HTML content sent to ExtractedContentViewer, and provides a * combo-box to select between multiple sources. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ExtractedContentPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index 2033dfbdb7..b56415094e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 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.keywordsearch; +import java.text.Normalizer; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -57,11 +58,7 @@ class Keyword { * than a substring. */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord) { - this.searchTerm = searchTerm; - this.isLiteral = isLiteral; - this.isWholeWord = isWholeWord; - this.listName = ""; - this.originalTerm = searchTerm; + this(searchTerm, isLiteral, isWholeWord, "", searchTerm); } /** @@ -93,11 +90,12 @@ class Keyword { * used (e.g. for highlighting purposes). */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord, String listName, String originalTerm) { - this.searchTerm = searchTerm; + this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKC); this.isLiteral = isLiteral; this.isWholeWord = isWholeWord; this.listName = listName; this.originalTerm = originalTerm; + } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java index c34e672a1c..156bbd0220 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; /** * Global options panel for keyword searching. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class KeywordSearchGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 84605ac34f..ee0a85b2dd 100755 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.7.0 +HTML_OUTPUT = 4.8.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG index e5706a4df8..5dcc5e3d03 100644 Binary files a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG and b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG differ diff --git a/docs/doxygen-user/images/keyword-search-ocr-image.png b/docs/doxygen-user/images/keyword-search-ocr-image.png new file mode 100644 index 0000000000..a8fda73cc4 Binary files /dev/null and b/docs/doxygen-user/images/keyword-search-ocr-image.png differ diff --git a/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png b/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png new file mode 100644 index 0000000000..d6c3a0ac8c Binary files /dev/null and b/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png differ diff --git a/docs/doxygen-user/keyword_search.dox b/docs/doxygen-user/keyword_search.dox index 6edb313f38..b7509892f8 100644 --- a/docs/doxygen-user/keyword_search.dox +++ b/docs/doxygen-user/keyword_search.dox @@ -36,7 +36,7 @@ Under the Keyword list is the option to send ingest inbox messages for each hit. \image html keyword-search-inbox.PNG ## String Extraction tab {#stringExtractionTab} -The string extraction setting defines how strings are extracted from files from which text cannot be extracted because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. +The string extraction setting defines how strings are extracted from files from which text cannot be extracted normally because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. When we extract strings from binary files we need to interpret sequences of bytes as text differently, depending on the possible text encoding and script/language used. In many cases we don't know in advance what the specific encoding/language the text is encoded in. However, it helps if the investigator is looking for a specific language, because by selecting less languages the indexing performance will be improved and the number of false positives will be reduced. The default setting is to search for English strings only, encoded as either UTF8 or UTF16. This setting has the best performance (shortest ingest time). @@ -44,6 +44,14 @@ The user can also use the String Viewer first and try different script/language \image html keyword-search-configuration-dialog-string-extraction.PNG +There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The following shows a sample image containing text: + +\image html keyword-search-ocr-image.png + +The "Indexed Text" tab shows the results when running the keyword search module with the OCR option enabled. If we were to use Keyword Search to look for the word "forensics", this file would be a match. + +\image html keyword-search-ocr-indexed-text.png + ## General Settings tab {#generalSettingsTab} \image html keyword-search-configuration-dialog-general.PNG diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 5dbdbf1479..7facd03b19 100755 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,10 +38,10 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears a the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Graphical digital forensics platform for The Sleuth Kit and other tools." @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.7.0/ +HTML_OUTPUT = api-docs/4.8.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index 3cbde84d3e..1ce749b501 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,10 +4,10 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.7.0 +app.version=4.8.0 ### build.type must be one of: DEVELOPMENT, RELEASE -#build.type=RELEASE -build.type=DEVELOPMENT +build.type=RELEASE +#build.type=DEVELOPMENT project.org.netbeans.progress=org-netbeans-api-progress project.org.sleuthkit.autopsy.experimental=Experimental diff --git a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py index 14477f06df..6b43853f30 100644 --- a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py +++ b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py @@ -36,9 +36,11 @@ import jarray import inspect import os -import subprocess +import java.util.ArrayList as ArrayList from java.lang import Class from java.lang import System +from java.lang import ProcessBuilder +from java.io import File from java.util.logging import Level from org.sleuthkit.datamodel import SleuthkitCase from org.sleuthkit.datamodel import AbstractFile @@ -47,8 +49,10 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Image from org.sleuthkit.autopsy.ingest import IngestModule +from org.sleuthkit.autopsy.ingest import IngestJobContext from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException from org.sleuthkit.autopsy.ingest import DataSourceIngestModule +from org.sleuthkit.autopsy.ingest import DataSourceIngestModuleProcessTerminator from org.sleuthkit.autopsy.ingest import IngestModuleFactoryAdapter from org.sleuthkit.autopsy.ingest import IngestMessage from org.sleuthkit.autopsy.ingest import IngestServices @@ -58,6 +62,7 @@ from org.sleuthkit.autopsy.coreutils import PlatformUtil from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Services from org.sleuthkit.autopsy.datamodel import ContentUtils +from org.sleuthkit.autopsy.coreutils import ExecUtil # Factory that defines the name and details of the module and allows Autopsy @@ -102,10 +107,10 @@ class RunExeIngestModule(DataSourceIngestModule): # Get path to EXE based on where this script is run from. # Assumes EXE is in same folder as script # Verify it is there before any ingest starts - self.path_to_exe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe") - if not os.path.exists(self.path_to_exe): + exe_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe") + self.pathToEXE = File(exe_path) + if not self.pathToEXE.exists(): raise IngestModuleException("EXE was not found in module folder") - # Where the analysis is done. # The 'dataSource' object being passed in is of type org.sleuthkit.datamodel.Content. # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.4/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html @@ -115,7 +120,6 @@ class RunExeIngestModule(DataSourceIngestModule): # we don't know how much work there will be progressBar.switchToIndeterminate() - # Example has only a Windows EXE, so bail if we aren't on Windows if not PlatformUtil.isWindowsOS(): self.log(Level.INFO, "Ignoring data source. Not running on Windows") @@ -130,17 +134,27 @@ class RunExeIngestModule(DataSourceIngestModule): imagePaths = dataSource.getPaths() # We'll save our output to a file in the reports folder, named based on EXE and data source ID - reportPath = os.path.join(Case.getCurrentCase().getCaseDirectory(), "Reports", "img_stat-" + str(dataSource.getId()) + ".txt") - reportHandle = open(reportPath, 'w') - + reportFile = File(Case.getCurrentCase().getCaseDirectory() + "\\Reports" + "\\img_stat-" + str(dataSource.getId()) + ".txt") # Run the EXE, saving output to the report - # NOTE: we should really be checking for if the module has been - # cancelled and then killing the process. + # Check if the ingest is terminated and delete the incomplete report file + # Do not add report to the case tree if the ingest is cancelled before finish. + # This can be done by using IngestJobContext.dataSourceIngestIsCancelled + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.7.0/_ingest_job_context_8java.html self.log(Level.INFO, "Running program on data source") - subprocess.Popen([self.path_to_exe, imagePaths[0]], stdout=reportHandle).communicate()[0] - reportHandle.close() + cmd = ArrayList() + cmd.add(self.pathToEXE.toString()) + cmd.add(imagePaths[0]) + + processBuilder = ProcessBuilder(cmd); + processBuilder.redirectOutput(reportFile) + ExecUtil.execute(processBuilder,DataSourceIngestModuleProcessTerminator(self.context)) # Add the report to the case, so it shows up in the tree - Case.getCurrentCase().addReport(reportPath, "Run EXE", "img_stat output") - + if not self.context.dataSourceIngestIsCancelled(): + Case.getCurrentCase().addReport(reportFile.toString(), "Run EXE", "img_stat output") + else: + if reportFile.exists(): + if not reportFile.delete(): + self.log(LEVEL.warning,"Error deleting the incomplete report file") + return IngestModule.ProcessResult.OK diff --git a/test/script/regression.py b/test/script/regression.py index bf8068b4c5..e5c608d233 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -884,7 +884,7 @@ class TestConfiguration(object): linkFile = open(os.path.join(self.args.diff_files_output_folder, OUTPUT_DIR_LINK_FILE), "a") index = self.output_dir.find("\\") - linkStr = "file://" + linkStr = "file:\\" linkOutputDir = self.output_dir[index+2:].replace("//", "/").replace("\\\\", "\\") if index == 0: linkStr = linkStr + linkOutputDir diff --git a/thirdparty/opencv/ext/opencv-2413.jar b/thirdparty/opencv/ext/opencv-2413.jar new file mode 100644 index 0000000000..dccffb96a9 Binary files /dev/null and b/thirdparty/opencv/ext/opencv-2413.jar differ diff --git a/thirdparty/opencv/ext/opencv-248.jar b/thirdparty/opencv/ext/opencv-248.jar deleted file mode 100755 index f09b29077d..0000000000 Binary files a/thirdparty/opencv/ext/opencv-248.jar and /dev/null differ diff --git a/thirdparty/opencv/lib/amd64/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/amd64/opencv_ffmpeg2413_64.dll old mode 100755 new mode 100644 similarity index 100% rename from thirdparty/opencv/lib/amd64/opencv_ffmpeg248_64.dll rename to thirdparty/opencv/lib/amd64/opencv_ffmpeg2413_64.dll diff --git a/thirdparty/opencv/lib/amd64/opencv_java2413.dll b/thirdparty/opencv/lib/amd64/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/amd64/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_ffmpeg248.dll b/thirdparty/opencv/lib/i386/opencv_ffmpeg2413.dll old mode 100755 new mode 100644 similarity index 100% rename from thirdparty/opencv/lib/i386/opencv_ffmpeg248.dll rename to thirdparty/opencv/lib/i386/opencv_ffmpeg2413.dll diff --git a/thirdparty/opencv/lib/x86_64/opencv_java248.dll b/thirdparty/opencv/lib/i386/opencv_java2413.dll old mode 100755 new mode 100644 similarity index 50% rename from thirdparty/opencv/lib/x86_64/opencv_java248.dll rename to thirdparty/opencv/lib/i386/opencv_java2413.dll index ab989a6b11..c25b6209f8 Binary files a/thirdparty/opencv/lib/x86_64/opencv_java248.dll and b/thirdparty/opencv/lib/i386/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_java248.dll b/thirdparty/opencv/lib/i386/opencv_java248.dll deleted file mode 100755 index f1c1bc9a43..0000000000 Binary files a/thirdparty/opencv/lib/i386/opencv_java248.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/i586/opencv_ffmpeg2413_64.dll old mode 100755 new mode 100644 similarity index 100% rename from thirdparty/opencv/lib/x86_64/opencv_ffmpeg248_64.dll rename to thirdparty/opencv/lib/i586/opencv_ffmpeg2413_64.dll diff --git a/thirdparty/opencv/lib/i586/opencv_java2413.dll b/thirdparty/opencv/lib/i586/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/i586/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i586/opencv_java248.dll b/thirdparty/opencv/lib/i586/opencv_java248.dll deleted file mode 100755 index f1c1bc9a43..0000000000 Binary files a/thirdparty/opencv/lib/i586/opencv_java248.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll old mode 100755 new mode 100644 similarity index 53% rename from thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll rename to thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll index b1e70df6a3..37236e5424 Binary files a/thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll and b/thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_java2413.dll b/thirdparty/opencv/lib/i686/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/i686/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_java248.dll b/thirdparty/opencv/lib/i686/opencv_java248.dll deleted file mode 100755 index f1c1bc9a43..0000000000 Binary files a/thirdparty/opencv/lib/i686/opencv_java248.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/i586/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/x86/opencv_ffmpeg2413.dll old mode 100755 new mode 100644 similarity index 100% rename from thirdparty/opencv/lib/i586/opencv_ffmpeg248_64.dll rename to thirdparty/opencv/lib/x86/opencv_ffmpeg2413.dll diff --git a/thirdparty/opencv/lib/amd64/opencv_java248.dll b/thirdparty/opencv/lib/x86/opencv_java2413.dll old mode 100755 new mode 100644 similarity index 50% rename from thirdparty/opencv/lib/amd64/opencv_java248.dll rename to thirdparty/opencv/lib/x86/opencv_java2413.dll index ab989a6b11..c25b6209f8 Binary files a/thirdparty/opencv/lib/amd64/opencv_java248.dll and b/thirdparty/opencv/lib/x86/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/x86/opencv_java248.dll b/thirdparty/opencv/lib/x86/opencv_java248.dll deleted file mode 100755 index f1c1bc9a43..0000000000 Binary files a/thirdparty/opencv/lib/x86/opencv_java248.dll and /dev/null differ diff --git a/thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll old mode 100755 new mode 100644 similarity index 53% rename from thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll rename to thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll index b1e70df6a3..37236e5424 Binary files a/thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll and b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_java2413.dll b/thirdparty/opencv/lib/x86_64/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/x86_64/opencv_java2413.dll differ