diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 3f1e5f172d..216a8e0f82 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -15,6 +15,22 @@ 1.28.1 + + org.netbeans.api.progress.compat8 + + + + 1.46.1 + + + + org.netbeans.api.progress.nb + + + + 1.46.1 + + org.netbeans.core @@ -123,6 +139,22 @@ 7.62.1 + + org.openide.filesystems.compat8 + + + + 9.7.1 + + + + org.openide.filesystems.nb + + + + 9.7.1 + + org.openide.modules @@ -163,6 +195,14 @@ 8.15.1 + + org.openide.util.ui + + + + 9.4.1 + + org.openide.windows diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 2111f3a554..28ce44b3c7 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2013-2015 Basis Technology Corp. + * + * Copyright 2011-2016 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. @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.actions; import java.util.Collection; +import java.util.HashSet; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -26,8 +27,8 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** @@ -38,7 +39,6 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every // node in the array returns a reference to the same action object from Node.getActions(boolean). - private static AddBlackboardArtifactTagAction instance; public static synchronized AddBlackboardArtifactTagAction getInstance() { @@ -63,7 +63,14 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { - final Collection selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); + /* + * The documentation for Lookup.lookupAll() explicitly says that the + * collection it returns may contain duplicates. Within this invocation + * of addTag(), we don't want to tag the same BlackboardArtifact more + * than once, so we dedupe the BlackboardArtifacts by stuffing them into + * a HashSet. + */ + final Collection selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); new Thread(() -> { for (BlackboardArtifact artifact : selectedArtifacts) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index d504600446..e32f9df902 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2013-2015 Basis Technology Corp. + * + * Copyright 2011-2016 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. @@ -19,13 +19,13 @@ package org.sleuthkit.autopsy.actions; import java.util.Collection; +import java.util.HashSet; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -40,7 +40,6 @@ public class AddContentTagAction extends AddTagAction { // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every // node in the array returns a reference to the same action object from Node.getActions(boolean). - private static AddContentTagAction instance; public static synchronized AddContentTagAction getInstance() { @@ -63,7 +62,13 @@ public class AddContentTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { - final Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + /* + * The documentation for Lookup.lookupAll() explicitly says that the + * collection it returns may contain duplicates. Within this invocation + * of addTag(), we don't want to tag the same AbstractFile more than + * once, so we dedupe the AbstractFiles by stuffing them into a HashSet. + */ + final Collection selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); new Thread(() -> { for (AbstractFile file : selectedFiles) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java new file mode 100755 index 0000000000..15cacf490d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.actions; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.logging.Level; +import org.openide.LifecycleManager; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; + +@ActionRegistration(displayName = "Exit", iconInMenu = true) +@ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999) +@ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case") + +final public class ExitAction implements ActionListener { + + @Override + public void actionPerformed(ActionEvent e) { + try { + Case currentCase = Case.getCurrentCase(); + if (currentCase != null) { + currentCase.closeCase(); + } + } catch (Exception ex) { + Logger.getLogger(ExitAction.class.getName()).log(Level.SEVERE, "Had a problem closing the case.", ex); //NON-NLS + } finally { + LifecycleManager.getDefault().exit(); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index be492578a0..e5d922af2d 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -39,7 +39,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ @ActionRegistration( displayName = "#CTL_OpenOutputFolder", iconInMenu = true) -@ActionReference(path = "Menu/Help", position = 1850) +@ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 474b7f6ace..f0a42c0fd6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -4,7 +4,7 @@ CTL_CaseCloseAct=Close Case CTL_CaseNewAction=New Case... CTL_CasePropertiesAction=Case Properties... CTL_OpenAction=Open Case... -Menu/File/OpenRecentCase=Open Recent Case +Menu/Case/OpenRecentCase=Open Recent Case CTL_CaseDeleteAction=Delete Case OpenIDE-Module-Name=Case NewCaseVisualPanel1.jLabel1.text_1=Enter New Case Information: @@ -18,13 +18,8 @@ NewCaseVisualPanel1.caseDirTextField.text_1= CasePropertiesForm.caseDirLabel.text=Case Directory: CasePropertiesForm.crDateLabel.text=Created Date: CasePropertiesForm.caseNameLabel.text=Case Name: -CasePropertiesForm.crDateTextField.text= CasePropertiesForm.caseNameTextField.text= -CasePropertiesForm.updateCaseNameButton.text=Update -CasePropertiesForm.casePropLabel.text=Case Information -CasePropertiesForm.genInfoLabel.text=General Information -CasePropertiesForm.imgInfoLabel.text=Images Information -CasePropertiesForm.OKButton.text=OK +CasePropertiesForm.updateCaseNameButton.text=Update Name CasePropertiesForm.deleteCaseButton.text=Delete Case CueBannerPanel.autopsyLogo.text= CueBannerPanel.createNewLabel.text=Create New Case @@ -38,8 +33,6 @@ OpenRecentCasePanel.cancelButton.text=Cancel OpenRecentCasePanel.jLabel1.text=Recent Cases CasePropertiesForm.caseNumberLabel.text=Case Number: CasePropertiesForm.examinerLabel.text=Examiner: -CasePropertiesForm.caseNumberTextField.text= -CasePropertiesForm.examinerTextField.text= NewCaseVisualPanel2.caseNumberTextField.text= NewCaseVisualPanel2.examinerLabel.text=Examiner: NewCaseVisualPanel2.caseNumberLabel.text=Case Number: @@ -231,9 +224,7 @@ NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user NewCaseVisualPanel1.caseTypeLabel.text=Case Type: CasePropertiesForm.lbDbType.text=Case Type: -CasePropertiesForm.tbDbType.text= CasePropertiesForm.lbDbName.text=Database Name: -CasePropertiesForm.tbDbName.text= SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user! SingleUserCaseConverter.NonUniqueDatabaseName=Database name not unique. @@ -245,3 +236,10 @@ CasePropertiesForm.imagesTable.columnModel.title1=Remove CasePropertiesForm.imagesTable.columnModel.title0=Path LocalFilesPanel.jButton1.text=Change LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default +CaseInformationPanel.jPanel1.TabConstraints.tabTitle=tab1 +CasePropertiesForm.caseNumberField.text= +CasePropertiesForm.examinerField.text= +CasePropertiesForm.crDateField.text= +CasePropertiesForm.caseDirField.text= +CasePropertiesForm.caseTypeField.text= +CasePropertiesForm.dbNameField.text= diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 4a60a9a404..b033efa9ca 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -4,7 +4,7 @@ CTL_CaseCloseAct=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b CTL_CaseNewAction=\u65b0\u898f\u30b1\u30fc\u30b9... CTL_CasePropertiesAction=\u30b1\u30fc\u30b9\u30d7\u30ed\u30d1\u30c6\u30a3... CTL_OpenAction=\u30b1\u30fc\u30b9\u3092\u958b\u304f... -Menu/File/OpenRecentCase=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f +Menu/Case/OpenRecentCase=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f CTL_CaseDeleteAction=\u30b1\u30fc\u30b9\u3092\u524a\u9664 OpenIDE-Module-Name=\u30b1\u30fc\u30b9 NewCaseVisualPanel1.jLabel1.text_1=\u65b0\u898f\u30b1\u30fc\u30b9\u60c5\u5831\u3092\u5165\u529b\uff1a @@ -16,10 +16,6 @@ CasePropertiesForm.caseDirLabel.text=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\ CasePropertiesForm.crDateLabel.text=\u4f5c\u6210\u65e5\uff1a CasePropertiesForm.caseNameLabel.text=\u30b1\u30fc\u30b9\u540d\uff1a CasePropertiesForm.updateCaseNameButton.text=\u66f4\u65b0 -CasePropertiesForm.casePropLabel.text=\u30b1\u30fc\u30b9\u60c5\u5831 -CasePropertiesForm.genInfoLabel.text=\u4e00\u822c\u60c5\u5831 -CasePropertiesForm.imgInfoLabel.text=\u30a4\u30e1\u30fc\u30b8\u60c5\u5831 -CasePropertiesForm.OKButton.text=OK CasePropertiesForm.deleteCaseButton.text=\u30b1\u30fc\u30b9\u3092\u524a\u9664 CueBannerPanel.createNewLabel.text=\u65b0\u898f\u30b1\u30fc\u30b9\u3092\u4f5c\u6210 CueBannerPanel.openLabel.text=\u65e2\u5b58\u30b1\u30fc\u30b9\u3092\u958b\u304f diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 1cd1f9313a..75defea2f3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -75,7 +75,7 @@ import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.RunIngestAction; +import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; @@ -1519,27 +1519,20 @@ public class Case implements SleuthkitCase.ErrorObserver { if (RuntimeProperties.coreComponentsAreActive()) { // enable these menus - CallableSystemAction.get(AddImageAction.class).setEnabled(true); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); - CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu - CallableSystemAction.get(RunIngestAction.class).setEnabled(true); - - if (toChangeTo.hasData()) { - // open all top components - SwingUtilities.invokeLater(() -> { - CoreComponentControl.openCoreWindows(); - }); - } else { - // close all top components - SwingUtilities.invokeLater(() -> { - CoreComponentControl.closeCoreWindows(); - }); - } - } - - if (RuntimeProperties.coreComponentsAreActive()) { SwingUtilities.invokeLater(() -> { + CallableSystemAction.get(AddImageAction.class).setEnabled(true); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); + CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + + if (toChangeTo.hasData()) { + // open all top components + CoreComponentControl.openCoreWindows(); + } else { + // close all top components + CoreComponentControl.closeCoreWindows(); + } updateMainWindowTitle(currentCase.getName()); }); } else { @@ -1550,9 +1543,9 @@ public class Case implements SleuthkitCase.ErrorObserver { } } else { // case is closed - if (RuntimeProperties.coreComponentsAreActive()) { + SwingUtilities.invokeLater(() -> { + if (RuntimeProperties.coreComponentsAreActive()) { - SwingUtilities.invokeLater(() -> { // close all top components first CoreComponentControl.closeCoreWindows(); @@ -1561,16 +1554,11 @@ public class Case implements SleuthkitCase.ErrorObserver { CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu - CallableSystemAction.get(RunIngestAction.class).setEnabled(false); - }); - } + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false); + } - //clear pending notifications - SwingUtilities.invokeLater(() -> { + //clear pending notifications MessageNotifyUtil.Notify.clear(); - }); - - SwingUtilities.invokeLater(() -> { Frame f = WindowManager.getDefault().getMainWindow(); f.setTitle(Case.getAppName()); // set the window name to just application name }); @@ -1666,7 +1654,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * Deletes reports from the case. * * @param reports Collection of Report to be deleted from the case. - * @param deleteFromDisk No longer supported - ignored. + * @param deleteFromDisk No longer supported - ignored. * * @throws TskCoreException * @deprecated Use deleteReports(Collection reports) @@ -1676,5 +1664,5 @@ public class Case implements SleuthkitCase.ErrorObserver { public void deleteReports(Collection reports, boolean deleteFromDisk) throws TskCoreException { deleteReports(reports); } - -} + +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.form new file mode 100755 index 0000000000..755d1dca70 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.form @@ -0,0 +1,63 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java new file mode 100755 index 0000000000..2e7711d8de --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -0,0 +1,90 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.util.Map; +import org.openide.util.NbBundle.Messages; + +/** + * + * @author oliver + */ +public class CaseInformationPanel extends javax.swing.JPanel { + + /** + * Creates new form CaseInformationPanel + */ + public CaseInformationPanel() { + initComponents(); + customizeComponents(); + } + + @Messages({"CaseInformationPanel.caseDetails.header=Case Details"}) + private void customizeComponents() { + try { + Case currentCase = Case.getCurrentCase(); + String crDate = currentCase.getCreatedDate(); + String caseDir = currentCase.getCaseDirectory(); + + // put the image paths information into hashmap + Map imgPaths = Case.getImagePaths(currentCase.getSleuthkitCase()); + CasePropertiesForm cpf = new CasePropertiesForm(currentCase, crDate, caseDir, imgPaths); + cpf.setSize(cpf.getPreferredSize()); + this.tabbedPane.addTab(Bundle.CaseInformationPanel_caseDetails_header(), cpf); + } catch (CaseMetadata.CaseMetadataException ex) { + //TOLOG + } + } + + + /** + * 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() { + + jPanel1 = new javax.swing.JPanel(); + tabbedPane = new javax.swing.JTabbedPane(); + + tabbedPane.setPreferredSize(new java.awt.Dimension(420, 200)); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addComponent(tabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 228, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel jPanel1; + private javax.swing.JTabbedPane tabbedPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java index 5cae020845..b54d342a44 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java @@ -20,9 +20,6 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Dimension; import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Map; import java.util.logging.Level; import javax.swing.Action; import javax.swing.JDialog; @@ -61,32 +58,16 @@ final class CasePropertiesAction extends CallableSystemAction { // create the popUp window for it String title = NbBundle.getMessage(this.getClass(), "CasePropertiesAction.window.title"); final JFrame frame = new JFrame(title); - popUpWindow = new JDialog(frame, title, true); // to make the popUp Window to be modal - - // get the information that needed - Case currentCase = Case.getCurrentCase(); - String crDate = currentCase.getCreatedDate(); - String caseDir = currentCase.getCaseDirectory(); - - // put the image paths information into hashmap - Map imgPaths = Case.getImagePaths(currentCase.getSleuthkitCase()); + popUpWindow = new JDialog(frame, title, false); // to make the popUp Window to be modal // create the case properties form - CasePropertiesForm cpf = new CasePropertiesForm(currentCase, crDate, caseDir, imgPaths); - - // add the command to close the window to the button on the Case Properties form / panel - cpf.setOKButtonActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - popUpWindow.dispose(); - } - }); + CaseInformationPanel caseInformationPanel = new CaseInformationPanel(); // add the case properties form / panel to the popup window - popUpWindow.add(cpf); + popUpWindow.add(caseInformationPanel); + popUpWindow.setResizable(true); popUpWindow.pack(); - popUpWindow.setResizable(false); + // set the location of the popUp Window on the center of the screen Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form index 14b675bc54..9ceccdad62 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.form @@ -33,437 +33,307 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - -
+ + - - - - - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CasePropertiesForm.imagesTable.path" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - - - - - - - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CasePropertiesForm.imagesTable.remove" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java index 537cd0f3d2..80605bc8c1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesForm.java @@ -17,36 +17,33 @@ * limitations under the License. */ -/* + /* * CasePropertiesForm.java * * Created on Mar 14, 2011, 1:48:20 PM */ package org.sleuthkit.autopsy.casemodule; -import java.nio.file.Paths; -import java.awt.event.ActionListener; import java.io.File; import java.util.Map; import java.util.logging.Level; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.table.DefaultTableModel; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.coreutils.Logger; /** * The form where user can change / update the properties of the current case * metadata. */ class CasePropertiesForm extends javax.swing.JPanel { - + private static final long serialVersionUID = 1L; - + private Case current = null; private static JPanel caller; // panel for error @@ -55,15 +52,15 @@ class CasePropertiesForm extends javax.swing.JPanel { private String shrinkPath(String path, int targetLength) { if (path.length() > targetLength) { String fill = "..."; - + int partsLength = targetLength - fill.length(); - + String front = path.substring(0, partsLength / 4); int frontSep = front.lastIndexOf(File.separatorChar); if (frontSep != -1) { front = front.substring(0, frontSep + 1); } - + String back = path.substring(partsLength * 3 / 4); int backSep = back.indexOf(File.separatorChar); if (backSep != -1) { @@ -81,52 +78,35 @@ class CasePropertiesForm extends javax.swing.JPanel { CasePropertiesForm(Case currentCase, String crDate, String caseDir, Map imgPaths) throws CaseMetadata.CaseMetadataException { initComponents(); caseNameTextField.setText(currentCase.getName()); - caseNumberTextField.setText(currentCase.getNumber()); - examinerTextField.setText(currentCase.getExaminer()); - crDateTextField.setText(crDate); - caseDirTextArea.setText(caseDir); + String caseNumber = currentCase.getNumber(); + if (!caseNumber.equals("")) { + caseNumberField.setText(caseNumber); + } else { + caseNumberField.setText("N/A"); + } + String examiner = currentCase.getExaminer(); + if (!examiner.equals("")) { + examinerField.setText(examiner); + } else { + examinerField.setText("N/A"); + } + crDateField.setText(crDate); + caseDirField.setText(caseDir); current = currentCase; - + CaseMetadata caseMetadata = currentCase.getCaseMetadata(); if (caseMetadata.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { - tbDbName.setText(caseMetadata.getCaseDatabasePath()); + dbNameField.setText(caseMetadata.getCaseDatabasePath()); } else { - tbDbName.setText(caseMetadata.getCaseDatabaseName()); + dbNameField.setText(caseMetadata.getCaseDatabaseName()); } Case.CaseType caseType = caseMetadata.getCaseType(); - tbDbType.setText(caseType.getLocalizedDisplayName()); + caseTypeField.setText(caseType.getLocalizedDisplayName()); if (caseType == Case.CaseType.SINGLE_USER_CASE) { deleteCaseButton.setEnabled(true); } else { deleteCaseButton.setEnabled(false); } - - int totalImages = imgPaths.size(); - - // create the headers and add all the rows - // Header names are internationalized via the generated code, do not overwrite. - String[] headers = {imagesTable.getColumnName(0), - imagesTable.getColumnName(1)}; - String[][] rows = new String[totalImages][]; - - int i = 0; - for (long key : imgPaths.keySet()) { - String path = imgPaths.get(key); - String shortenPath = shrinkPath(path, 70); - rows[i] = new String[]{shortenPath}; - i++; - } - - // create the table inside with the imgPaths information - DefaultTableModel model = new DefaultTableModel(rows, headers) { - @Override - // make the cells in the FileContentTable "read only" - public boolean isCellEditable(int row, int column) { - return false; - //return column == lastColumn; // make the last column (Remove button), only the editable - } - }; - imagesTable.setModel(model); } /** @@ -149,38 +129,28 @@ class CasePropertiesForm extends javax.swing.JPanel { jScrollPane1 = new javax.swing.JScrollPane(); jTextArea1 = new javax.swing.JTextArea(); - casePropLabel = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); caseNameLabel = new javax.swing.JLabel(); crDateLabel = new javax.swing.JLabel(); caseDirLabel = new javax.swing.JLabel(); - crDateTextField = new javax.swing.JTextField(); caseNameTextField = new javax.swing.JTextField(); updateCaseNameButton = new javax.swing.JButton(); - genInfoLabel = new javax.swing.JLabel(); - imgInfoLabel = new javax.swing.JLabel(); - OKButton = new javax.swing.JButton(); - imagesTableScrollPane = new javax.swing.JScrollPane(); - imagesTable = new javax.swing.JTable(); - jScrollPane2 = new javax.swing.JScrollPane(); - caseDirTextArea = new javax.swing.JTextArea(); deleteCaseButton = new javax.swing.JButton(); caseNumberLabel = new javax.swing.JLabel(); examinerLabel = new javax.swing.JLabel(); - caseNumberTextField = new javax.swing.JTextField(); - examinerTextField = new javax.swing.JTextField(); lbDbType = new javax.swing.JLabel(); - tbDbType = new javax.swing.JTextField(); lbDbName = new javax.swing.JLabel(); - tbDbName = new javax.swing.JTextField(); + caseNumberField = new javax.swing.JLabel(); + examinerField = new javax.swing.JLabel(); + crDateField = new javax.swing.JLabel(); + caseDirField = new javax.swing.JLabel(); + dbNameField = new javax.swing.JLabel(); + caseTypeField = new javax.swing.JLabel(); jTextArea1.setColumns(20); jTextArea1.setRows(5); jScrollPane1.setViewportView(jTextArea1); - casePropLabel.setFont(casePropLabel.getFont().deriveFont(casePropLabel.getFont().getStyle() | java.awt.Font.BOLD, 24)); - casePropLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - casePropLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.casePropLabel.text")); // NOI18N - caseNameLabel.setFont(caseNameLabel.getFont().deriveFont(caseNameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); caseNameLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameLabel.text")); // NOI18N @@ -190,12 +160,13 @@ class CasePropertiesForm extends javax.swing.JPanel { caseDirLabel.setFont(caseDirLabel.getFont().deriveFont(caseDirLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); caseDirLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseDirLabel.text")); // NOI18N - crDateTextField.setEditable(false); - crDateTextField.setFont(crDateTextField.getFont().deriveFont(crDateTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - crDateTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.crDateTextField.text")); // NOI18N - caseNameTextField.setFont(caseNameTextField.getFont().deriveFont(caseNameTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); caseNameTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNameTextField.text")); // NOI18N + caseNameTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + caseNameTextFieldActionPerformed(evt); + } + }); updateCaseNameButton.setFont(updateCaseNameButton.getFont().deriveFont(updateCaseNameButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); updateCaseNameButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.updateCaseNameButton.text")); // NOI18N @@ -205,53 +176,6 @@ class CasePropertiesForm extends javax.swing.JPanel { } }); - genInfoLabel.setFont(genInfoLabel.getFont().deriveFont(genInfoLabel.getFont().getStyle() | java.awt.Font.BOLD, 14)); - genInfoLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.genInfoLabel.text")); // NOI18N - - imgInfoLabel.setFont(imgInfoLabel.getFont().deriveFont(imgInfoLabel.getFont().getStyle() | java.awt.Font.BOLD, 14)); - imgInfoLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.imgInfoLabel.text")); // NOI18N - - OKButton.setFont(OKButton.getFont().deriveFont(OKButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - OKButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.OKButton.text")); // NOI18N - - imagesTableScrollPane.setFont(imagesTableScrollPane.getFont().deriveFont(imagesTableScrollPane.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - - imagesTable.setFont(imagesTable.getFont().deriveFont(imagesTable.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - imagesTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - "Path", "Remove" - } - ) { - boolean[] canEdit = new boolean [] { - false, true - }; - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return canEdit [columnIndex]; - } - }); - imagesTable.setShowHorizontalLines(false); - imagesTable.setShowVerticalLines(false); - imagesTable.getTableHeader().setReorderingAllowed(false); - imagesTable.setUpdateSelectionOnSort(false); - imagesTableScrollPane.setViewportView(imagesTable); - if (imagesTable.getColumnModel().getColumnCount() > 0) { - imagesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.imagesTable.columnModel.title0")); // NOI18N - imagesTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.imagesTable.columnModel.title1")); // NOI18N - } - - jScrollPane2.setFont(jScrollPane2.getFont().deriveFont(jScrollPane2.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - - caseDirTextArea.setEditable(false); - caseDirTextArea.setBackground(new java.awt.Color(240, 240, 240)); - caseDirTextArea.setColumns(20); - caseDirTextArea.setRows(1); - caseDirTextArea.setRequestFocusEnabled(false); - jScrollPane2.setViewportView(caseDirTextArea); - deleteCaseButton.setFont(deleteCaseButton.getFont().deriveFont(deleteCaseButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); deleteCaseButton.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.deleteCaseButton.text")); // NOI18N deleteCaseButton.addActionListener(new java.awt.event.ActionListener() { @@ -266,117 +190,119 @@ class CasePropertiesForm extends javax.swing.JPanel { examinerLabel.setFont(examinerLabel.getFont().deriveFont(examinerLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); examinerLabel.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerLabel.text")); // NOI18N - caseNumberTextField.setEditable(false); - caseNumberTextField.setFont(caseNumberTextField.getFont().deriveFont(caseNumberTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - caseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNumberTextField.text")); // NOI18N - - examinerTextField.setEditable(false); - examinerTextField.setFont(examinerTextField.getFont().deriveFont(examinerTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - examinerTextField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerTextField.text")); // NOI18N - lbDbType.setFont(lbDbType.getFont().deriveFont(lbDbType.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); lbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbType.text")); // NOI18N - tbDbType.setEditable(false); - tbDbType.setFont(tbDbType.getFont().deriveFont(tbDbType.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - tbDbType.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.tbDbType.text")); // NOI18N - lbDbName.setFont(lbDbName.getFont().deriveFont(lbDbName.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); lbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.lbDbName.text")); // NOI18N - tbDbName.setEditable(false); - tbDbName.setFont(tbDbName.getFont().deriveFont(tbDbName.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); - tbDbName.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.tbDbName.text")); // NOI18N + caseNumberField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseNumberField.text")); // NOI18N + + examinerField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.examinerField.text")); // NOI18N + + crDateField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.crDateField.text")); // NOI18N + + caseDirField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseDirField.text")); // NOI18N + caseDirField.setMinimumSize(new java.awt.Dimension(25, 14)); + + dbNameField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.dbNameField.text")); // NOI18N + dbNameField.setMinimumSize(new java.awt.Dimension(25, 14)); + + caseTypeField.setText(org.openide.util.NbBundle.getMessage(CasePropertiesForm.class, "CasePropertiesForm.caseTypeField.text")); // NOI18N + caseTypeField.setMaximumSize(new java.awt.Dimension(1, 0)); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbDbName) + .addComponent(lbDbType) + .addComponent(caseDirLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(caseDirField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(caseTypeField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(dbNameField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(crDateLabel) + .addComponent(examinerLabel) + .addComponent(caseNumberLabel)) + .addGap(18, 18, 18) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(examinerField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(caseNumberField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(caseNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 243, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(updateCaseNameButton, javax.swing.GroupLayout.PREFERRED_SIZE, 104, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(crDateField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(deleteCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(caseNameLabel) + .addContainerGap(392, Short.MAX_VALUE))) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(caseNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(updateCaseNameButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(caseNumberField, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(examinerField, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(examinerLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(crDateField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(crDateLabel, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(caseDirLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(caseDirField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(caseTypeField, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDbType)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbDbName) + .addComponent(dbNameField, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(caseNumberLabel)) + .addGap(10, 10, 10) + .addComponent(deleteCaseButton) + .addContainerGap()) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(caseNameLabel) + .addContainerGap(173, Short.MAX_VALUE))) + ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(casePropLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE) - .addComponent(imagesTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseNameLabel) - .addComponent(caseNumberLabel) - .addComponent(examinerLabel) - .addComponent(caseDirLabel) - .addComponent(crDateLabel) - .addComponent(lbDbType)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(caseNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) - .addComponent(caseNumberTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) - .addComponent(examinerTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) - .addComponent(crDateTextField) - .addComponent(jScrollPane2) - .addComponent(tbDbType) - .addComponent(tbDbName)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(deleteCaseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(updateCaseNameButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(genInfoLabel) - .addComponent(imgInfoLabel) - .addGroup(layout.createSequentialGroup() - .addGap(181, 181, 181) - .addComponent(OKButton, javax.swing.GroupLayout.PREFERRED_SIZE, 78, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(lbDbName)) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(casePropLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(genInfoLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(caseNameLabel) - .addComponent(caseNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(updateCaseNameButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(caseNumberLabel) - .addComponent(caseNumberTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(examinerLabel) - .addComponent(examinerTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(crDateLabel) - .addComponent(crDateTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(caseDirLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(tbDbType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbDbType)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbDbName) - .addComponent(tbDbName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addComponent(imgInfoLabel)) - .addGroup(layout.createSequentialGroup() - .addGap(9, 9, 9) - .addComponent(deleteCaseButton))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(imagesTableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 170, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(OKButton) - .addContainerGap()) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) ); }// //GEN-END:initComponents @@ -399,38 +325,36 @@ class CasePropertiesForm extends javax.swing.JPanel { NbBundle.getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.empty.title"), JOptionPane.ERROR_MESSAGE); + } else // check if case Name contain one of this following symbol: + // \ / : * ? " < > | + if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":") + || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"") + || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) { + String errorMsg = NbBundle + .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg"); + JOptionPane.showMessageDialog(caller, errorMsg, + NbBundle.getMessage(this.getClass(), + "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"), + JOptionPane.ERROR_MESSAGE); } else { - // check if case Name contain one of this following symbol: - // \ / : * ? " < > | - if (newCaseName.contains("\\") || newCaseName.contains("/") || newCaseName.contains(":") - || newCaseName.contains("*") || newCaseName.contains("?") || newCaseName.contains("\"") - || newCaseName.contains("<") || newCaseName.contains(">") || newCaseName.contains("|")) { - String errorMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg"); - JOptionPane.showMessageDialog(caller, errorMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title"), - JOptionPane.ERROR_MESSAGE); - } else { - // ask for the confirmation first - String confMsg = NbBundle - .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName, - newCaseName); - NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg, - NbBundle.getMessage(this.getClass(), - "CasePropertiesForm.updateCaseName.confMsg.title"), - NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - d.setValue(NotifyDescriptor.NO_OPTION); - - Object res = DialogDisplayer.getDefault().notify(d); - if (res != null && res == DialogDescriptor.YES_OPTION) { - // if user select "Yes" - String oldPath = current.getCaseMetadata().getFilePath().toString(); - try { - current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); - } catch (Exception ex) { - Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS - } + // ask for the confirmation first + String confMsg = NbBundle + .getMessage(this.getClass(), "CasePropertiesForm.updateCaseName.confMsg.msg", oldCaseName, + newCaseName); + NotifyDescriptor d = new NotifyDescriptor.Confirmation(confMsg, + NbBundle.getMessage(this.getClass(), + "CasePropertiesForm.updateCaseName.confMsg.title"), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); + d.setValue(NotifyDescriptor.NO_OPTION); + + Object res = DialogDisplayer.getDefault().notify(d); + if (res != null && res == DialogDescriptor.YES_OPTION) { + // if user select "Yes" + String oldPath = current.getCaseMetadata().getFilePath().toString(); + try { + current.updateCaseName(oldCaseName, oldPath, newCaseName, oldPath); + } catch (Exception ex) { + Logger.getLogger(CasePropertiesForm.class.getName()).log(Level.WARNING, "Error: problem updating case name.", ex); //NON-NLS } } } @@ -441,40 +365,30 @@ class CasePropertiesForm extends javax.swing.JPanel { CallableSystemAction.get(CaseDeleteAction.class).actionPerformed(evt); }//GEN-LAST:event_deleteCaseButtonActionPerformed - /** - * Sets the listener for the OK button - * - * @param e The action listener - */ - public void setOKButtonActionListener(ActionListener e) { - OKButton.addActionListener(e); - } + private void caseNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseNameTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_caseNameTextFieldActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton OKButton; + private javax.swing.JLabel caseDirField; private javax.swing.JLabel caseDirLabel; - private javax.swing.JTextArea caseDirTextArea; private javax.swing.JLabel caseNameLabel; private javax.swing.JTextField caseNameTextField; + private javax.swing.JLabel caseNumberField; private javax.swing.JLabel caseNumberLabel; - private javax.swing.JTextField caseNumberTextField; - private javax.swing.JLabel casePropLabel; + private javax.swing.JLabel caseTypeField; + private javax.swing.JLabel crDateField; private javax.swing.JLabel crDateLabel; - private javax.swing.JTextField crDateTextField; + private javax.swing.JLabel dbNameField; private javax.swing.JButton deleteCaseButton; + private javax.swing.JLabel examinerField; private javax.swing.JLabel examinerLabel; - private javax.swing.JTextField examinerTextField; - private javax.swing.JLabel genInfoLabel; - private javax.swing.JTable imagesTable; - private javax.swing.JScrollPane imagesTableScrollPane; - private javax.swing.JLabel imgInfoLabel; + private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JScrollPane jScrollPane2; private javax.swing.JTextArea jTextArea1; private javax.swing.JLabel lbDbName; private javax.swing.JLabel lbDbType; - private javax.swing.JTextField tbDbName; - private javax.swing.JTextField tbDbType; private javax.swing.JButton updateCaseNameButton; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java index d5064e3b4e..70b10b0f0e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java @@ -25,17 +25,30 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; /** - * Represents the blackboard, a place where artifacts and their attributes are - * posted. + * A representation of the blackboard, a place where artifacts and their + * attributes are posted. * * NOTE: This API of this class is under development. */ public final class Blackboard implements Closeable { + private SleuthkitCase caseDb; + + /** + * Constructs a representation of the blackboard, a place where artifacts + * and their attributes are posted. + * + * @param casedb The case database. + */ + Blackboard(SleuthkitCase casedb) { + this.caseDb = casedb; + } + /** * Indexes the text associated with the an artifact. * @@ -43,7 +56,10 @@ public final class Blackboard implements Closeable { * * @throws BlackboardException If there is a problem indexing the artifact. */ - public void indexArtifact(BlackboardArtifact artifact) throws BlackboardException { + public synchronized void indexArtifact(BlackboardArtifact artifact) throws BlackboardException { + if (null == caseDb) { + throw new BlackboardException("Blackboard has been closed"); + } KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class); if (null == searchService) { throw new BlackboardException("Keyword search service not found"); @@ -67,12 +83,15 @@ public final class Blackboard implements Closeable { * @throws BlackboardBlackboardException If there is a problem getting or * adding the artifact type. */ - public BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName) throws BlackboardException { + public synchronized BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName) throws BlackboardException { + if (null == caseDb) { + throw new BlackboardException("Blackboard has been closed"); + } try { - return Case.getCurrentCase().getSleuthkitCase().addBlackboardArtifactType(typeName, displayName); + return caseDb.addBlackboardArtifactType(typeName, displayName); } catch (TskDataException typeExistsEx) { try { - return Case.getCurrentCase().getSleuthkitCase().getArtifactType(typeName); + return caseDb.getArtifactType(typeName); } catch (TskCoreException ex) { throw new BlackboardException("Failed to get or add artifact type", ex); } @@ -94,12 +113,15 @@ public final class Blackboard implements Closeable { * @throws BlackboardBlackboardException If there is a problem getting or * adding the attribute type. */ - public BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException { + public synchronized BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException { + if (null == caseDb) { + throw new BlackboardException("Blackboard has been closed"); + } try { - return Case.getCurrentCase().getSleuthkitCase().addArtifactAttributeType(typeName, valueType, displayName); + return caseDb.addArtifactAttributeType(typeName, valueType, displayName); } catch (TskDataException typeExistsEx) { try { - return Case.getCurrentCase().getSleuthkitCase().getAttributeType(typeName); + return caseDb.getAttributeType(typeName); } catch (TskCoreException ex) { throw new BlackboardException("Failed to get or add attribute type", ex); } @@ -109,13 +131,16 @@ public final class Blackboard implements Closeable { } /** - * Cloese this blackboard and releases any resources associated with it. - * @throws IOException + * Closes the blackboard. + * + * @throws IOException If there is a problem closing the blackboard. */ @Override - public void close() throws IOException { + public synchronized void close() throws IOException { + caseDb = null; } + /** * A blackboard exception. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index 9353ca914b..1a3be9454f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -1,16 +1,3 @@ -FileManager.findFiles.exception.msg=Attempted to use FileManager after it was closed. -FileManager.findFiles2.exception.msg=Attempted to use FileManager after it was closed. -FileManager.findFiles3.exception.msg=Attempted to use FileManager after it was closed. -FileManager.openFiles.exception.msg=Attempted to use FileManager after it was closed. -FileManager.addDerivedFile.exception.msg=Attempted to use FileManager after it was closed. -FileManager.addCarvedFile.exception.msg=Attempted to use FileManager after it was closed. -FileManager.addLocalFilesDirs.exception.notReadable.msg=One of the local files/dirs to add is not readable\: {0}, aborting the process before any files added -FileManager.addLocalFilesDirs.exception.cantAdd.msg=One of the local files/dirs could not be added\: {0} -FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg=Error creating local file set dir\: {0} -FileManager.addLocalDirInt.exception.closed.msg=Attempted to use FileManager after it was closed. -FileManager.addLocalDirInt.exception.doesntExist.msg=Attempted to add a local dir that does not exist\: {0} -FileManager.addLocalDirInt.exception.notReadable.msg=Attempted to add a local dir that is not readable\: {0} -FileManager.addLocalDirInt2.exception.closed.msg=Attempted to use FileManager after it was closed. TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} out of content size range (0 - {1}) TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1}) TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties index 956a5e6169..ca8ed3431a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties @@ -1,16 +1,3 @@ -FileManager.findFiles.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.findFiles2.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.findFiles3.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.openFiles.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.addDerivedFile.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.addCarvedFile.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.addLocalFilesDirs.exception.notReadable.msg=\u8ffd\u52a0\u3059\u308b\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u4e2d\u306b\u8aad\u307f\u53d6\u308c\u306a\u3044\u3082\u306e\u304c\uff11\u500b\u3042\u308a\u307e\u3059\uff1a{0}\u3001\u30d5\u30a1\u30a4\u30eb\u304c\u8ffd\u52a0\u3055\u308c\u308b\u524d\u306b\u51e6\u7406\u3092\u4e2d\u6b62\u3057\u307e\u3059 -FileManager.addLocalFilesDirs.exception.cantAdd.msg=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\uff11\u500b\u306f\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0} -FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u8d77\u3053\u308a\u307e\u3057\u305f\uff1a {0} -FileManager.addLocalDirInt.exception.closed.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 -FileManager.addLocalDirInt.exception.doesntExist.msg=\u5b58\u5728\u3057\u306a\u3044\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u8ffd\u52a0\u3092\u8a66\u307f\u307e\u3057\u305f\: {0} -FileManager.addLocalDirInt.exception.notReadable.msg=\u8aad\u307f\u53d6\u308a\u3067\u304d\u306a\u3044\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u8ffd\u52a0\u3092\u8a66\u307f\u307e\u3057\u305f\: {0} -FileManager.addLocalDirInt2.exception.closed.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002 TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059 TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059 TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index 5b958cd5a7..2787924d93 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -1,19 +1,19 @@ /* * * Autopsy Forensic Browser - * + * * Copyright 2011-2016 Basis Technology Corp. - * + * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: 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. @@ -25,10 +25,9 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.logging.Level; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; @@ -36,67 +35,102 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskFileRange; import org.sleuthkit.datamodel.VirtualDirectory; -import org.sleuthkit.datamodel.CarvedFileContainer; import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskDataException; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.datamodel.CarvingResult; /** - * Abstraction to facilitate access to localFiles and directories. + * A manager that provides methods for retrieving files from the current case + * and for adding local files, carved files, and derived files to the current + * case. */ public class FileManager implements Closeable { - private SleuthkitCase tskCase; - private static final Logger logger = Logger.getLogger(FileManager.class.getName()); - private volatile int curNumFileSets; //current number of filesets (root virt dir objects) - - public FileManager(SleuthkitCase tskCase) { - this.tskCase = tskCase; - init(); - } + private SleuthkitCase caseDb; /** - * initialize the file manager for the case + * Constructs a manager that provides methods for retrieving files from the + * current case and for adding local files, carved files, and derived files + * to the current case. + * + * @param caseDb The case database. */ - private synchronized void init() { - //get the number of local file sets in db - List virtRoots; - curNumFileSets = 0; - try { - virtRoots = tskCase.getVirtualDirectoryRoots(); - for (VirtualDirectory vd : virtRoots) { - if (vd.getName().startsWith(VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX)) { - ++curNumFileSets; - } - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error initializing FileManager and getting number of local file sets"); //NON-NLS - } - + public FileManager(SleuthkitCase caseDb) { + this.caseDb = caseDb; } /** - * Finds a set of localFiles that meets the name criteria in all data - * sources in the current case. + * Finds all files with types that match one of a collection of MIME types. * - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). + * @param mimeTypes The MIME types. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches the given fileName + * @return The files. + * + * @throws TskCoreException If there is a problem querying the case + * database. + */ + public synchronized List findFilesByMimeType(Collection mimeTypes) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } + return caseDb.findAllFilesWhere(createFileTypeInCondition(mimeTypes)); + } + + /** + * Finds all files in a given data source (image, local/logical files set, + * etc.) with types that match one of a collection of MIME types. + * + * @param dataSource The data source. + * @param mimeTypes The MIME types. + * + * @return The files. + * + * @throws TskCoreException If there is a problem querying the case + * database. + */ + public synchronized List findFilesByMimeType(Content dataSource, Collection mimeTypes) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } + return caseDb.findAllFilesWhere("data_source_obj_id = " + dataSource.getId() + " AND " + createFileTypeInCondition(mimeTypes)); + } + + /** + * Converts a list of MIME types into an SQL "mime_type IN" condition. + * + * @param mimeTypes The MIIME types. + * + * @return The condition string. + */ + private static String createFileTypeInCondition(Collection mimeTypes) { + String types = StringUtils.join(mimeTypes, "', '"); + return "mime_type IN ('" + types + "')"; + } + + /** + * Finds all files and directories with a given file name. The name search + * is for full or partial matches and is case insensitive (a case + * insensitive SQL LIKE clause is used to query the case database). + * + * @param fileName The full or partial file name. + * + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ public synchronized List findFiles(String fileName) throws TskCoreException { - List result = new ArrayList<>(); - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles.exception.msg")); + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - List dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { result.addAll(findFiles(dataSource, fileName)); } @@ -104,290 +138,221 @@ public class FileManager implements Closeable { } /** - * Finds a set of localFiles that meets the name criteria in all data - * sources in the current case. + * Finds all files and directories with a given file name and parent file or + * directory name. The name searches are for full or partial matches and are + * case insensitive (a case insensitive SQL LIKE clause is used to query the + * case database). * - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). - * @param dirName Pattern of the name of the parent directory to use as the - * root of the search (case insensitive, used in LIKE SQL - * statement). + * @param fileName The full or partial file name. + * @param parentName The full or partial parent file or directory name. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches fileName and whose parent directory contains dirName. + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ - public synchronized List findFiles(String fileName, String dirName) throws TskCoreException { - List result = new ArrayList<>(); - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles2.exception.msg")); + public synchronized List findFiles(String fileName, String parentName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - List dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { - result.addAll(findFiles(dataSource, fileName, dirName)); + result.addAll(findFiles(dataSource, fileName, parentName)); } return result; } /** - * Finds a set of localFiles that meets the name criteria in all data - * sources in the current case. + * Finds all files and directories with a given file name and parent file or + * directory. The name search is for full or partial matches and is case + * insensitive (a case insensitive SQL LIKE clause is used to query the case + * database). * - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). - * @param parentFile Object of root/parent directory to restrict search to. + * @param fileName The full or partial file name. + * @param parent The parent file or directory. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches fileName and that were inside a directory described by - * parentFsContent. + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ - public synchronized List findFiles(String fileName, AbstractFile parentFile) throws TskCoreException { - List result = new ArrayList<>(); - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles3.exception.msg")); + public synchronized List findFiles(String fileName, AbstractFile parent) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - List dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { - result.addAll(findFiles(dataSource, fileName, parentFile)); + result.addAll(findFiles(dataSource, fileName, parent)); } return result; } /** - * Finds a set of localFiles that meets the name criteria. + * Finds all files and directories with a given file name in a given data + * source (image, local/logical files set, etc.). The name search is for + * full or partial matches and is case insensitive (a case insensitive SQL + * LIKE clause is used to query the case database). * - * @param dataSource Root data source to limit search results to (Image, - * VirtualDirectory, etc.). - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). + * @param dataSource The data source. + * @param fileName The full or partial file name. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches the given fileName + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ public synchronized List findFiles(Content dataSource, String fileName) throws TskCoreException { - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles.exception.msg")); + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - return tskCase.findFiles(dataSource, fileName); + return caseDb.findFiles(dataSource, fileName); } /** - * Finds a set of localFiles that meets the name criteria. + * Finds all files and directories with a given file name and parent file or + * directory name in a given data source (image, local/logical files set, + * etc.). The name searches are for full or partial matches and are case + * insensitive (a case insensitive SQL LIKE clause is used to query the case + * database). * - * @param dataSource Root data source to limit search results to (Image, - * VirtualDirectory, etc.). - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). - * @param dirName Pattern of the name of the parent directory to use as - * the root of the search (case insensitive, used in LIKE - * SQL statement). + * @param dataSource The data source. + * @param fileName The full or partial file name. + * @param parentName The full or partial parent file or directory name. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches fileName and whose parent directory contains dirName. + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ - public synchronized List findFiles(Content dataSource, String fileName, String dirName) throws TskCoreException { - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles2.exception.msg")); + public synchronized List findFiles(Content dataSource, String fileName, String parentName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - return tskCase.findFiles(dataSource, fileName, dirName); + return caseDb.findFiles(dataSource, fileName, parentName); } /** - * Finds a set of localFiles that meets the name criteria. + * Finds all files and directories with a given file name and given parent + * file or directory in a given data source (image, local/logical files set, + * etc.). The name search is for full or partial matches and is case + * insensitive (a case insensitive SQL LIKE clause is used to query the case + * database). * - * @param dataSource Root data source to limit search results to (Image, - * VirtualDirectory, etc.). - * @param fileName Pattern of the name of the file or directory to match - * (case insensitive, used in LIKE SQL statement). - * @param parentFile Object of root/parent directory to restrict search to. + * @param dataSource The data source. + * @param fileName The full or partial file name. + * @param parent The parent file or directory. * - * @return a list of AbstractFile for localFiles/directories whose name - * matches fileName and that were inside a directory described by - * parentFsContent. + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ - public synchronized List findFiles(Content dataSource, String fileName, AbstractFile parentFile) throws TskCoreException { - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles3.exception.msg")); + public synchronized List findFiles(Content dataSource, String fileName, AbstractFile parent) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - return findFiles(dataSource, fileName, parentFile.getName()); + return findFiles(dataSource, fileName, parent.getName()); } /** - * @param dataSource data source Content (Image, parent-less - * VirtualDirectory) where to find localFiles - * @param filePath The full path to the file(s) of interest. This can - * optionally include the image and volume names. + * Finds all files and directories with a given file name and path in a + * given data source (image, local/logical files set, etc.). The name search + * is for full or partial matches and is case insensitive (a case + * insensitive SQL LIKE clause is used to query the case database). Any path + * components at the volume level and above are removed for the search. * - * @return a list of AbstractFile that have the given file path. + * @param dataSource The data source. + * @param fileName The full or partial file name. + * @param filePath The file path (path components volume at the volume + * level or above will be removed). + * + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. */ public synchronized List openFiles(Content dataSource, String filePath) throws TskCoreException { - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.openFiles.exception.msg")); + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - return tskCase.openFiles(dataSource, filePath); + return caseDb.openFiles(dataSource, filePath); } /** - * Creates a derived file, adds it to the database and returns it. + * Adds a derived file to the case. * - * @param fileName file name the derived file - * @param localPath local path of the derived file, including the file - * name. The path is relative to the case folder. - * @param size size of the derived file in bytes - * @param ctime - * @param crtime - * @param atime - * @param mtime - * @param isFile whether a file or directory, true if a file - * @param parentFile the parent file object this the new file was - * derived from, either a fs file or parent derived - * file/dikr\\r - * @param rederiveDetails details needed to re-derive file (will be specific - * to the derivation method), currently unused - * @param toolName name of derivation method/tool, currently unused - * @param toolVersion version of derivation method/tool, currently - * unused - * @param otherDetails details of derivation method/tool, currently - * unused + * @param fileName The name of the file. + * @param localPath The local path of the file, relative to the case + * folder and including the file name. + * @param size The size of the file in bytes. + * @param ctime The change time of the file. + * @param crtime The create time of the file + * @param atime The accessed time of the file. + * @param mtime The modified time of the file. + * @param isFile True if a file, false if a directory. + * @param parentFile The parent file from which the file was derived. + * @param rederiveDetails The details needed to re-derive file (will be + * specific to the derivation method), currently + * unused. + * @param toolName The name of the derivation method or tool, + * currently unused. + * @param toolVersion The version of the derivation method or tool, + * currently unused. + * @param otherDetails Other details of the derivation method or tool, + * currently unused. * - * @return newly created derived file object added to the database - * - * @throws TskCoreException exception thrown if the object creation failed - * due to a critical system error or of the file - * manager has already been closed + * @return A DerivedFile object representing the derived file. * + * @throws TskCoreException if there is a problem adding the file to the + * case database. */ - public synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size, + public synchronized DerivedFile addDerivedFile(String fileName, + String localPath, + long size, long ctime, long crtime, long atime, long mtime, - boolean isFile, AbstractFile parentFile, + boolean isFile, + AbstractFile parentFile, String rederiveDetails, String toolName, String toolVersion, String otherDetails) throws TskCoreException { - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addDerivedFile.exception.msg")); + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - - return tskCase.addDerivedFile(fileName, localPath, size, + return caseDb.addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile, parentFile, rederiveDetails, toolName, toolVersion, otherDetails); } /** - * Adds a carved file to the VirtualDirectory '$CarvedFiles' in the volume - * or image given by systemId. + * Adds a carving result to the case database. * - * @param carvedFileName the name of the carved file (containing appropriate - * extension) - * @param carvedFileSize size of the carved file to add - * @param systemId the ID of the parent volume or file system - * @param sectors a list of SectorGroups giving this sectors that - * make up this carved file. + * @param carvingResult The carving result (a set of carved files and their + * parent) to be added. * - * @throws TskCoreException exception thrown when critical tsk error - * occurred and carved file could not be added + * @return A list of LayoutFile representations of the carved files. + * + * @throws TskCoreException If there is a problem completing a case database + * operation. */ - public synchronized LayoutFile addCarvedFile(String carvedFileName, long carvedFileSize, - long systemId, List sectors) throws TskCoreException { - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addCarvedFile.exception.msg")); + public synchronized List addCarvedFiles(CarvingResult carvingResult) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - - return tskCase.addCarvedFile(carvedFileName, carvedFileSize, systemId, sectors); + return caseDb.addCarvedFiles(carvingResult); } - + /** - * Adds a collection of carved localFiles to the VirtualDirectory - * '$CarvedFiles' in the volume or image given by systemId. Creates - * $CarvedFiles if it does not exist already. - * - * @param filesToAdd a list of CarvedFileContainer localFiles to add as - * carved localFiles - * - * @return List This is a list of the localFiles added to the - * database - * - * @throws org.sleuthkit.datamodel.TskCoreException - */ - public List addCarvedFiles(List filesToAdd) throws TskCoreException { - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addCarvedFile.exception.msg")); - } else { - return tskCase.addCarvedFiles(filesToAdd); - } - } - - /** - * - * Interface for receiving notifications on folders being added via a - * callback + * Interface for receiving a notification for each file or directory added + * to the case database by a FileManager add files operation. */ public interface FileAddProgressUpdater { /** - * Called when new folders has been added + * Called after a file or directory is added to the case database. * - * @param newFile the file/folder added to the Case + * @param An AbstractFile represeting the added file or directory. */ - public void fileAdded(AbstractFile newFile); - } - - /** - * Add a set of local/logical localFiles and dirs. - * - * @param localAbsPaths list of absolute paths to local localFiles and - * dirs - * @param addProgressUpdater notifier to receive progress notifications on - * folders added, or null if not used - * - * @return file set root VirtualDirectory contained containing all - * AbstractFile objects added - * - * @throws TskCoreException exception thrown if the object creation failed - * due to a critical system error or of the file - * manager has already been closed. There is no - * "revert" logic if one of the additions fails. - * The addition stops with the first error - * encountered. - */ - public synchronized VirtualDirectory addLocalFilesDirs(List localAbsPaths, FileAddProgressUpdater addProgressUpdater) throws TskCoreException { - List rootsToAdd; - try { - rootsToAdd = getFilesAndDirectories(localAbsPaths); - } catch (TskDataException ex) { - throw new TskCoreException(ex.getLocalizedMessage(), ex); - } - - CaseDbTransaction trans = tskCase.beginTransaction(); - // make a virtual top-level directory for this set of localFiles/dirs - final VirtualDirectory fileSetRootDir = addLocalFileSetRootDir(trans); - - try { - // recursively add each item in the set - for (java.io.File localRootToAdd : rootsToAdd) { - AbstractFile localFileAdded = addLocalDirInt(trans, fileSetRootDir, localRootToAdd, addProgressUpdater); - - if (localFileAdded == null) { - String msg = NbBundle - .getMessage(this.getClass(), "FileManager.addLocalFilesDirs.exception.cantAdd.msg", - localRootToAdd.getAbsolutePath()); - logger.log(Level.SEVERE, msg); - throw new TskCoreException(msg); - } else { - //added.add(localFileAdded); - //send new content event - //for now reusing ingest events, in future this will be replaced by datamodel / observer sending out events - // @@@ Is this the right place for this? A directory tree refresh will be triggered, so this may be creating a race condition - // since the transaction is not yet committed. - IngestServices.getInstance().fireModuleContentEvent(new ModuleContentEvent(localFileAdded)); - } - } - - trans.commit(); - } catch (TskCoreException ex) { - trans.rollback(); - } - return fileSetRootDir; + void fileAdded(AbstractFile newFile); } /** @@ -419,20 +384,27 @@ public class FileManager implements Closeable { * directory that does not exist or cannot be read. */ public synchronized LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootVirtualDirectoryName, String timeZone, List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } List localFiles = getFilesAndDirectories(localFilePaths); CaseDbTransaction trans = null; try { String rootDirectoryName = rootVirtualDirectoryName; - int newLocalFilesSetCount = curNumFileSets + 1; - if (rootVirtualDirectoryName.isEmpty()) { - rootDirectoryName = VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + newLocalFilesSetCount; + if (rootDirectoryName.isEmpty()) { + rootDirectoryName = generateFilesDataSourceName(caseDb); } - trans = tskCase.beginTransaction(); - LocalFilesDataSource dataSource = tskCase.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, trans); + + /* + * Add the root virtual directory and its local/logical file + * children to the case database. + */ + trans = caseDb.beginTransaction(); + LocalFilesDataSource dataSource = caseDb.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, trans); VirtualDirectory rootDirectory = dataSource.getRootDirectory(); List filesAdded = new ArrayList<>(); for (java.io.File localFile : localFiles) { - AbstractFile fileAdded = addLocalDirInt(trans, rootDirectory, localFile, progressUpdater); + AbstractFile fileAdded = addLocalFile(trans, rootDirectory, localFile, progressUpdater); if (null != fileAdded) { filesAdded.add(fileAdded); } else { @@ -440,13 +412,16 @@ public class FileManager implements Closeable { } } trans.commit(); - if (rootVirtualDirectoryName.isEmpty()) { - curNumFileSets = newLocalFilesSetCount; - } + + /* + * Publish content added events for the added files and directories. + */ for (AbstractFile fileAdded : filesAdded) { IngestServices.getInstance().fireModuleContentEvent(new ModuleContentEvent(fileAdded)); } + return dataSource; + } catch (TskCoreException ex) { if (null != trans) { trans.rollback(); @@ -455,6 +430,34 @@ public class FileManager implements Closeable { } } + /** + * Generates a name for the root virtual directory for the data source. + * + * NOTE: Although this method is guarded by the file manager's monitor, + * there is currently a minimal chance of default name duplication for + * multi-user cases with multiple FileManagers running on different nodes. + * + * @return A default name for a local/logical files data source of the form: + * LogicalFileSet[N]. + * + * @throws TskCoreException If there is a problem querying the case + * database. + */ + private static synchronized String generateFilesDataSourceName(SleuthkitCase caseDb) throws TskCoreException { + int localFileDataSourcesCounter = 0; + try { + List localFileDataSources = caseDb.getVirtualDirectoryRoots(); + for (VirtualDirectory vd : localFileDataSources) { + if (vd.getName().startsWith(VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX)) { + ++localFileDataSourcesCounter; + } + } + return VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + (localFileDataSourcesCounter + 1); + } catch (TskCoreException ex) { + throw new TskCoreException("Error querying for existing local file data sources with defualt names", ex); + } + } + /** * Converts a list of local/logical file and/or directory paths to a list of * file objects. @@ -472,7 +475,7 @@ public class FileManager implements Closeable { for (String path : localFilePaths) { java.io.File localFile = new java.io.File(path); if (!localFile.exists() || !localFile.canRead()) { - throw new TskDataException(NbBundle.getMessage(this.getClass(), "FileManager.addLocalFilesDirs.exception.notReadable.msg", localFile.getAbsolutePath())); + throw new TskDataException(String.format("File at %s does not exist or cannot be read", localFile.getAbsolutePath())); } localFiles.add(localFile); } @@ -480,130 +483,141 @@ public class FileManager implements Closeable { } /** - * Adds a new virtual directory root object with FileSet X name and - * consecutive sequence number characteristic to every add operation - * - * @return the virtual dir root container created - * - * @throws TskCoreException - */ - private VirtualDirectory addLocalFileSetRootDir(CaseDbTransaction trans) throws TskCoreException { - - VirtualDirectory created = null; - - int newFileSetCount = curNumFileSets + 1; - final String fileSetName = VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + newFileSetCount; - - try { - created = tskCase.addVirtualDirectory(0, fileSetName, trans); - curNumFileSets = newFileSetCount; - } catch (TskCoreException ex) { - String msg = NbBundle - .getMessage(this.getClass(), "FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg", - fileSetName); - logger.log(Level.SEVERE, msg, ex); - throw new TskCoreException(msg, ex); - } - - return created; - } - - /** - * Helper (internal) method to recursively add contents of a folder. Node - * passed in can be a file or directory. Children of directories are added. + * Adds a file or directory of logical/local files data source to the case + * database, recursively adding the contents of directories. * * @param trans A case database transaction. - * @param parentVd Dir that is the parent of localFile - * @param localFile File/Dir that we are adding + * @param parentDirectory The root virtual direcotry of the data source. + * @param localFile The local/logical file or directory. * @param addProgressUpdater notifier to receive progress notifications on * folders added, or null if not used * * @returns File object of file added or new virtualdirectory for the * directory. - * @throws TskCoreException + * @param progressUpdater Called after each file/directory is added to + * the case database. + * + * @return An AbstractFile representation of the local/logical file. + * + * @throws TskCoreException If there is a problem completing a database + * operation. */ - private AbstractFile addLocalDirInt(CaseDbTransaction trans, VirtualDirectory parentVd, - java.io.File localFile, FileAddProgressUpdater addProgressUpdater) throws TskCoreException { - - if (tskCase == null) { - throw new TskCoreException( - NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.closed.msg")); - } - - //final String localName = localDir.getName(); - if (!localFile.exists()) { - throw new TskCoreException( - NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.doesntExist.msg", - localFile.getAbsolutePath())); - } - if (!localFile.canRead()) { - throw new TskCoreException( - NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.notReadable.msg", - localFile.getAbsolutePath())); - } - + private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException { if (localFile.isDirectory()) { - //create virtual folder (we don't have a notion of a 'local folder') - final VirtualDirectory childVd = tskCase.addVirtualDirectory(parentVd.getId(), localFile.getName(), trans); - if (childVd != null && addProgressUpdater != null) { - addProgressUpdater.fileAdded(childVd); - } - //add children recursively - final java.io.File[] childrenFiles = localFile.listFiles(); - if (childrenFiles != null) { - for (java.io.File childFile : childrenFiles) { - addLocalDirInt(trans, childVd, childFile, addProgressUpdater); + /* + * Add the directory as a virtual directory. + */ + VirtualDirectory virtualDirectory = caseDb.addVirtualDirectory(parentDirectory.getId(), localFile.getName(), trans); + progressUpdater.fileAdded(virtualDirectory); + + /* + * Add its children, if any. + */ + final java.io.File[] childFiles = localFile.listFiles(); + if (childFiles != null && childFiles.length > 0) { + for (java.io.File childFile : childFiles) { + addLocalFile(trans, virtualDirectory, childFile, progressUpdater); } } - return childVd; + + return virtualDirectory; } else { - //add leaf file, base case - return this.addLocalFileInt(parentVd, localFile, trans); + return caseDb.addLocalFile(localFile.getName(), localFile.getAbsolutePath(), localFile.length(), + 0, 0, 0, 0, + localFile.isFile(), parentDirectory, trans); } } /** - * Adds a single local/logical file to the case. Adds it to the database. - * Does not refresh the views of data. Assumes that the local file exists - * and can be read. This checking is done by addLocalDirInt(). + * Closes the file manager. * - * @param parentFile parent file object container (such as virtual - * directory, another local file, or fscontent File), - * @param localFile File that we are adding - * @param trans A case database transaction. - * - * @return newly created local file object added to the database - * - * @throws TskCoreException exception thrown if the object creation failed - * due to a critical system error or of the file - * manager has already been closed + * @throws IOException If there is a problem closing the file manager. */ - private synchronized LocalFile addLocalFileInt(AbstractFile parentFile, java.io.File localFile, CaseDbTransaction trans) throws TskCoreException { - - if (tskCase == null) { - throw new TskCoreException( - NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt2.exception.closed.msg")); - } - - long size = localFile.length(); - boolean isFile = localFile.isFile(); - - long ctime = 0; - long crtime = 0; - long atime = 0; - long mtime = 0; - - String fileName = localFile.getName(); - - LocalFile lf = tskCase.addLocalFile(fileName, localFile.getAbsolutePath(), size, - ctime, crtime, atime, mtime, - isFile, parentFile, trans); - - return lf; - } - @Override public synchronized void close() throws IOException { - tskCase = null; + caseDb = null; } + + /** + * Adds a set of local/logical files and/or directories to the case database + * as data source. + * + * @param localFilePaths A list of local/logical file and/or directory + * localFilePaths. + * @param progressUpdater Called after each file/directory is added to the + * case database. + * + * @return The root virtual directory for the local/logical files data + * source. + * + * @throws TskCoreException If any of the local file paths is for a file or + * directory that does not exist or cannot be read, + * or there is a problem completing a database + * operation. + * @deprecated Use addLocalFilesDataSource instead. + */ + @Deprecated + public synchronized VirtualDirectory addLocalFilesDirs(List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } + try { + return addLocalFilesDataSource("", "", "", localFilePaths, progressUpdater).getRootDirectory(); + } catch (TskDataException ex) { + throw new TskCoreException(ex.getLocalizedMessage(), ex); + } + } + + /** + * Adds a carved file to the '$CarvedFiles' virtual directory of a data + * source, volume or file system. + * + * @param fileName The name of the file. + * @param fileSize The size of the file. + * @param parentObjId The object id of the parent data source, volume or + * file system. + * @param layout A list of the offsets and sizes that gives the layout + * of the file within its parent. + * + * @return A LayoutFile object representing the carved file. + * + * @throws TskCoreException if there is a problem adding the file to the + * case database. + * @deprecated Use List addCarvedFiles(CarvingResult + * carvingResult instead. + */ + @Deprecated + public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List layout) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } + Content parent = caseDb.getContentById(parentObjId); + List carvedFiles = new ArrayList<>(); + carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, layout)); + List layoutFiles = caseDb.addCarvedFiles(new CarvingResult(parent, carvedFiles)); + return layoutFiles.get(0); + } + + /** + * Adds a collection of carved files to the '$CarvedFiles' virtual directory + * of a data source, volume or file system. + * + * @param A collection of CarvedFileContainer objects, one per carved file, + * all of which must have the same parent object id. + * + * @return A collection of LayoutFile object representing the carved files. + * + * @throws TskCoreException if there is a problem adding the files to the + * case database. + * @deprecated Use List addCarvedFiles(CarvingResult + * carvingResult instead. + */ + @Deprecated + public synchronized List addCarvedFiles(List filesToAdd) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } + return caseDb.addCarvedFiles(filesToAdd); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java index 880bc8008e..a8d11d184c 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java @@ -1,19 +1,18 @@ /* * * Autopsy Forensic Browser - * - * Copyright 2012-2015 Basis Technology Corp. - * + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com - * Project Contact/Architect: 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. @@ -31,7 +30,8 @@ import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.datamodel.SleuthkitCase; /** - * A class to manage various services. + * A collection of case-level services (e.g., file manager, tags manager, + * keyword search, blackboard). */ public class Services implements Closeable { @@ -41,36 +41,67 @@ public class Services implements Closeable { private final KeywordSearchService keywordSearchService; private final Blackboard blackboard; - public Services(SleuthkitCase tskCase) { - fileManager = new FileManager(tskCase); + /** + * Constructs a collection of case-level services (e.g., file manager, tags + * manager, keyword search, blackboard). + * + * @param caseDb The case database for the current case. + */ + public Services(SleuthkitCase caseDb) { + fileManager = new FileManager(caseDb); services.add(fileManager); - tagsManager = new TagsManager(tskCase); + tagsManager = new TagsManager(caseDb); services.add(tagsManager); keywordSearchService = Lookup.getDefault().lookup(KeywordSearchService.class); services.add(keywordSearchService); - - blackboard = new Blackboard(); + + blackboard = new Blackboard(caseDb); services.add(blackboard); } + /** + * Gets the file manager service for the current case. + * + * @return The file manager service for the current case. + */ public FileManager getFileManager() { return fileManager; } + /** + * Gets the tags manager service for the current case. + * + * @return The tags manager service for the current case. + */ public TagsManager getTagsManager() { return tagsManager; } + /** + * Gets the keyword search service for the current case. + * + * @return The keyword search service for the current case. + */ public KeywordSearchService getKeywordSearchService() { return keywordSearchService; } - + + /** + * Gets the blackboard service for the current case. + * + * @return The blackboard service for the current case. + */ public Blackboard getBlackboard() { return blackboard; } + /** + * Closes the services for the current case. + * + * @throws IOException if there is a problem closing the services. + */ @Override public void close() throws IOException { for (Closeable service : services) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 53d6229688..e8d61bdeba 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-16 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,7 @@ public class TagsManager implements Closeable { private static final Logger logger = Logger.getLogger(TagsManager.class.getName()); private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS - private final SleuthkitCase caseDb; + private SleuthkitCase caseDb; private final HashMap uniqueTagNames = new HashMap<>(); private boolean tagNamesLoaded = false; @@ -53,8 +53,8 @@ public class TagsManager implements Closeable { * Constructs a per case Autopsy service that manages the creation, * updating, and deletion of tags applied to content and blackboard * artifacts by users. - * - * @param caseDb The case database for the current case. + * + * @param caseDb The case database. */ TagsManager(SleuthkitCase caseDb) { this.caseDb = caseDb; @@ -70,6 +70,9 @@ public class TagsManager implements Closeable { * database. */ public synchronized List getAllTagNames() throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getAllTagNames(); } @@ -84,6 +87,9 @@ public class TagsManager implements Closeable { * database. */ public synchronized List getTagNamesInUse() throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getTagNamesInUse(); } @@ -114,6 +120,9 @@ public class TagsManager implements Closeable { * to the case database. */ public TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } return addTagName(displayName, "", TagName.HTML_COLOR.NONE); } @@ -132,6 +141,9 @@ public class TagsManager implements Closeable { * to the case database. */ public TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } return addTagName(displayName, description, TagName.HTML_COLOR.NONE); } @@ -151,6 +163,9 @@ public class TagsManager implements Closeable { * to the case database. */ public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); if (uniqueTagNames.containsKey(displayName)) { throw new TagNameAlreadyExistsException(); @@ -182,6 +197,9 @@ public class TagsManager implements Closeable { * database. */ public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } return addContentTag(content, tagName, "", -1, -1); } @@ -198,6 +216,9 @@ public class TagsManager implements Closeable { * database. */ public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } return addContentTag(content, tagName, comment, -1, -1); } @@ -218,10 +239,17 @@ public class TagsManager implements Closeable { * the case database. */ public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } ContentTag tag; synchronized (this) { lazyLoadExistingTagNames(); + if (null == comment) { + throw new IllegalArgumentException("Passed null comment argument"); + } + if (beginByteOffset >= 0 && endByteOffset >= 1) { if (beginByteOffset > content.getSize() - 1) { throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(), @@ -261,6 +289,9 @@ public class TagsManager implements Closeable { * case database. */ public void deleteContentTag(ContentTag tag) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } synchronized (this) { lazyLoadExistingTagNames(); caseDb.deleteContentTag(tag); @@ -282,6 +313,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getAllContentTags() throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getAllContentTags(); } @@ -297,6 +331,9 @@ public class TagsManager implements Closeable { * the case database. */ public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getContentTagsCountByTagName(tagName); } @@ -312,6 +349,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized ContentTag getContentTagByTagID(long tagID) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getContentTagByID(tagID); } @@ -328,6 +368,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getContentTagsByTagName(TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getContentTagsByTagName(tagName); } @@ -344,6 +387,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getContentTagsByContent(Content content) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getContentTagsByContent(content); } @@ -361,6 +407,9 @@ public class TagsManager implements Closeable { * database. */ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } return addBlackboardArtifactTag(artifact, tagName, ""); } @@ -378,9 +427,15 @@ public class TagsManager implements Closeable { * database. */ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } BlackboardArtifactTag tag; synchronized (this) { lazyLoadExistingTagNames(); + if (null == comment) { + throw new IllegalArgumentException("Passed null comment argument"); + } tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment); } @@ -401,6 +456,9 @@ public class TagsManager implements Closeable { * case database. */ public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } synchronized (this) { lazyLoadExistingTagNames(); caseDb.deleteBlackboardArtifactTag(tag); @@ -422,6 +480,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getAllBlackboardArtifactTags() throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getAllBlackboardArtifactTags(); } @@ -438,6 +499,9 @@ public class TagsManager implements Closeable { * the case database. */ public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } @@ -453,6 +517,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagID) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagByID(tagID); } @@ -469,6 +536,9 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsByTagName(tagName); } @@ -485,16 +555,25 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("Tags manager has been closed"); + } lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsByArtifact(artifact); } /** - * Saves the avaialble tag names to secondary storage. + * Closes the tags manager, saving the avaialble tag names to secondary + * storage. + * + * @throws IOException If there is a problem closing the tags manager. + * @deprecated Tags manager clients should not close the tags manager. */ @Override + @Deprecated public synchronized void close() throws IOException { saveTagNamesToTagsSettings(); + caseDb = null; } /** diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 8e71419b45..cd72a061f9 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -5,12 +5,12 @@ - - - - + + + + @@ -39,7 +39,7 @@ Actions ====================================================== --> - + @@ -145,24 +145,25 @@ - + + - + - + - + - + - + @@ -170,7 +171,7 @@ - + @@ -182,7 +183,7 @@ - + @@ -361,7 +362,8 @@ - + + @@ -385,11 +387,11 @@ - + - + @@ -404,10 +406,10 @@ ====================================================== --> - + - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form index 6c2a6cc2f6..0a6ea4b4a6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form @@ -3,7 +3,7 @@
- + @@ -56,6 +56,9 @@ + + + @@ -73,6 +76,11 @@ + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 3c5091bd50..bb54c28a37 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,6 +56,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener { private ExplorerManager explorerManager; + private ExplorerManagerNodeSelectionListener emNodeSelectionListener; + private Node rootNode; private PropertyChangeSupport pcs; @@ -232,7 +234,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C // can push the selections both to its child DataResultViewers and to a DataContent object. // The default DataContent object is a DataContentTopComponent in the data content mode (area), // and is the parent of a DataContentPanel that hosts a set of DataContentViewers. - explorerManager.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); + emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); + explorerManager.addPropertyChangeListener(emNodeSelectionListener); } // Add all the DataContentViewer to the tabbed pannel. @@ -336,6 +339,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C pcs.removePropertyChangeListener(pcl[i]); } + if (null != explorerManager && null != emNodeSelectionListener) { + explorerManager.removePropertyChangeListener(emNodeSelectionListener); + explorerManager = null; + } + // clear all set nodes for (UpdateWrapper drv : this.viewers) { drv.setNode(null); @@ -536,26 +544,29 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C matchLabel = new javax.swing.JLabel(); dataResultTabbedPanel = new javax.swing.JTabbedPane(); - setMinimumSize(new java.awt.Dimension(5, 5)); + setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N + directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N + dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5)); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(directoryTablePath) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 518, Short.MAX_VALUE) + .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(numberMatchLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(matchLabel)) - .addComponent(dataResultTabbedPanel) + .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -564,9 +575,9 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(numberMatchLabel) .addComponent(matchLabel)) - .addComponent(directoryTablePath)) + .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE)) + .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index f4f1e99ab3..9f5d9e6cbc 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -1,4 +1,4 @@ - /* +/* * Autopsy Forensic Browser * * Copyright 2012-16 Basis Technology Corp. @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -89,7 +90,7 @@ public class ImageUtils { private static final List GIF_EXTENSION_LIST = Arrays.asList("gif"); private static final SortedSet GIF_MIME_SET = ImmutableSortedSet.copyOf(new String[]{"image/gif"}); - private static final List SUPPORTED_IMAGE_EXTENSIONS; + private static final List SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>(); private static final SortedSet SUPPORTED_IMAGE_MIME_TYPES; private static final boolean openCVLoaded; @@ -124,7 +125,8 @@ public class ImageUtils { } openCVLoaded = openCVLoadedTemp; - SUPPORTED_IMAGE_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes()); + SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes())); + SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes())); /* * special cases and variants that we support, but don't get registered @@ -376,6 +378,45 @@ public class ImageUtils { } } + /** + * Find the offset for the first Start Of Image marker (0xFFD8) in JFIF, + * allowing for leading End Of Image markers. + * + * @param file the AbstractFile to parse + * + * @return Offset of first Start Of Image marker, or 0 if none found. This + * will let ImageIO try to open it from offset 0. + */ + private static long getJfifStartOfImageOffset(AbstractFile file) { + byte[] fileHeaderBuffer; + long length; + try { + length = file.getSize(); + if (length % 2 != 0) { + length -= 1; // Make it an even number so we can parse two bytes at a time + } + if (length >= 1024) { + length = 1024; + } + fileHeaderBuffer = readHeader(file, (int) length); // read up to first 1024 bytes + } catch (TskCoreException ex) { + // Couldn't read header. Let ImageIO try it. + return 0; + } + + if (fileHeaderBuffer != null) { + for (int index = 0; index < length; index += 2) { + // Look for Start Of Image marker and return the index when it's found + if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) { + return index; + } + } + } + + // Didn't match JFIF. Let ImageIO try to open it from offset 0. + return 0; + } + /** * Check if the given file is a png based on header. * @@ -711,7 +752,7 @@ public class ImageUtils { */ static private abstract class ReadImageTaskBase extends Task implements IIOReadProgressListener { - private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS + private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS final AbstractFile file; // private ImageReader reader; @@ -726,8 +767,17 @@ public class ImageUtils { if (image.isError() == false) { return image; } - //fall through to default image reading code if there was an error + } else if (file.getNameExtension().equalsIgnoreCase("tec")) { //NON-NLS + ReadContentInputStream readContentInputStream = new ReadContentInputStream(file); + // Find first Start Of Image marker + readContentInputStream.seek(getJfifStartOfImageOffset(file)); + //use JavaFX to directly read .tec files + javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(readContentInputStream)); + if (image.isError() == false) { + return image; + } } + //fall through to default image reading code if there was an error if (isCancelled()) { return null; } @@ -747,7 +797,7 @@ public class ImageUtils { try { bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object } catch (IOException iOException) { - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS } finally { imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this); } @@ -755,7 +805,8 @@ public class ImageUtils { return null; } return SwingFXUtils.toFXImage(bufferedImage, null); - }); + } + ); } @Override @@ -775,12 +826,10 @@ public class ImageUtils { try { javafx.scene.image.Image fxImage = get(); if (fxImage == null) { - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file)); - } else { - if (fxImage.isError()) { - //if there was somekind of error, log it - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); - } + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, ImageUtils.getContentPathSafe(file)); + } else if (fxImage.isError()) { + //if there was somekind of error, log it + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); } } catch (InterruptedException | ExecutionException ex) { failed(); @@ -790,7 +839,7 @@ public class ImageUtils { @Override protected void failed() { super.failed(); - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file)); + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file)); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java index 053968d598..f2eed8e474 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java @@ -57,4 +57,26 @@ public class TextUtil { return orientation; } + + + /** + * This method determines if a passed-in Java char (16 bits) is a valid + * UTF-8 printable character, returning true if so, false if not. + * + * Note that this method can have ramifications for characters outside the + * Unicode Base Multilingual Plane (BMP), which require more than 16 bits. + * We are using Java characters (16 bits) to look at the data and this will + * not accurately identify any non-BMP character (larger than 16 bits) + * ending with 0xFFFF and 0xFFFE. In the interest of a fast solution, we + * have chosen to ignore the extended planes above Unicode BMP for the time + * being. The net result of this is some non-BMP characters may be + * interspersed with '^' characters in Autopsy. + * + * @param ch the character to test + * + * @return Returns true if the character is valid UTF-8, false if not. + */ + public static boolean isValidSolrUTF8(char ch) { + return ((ch <= 0xFDD0 || ch >= 0xFDEF) && (ch > 0x1F || ch == 0x9 || ch == 0xA || ch == 0xD) && (ch != 0xFFFF) && (ch != 0xFFFE)); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index b0b6c92fca..0458ec9a59 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -268,7 +268,7 @@ public abstract class AbstractAbstractFileNode extends A map.put(AbstractFilePropertyType.USER_ID.toString(), content.getUid()); map.put(AbstractFilePropertyType.GROUP_ID.toString(), content.getGid()); map.put(AbstractFilePropertyType.META_ADDR.toString(), content.getMetaAddr()); - map.put(AbstractFilePropertyType.ATTR_ADDR.toString(), Long.toString(content.getAttrType().getValue()) + "-" + Long.toString(content.getAttrId())); + map.put(AbstractFilePropertyType.ATTR_ADDR.toString(), Long.toString(content.getAttrType().getValue()) + "-" + content.getAttributeId()); map.put(AbstractFilePropertyType.TYPE_DIR.toString(), content.getDirType().getLabel()); map.put(AbstractFilePropertyType.TYPE_META.toString(), content.getMetaType().toString()); map.put(AbstractFilePropertyType.KNOWN.toString(), content.getKnown().getName()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index 257f8ddde1..c0e5ae555a 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -20,13 +20,13 @@ package org.sleuthkit.autopsy.datamodel; import java.util.List; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.Action; 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.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.TskCoreException; @@ -93,7 +93,11 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { @Override public Action[] getActions(boolean context) { List actions = DataModelActionsFactory.getActions(tag.getContent(), true); - actions.add(null); // Adds a menu item separator. + for (Action a : super.getActions(true)) { + actions.add(a); + } + actions.add(null); // Adds a menu item separator. + actions.add(DeleteBlackboardArtifactTagAction.getInstance()); 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 8f1a007aab..b8e7fa071f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -147,7 +147,6 @@ HashsetHits.createSheet.name.name=Name HashsetHits.createSheet.name.displayName=Name HashsetHits.createSheet.name.desc=no description ImageNode.getActions.viewInNewWin.text=View in New Window -ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes ImageNode.createSheet.name.name=Name ImageNode.createSheet.name.displayName=Name ImageNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 8be6cda9f4..890e33b773 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -107,6 +107,9 @@ class ContentTagNode extends DisplayableItemNode { @Override public Action[] getActions(boolean context) { List actions = DataModelActionsFactory.getActions(tag.getContent(), false); + for (Action a : super.getActions(true)) { + actions.add(a); + } actions.add(null); // Adds a menu item separator. actions.add(DeleteContentTagAction.getInstance()); return actions.toArray(new Action[0]); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 303ca71f15..a96a1ba662 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -21,10 +21,9 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; import java.util.List; import javax.swing.Action; - import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -72,6 +71,9 @@ public class DirectoryNode extends AbstractFsContentNode { @Override public Action[] getActions(boolean popup) { List actions = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actions.add(a); + } if (!getDirectoryBrowseMode()) { actions.add(new ViewContextAction( NbBundle.getMessage(this.getClass(), "DirectoryNode.getActions.viewFileInDir.text"), this)); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 8b54e5a7b6..ff671a5dec 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -79,18 +79,21 @@ public class FileNode extends AbstractFsContentNode { @Override public Action[] getActions(boolean popup) { List actionsList = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actionsList.add(a); + } if (!this.getDirectoryBrowseMode()) { - actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "FileNode.viewFileInDir.text"), this)); + actionsList.add(new ViewContextAction(NbBundle.getMessage(FileNode.class, "FileNode.viewFileInDir.text"), this)); actionsList.add(null); // creates a menu separator } actionsList.add(new NewWindowViewAction( - NbBundle.getMessage(this.getClass(), "FileNode.getActions.viewInNewWin.text"), this)); + NbBundle.getMessage(FileNode.class, "FileNode.getActions.viewInNewWin.text"), this)); actionsList.add(new ExternalViewerAction( - NbBundle.getMessage(this.getClass(), "FileNode.getActions.openInExtViewer.text"), this)); + NbBundle.getMessage(FileNode.class, "FileNode.getActions.openInExtViewer.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(new HashSearchAction( - NbBundle.getMessage(this.getClass(), "FileNode.getActions.searchFilesSameMD5.text"), this)); + NbBundle.getMessage(FileNode.class, "FileNode.getActions.searchFilesSameMD5.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); actionsList.addAll(ContextMenuExtensionPoint.getActions()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java index 963b081b13..d9cd01b791 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java @@ -27,7 +27,7 @@ import java.util.List; */ public class FileTypeExtensions { - private final static List IMAGE_EXTENSIONS = Arrays.asList(".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp"); //NON-NLS + private final static List IMAGE_EXTENSIONS = Arrays.asList(".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec"); //NON-NLS private final static List VIDEO_EXTENSIONS = Arrays.asList(".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf"); //NON-NLS private final static List AUDIO_EXTENSIONS = Arrays.asList(".aiff", ".aif", ".flac", ".wav", ".m4a", ".ape", //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 4a822aec09..fbdb046ae1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -18,15 +18,28 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.awt.event.ActionEvent; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; import javax.swing.Action; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; /** * This class is used to represent the "Node" for the image. The children of @@ -34,6 +47,8 @@ import org.sleuthkit.datamodel.Image; */ public class ImageNode extends AbstractContentNode { + private static final Logger logger = Logger.getLogger(ImageNode.class.getName()); + /** * Helper so that the display name and the name used in building the path * are determined the same way. @@ -66,19 +81,53 @@ public class ImageNode extends AbstractContentNode { * @return */ @Override + @Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules", + "ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",}) public Action[] getActions(boolean context) { + + + List actionsList = new ArrayList(); + for (Action a : super.getActions(true)) { + actionsList.add(a); + } + actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); + actionsList.add(new FileSearchAction( + Bundle.ImageNode_getActions_openFileSearchByAttr_text())); + actionsList.add(new AbstractAction( + Bundle.ImageNode_action_runIngestMods_text()) { + @Override + public void actionPerformed(ActionEvent e) { + final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.singletonList(content)); + ingestDialog.display(); + } + }); actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this)); - actionsList.add(new FileSearchAction( - NbBundle.getMessage(this.getClass(), "ImageNode.getActions.openFileSearchByAttr.text"))); - actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); - return actionsList.toArray(new Action[0]); } @Override + @Messages({"ImageNode.createSheet.size.name=Size (Bytes)", + "ImageNode.createSheet.size.displayName=Size (Bytes)", + "ImageNode.createSheet.size.desc=Size of the data source in bytes.", + "ImageNode.createSheet.type.name=Type", + "ImageNode.createSheet.type.displayName=Type", + "ImageNode.createSheet.type.desc=Type of the image.", + "ImageNode.createSheet.type.text=Image", + "ImageNode.createSheet.sectorSize.name=Sector Size (Bytes)", + "ImageNode.createSheet.sectorSize.displayName=Sector Size (Bytes)", + "ImageNode.createSheet.sectorSize.desc=Sector size of the image in bytes.", + "ImageNode.createSheet.md5.name=MD5 Hash", + "ImageNode.createSheet.md5.displayName=MD5 Hash", + "ImageNode.createSheet.md5.desc=MD5 Hash of the image", + "ImageNode.createSheet.timezone.name=Timezone", + "ImageNode.createSheet.timezone.displayName=Timezone", + "ImageNode.createSheet.timezone.desc=Timezone of the image", + "ImageNode.createSheet.deviceId.name=Device ID", + "ImageNode.createSheet.deviceId.displayName=Device ID", + "ImageNode.createSheet.deviceId.desc=Device ID of the image"}) protected Sheet createSheet() { Sheet s = super.createSheet(); Sheet.Set ss = s.get(Sheet.PROPERTIES); @@ -92,6 +141,42 @@ public class ImageNode extends AbstractContentNode { NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.desc"), getDisplayName())); + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_type_name(), + Bundle.ImageNode_createSheet_type_displayName(), + Bundle.ImageNode_createSheet_type_desc(), + Bundle.ImageNode_createSheet_type_text())); + + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_size_name(), + Bundle.ImageNode_createSheet_size_displayName(), + Bundle.ImageNode_createSheet_size_desc(), + this.content.getSize())); + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_sectorSize_name(), + Bundle.ImageNode_createSheet_sectorSize_displayName(), + Bundle.ImageNode_createSheet_sectorSize_desc(), + this.content.getSsize())); + + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_md5_name(), + Bundle.ImageNode_createSheet_md5_displayName(), + Bundle.ImageNode_createSheet_md5_desc(), + this.content.getMd5())); + + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_timezone_name(), + Bundle.ImageNode_createSheet_timezone_displayName(), + Bundle.ImageNode_createSheet_timezone_desc(), + this.content.getTimeZone())); + + try (CaseDbQuery query = Case.getCurrentCase().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { + ResultSet deviceIdSet = query.getResultSet(); + if (deviceIdSet.next()) { + ss.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), + Bundle.ImageNode_createSheet_deviceId_displayName(), + Bundle.ImageNode_createSheet_deviceId_desc(), + deviceIdSet.getString("device_id"))); + } + } catch (SQLException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex); + } + return s; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 0192f37912..c66d92882c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -25,11 +25,11 @@ import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskData; @@ -116,6 +116,9 @@ public class LayoutFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actionsList.add(a); + } actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.viewInNewWin.text"), this)); actionsList.add(new ExternalViewerAction( diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 45c9dd492b..cef8dd9a56 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -25,12 +25,12 @@ import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; @@ -82,6 +82,9 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actionsList.add(a); + } 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/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index f908251d28..e6b30a6669 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -18,19 +18,32 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.awt.event.ActionEvent; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import javax.swing.AbstractAction; import javax.swing.Action; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.datamodel.VirtualDirectory; +import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.VirtualDirectory; /** * Node for layout dir @@ -73,18 +86,46 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode actions = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actions.add(a); + } + actions.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.getActions.viewInNewWin.text"), this)); actions.add(null); // creates a menu separator actions.add(ExtractAction.getInstance()); actions.add(null); // creates a menu separator + actions.add(new FileSearchAction( + Bundle.ImageNode_getActions_openFileSearchByAttr_text())); + actions.add(new AbstractAction( + Bundle.VirtualDirectoryNode_action_runIngestMods_text()) { + @Override + public void actionPerformed(ActionEvent e) { + final RunIngestModulesDialog ingestDialog = new RunIngestModulesDialog(Collections.singletonList(content)); + ingestDialog.display(); + } + }); actions.addAll(ContextMenuExtensionPoint.getActions()); return actions.toArray(new Action[0]); } @Override + @Messages({"VirtualDirectoryNode.createSheet.size.name=Size (Bytes)", + "VirtualDirectoryNode.createSheet.size.displayName=Size (Bytes)", + "VirtualDirectoryNode.createSheet.size.desc=Size of the data source in bytes.", + "VirtualDirectoryNode.createSheet.type.name=Type", + "VirtualDirectoryNode.createSheet.type.displayName=Type", + "VirtualDirectoryNode.createSheet.type.desc=Type of the image.", + "VirtualDirectoryNode.createSheet.type.text=Logical File Set", + "VirtualDirectoryNode.createSheet.timezone.name=Timezone", + "VirtualDirectoryNode.createSheet.timezone.displayName=Timezone", + "VirtualDirectoryNode.createSheet.timezone.desc=Timezone of the image", + "VirtualDirectoryNode.createSheet.deviceId.name=Device ID", + "VirtualDirectoryNode.createSheet.deviceId.displayName=Device ID", + "VirtualDirectoryNode.createSheet.deviceId.desc=Device ID of the image"}) protected Sheet createSheet() { Sheet s = super.createSheet(); Sheet.Set ss = s.get(Sheet.PROPERTIES); @@ -93,18 +134,52 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode map = new LinkedHashMap<>(); - fillPropertyMap(map, content); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"), getName())); - final String NO_DESCR = NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.noDesc"); - for (Map.Entry entry : map.entrySet()) { - ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + if (!this.content.isDataSource()) { + Map map = new LinkedHashMap<>(); + fillPropertyMap(map, content); + + final String NO_DESCR = NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.noDesc"); + for (Map.Entry entry : map.entrySet()) { + ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); + } + } else { + ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(), + Bundle.VirtualDirectoryNode_createSheet_type_displayName(), + Bundle.VirtualDirectoryNode_createSheet_type_desc(), + Bundle.VirtualDirectoryNode_createSheet_type_text())); + ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_size_name(), + Bundle.VirtualDirectoryNode_createSheet_size_displayName(), + Bundle.VirtualDirectoryNode_createSheet_size_desc(), + this.content.getSize())); + try (SleuthkitCase.CaseDbQuery query = Case.getCurrentCase().getSleuthkitCase().executeQuery("SELECT time_zone FROM data_source_info WHERE obj_id = " + this.content.getId())) { + ResultSet timeZoneSet = query.getResultSet(); + if (timeZoneSet.next()) { + ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_timezone_name(), + Bundle.VirtualDirectoryNode_createSheet_timezone_displayName(), + Bundle.VirtualDirectoryNode_createSheet_timezone_desc(), + timeZoneSet.getString("time_zone"))); + } + } catch (SQLException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get time zone for the following image: " + this.content.getId(), ex); + } + try (SleuthkitCase.CaseDbQuery query = Case.getCurrentCase().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { + ResultSet deviceIdSet = query.getResultSet(); + if (deviceIdSet.next()) { + ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_deviceId_name(), + Bundle.VirtualDirectoryNode_createSheet_deviceId_displayName(), + Bundle.VirtualDirectoryNode_createSheet_deviceId_desc(), + deviceIdSet.getString("device_id"))); + } + } catch (SQLException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex); + } + } return s; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index 73b7ddaa0c..7af125a8d9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -131,6 +131,10 @@ public class VolumeNode extends AbstractContentNode { public Action[] getActions(boolean popup) { List actionsList = new ArrayList<>(); + for (Action a : super.getActions(true)) { + actionsList.add(a); + } + actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "VolumeNode.getActions.viewInNewWin.text"), this)); actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index d352956556..27cc6dbcca 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -18,27 +18,14 @@ */ package org.sleuthkit.autopsy.directorytree; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JTable; -import javax.swing.table.DefaultTableModel; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.DerivedFile; @@ -77,8 +64,6 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Image img) { List lst = new ArrayList<>(); - lst.add(new ImageDetails( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.imgDetails.title"), img)); //TODO lst.add(new ExtractAction("Extract Image", img)); lst.add(new ExtractUnallocAction( NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.extUnallocToSingleFiles"), img)); @@ -87,15 +72,12 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final FileSystem fs) { - return Collections.singletonList(new FileSystemDetails( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.fileSystemDetails.title"), fs)); + return new ArrayList<>(); } @Override public List visit(final Volume vol) { List lst = new ArrayList<>(); - lst.add(new VolumeDetails( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.volumeDetails.title"), vol)); lst.add(new ExtractUnallocAction( NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.extUnallocToSingleFile"), vol)); return lst; @@ -152,242 +134,4 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.DefaultemptyList(); } - //Below here are classes regarding node-specific actions - /** - * VolumeDetails class - */ - private class VolumeDetails extends AbstractAction { - - private final String title; - private final Volume vol; - - VolumeDetails(String title, Volume vol) { - super(title); - this.title = title; - this.vol = vol; - } - - @Override - public void actionPerformed(ActionEvent e) { - final JFrame frame = new JFrame(title); - final JDialog popUpWindow = new JDialog(frame, title, true); // to make the popUp Window to be modal - - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - - // set the popUp window / JFrame - popUpWindow.setSize(800, 400); - - int w = popUpWindow.getSize().width; - int h = popUpWindow.getSize().height; - - // set the location of the popUp Window on the center of the screen - popUpWindow.setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2); - - VolumeDetailsPanel volumeDetailPanel = new VolumeDetailsPanel(); - Boolean counter = false; - - volumeDetailPanel.setVolumeIDValue(Long.toString(vol.getAddr())); - volumeDetailPanel.setStartValue(Long.toString(vol.getStart())); - volumeDetailPanel.setLengthValue(Long.toString(vol.getLength())); - volumeDetailPanel.setDescValue(vol.getDescription()); - volumeDetailPanel.setFlagsValue(vol.getFlagsAsString()); - counter = true; - - if (counter) { - // add the volume detail panel to the popUp window - popUpWindow.add(volumeDetailPanel); - } else { - // error handler if no volume matches - JLabel error = new JLabel( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.volDetail.noVolMatchErr")); - error.setFont(error.getFont().deriveFont(Font.BOLD, 24)); - popUpWindow.add(error); - } - - // add the command to close the window to the button on the Volume Detail Panel - volumeDetailPanel.setOKButtonActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - popUpWindow.dispose(); - } - }); - popUpWindow.pack(); - popUpWindow.setResizable(false); - popUpWindow.setVisible(true); - - } - } - - /** - * ImageDetails panel class - */ - private class ImageDetails extends AbstractAction { - - final String title; - final Image img; - - ImageDetails(String title, Image img) { - super(title); - this.title = title; - this.img = img; - } - - @Override - public void actionPerformed(ActionEvent e) { - final JFrame frame = new JFrame(title); - final JDialog popUpWindow = new JDialog(frame, title, true); // to make the popUp Window to be modal - // if we select the Image Details menu - - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - - // set the popUp window / JFrame - popUpWindow.setSize(750, 400); - - int w = popUpWindow.getSize().width; - int h = popUpWindow.getSize().height; - - // set the location of the popUp Window on the center of the screen - popUpWindow.setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2); - - ImageDetailsPanel imgDetailPanel = new ImageDetailsPanel(); - Boolean counter = false; - - imgDetailPanel.setImgNameValue(img.getName()); - imgDetailPanel.setImgTypeValue(img.getType().getName()); - imgDetailPanel.setImgSectorSizeValue(Long.toString(img.getSsize())); - imgDetailPanel.setImgTotalSizeValue(Long.toString(img.getSize())); - String hash = img.getMd5(); - // don't show the hash if there isn't one - imgDetailPanel.setVisibleHashInfo(hash != null); - imgDetailPanel.setImgHashValue(hash); - - counter = true; - - if (counter) { - // add the volume detail panel to the popUp window - popUpWindow.add(imgDetailPanel); - } else { - // error handler if no volume matches - JLabel error = new JLabel( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr")); - error.setFont(error.getFont().deriveFont(Font.BOLD, 24)); - popUpWindow.add(error); - } - - // add the command to close the window to the button on the Volume Detail Panel - imgDetailPanel.setOKButtonActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - popUpWindow.dispose(); - } - }); - - popUpWindow.pack(); - popUpWindow.setResizable(false); - popUpWindow.setVisible(true); - } - } - - /** - * FileSystemDetails class - */ - private class FileSystemDetails extends AbstractAction { - - private final FileSystem fs; - private final String title; - - FileSystemDetails(String title, FileSystem fs) { - super(title); - this.title = title; - this.fs = fs; - } - - @Override - public void actionPerformed(ActionEvent e) { - Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); - - final JFrame frame = new JFrame(title); - final JDialog popUpWindow = new JDialog(frame, title, true); // to make the popUp Window to be modal - - // set the popUp window / JFrame - popUpWindow.setSize(1000, 500); - - int w = popUpWindow.getSize().width; - int h = popUpWindow.getSize().height; - - // set the location of the popUp Window on the center of the screen - popUpWindow.setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2); - - String[] columnNames = new String[]{ - "fs_id", //NON-NLS - "img_offset", //NON-NLS - "par_id", //NON-NLS - "fs_type", //NON-NLS - "block_size", //NON-NLS - "block_count", //NON-NLS - "root_inum", //NON-NLS - "first_inum", //NON-NLS - "last_inum" //NON-NLS - }; - - Object[][] rowValues = new Object[1][9]; - - Content parent = null; - try { - parent = fs.getParent(); - } catch (Exception ex) { - throw new RuntimeException( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.exception.probGetParent.text", - FileSystem.class.getName(), fs), ex); - } - long id = -1; - if (parent != null) { - id = parent.getId(); - } - - Arrays.fill(rowValues, 0, 1, new Object[]{ - fs.getId(), - fs.getImageOffset(), - id, - fs.getFsType(), - fs.getBlock_size(), - fs.getBlock_count(), - fs.getRoot_inum(), - fs.getFirst_inum(), - fs.getLastInum() - }); - - JTable table = new JTable(new DefaultTableModel(rowValues, columnNames)); - - FileSystemDetailsPanel fsdPanel = new FileSystemDetailsPanel(); - - // add the command to close the window to the button on the Volume Detail Panel - fsdPanel.setOKButtonActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - popUpWindow.dispose(); - } - }); - - try { - fsdPanel.setFileSystemTypeValue(table.getValueAt(0, 3).toString()); - fsdPanel.setImageOffsetValue(table.getValueAt(0, 1).toString()); - fsdPanel.setVolumeIDValue(table.getValueAt(0, 2).toString()); //TODO: fix this to parent id, not vol id - fsdPanel.setBlockSizeValue(table.getValueAt(0, 4).toString()); - fsdPanel.setBlockCountValue(table.getValueAt(0, 5).toString()); - fsdPanel.setRootInumValue(table.getValueAt(0, 6).toString()); - fsdPanel.setFirstInumValue(table.getValueAt(0, 7).toString()); - fsdPanel.setLastInumValue(table.getValueAt(0, 8).toString()); - - popUpWindow.add(fsdPanel); - } catch (Exception ex) { - Logger.getLogger(ExplorerNodeActionVisitor.class.getName()).log(Level.WARNING, "Error setting up File System Details panel.", ex); //NON-NLS - } - - popUpWindow.pack(); - popUpWindow.setResizable(false); - popUpWindow.setVisible(true); - - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java b/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java index 9deeb0e841..24255057d0 100644 --- a/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java +++ b/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -127,6 +128,7 @@ public final class ExternalResultsImporter { } } + @Messages({"ExternalResultsImporter.indexError.message=Failed to index imported artifact for keyword search."}) private void importArtifacts(ExternalResults results) { SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase(); for (ExternalResults.Artifact artifactData : results.getArtifacts()) { @@ -200,9 +202,9 @@ public final class ExternalResultsImporter { // index the artifact for keyword search blackboard.indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName()); + Bundle.ExternalResultsImporter_indexError_message(), artifact.getDisplayName()); } if (standardArtifactTypeIds.contains(artifactTypeId)) { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/AbstractFileSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/AbstractFileSearchFilter.java index 0d59301eea..67f65be80b 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/AbstractFileSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/AbstractFileSearchFilter.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.filesearch; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; /** @@ -39,4 +40,9 @@ abstract class AbstractFileSearchFilter implements FileSea public T getComponent() { return this.component; } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.getComponent().addPropertyChangeListener(listener); + } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index be8a799556..33e1ba6635 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java @@ -205,6 +205,11 @@ class DateSearchFilter extends AbstractFileSearchFilter { getComponent().addActionListener(l); } + @Override + public boolean isValid() { + return this.getComponent().isValidSearch(); + } + /** * Inner class to put the separator inside the combo box. */ diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form index 2d09e16507..e92d57fdd9 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form @@ -236,6 +236,9 @@ + + + @@ -244,6 +247,9 @@ + + + @@ -252,6 +258,9 @@ + + + @@ -260,6 +269,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java index ea3642a65d..9794e25700 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; @@ -37,6 +39,7 @@ class DateSearchPanel extends javax.swing.JPanel { DateFormat dateFormat; List timeZones; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); DateSearchPanel(DateFormat dateFormat, List timeZones) { this.dateFormat = dateFormat; @@ -133,6 +136,16 @@ class DateSearchPanel extends javax.swing.JPanel { this.changedCheckBox.setEnabled(enable); this.createdCheckBox.setEnabled(enable); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener pcl) { + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + pcs.removePropertyChangeListener(pcl); + } /** * This method is called from within the constructor to initialize the form. @@ -209,15 +222,35 @@ class DateSearchPanel extends javax.swing.JPanel { modifiedCheckBox.setSelected(true); modifiedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.modifiedCheckBox.text")); // NOI18N + modifiedCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + modifiedCheckBoxActionPerformed(evt); + } + }); changedCheckBox.setSelected(true); changedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.changedCheckBox.text")); // NOI18N + changedCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + changedCheckBoxActionPerformed(evt); + } + }); accessedCheckBox.setSelected(true); accessedCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.accessedCheckBox.text")); // NOI18N + accessedCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + accessedCheckBoxActionPerformed(evt); + } + }); createdCheckBox.setSelected(true); createdCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.createdCheckBox.text")); // NOI18N + createdCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createdCheckBoxActionPerformed(evt); + } + }); dateFromButtonCalendar.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.dateFromButtonCalendar.text")); // NOI18N dateFromButtonCalendar.addPropertyChangeListener(new java.beans.PropertyChangeListener() { @@ -349,8 +382,25 @@ class DateSearchPanel extends javax.swing.JPanel { private void dateCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateCheckBoxActionPerformed this.setComponentsEnabled(); + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); }//GEN-LAST:event_dateCheckBoxActionPerformed + private void modifiedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_modifiedCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_modifiedCheckBoxActionPerformed + + private void accessedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_accessedCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_accessedCheckBoxActionPerformed + + private void createdCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createdCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_createdCheckBoxActionPerformed + + private void changedCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_changedCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_changedCheckBoxActionPerformed + /** * Validate and set the datetime field on the screen given a datetime * string. @@ -379,6 +429,13 @@ class DateSearchPanel extends javax.swing.JPanel { dateToTextField.setText(dateStringResult); dateToButtonCalendar.setTargetDate(date); } + + boolean isValidSearch() { + return this.accessedCheckBox.isSelected() || + this.changedCheckBox.isSelected() || + this.createdCheckBox.isSelected() || + this.modifiedCheckBox.isSelected(); + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox accessedCheckBox; private javax.swing.JCheckBox changedCheckBox; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchFilter.java index 878c500c0b..457db56570 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchFilter.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; /** @@ -40,6 +41,13 @@ interface FileSearchFilter { */ boolean isEnabled(); + /** + * Checks if the panel has valid input for search. + * + * @return Whether the panel has valid input for search. + */ + boolean isValid(); + /** * Gets predicate expression to include in the SQL filter expression * @@ -56,6 +64,13 @@ interface FileSearchFilter { */ void addActionListener(ActionListener l); + /** + * Adds the property change listener to the panel + * + * @param listener the listener to add. + */ + void addPropertyChangeListener(PropertyChangeListener listener); + /** * Thrown if a filter's inputs are invalid */ diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index e796589d22..656e22ef9e 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -17,38 +17,35 @@ * limitations under the License. */ -/* + /* * FileSearchPanel.java * * Created on Mar 5, 2012, 1:51:50 PM */ package org.sleuthkit.autopsy.filesearch; -import java.awt.BorderLayout; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; - -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.BoxLayout; -import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; import org.sleuthkit.datamodel.AbstractFile; @@ -64,6 +61,10 @@ class FileSearchPanel extends javax.swing.JPanel { private static int resultWindowCount = 0; //keep track of result windows so they get unique names private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text"); + enum EVENT { + CHECKED + } + /** * Creates new form FileSearchPanel */ @@ -100,25 +101,39 @@ class FileSearchPanel extends javax.swing.JPanel { filterPanel.add(fa); } + for (FileSearchFilter filter : this.getFilters()) { + filter.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + searchButton.setEnabled(isValidSearch()); + } + }); + } + addListenerToAll(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { search(); } }); + searchButton.setEnabled(isValidSearch()); } /** * @return true if any of the filters in the panel are enabled (checked) */ - private boolean anyFiltersEnabled() { + private boolean isValidSearch() { + boolean enabled = false; for (FileSearchFilter filter : this.getFilters()) { if (filter.isEnabled()) { - return true; + enabled = true; + if (!filter.isValid()) { + return false; + } } } - return false; + return enabled; } /** @@ -129,7 +144,7 @@ class FileSearchPanel extends javax.swing.JPanel { // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - if (this.anyFiltersEnabled()) { + if (this.isValidSearch()) { String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); @@ -290,6 +305,7 @@ class FileSearchPanel extends javax.swing.JPanel { .addContainerGap()) ); }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel filterPanel; private javax.swing.JButton searchButton; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java index bd9776f61d..5f52afa035 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionListener; - import org.openide.util.NbBundle; import org.sleuthkit.datamodel.TskData.FileKnown; @@ -42,6 +41,7 @@ class KnownStatusSearchFilter extends AbstractFileSearchFilter + + + @@ -83,6 +86,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java index 291ced32e7..69ec869cfa 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java @@ -17,13 +17,15 @@ * limitations under the License. */ -/* + /* * KnownStatusSearchPanel.java * * Created on Oct 19, 2011, 11:45:44 AM */ package org.sleuthkit.autopsy.filesearch; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import javax.swing.JCheckBox; /** @@ -32,6 +34,8 @@ import javax.swing.JCheckBox; */ class KnownStatusSearchPanel extends javax.swing.JPanel { + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + /** * Creates new form KnownStatusSearchPanel */ @@ -55,7 +59,7 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { JCheckBox getUnknownOptionCheckBox() { return unknownOptionCheckBox; } - + private void setComponentsEnabled() { boolean enabled = this.knownCheckBox.isSelected(); this.unknownOptionCheckBox.setEnabled(enabled); @@ -63,6 +67,20 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { this.knownBadOptionCheckBox.setEnabled(enabled); } + @Override + public void addPropertyChangeListener(PropertyChangeListener pcl) { + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + pcs.removePropertyChangeListener(pcl); + } + + boolean isValidSearch() { + return this.unknownOptionCheckBox.isSelected() || this.knownBadOptionCheckBox.isSelected() || this.knownOptionCheckBox.isSelected(); + } + /** * 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 @@ -86,6 +104,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { unknownOptionCheckBox.setSelected(true); unknownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.unknownOptionCheckBox.text")); // NOI18N + unknownOptionCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + unknownOptionCheckBoxActionPerformed(evt); + } + }); knownOptionCheckBox.setSelected(true); knownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownOptionCheckBox.text")); // NOI18N @@ -97,6 +120,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { knownBadOptionCheckBox.setSelected(true); knownBadOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownBadOptionCheckBox.text")); // NOI18N + knownBadOptionCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + knownBadOptionCheckBoxActionPerformed(evt); + } + }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -127,13 +155,22 @@ class KnownStatusSearchPanel extends javax.swing.JPanel { }// //GEN-END:initComponents private void knownOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownOptionCheckBoxActionPerformed - // TODO add your handling code here: + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); }//GEN-LAST:event_knownOptionCheckBoxActionPerformed private void knownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownCheckBoxActionPerformed setComponentsEnabled(); + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); }//GEN-LAST:event_knownCheckBoxActionPerformed + private void unknownOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unknownOptionCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_unknownOptionCheckBoxActionPerformed + + private void knownBadOptionCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadOptionCheckBoxActionPerformed + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_knownBadOptionCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox knownBadOptionCheckBox; private javax.swing.JCheckBox knownCheckBox; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypeFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypeFilter.java index b119566299..4d3078fd46 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypeFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypeFilter.java @@ -10,28 +10,28 @@ import java.awt.event.ActionListener; /** * Filter by mime type used in filter areas of file search by attribute. */ -class MimeTypeFilter extends AbstractFileSearchFilter { +class MimeTypeFilter extends AbstractFileSearchFilter { public MimeTypeFilter(MimeTypePanel component) { super(component); } + public MimeTypeFilter() { this(new MimeTypePanel()); } @Override public boolean isEnabled() { - return this.getComponent().isSelected() && - !this.getComponent().getMimeTypesSelected().isEmpty(); + return this.getComponent().isSelected(); } @Override public String getPredicate() throws FilterValidationException { String predicate = ""; - for(String mimeType : this.getComponent().getMimeTypesSelected()) { + for (String mimeType : this.getComponent().getMimeTypesSelected()) { predicate += "mime_type = '" + mimeType + "' OR "; } - if(predicate.length() > 3) { + if (predicate.length() > 3) { predicate = predicate.substring(0, predicate.length() - 3); } return predicate; @@ -40,5 +40,9 @@ class MimeTypeFilter extends AbstractFileSearchFilter { @Override public void addActionListener(ActionListener l) { } - + + @Override + public boolean isValid() { + return !this.getComponent().getMimeTypesSelected().isEmpty(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form index a8c7fa65f9..7eb2d436df 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form @@ -31,7 +31,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java index b4172354a4..e14e5e0dc4 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java @@ -5,12 +5,16 @@ */ package org.sleuthkit.autopsy.filesearch; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.logging.Level; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MimeTypes; import org.sleuthkit.autopsy.coreutils.Logger; @@ -25,6 +29,7 @@ public class MimeTypePanel extends javax.swing.JPanel { private static final SortedSet mediaTypes = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes(); private static final Logger logger = Logger.getLogger(MimeTypePanel.class.getName()); private static final long serialVersionUID = 1L; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * Creates new form MimeTypePanel @@ -32,6 +37,12 @@ public class MimeTypePanel extends javax.swing.JPanel { public MimeTypePanel() { initComponents(); setComponentsEnabled(); + this.mimeTypeList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + }); } private String[] getMimeTypeArray() { @@ -75,6 +86,16 @@ public class MimeTypePanel extends javax.swing.JPanel { this.mimeTypeList.setEnabled(enabled); this.jLabel1.setEnabled(enabled); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener pcl) { + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + pcs.removePropertyChangeListener(pcl); + } /** * This method is called from within the constructor to initialize the form. @@ -141,6 +162,8 @@ public class MimeTypePanel extends javax.swing.JPanel { private void mimeTypeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeTypeCheckBoxActionPerformed setComponentsEnabled(); + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + this.mimeTypeList.setSelectedIndices(new int[0]); }//GEN-LAST:event_mimeTypeCheckBoxActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchFilter.java index 2433a71ba3..ee70ebd5e1 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionListener; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; @@ -63,4 +62,9 @@ class NameSearchFilter extends AbstractFileSearchFilter { public void addActionListener(ActionListener l) { getComponent().addActionListener(l); } + + @Override + public boolean isValid() { + return !this.getComponent().getSearchTextField().getText().isEmpty(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java index 1ae2842947..4ffc2c86f7 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java @@ -17,7 +17,7 @@ * limitations under the License. */ -/* + /* * NameSearchPanel.java * * Created on Oct 19, 2011, 11:58:53 AM @@ -26,9 +26,13 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import javax.swing.JCheckBox; import javax.swing.JMenuItem; import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; /** * @@ -36,6 +40,8 @@ import javax.swing.JTextField; */ class NameSearchPanel extends javax.swing.JPanel { + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + /** * Creates new form NameSearchPanel */ @@ -67,6 +73,22 @@ class NameSearchPanel extends javax.swing.JPanel { copyMenuItem.addActionListener(actList); pasteMenuItem.addActionListener(actList); selectAllMenuItem.addActionListener(actList); + this.searchTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + + @Override + public void removeUpdate(DocumentEvent e) { + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + + @Override + public void changedUpdate(DocumentEvent e) { + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + }); } @@ -77,12 +99,22 @@ class NameSearchPanel extends javax.swing.JPanel { JTextField getSearchTextField() { return searchTextField; } - + void setComponentsEnabled() { boolean enabled = nameCheckBox.isSelected(); this.searchTextField.setEnabled(enabled); this.noteNameLabel.setEnabled(enabled); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener pcl) { + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + pcs.removePropertyChangeListener(pcl); + } /** * This method is called from within the constructor to initialize the form. @@ -168,6 +200,7 @@ class NameSearchPanel extends javax.swing.JPanel { private void nameCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameCheckBoxActionPerformed setComponentsEnabled(); + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); }//GEN-LAST:event_nameCheckBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java index 03fc59b8d4..ca43dac7e0 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionListener; import javax.swing.JComboBox; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; @@ -73,4 +72,9 @@ class SizeSearchFilter extends AbstractFileSearchFilter { public void addActionListener(ActionListener l) { getComponent().addActionListener(l); } + + @Override + public boolean isValid() { + return true; + } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java index 4a0bc91d5e..4bde4eb5e7 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.text.NumberFormat; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -32,6 +34,8 @@ import javax.swing.JMenuItem; */ class SizeSearchPanel extends javax.swing.JPanel { + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + /** * Creates new form SizeSearchPanel */ @@ -81,13 +85,23 @@ class SizeSearchPanel extends javax.swing.JPanel { JComboBox getSizeUnitComboBox() { return sizeUnitComboBox; } - + void setComponentsEnabled() { boolean enabled = this.sizeCheckBox.isSelected(); this.sizeCompareComboBox.setEnabled(enabled); this.sizeUnitComboBox.setEnabled(enabled); this.sizeTextField.setEnabled(enabled); } + + @Override + public void addPropertyChangeListener(PropertyChangeListener pcl) { + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + pcs.removePropertyChangeListener(pcl); + } /** * This method is called from within the constructor to initialize the form. @@ -168,6 +182,7 @@ class SizeSearchPanel extends javax.swing.JPanel { private void sizeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sizeCheckBoxActionPerformed setComponentsEnabled(); + pcs.firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); }//GEN-LAST:event_sizeCheckBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 12930abba9..1869be9169 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -32,9 +32,17 @@ import javax.swing.JOptionPane; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType; +import org.sleuthkit.datamodel.IngestModuleInfo; +import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Encapsulates a data source and the ingest module pipelines used to process @@ -151,6 +159,8 @@ final class DataSourceIngestJob { private ProgressHandle fileIngestProgress; private String currentFileIngestModule = ""; private String currentFileIngestTask = ""; + private List ingestModules = new ArrayList<>(); + private IngestJobInfo ingestJob; /** * A data source ingest job uses this field to report its creation time. @@ -243,6 +253,20 @@ final class DataSourceIngestJob { */ Thread.currentThread().interrupt(); } + SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); + try { + this.addIngestModules(firstStageDataSourceModuleTemplates, IngestModuleType.DATA_SOURCE_LEVEL, skCase); + this.addIngestModules(fileIngestModuleTemplates, IngestModuleType.FILE_LEVEL, skCase); + this.addIngestModules(secondStageDataSourceModuleTemplates, IngestModuleType.DATA_SOURCE_LEVEL, skCase); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to add ingest modules to database.", ex); + } + } + + private void addIngestModules(List templates, IngestModuleType type, SleuthkitCase skCase) throws TskCoreException { + for (IngestModuleTemplate module : templates) { + ingestModules.add(skCase.addIngestModule(module.getModuleName(), FactoryClassNameNormalizer.normalize(module.getModuleFactory().getClass().getCanonicalName()), type, module.getModuleFactory().getModuleVersionNumber())); + } } /** @@ -365,6 +389,11 @@ final class DataSourceIngestJob { logger.log(Level.INFO, "Starting second stage analysis for {0} (jobId={1}), no first stage configured", new Object[]{dataSource.getName(), this.id}); //NON-NLS this.startSecondStage(); } + try { + this.ingestJob = Case.getCurrentCase().getSleuthkitCase().addIngestJob(dataSource, NetworkUtils.getLocalHostName(), ingestModules, new Date(this.createTime), new Date(0), IngestJobStatusType.STARTED, ""); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to add ingest job to database.", ex); + } } return errors; } @@ -641,8 +670,26 @@ final class DataSourceIngestJob { } } } - + if (this.cancelled) { + try { + ingestJob.setIngestJobStatus(IngestJobStatusType.CANCELLED); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to set ingest status for ingest job in database.", ex); + } + } else { + try { + ingestJob.setIngestJobStatus(IngestJobStatusType.COMPLETED); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to set ingest status for ingest job in database.", ex); + } + } + try { + this.ingestJob.setEndDateTime(new Date()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to set end date for ingest job in database.", ex); + } this.parentJob.dataSourceJobFinished(this); + } /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java b/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java new file mode 100755 index 0000000000..a03abea38a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java @@ -0,0 +1,42 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.ingest; + +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Used to strip python ids on factory class names. + */ +class FactoryClassNameNormalizer { + + private static final CharSequence pythonModuleSettingsPrefixCS = "org.python.proxies.".subSequence(0, "org.python.proxies.".length() - 1); //NON-NLS + private static final Logger logger = Logger.getLogger(FactoryClassNameNormalizer.class.getName()); + + static String normalize(String canonicalClassName) { + if (isPythonModuleSettingsFile(canonicalClassName)) { + // compiled python modules have variable instance number as a part of their file name. + // This block of code gets rid of that variable instance number and helps maitains constant module name over multiple runs. + String moduleClassName = canonicalClassName.replaceAll("[$][\\d]", ""); //NON-NLS NON-NLS + return moduleClassName; + } + return canonicalClassName; + } + + /** + * Determines if the moduleSettingsFilePath is that of a serialized jython + * instance. Serialized Jython instances (settings saved on the disk) + * contain "org.python.proxies." in their fileName based on the current + * implementation. + * + * @param moduleSettingsFilePath path to the module settings file. + * + * @return True or false + */ + private static boolean isPythonModuleSettingsFile(String moduleSettingsFilePath) { + return moduleSettingsFilePath.contains(pythonModuleSettingsPrefixCS); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index 827bbc016d..bea9d2a10b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -33,10 +33,10 @@ import java.util.logging.Level; import org.openide.util.NbBundle; import org.openide.util.io.NbObjectInputStream; import org.openide.util.io.NbObjectOutputStream; +import org.python.util.PythonObjectInputStream; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.python.util.PythonObjectInputStream; /** * Encapsulates the ingest job settings for a particular execution context. @@ -467,17 +467,9 @@ public class IngestJobSettings { * @param settings The ingest job settings for the ingest module */ private void saveModuleSettings(IngestModuleFactory factory, IngestModuleIngestJobSettings settings) { - try { - String moduleSettingsFilePath = getModuleSettingsFilePath(factory); - // compiled python modules have substring org.python.proxies. It can be used to identify them. - if (isPythonModuleSettingsFile(moduleSettingsFilePath)) { - // compiled python modules have variable instance number as a part of their file name. - // This block of code gets rid of that variable instance number and helps maitains constant module name over multiple runs. - moduleSettingsFilePath = moduleSettingsFilePath.replaceAll("[$][\\d]+.settings$", "\\$.settings"); //NON-NLS NON-NLS - } - try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(moduleSettingsFilePath))) { - out.writeObject(settings); - } + String moduleSettingsFilePath = Paths.get(this.moduleSettingsFolderPath, FactoryClassNameNormalizer.normalize(factory.getClass().getCanonicalName()) + MODULE_SETTINGS_FILE_EXT).toString(); + try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(moduleSettingsFilePath))) { + out.writeObject(settings); } catch (IOException ex) { String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsSave.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS logger.log(Level.SEVERE, warning, ex); diff --git a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java index 69b7c57f4f..af43facec9 100644 --- a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java +++ b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,9 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; /** - * + * An implementation of a keyword search service. + * + * TODO (AUT-2158: This interface should not extend Closeable. */ public interface KeywordSearchService extends Closeable { @@ -49,4 +51,4 @@ public interface KeywordSearchService extends Closeable { */ public void tryConnect(String host, int port) throws KeywordSearchServiceException; - } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java index d7f0543c49..d76abda578 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java @@ -26,7 +26,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; @@ -72,6 +71,7 @@ class BrowserLocationAnalyzer { } } + @NbBundle.Messages({"BrowserLocationAnalyzer.indexError.message=Failed to index GPS trackpoint artifact for keyword search."}) private static void findGeoLocationsInDB(String DatabasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -110,9 +110,9 @@ class BrowserLocationAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactTypeName(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.BrowserLocationAnalyzer_indexError_message(), bba.getDisplayName()); } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java index 266bf09469..9f36f3cf83 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java @@ -25,8 +25,8 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.List; import java.util.logging.Level; - import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -78,6 +78,7 @@ class CacheLocationAnalyzer { } } + @Messages({"CacheLocationAnalyzer.indexError.message=Failed to index GPS trackpoint artifact for keyword search."}) private static void findGeoLocationsInFile(File file, AbstractFile f) { byte[] bytes; // will temporarily hold bytes to be converted into the correct data types @@ -140,14 +141,13 @@ class CacheLocationAnalyzer { //Not storing these for now. // bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(),moduleName, accuracy)); // bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(),moduleName, confidence)); - try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.CacheLocationAnalyzer_indexError_message(), bba.getDisplayName()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java index 7df25e15ff..198a503e4a 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java @@ -28,7 +28,7 @@ import java.sql.Statement; import java.util.Arrays; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -58,7 +58,7 @@ class CallLogAnalyzer { private static final Iterable tableNames = Arrays.asList("calls", "logs"); //NON-NLS public static void findCallLogs(Content dataSource, FileManager fileManager) { - blackboard = Case.getCurrentCase().getServices().getBlackboard(); + blackboard = Case.getCurrentCase().getServices().getBlackboard(); try { List absFiles = fileManager.findFiles(dataSource, "logs.db"); //NON-NLS absFiles.addAll(fileManager.findFiles(dataSource, "contacts.db")); //NON-NLS @@ -77,6 +77,7 @@ class CallLogAnalyzer { } } + @Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."}) private static void findCallLogsInDB(String DatabasePath, AbstractFile f) { if (DatabasePath == null || DatabasePath.isEmpty()) { @@ -113,16 +114,16 @@ class CallLogAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName()); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error posting call log record to the Blackboard", ex); //NON-NLS } } } catch (SQLException e) { - logger.log(Level.WARNING, "Could not read table {0} in db {1}", new Object[]{tableName, DatabasePath}); //NON-NLS + logger.log(Level.WARNING, String.format("Could not read table %s in db %s", tableName, DatabasePath), e); //NON-NLS } } } catch (SQLException e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java index 25affa740f..9eb8ac0ae9 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java @@ -27,7 +27,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -79,6 +79,7 @@ class ContactAnalyzer { * path The fileId will be the Abstract file associated * with the artifacts */ + @Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."}) private static void findContactsInDB(String databasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -154,9 +155,9 @@ class ContactAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java index d7889257dc..fbb6c9befd 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java @@ -26,8 +26,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; - import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -71,6 +71,7 @@ class GoogleMapLocationAnalyzer { } } + @Messages({"GoogleMapLocationAnalyzer.indexError.message=Failed to index GPS route artifact for keyword search."}) private static void findGeoLocationsInDB(String DatabasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -136,9 +137,9 @@ class GoogleMapLocationAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.GoogleMapLocationAnalyzer_indexError_message(), bba.getDisplayName()); } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java index 09dba30821..41d8676eed 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.logging.Level; import org.apache.commons.codec.binary.Base64; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -68,6 +69,7 @@ class TangoMessageAnalyzer { } } + @Messages({"TangoMessageAnalyzer.indexError.message=Failed to index Tango message artifact for keyword search."}) private static void findTangoMessagesInDB(String DatabasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -110,14 +112,14 @@ class TangoMessageAnalyzer { bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(TangoMessageAnalyzer.class, "TangoMessageAnalyzer.bbAttribute.tangoMessage"))); - + try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.TangoMessageAnalyzer_indexError_message(), bba.getDisplayName()); + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java index edf12e0b66..71139690da 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java @@ -26,8 +26,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; - import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -68,6 +68,7 @@ class TextMessageAnalyzer { } } + @Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."}) private static void findTextsInDB(String DatabasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -123,14 +124,14 @@ class TextMessageAnalyzer { bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(TextMessageAnalyzer.class, "TextMessageAnalyzer.bbAttribute.smsMessage"))); - + try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName()); } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java index 87bbd8dc4d..8db7132bf0 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java @@ -26,8 +26,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; - import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -71,6 +71,7 @@ class WWFMessageAnalyzer { } } + @Messages({"WWFMessageAnalyzer.indexError.message=Failed to index WWF message artifact for keyword search."}) private static void findWWFMessagesInDB(String DatabasePath, AbstractFile f) { Connection connection = null; ResultSet resultSet = null; @@ -110,14 +111,14 @@ class WWFMessageAnalyzer { bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(WWFMessageAnalyzer.class, "WWFMessageAnalyzer.bbAttribute.wordsWithFriendsMsg"))); - + try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.WWFMessageAnalyzer_indexError_message(), bba.getDisplayName()); } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 8e25feaa23..18a81126fa 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -42,6 +42,7 @@ import net.sf.sevenzipjbinding.simple.ISimpleInArchive; import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; @@ -50,7 +51,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; -import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; @@ -267,6 +267,7 @@ class SevenZipExtractor { * * @return list of unpacked derived files */ + @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) void unpack(AbstractFile archiveFile) { blackboard = Case.getCurrentCase().getServices().getBlackboard(); String archiveFilePath; @@ -586,9 +587,9 @@ class SevenZipExtractor { // index the artifact for keyword search blackboard.indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName()); + Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName()); } services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED)); diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index 2534d376f7..1fd1bd9116 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -38,15 +38,16 @@ import java.util.TimeZone; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -133,6 +134,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule { return processFile(content); } + @Messages({"ExifParserFileIngestModule.indexError.message=Failed to index EXIF Metadata artifact for keyword search."}) ProcessResult processFile(AbstractFile f) { InputStream in = null; BufferedInputStream bin = null; @@ -206,9 +208,9 @@ public final class ExifParserFileIngestModule implements FileIngestModule { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.ExifParserFileIngestModule_indexError_message(), bba.getDisplayName()); } filesToFire = true; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java index 6f9a9ea98c..07d2a9a5bd 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -105,6 +106,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule { } @Override + @Messages({"FileExtMismatchIngestModule.indexError.message=Failed to index file extension mismatch artifact for keyword search."}) public ProcessResult process(AbstractFile abstractFile) { blackboard = Case.getCurrentCase().getServices().getBlackboard(); if (this.settings.skipKnownFiles() && (abstractFile.getKnown() == FileKnown.KNOWN)) { @@ -139,9 +141,9 @@ public class FileExtMismatchIngestModule implements FileIngestModule { // index the artifact for keyword search blackboard.indexArtifact(bart); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bart.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bart.getDisplayName()); + Bundle.FileExtMismatchIngestModule_indexError_message(), bart.getDisplayName()); } services.fireModuleDataEvent(new ModuleDataEvent(FileExtMismatchDetectorModuleFactory.getModuleName(), ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED, Collections.singletonList(bart))); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java index 784dea47b9..4e06ee7d74 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java @@ -104,6 +104,19 @@ final class CustomFileTypesManager { return customTypes; } + /** + * Gets the custom file types defined by Autopsy. + * + * @return A list of custom file types, possibly empty. + */ + synchronized List getAutopsyDefinedFileTypes() { + /** + * It is safe to return references instead of copies in this snapshot + * because FileType objects are immutable. + */ + return new ArrayList<>(autopsyDefinedFileTypes); + } + /** * Gets the user-defined custom file types. * @@ -287,6 +300,15 @@ final class CustomFileTypesManager { fileType = new FileType("application/x-iff", signatureList); //NON-NLS autopsyDefinedFileTypes.add(fileType); + /* + * Add type for .tec files with leading End Of Image marker (JFIF JPEG) + */ + byteArray = DatatypeConverter.parseHexBinary("FFD9FFD8"); //NON-NLS + signatureList.clear(); + signatureList.add(new Signature(byteArray, 0L)); + fileType = new FileType("image/jpeg", signatureList); //NON-NLS + autopsyDefinedFileTypes.add(fileType); + } catch (IllegalArgumentException ex) { /* * parseHexBinary() throws this if the argument passed in is not hex diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 44a7cda7da..487335d828 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -56,8 +56,8 @@ public class FileTypeDetector { */ public FileTypeDetector() throws FileTypeDetectorInitException { try { - userDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes(); - autopsyDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes(); + userDefinedFileTypes = CustomFileTypesManager.getInstance().getUserDefinedFileTypes(); + autopsyDefinedFileTypes = CustomFileTypesManager.getInstance().getAutopsyDefinedFileTypes(); } catch (CustomFileTypesManager.CustomFileTypesException ex) { throw new FileTypeDetectorInitException("Error loading custom file types", ex); //NON-NLS } @@ -168,7 +168,7 @@ public class FileTypeDetector { /** * Detects the MIME type of a file. The result is saved to the case database - * only if the add to case dastabase flag is set. + * only if the add to case database flag is set. * * @param file The file to test. * @param addToCaseDb Whether the MIME type should be added to the case diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form index 741c775f3c..0b33ec364f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form @@ -25,22 +25,24 @@ - + - - - - + + + + + + @@ -62,7 +64,7 @@ - + @@ -101,7 +103,10 @@ - + + + + @@ -110,7 +115,7 @@ - + @@ -119,7 +124,10 @@ - + + + + @@ -154,14 +162,14 @@ - - + + - + @@ -263,7 +271,7 @@ - + @@ -291,11 +299,11 @@ - - - - + + + + @@ -320,6 +328,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java index da67c5697a..a4b4ff49d3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -36,8 +36,8 @@ import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; -import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; import org.sleuthkit.autopsy.modules.filetypeid.CustomFileTypesManager.CustomFileTypesException; +import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * A panel to allow a user to make custom file type definitions. In addition to @@ -329,6 +329,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane setMaximumSize(null); setPreferredSize(new java.awt.Dimension(752, 507)); + jPanel3.setPreferredSize(new java.awt.Dimension(781, 339)); + ingestRunningWarningLabel.setFont(ingestRunningWarningLabel.getFont().deriveFont(ingestRunningWarningLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); ingestRunningWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(ingestRunningWarningLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text")); // NOI18N @@ -336,11 +338,13 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane jLabel3.setFont(jLabel3.getFont().deriveFont(jLabel3.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel3.text")); // NOI18N - jScrollPane2.setMinimumSize(new java.awt.Dimension(300, 100)); + jScrollPane2.setMinimumSize(new java.awt.Dimension(300, 0)); + jScrollPane2.setPreferredSize(new java.awt.Dimension(550, 275)); - jSplitPane1.setMinimumSize(new java.awt.Dimension(558, 285)); + jSplitPane1.setMinimumSize(new java.awt.Dimension(0, 0)); - jPanel1.setMinimumSize(new java.awt.Dimension(362, 283)); + jPanel1.setMinimumSize(new java.awt.Dimension(362, 0)); + jPanel1.setPreferredSize(new java.awt.Dimension(362, 0)); jLabel2.setFont(jLabel2.getFont().deriveFont(jLabel2.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel2.text")); // NOI18N @@ -399,20 +403,20 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addGap(12, 12, 12) .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(typesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(typesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 203, Short.MAX_VALUE) + .addGap(10, 10, 10) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(newTypeButton) .addComponent(editTypeButton) .addComponent(deleteTypeButton)) - .addContainerGap()) + .addGap(7, 7, 7)) ); jPanel1Layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteTypeButton, newTypeButton}); jSplitPane1.setLeftComponent(jPanel1); - jPanel2.setMinimumSize(new java.awt.Dimension(79, 283)); + jPanel2.setMinimumSize(new java.awt.Dimension(79, 0)); org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel1.text")); // NOI18N @@ -421,6 +425,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane public int getSize() { return signatures.length; } public Signature getElementAt(int i) { return signatures[i]; } }); + signatureList.setEnabled(false); signatureList.setMaximumSize(new java.awt.Dimension(32767, 32767)); signatureList.setPreferredSize(null); jScrollPane1.setViewportView(signatureList); @@ -444,8 +449,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addContainerGap() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) - .addGap(40, 40, 40)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 235, Short.MAX_VALUE) + .addContainerGap()) ); jSplitPane1.setRightComponent(jPanel2); @@ -470,7 +475,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addContainerGap() .addComponent(jLabel3) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(ingestRunningWarningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) @@ -481,14 +486,12 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, 752, Short.MAX_VALUE) .addGap(0, 0, 0)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(0, 0, 0)) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, 345, Short.MAX_VALUE) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 96f0c1b16e..1b60250a6b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -22,6 +22,7 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -30,6 +31,7 @@ import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException; @@ -273,12 +275,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private void saveAsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsButtonActionPerformed try { - String lastBaseDirectory = ""; + String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString(); if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) { lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY); } StringBuilder path = new StringBuilder(); path.append(lastBaseDirectory); + File hashDbFolder = new File(path.toString()); + // create the folder if it doesn't exist + if (!hashDbFolder.exists()){ + hashDbFolder.mkdir(); + } if (!hashSetNameTextField.getText().isEmpty()) { path.append(File.separator).append(hashSetNameTextField.getText()); } else { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index 18fd2c6202..337fa49f0b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -22,6 +22,7 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -31,6 +32,7 @@ import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException; @@ -249,9 +251,16 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { }// //GEN-END:initComponents private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed + String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString(); if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) { - fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY))); + lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY); } + File hashDbFolder = new File(lastBaseDirectory); + // create the folder if it doesn't exist + if (!hashDbFolder.exists()) { + hashDbFolder.mkdir(); + } + fileChooser.setCurrentDirectory(hashDbFolder); if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File databaseFile = fileChooser.getSelectedFile(); try { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index 0199727cb2..13f0d9914d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -26,27 +26,28 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.HashHitInfo; import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; -import org.sleuthkit.autopsy.ingest.FileIngestModule; -import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; -import org.sleuthkit.datamodel.HashHitInfo; @NbBundle.Messages({ "HashDbIngestModule.noKnownBadHashDbSetMsg=No known bad hash database set.", @@ -285,6 +286,7 @@ public class HashDbIngestModule implements FileIngestModule { return ret; } + @Messages({"HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search."}) private void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, String comment, boolean showInboxMessage) { try { String MODULE_NAME = NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.moduleName"); @@ -303,9 +305,9 @@ public class HashDbIngestModule implements FileIngestModule { // index the artifact for keyword search blackboard.indexArtifact(badFile); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", badFile.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + badFile.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), badFile.getDisplayName()); + Bundle.HashDbIngestModule_indexError_message(), badFile.getDisplayName()); } if (showInboxMessage) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java index 22ca91c064..b3627c3ef3 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java @@ -25,7 +25,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -74,6 +74,7 @@ class CallLogAnalyzer { } } + @Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."}) private void findCallLogsInDB(String DatabasePath, long fId) { if (DatabasePath == null || DatabasePath.isEmpty()) { return; @@ -128,9 +129,9 @@ class CallLogAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName()); } } } catch (Exception e) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index 7c63e28a8d..8741f4077b 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -30,18 +30,18 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.datamodel.ReadContentInputStream; class ContactAnalyzer { @@ -91,6 +91,7 @@ class ContactAnalyzer { * path The fileId will be the Abstract file associated * with the artifacts */ + @Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."}) private void findContactsInDB(String DatabasePath, long fId) { if (DatabasePath == null || DatabasePath.isEmpty()) { return; @@ -149,9 +150,9 @@ class ContactAnalyzer { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java index 44c08ae0cf..9ef547f4d9 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java @@ -26,6 +26,7 @@ import java.sql.Statement; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -74,6 +75,7 @@ class TextMessageAnalyzer { } } + @Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."}) private void findTextsInDB(String DatabasePath, long fId) { if (DatabasePath == null || DatabasePath.isEmpty()) { return; @@ -127,14 +129,14 @@ class TextMessageAnalyzer { bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, moduleName, subject)); bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, moduleName, body)); bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.smsMessage"))); - + try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java index 62b423015b..95f52b06e1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -100,6 +99,7 @@ final class FilesIdentifierIngestModule implements FileIngestModule { * @inheritDoc */ @Override + @Messages({"FilesIdentifierIngestModule.indexError.message=Failed to index interesting file hit artifact for keyword search."}) public ProcessResult process(AbstractFile file) { blackboard = Case.getCurrentCase().getServices().getBlackboard(); @@ -131,9 +131,8 @@ final class FilesIdentifierIngestModule implements FileIngestModule { // index the artifact for keyword search blackboard.indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS - MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName()); + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.FilesIdentifierIngestModule_indexError_message(), artifact.getDisplayName()); } IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, Collections.singletonList(artifact))); diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java index 03a64220fb..e51501fbbe 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverFileIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,9 +30,9 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -40,29 +40,25 @@ import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator; 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.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.Volume; -import org.sleuthkit.autopsy.coreutils.FileUtil; -import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.ingest.ProcTerminationCode; -import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.autopsy.ingest.ProcTerminationCode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskData; /** * A file ingest module that runs the Unallocated Carver executable with @@ -182,12 +178,6 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { Path tempFilePath = null; try { - long id = getRootId(file); - // make sure we have a valid systemID - if (id == -1) { - return IngestModule.ProcessResult.ERROR; - } - // Verify initialization succeeded. if (null == this.executableFile) { logger.log(Level.SEVERE, "PhotoRec carver called after failed start up"); // NON-NLS @@ -205,6 +195,12 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.NotEnoughDiskSpace")); return IngestModule.ProcessResult.ERROR; } + if (this.context.fileIngestIsCancelled() == true) { + // if it was cancelled by the user, result is OK + logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS + MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); + return IngestModule.ProcessResult.OK; + } // Write the file to disk. long writestart = System.currentTimeMillis(); @@ -212,6 +208,13 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName()); ContentUtils.writeToFile(file, tempFilePath.toFile()); + if (this.context.fileIngestIsCancelled() == true) { + // if it was cancelled by the user, result is OK + logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS + MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); + return IngestModule.ProcessResult.OK; + } + // Create a subdirectory for this file. Path outputDirPath = Paths.get(paths.getOutputDirPath().toString(), file.getName()); Files.createDirectory(outputDirPath); @@ -262,6 +265,12 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { java.io.File newAuditFile = new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_REPORT).toString()); //NON-NLS oldAuditFile.renameTo(newAuditFile); + if (this.context.fileIngestIsCancelled() == true) { + // if it was cancelled by the user, result is OK + logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS + MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); + return IngestModule.ProcessResult.OK; + } Path pathToRemove = Paths.get(outputDirPath.toAbsolutePath().toString()); try (DirectoryStream stream = Files.newDirectoryStream(pathToRemove)) { for (Path entry : stream) { @@ -276,10 +285,16 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { // Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database long calcstart = System.currentTimeMillis(); PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath); - List carvedItems = parser.parse(newAuditFile, id, file); + if (this.context.fileIngestIsCancelled() == true) { + // if it was cancelled by the user, result is OK + logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS + MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); + return IngestModule.ProcessResult.OK; + } + List carvedItems = parser.parse(newAuditFile, file, context); long calcdelta = (System.currentTimeMillis() - calcstart); totals.totalParsetime.addAndGet(calcdelta); - if (carvedItems != null) { // if there were any results from carving, add the unallocated carving event to the reports list. + if (carvedItems != null && !carvedItems.isEmpty()) { // if there were any results from carving, add the unallocated carving event to the reports list. totals.totalItemsRecovered.addAndGet(carvedItems.size()); context.addFilesToJob(new ArrayList<>(carvedItems)); services.fireModuleContentEvent(new ModuleContentEvent(carvedItems.get(0))); // fire an event to update the tree @@ -411,31 +426,6 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule { return path; } - /** - * Finds the root Volume or Image of the AbstractFile passed in. - * - * @param file The file we want to find the root parent for - * - * @return The ID of the root parent Volume or Image - */ - private static long getRootId(AbstractFile file) { - long id = -1; - Content parent = null; - try { - parent = file.getParent(); - while (parent != null) { - if (parent instanceof Volume || parent instanceof Image) { - id = parent.getId(); - break; - } - parent = parent.getParent(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "PhotoRec carver exception while trying to get parent of AbstractFile.", ex); //NON-NLS - } - return id; - } - /** * Finds and returns the path to the executable, if able. * diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java index caff08b50c..ca42256063 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverOutputParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,13 +27,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.XMLUtil; +import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.CarvingResult; import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.CarvedFileContainer; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskFileRange; import org.w3c.dom.Document; @@ -70,24 +73,24 @@ class PhotoRecCarverOutputParser { * @throws FileNotFoundException * @throws IOException */ - List parse(File xmlInputFile, long id, AbstractFile af) throws FileNotFoundException, IOException { + List parse(File xmlInputFile, AbstractFile af, IngestJobContext context) throws FileNotFoundException, IOException { try { final Document doc = XMLUtil.loadDoc(PhotoRecCarverOutputParser.class, xmlInputFile.toString()); if (doc == null) { - return null; + return new ArrayList<>(); } Element root = doc.getDocumentElement(); if (root == null) { logger.log(Level.SEVERE, "Error loading config file: invalid file format (bad root)."); //NON-NLS - return null; + return new ArrayList<>(); } NodeList fileObjects = root.getElementsByTagName("fileobject"); //NON-NLS final int numberOfFiles = fileObjects.getLength(); if (numberOfFiles == 0) { - return null; + return new ArrayList<>(); } String fileName; Long fileSize; @@ -99,9 +102,14 @@ class PhotoRecCarverOutputParser { FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); // create and initialize the list to put into the database - List carvedFileContainer = new ArrayList<>(); - + List carvedFiles = new ArrayList<>(); for (int fileIndex = 0; fileIndex < numberOfFiles; ++fileIndex) { + if (context.fileIngestIsCancelled() == true) { + // if it was cancelled by the user, result is OK + logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS + MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); + break; + } entry = (Element) fileObjects.item(fileIndex); fileNames = entry.getElementsByTagName("filename"); //NON-NLS fileSizes = entry.getElementsByTagName("filesize"); //NON-NLS @@ -133,7 +141,7 @@ class PhotoRecCarverOutputParser { if (fileByteEnd > af.getSize()) { long overshoot = fileByteEnd - af.getSize(); if (fileSize > overshoot) { - fileSize = fileSize - overshoot; + fileSize -= overshoot; } else { // This better never happen... Data for this file is corrupted. Skip it. continue; @@ -144,10 +152,10 @@ class PhotoRecCarverOutputParser { } if (!tskRanges.isEmpty()) { - carvedFileContainer.add(new CarvedFileContainer(fileName, fileSize, id, tskRanges)); + carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, tskRanges)); } } - return fileManager.addCarvedFiles(carvedFileContainer); + return fileManager.addCarvedFiles(new CarvingResult(af, carvedFiles)); } catch (NumberFormatException | TskCoreException ex) { logger.log(Level.SEVERE, "Error parsing PhotoRec output and inserting it into the database: {0}", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle.properties index 10a5c0c593..cb44363a4a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle.properties @@ -6,7 +6,7 @@ STIXReportModuleConfigPanel.jLabel2.text=Select a STIX file or directory of STIX STIXReportModuleConfigPanel.jTextField1.text= STIXReportModuleConfigPanel.jButton1.text=Choose file STIXReportModuleConfigPanel.jCheckBox1.text=Include results for false indicators in output file -STIXReportModule.notifyMsg.unableToOpenReportFile=Unable to open STIX report file {0} +STIXReportModule.notifyMsg.unableToOpenReportFile=Unable to complete STIX report. STIXReportModule.progress.completedWithErrors=Completed with errors STIXReportModule.notifyMsg.unableToOpenFileDir=Unable to open STIX file/directory {0} STIXReportModule.progress.couldNotOpenFileDir=Could not open file/directory {0} diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle_ja.properties index 242f9b6b5c..5dfc25469b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/Bundle_ja.properties @@ -1,14 +1,13 @@ -OpenIDE-Module-Name=stix\u30E2\u30B8\u30E5\u30FC\u30EB -STIXReportModule.getDesc.text=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306B\u5BFE\u3057\u3066\u5E7E\u3064\u304B\u306ESTIX\uFF08Structured Threat Information eXpression\uFF1B\u8105\u5A01\u60C5\u5831\u69CB\u9020\u5316\u8A18\u8FF0\u5F62\u5F0F\uFF09\u30D5\u30A1\u30A4\u30EB\u3092\u5B9F\u884C\u3057\u3001\u30EC\u30DD\u30FC\u30C8\u3092\u751F\u6210\u3057\u307E\u3059\u3002\u307E\u305F\u3001\u7591\u308F\u3057\u3044\u30D5\u30A1\u30A4\u30EB\u5185\u306B\u30A2\u30FC\u30C6\u30A3\u30D5\u30A1\u30AF\u30C8\u3092\u4F5C\u6210\u3002 +OpenIDE-Module-Name=stix\u30e2\u30b8\u30e5\u30fc\u30eb +STIXReportModule.getDesc.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u5bfe\u3057\u3066\u5e7e\u3064\u304b\u306eSTIX\uff08Structured Threat Information eXpression\uff1b\u8105\u5a01\u60c5\u5831\u69cb\u9020\u5316\u8a18\u8ff0\u5f62\u5f0f\uff09\u30d5\u30a1\u30a4\u30eb\u3092\u5b9f\u884c\u3057\u3001\u30ec\u30dd\u30fc\u30c8\u3092\u751f\u6210\u3057\u307e\u3059\u3002\u307e\u305f\u3001\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u5185\u306b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u4f5c\u6210\u3002 STIXReportModule.getName.text=STIX -STIXReportModule.notifyMsg.tooManyArtifactsgt1000="{0}"\u7528\u306B\u751F\u6210\u3055\u308C\u305FSTIX\u95A2\u9023\u306E\u30A2\u30FC\u30C6\u30A3\u30D5\u30A1\u30AF\u30C8\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u6700\u521D\u306E1000\u306E\u307F\u4FDD\u5B58\u3055\u308C\u307E\u3059\u3002 -STIXReportModule.notifyMsg.unableToOpenFileDir=STIX\u30D5\u30A1\u30A4\u30EB\uFF0F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA{0}\u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F -STIXReportModule.notifyMsg.unableToOpenReportFile=STIX\u30EC\u30DD\u30FC\u30C8\u30D5\u30A1\u30A4\u30EB{0}\u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F -STIXReportModule.progress.completedWithErrors=\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u304C\u3001\u5B8C\u4E86\u3057\u307E\u3057\u305F -STIXReportModule.progress.couldNotOpenFileDir=\u30D5\u30A1\u30A4\u30EB\uFF0F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA{0}\u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F -STIXReportModule.progress.readSTIX=STIX\u30D5\u30A1\u30A4\u30EB\u3092\u30D1\u30FC\u30B9\u4E2D -STIXReportModuleConfigPanel.jButton1.text=\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E -STIXReportModuleConfigPanel.jCheckBox1.text=\u30A2\u30A6\u30C8\u30D7\u30C3\u30C8\u30D5\u30A1\u30A4\u30EB\u306E\u8AA4\u3063\u305F\u30A4\u30F3\u30B8\u30B1\u30FC\u30BF\u30FC\u306E\u7D50\u679C\u3082\u542B\u3080 -STIXReportModuleConfigPanel.jLabel2.text=STIX\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306FSTIX\u30D5\u30A1\u30A4\u30EB\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u9078\u629E -STIXReportModule.notifyErr.noFildDirProvided=STIX\u30D5\u30A1\u30A4\u30EB\uFF0F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u63D0\u4F9B\u3055\u308C\u3066\u3044\u307E\u305B\u3093 -STIXReportModule.progress.noFildDirProvided=STIX\u30D5\u30A1\u30A4\u30EB\uFF0F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u63D0\u4F9B\u3055\u308C\u3066\u3044\u307E\u305B\u3093 \ No newline at end of file +STIXReportModule.notifyMsg.tooManyArtifactsgt1000="{0}"\u7528\u306b\u751f\u6210\u3055\u308c\u305fSTIX\u95a2\u9023\u306e\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u6700\u521d\u306e1000\u306e\u307f\u4fdd\u5b58\u3055\u308c\u307e\u3059\u3002 +STIXReportModule.notifyMsg.unableToOpenFileDir=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{0}\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f +STIXReportModule.progress.completedWithErrors=\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u304c\u3001\u5b8c\u4e86\u3057\u307e\u3057\u305f +STIXReportModule.progress.couldNotOpenFileDir=\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{0}\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f +STIXReportModule.progress.readSTIX=STIX\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30fc\u30b9\u4e2d +STIXReportModuleConfigPanel.jButton1.text=\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e +STIXReportModuleConfigPanel.jCheckBox1.text=\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30d5\u30a1\u30a4\u30eb\u306e\u8aa4\u3063\u305f\u30a4\u30f3\u30b8\u30b1\u30fc\u30bf\u30fc\u306e\u7d50\u679c\u3082\u542b\u3080 +STIXReportModuleConfigPanel.jLabel2.text=STIX\u30d5\u30a1\u30a4\u30eb\u307e\u305f\u306fSTIX\u30d5\u30a1\u30a4\u30eb\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u9078\u629e +STIXReportModule.notifyErr.noFildDirProvided=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +STIXReportModule.progress.noFildDirProvided=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java index 2762f21e26..3cc97c3e10 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java @@ -18,48 +18,47 @@ */ package org.sleuthkit.autopsy.modules.stix; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.logging.Level; import java.io.BufferedWriter; +import java.io.File; import java.io.FileWriter; +import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; import javax.swing.JPanel; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; - import org.mitre.cybox.cybox_2.ObjectType; import org.mitre.cybox.cybox_2.Observable; import org.mitre.cybox.cybox_2.ObservableCompositionType; +import org.mitre.cybox.cybox_2.OperatorTypeEnum; +import org.mitre.cybox.objects.AccountObjectType; +import org.mitre.cybox.objects.Address; +import org.mitre.cybox.objects.DomainName; +import org.mitre.cybox.objects.EmailMessage; +import org.mitre.cybox.objects.FileObjectType; +import org.mitre.cybox.objects.SystemObjectType; +import org.mitre.cybox.objects.URIObjectType; +import org.mitre.cybox.objects.URLHistory; +import org.mitre.cybox.objects.WindowsNetworkShare; +import org.mitre.cybox.objects.WindowsRegistryKey; import org.mitre.stix.common_1.IndicatorBaseType; import org.mitre.stix.indicator_2.Indicator; import org.mitre.stix.stix_1.STIXPackage; - -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.report.GeneralReportModule; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.report.ReportProgressPanel; -import org.sleuthkit.datamodel.TskCoreException; - -import org.mitre.cybox.cybox_2.OperatorTypeEnum; -import org.mitre.cybox.objects.Address; -import org.mitre.cybox.objects.FileObjectType; -import org.mitre.cybox.objects.URIObjectType; -import org.mitre.cybox.objects.EmailMessage; -import org.mitre.cybox.objects.WindowsNetworkShare; -import org.mitre.cybox.objects.AccountObjectType; -import org.mitre.cybox.objects.SystemObjectType; -import org.mitre.cybox.objects.URLHistory; -import org.mitre.cybox.objects.DomainName; -import org.mitre.cybox.objects.WindowsRegistryKey; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.report.GeneralReportModule; +import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; +import org.sleuthkit.datamodel.TskCoreException; /** * @@ -79,8 +78,6 @@ public class STIXReportModule implements GeneralReportModule { private final boolean skipShortCircuit = true; - private BufferedWriter output = null; - // Hidden constructor for the report private STIXReportModule() { } @@ -100,38 +97,23 @@ public class STIXReportModule implements GeneralReportModule { * @param progressPanel panel to update the report's progress */ @Override + @Messages({"STIXReportModule.srcModuleName.text=STIX Report"}) public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { // Start the progress bar and setup the report progressPanel.setIndeterminate(false); progressPanel.start(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX")); reportPath = baseReportDir + getRelativeFilePath(); - + File reportFile = new File(reportPath); // Check if the user wants to display all output or just hits reportAllResults = configPanel.getShowAllResults(); - // Set up the output file - try { - File file = new File(reportPath); - output = new BufferedWriter(new FileWriter(file)); - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Unable to open STIX report file %s", reportPath), ex); //NON-NLS - MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS - NbBundle.getMessage(this.getClass(), - "STIXReportModule.notifyMsg.unableToOpenReportFile", - reportPath), - MessageNotifyUtil.MessageType.ERROR); - progressPanel.complete(ReportStatus.ERROR); - progressPanel.updateStatusLabel( - NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors")); - return; - } - // Keep track of whether any errors occur during processing boolean hadErrors = false; // Process the file/directory name entry String stixFileName = configPanel.getStixFile(); + if (stixFileName == null) { logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS MessageNotifyUtil.Message.error( @@ -139,6 +121,7 @@ public class STIXReportModule implements GeneralReportModule { progressPanel.complete(ReportStatus.ERROR); progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided")); + new File(baseReportDir).delete(); return; } if (stixFileName.isEmpty()) { @@ -148,6 +131,7 @@ public class STIXReportModule implements GeneralReportModule { progressPanel.complete(ReportStatus.ERROR); progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided")); + new File(baseReportDir).delete(); return; } File stixFile = new File(stixFileName); @@ -160,58 +144,66 @@ public class STIXReportModule implements GeneralReportModule { progressPanel.complete(ReportStatus.ERROR); progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName)); + new File(baseReportDir).delete(); return; } - // Store the path - ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName); //NON-NLS + try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) { + // Store the path + ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName); //NON-NLS - // Create array of stix file(s) - File[] stixFiles; - if (stixFile.isFile()) { - stixFiles = new File[1]; - stixFiles[0] = stixFile; - } else { - stixFiles = stixFile.listFiles(); - } - - // Set the length of the progress bar - we increment twice for each file - progressPanel.setMaximumProgress(stixFiles.length * 2 + 1); - - // Process each STIX file - for (File file : stixFiles) { - try { - processFile(file.getAbsolutePath(), progressPanel); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Unable to process STIX file %s", file), ex); //NON-NLS - MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS - ex.getLocalizedMessage(), - MessageNotifyUtil.MessageType.ERROR); - hadErrors = true; + // Create array of stix file(s) + File[] stixFiles; + if (stixFile.isFile()) { + stixFiles = new File[1]; + stixFiles[0] = stixFile; + } else { + stixFiles = stixFile.listFiles(); } - // Clear out the ID maps before loading the next file - idToObjectMap = new HashMap(); - idToResult = new HashMap(); - } + // Set the length of the progress bar - we increment twice for each file + progressPanel.setMaximumProgress(stixFiles.length * 2 + 1); - // Close the output file - if (output != null) { - try { - output.close(); - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error closing STIX report file %s", reportPath), ex); //NON-NLS + // Process each STIX file + for (File file : stixFiles) { + if (progressPanel.getStatus() == ReportStatus.CANCELED) { + return; + } + try { + processFile(file.getAbsolutePath(), progressPanel, output); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Unable to process STIX file %s", file), ex); //NON-NLS + MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS + ex.getLocalizedMessage(), + MessageNotifyUtil.MessageType.ERROR); + hadErrors = true; + } + // Clear out the ID maps before loading the next file + idToObjectMap = new HashMap(); + idToResult = new HashMap(); } - } - // Set the progress bar to done. If any errors occurred along the way, modify - // the "complete" message to indicate this. - if (hadErrors) { + // Set the progress bar to done. If any errors occurred along the way, modify + // the "complete" message to indicate this. + Case.getCurrentCase().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), ""); + if (hadErrors) { + progressPanel.complete(ReportStatus.ERROR); + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors")); + } else { + progressPanel.complete(ReportStatus.COMPLETE); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS + MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS + NbBundle.getMessage(this.getClass(), + "STIXReportModule.notifyMsg.unableToOpenReportFile"), + MessageNotifyUtil.MessageType.ERROR); progressPanel.complete(ReportStatus.ERROR); progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors")); - } else { - progressPanel.complete(ReportStatus.COMPLETE); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to add report to database.", ex); } } @@ -223,14 +215,14 @@ public class STIXReportModule implements GeneralReportModule { * * @throws TskCoreException */ - private void processFile(String stixFile, ReportProgressPanel progressPanel) throws + private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws TskCoreException { // Load the STIX file STIXPackage stix; stix = loadSTIXFile(stixFile); - printFileHeader(stixFile); + printFileHeader(stixFile, output); // Save any observables listed up front processObservables(stix); @@ -240,7 +232,7 @@ public class STIXReportModule implements GeneralReportModule { registryFileData = EvalRegistryObj.copyRegistryFiles(); // Process the indicators - processIndicators(stix); + processIndicators(stix, output); progressPanel.increment(); } @@ -292,7 +284,7 @@ public class STIXReportModule implements GeneralReportModule { * * @param stix STIXPackage */ - private void processIndicators(STIXPackage stix) throws TskCoreException { + private void processIndicators(STIXPackage stix, BufferedWriter output) throws TskCoreException { if (stix.getIndicators() != null) { List s = stix.getIndicators().getIndicators(); for (IndicatorBaseType t : s) { @@ -302,7 +294,7 @@ public class STIXReportModule implements GeneralReportModule { if (ind.getObservable().getObject() != null) { ObservableResult result = evaluateSingleObservable(ind.getObservable(), ""); if (result.isTrue() || reportAllResults) { - writeResultsToFile(ind, result.getDescription(), result.isTrue()); + writeResultsToFile(ind, result.getDescription(), result.isTrue(), output); } if (result.isTrue()) { saveResultsAsArtifacts(ind, result); @@ -311,7 +303,7 @@ public class STIXReportModule implements GeneralReportModule { ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " "); if (result.isTrue() || reportAllResults) { - writeResultsToFile(ind, result.getDescription(), result.isTrue()); + writeResultsToFile(ind, result.getDescription(), result.isTrue(), output); } if (result.isTrue()) { saveResultsAsArtifacts(ind, result); @@ -376,7 +368,7 @@ public class STIXReportModule implements GeneralReportModule { * @param resultStr - Full results for this indicator * @param found - true if the indicator was found in datasource(s) */ - private void writeResultsToFile(Indicator ind, String resultStr, boolean found) { + private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) { if (output != null) { try { if (found) { @@ -412,7 +404,7 @@ public class STIXReportModule implements GeneralReportModule { * * @param a_fileName */ - private void printFileHeader(String a_fileName) { + private void printFileHeader(String a_fileName, BufferedWriter output) { if (output != null) { try { char[] chars = new char[a_fileName.length() + 8]; diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java index 9a19e4cedf..8a8466d89f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java @@ -19,7 +19,7 @@ package org.sleuthkit.autopsy.modules.stix; import java.util.logging.Level; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; @@ -58,6 +58,7 @@ class StixArtifactData { objType = a_objType; } + @Messages({"StixArtifactData.indexError.message=Failed to index STIX interesting file hit artifact for keyword search."}) public void createArtifact(String a_title) throws TskCoreException { Blackboard blackboard = Case.getCurrentCase().getServices().getBlackboard(); @@ -72,14 +73,13 @@ class StixArtifactData { bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, "Stix", setName)); //NON-NLS bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE, "Stix", observableId)); //NON-NLS bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, "Stix", objType)); //NON-NLS - + try { // index the artifact for keyword search blackboard.indexArtifact(bba); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS - MessageNotifyUtil.Notify.error( - NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName()); + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.StixArtifactData_indexError_message(), bba.getDisplayName()); } } diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index 0be3a1924f..6e920ad631 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -179,7 +179,7 @@ ReportHTML.addThumbRows.dataType.msg=Tagged Results and Contents that contain im ReportHTML.thumbLink.tags=Tags\: ReportHTML.getName.text=Results - HTML ReportHTML.getDesc.text=A report about results and tagged items in HTML format. -ReportHTML.writeIndex.title=Autopsy Report for case {0} +ReportHTML.writeIndex.title=for case {0} ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup. ReportHTML.writeIndex.noFrames.seeNav=Please see the navigation page for artifact links, ReportHTML.writeIndex.seeSum=and the summary page for a case summary. @@ -225,7 +225,8 @@ ReportHTML.writeSum.noCaseNum=No case number ReportBodyFile.generateReport.srcModuleName.text=TSK Body File ReportExcel.endReport.srcModuleName.text=Excel Report ReportHTML.writeIndex.srcModuleName.text=HTML Report -ReportKML.genReport.srcModuleName.text=KML Report +ReportKML.genReport.srcModuleName.text=Geospatial Data +ReportKML.genReport.reportName=KML Report ReportGenerator.artTableColHdr.extension.text=Extension ReportGenerator.artTableColHdr.mimeType.text=MIME Type ReportGenerator.artTableColHdr.processorArchitecture.text=Processor Architecture diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties index 6ff657b68a..a877c52899 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties @@ -188,7 +188,7 @@ ReportExcel.endReport.srcModuleName.text=Excel\u30ec\u30dd\u30fc\u30c8 ReportGenerator.artTableColHdr.extension.text=\u62e1\u5f35\u5b50 ReportGenerator.artTableColHdr.mimeType.text=MIME\u30bf\u30a4\u30d7 ReportHTML.writeIndex.srcModuleName.text=HTML\u30ec\u30dd\u30fc\u30c8 -ReportKML.genReport.srcModuleName.text=KML\u30ec\u30dd\u30fc\u30c8 +ReportKML.genReport.reportName=KML\u30ec\u30dd\u30fc\u30c8 ReportGenerator.artTableColHdr.associatedArtifact=\u95a2\u4fc2\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8 ReportGenerator.artTableColHdr.count=\u30ab\u30a6\u30f3\u30c8 ReportGenerator.artTableColHdr.devMake=\u6a5f\u5668\u578b\u540d diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 1f49588837..834c221d9d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -26,25 +26,14 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; -import java.sql.ResultSet; -import java.sql.SQLException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JDialog; @@ -53,47 +42,28 @@ import javax.swing.SwingWorker; import org.openide.filesystems.FileUtil; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.coreutils.EscapeUtil; -import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.BlackboardAttribute.Type; -/** - * Instances of this class use GeneralReportModules, TableReportModules and - * FileReportModules to generate a report. If desired, displayProgressPanels() - * can be called to show report generation progress using ReportProgressPanel - * objects displayed using a dialog box. - */ class ReportGenerator { private static final Logger logger = Logger.getLogger(ReportGenerator.class.getName()); private Case currentCase = Case.getCurrentCase(); - private SleuthkitCase skCase = currentCase.getSleuthkitCase(); - private Map tableProgress; - private Map generalProgress; - private Map fileProgress; - private Map> columnHeaderMap; + /** + * Progress reportGenerationPanel that can be used to check for cancellation. + */ + private ReportProgressPanel progressPanel; - private String reportPath; - private ReportGenerationPanel panel = new ReportGenerationPanel(); + private final String reportPath; + private final ReportGenerationPanel reportGenerationPanel = new ReportGenerationPanel(); static final String REPORTS_DIR = "Reports"; //NON-NLS @@ -102,8 +72,6 @@ class ReportGenerator { /** * Displays the list of errors during report generation in user-friendly * way. MessageNotifyUtil used to display bubble notification. - * - * @param listOfErrors List of strings explaining the errors. */ private void displayReportErrors() { if (!errorList.isEmpty()) { @@ -113,18 +81,20 @@ class ReportGenerator { } MessageNotifyUtil.Notify.error( NbBundle.getMessage(this.getClass(), "ReportGenerator.notifyErr.errsDuringRptGen"), errorString); - return; } } - ReportGenerator(Map tableModuleStates, Map generalModuleStates, Map fileListModuleStates) { + /** + * Creates a report generator. + */ + ReportGenerator() { // Create the root reports directory path of the form: /Reports/ / DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); Date date = new Date(); String dateNoTime = dateFormat.format(date); this.reportPath = currentCase.getReportDirectory() + File.separator + currentCase.getName() + " " + dateNoTime + File.separator; - this.errorList = new ArrayList(); + this.errorList = new ArrayList<>(); // Create the root reports directory. try { @@ -132,69 +102,6 @@ class ReportGenerator { } catch (IOException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - return; - } - - // Initialize the progress panels - generalProgress = new HashMap<>(); - tableProgress = new HashMap<>(); - fileProgress = new HashMap<>(); - setupProgressPanels(tableModuleStates, generalModuleStates, fileListModuleStates); - this.columnHeaderMap = new HashMap<>(); - } - - /** - * Create a ReportProgressPanel for each report generation module selected - * by the user. - * - * @param tableModuleStates The enabled/disabled state of each - * TableReportModule - * @param generalModuleStates The enabled/disabled state of each - * GeneralReportModule - * @param fileListModuleStates The enabled/disabled state of each - * FileReportModule - */ - private void setupProgressPanels(Map tableModuleStates, Map generalModuleStates, Map fileListModuleStates) { - if (null != tableModuleStates) { - for (Entry entry : tableModuleStates.entrySet()) { - if (entry.getValue()) { - TableReportModule module = entry.getKey(); - String reportFilePath = module.getRelativeFilePath(); - if (!reportFilePath.isEmpty()) { - tableProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); - } else { - tableProgress.put(module, panel.addReport(module.getName(), null)); - } - } - } - } - - if (null != generalModuleStates) { - for (Entry entry : generalModuleStates.entrySet()) { - if (entry.getValue()) { - GeneralReportModule module = entry.getKey(); - String reportFilePath = module.getRelativeFilePath(); - if (!reportFilePath.isEmpty()) { - generalProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); - } else { - generalProgress.put(module, panel.addReport(module.getName(), null)); - } - } - } - } - - if (null != fileListModuleStates) { - for (Entry entry : fileListModuleStates.entrySet()) { - if (entry.getValue()) { - FileReportModule module = entry.getKey(); - String reportFilePath = module.getRelativeFilePath(); - if (!reportFilePath.isEmpty()) { - fileProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath)); - } else { - fileProgress.put(module, panel.addReport(module.getName(), null)); - } - } - } } } @@ -202,14 +109,14 @@ class ReportGenerator { * Display the progress panels to the user, and add actions to close the * parent dialog. */ - public void displayProgressPanels() { + private void displayProgressPanel() { final JDialog dialog = new JDialog(new JFrame(), true); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.setTitle(NbBundle.getMessage(this.getClass(), "ReportGenerator.displayProgress.title.text")); - dialog.add(this.panel); + dialog.add(this.reportGenerationPanel); dialog.pack(); - panel.addCloseAction(new ActionListener() { + reportGenerationPanel.addCloseAction(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { dialog.dispose(); @@ -219,7 +126,7 @@ class ReportGenerator { dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { - panel.close(); + reportGenerationPanel.close(); } }); @@ -235,9 +142,15 @@ class ReportGenerator { /** * Run the GeneralReportModules using a SwingWorker. */ - public void generateGeneralReports() { - GeneralReportsWorker worker = new GeneralReportsWorker(); - worker.execute(); + void generateGeneralReport(GeneralReportModule generalReportModule) { + if (generalReportModule != null) { + setupProgressPanel(generalReportModule); + ReportWorker worker = new ReportWorker(() -> { + generalReportModule.generateReport(reportPath, progressPanel); + }); + worker.execute(); + displayProgressPanel(); + } } /** @@ -248,10 +161,18 @@ class ReportGenerator { * @param tagSelections the enabled/disabled state of the tag names * to be included in the report */ - public void generateTableReports(Map artifactTypeSelections, Map tagNameSelections) { - if (!tableProgress.isEmpty() && null != artifactTypeSelections) { - TableReportsWorker worker = new TableReportsWorker(artifactTypeSelections, tagNameSelections); + void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) { + if (tableReport != null && null != artifactTypeSelections) { + setupProgressPanel(tableReport); + ReportWorker worker = new ReportWorker(() -> { + tableReport.startReport(reportPath); + TableReportGenerator generator = new TableReportGenerator(artifactTypeSelections, tagNameSelections, progressPanel, tableReport); + generator.execute(); + tableReport.endReport(); + errorList = generator.getErrorList(); + }); worker.execute(); + displayProgressPanel(); } } @@ -261,146 +182,101 @@ class ReportGenerator { * @param enabledInfo the Information that should be included about each * file in the report. */ - public void generateFileListReports(Map enabledInfo) { - if (!fileProgress.isEmpty() && null != enabledInfo) { + void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) { + if (fileReportModule != null && null != enabledInfo) { List enabled = new ArrayList<>(); for (Entry e : enabledInfo.entrySet()) { if (e.getValue()) { enabled.add(e.getKey()); } } - FileReportsWorker worker = new FileReportsWorker(enabled); - worker.execute(); - } - } - - /** - * SwingWorker to run GeneralReportModules. - */ - private class GeneralReportsWorker extends SwingWorker { - - @Override - protected Integer doInBackground() throws Exception { - for (Entry entry : generalProgress.entrySet()) { - GeneralReportModule module = entry.getKey(); - if (generalProgress.get(module).getStatus() != ReportStatus.CANCELED) { - module.generateReport(reportPath, generalProgress.get(module)); - } - } - return 0; - } - - @Override - protected void done() { - try { - get(); - } catch (InterruptedException | ExecutionException ex) { - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), - MessageNotifyUtil.MessageType.ERROR); - logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS - } // catch and ignore if we were cancelled - catch (java.util.concurrent.CancellationException ex) { - } finally { - displayReportErrors(); - errorList.clear(); - } - } - - } - - /** - * SwingWorker to run FileReportModules. - */ - private class FileReportsWorker extends SwingWorker { - - private List enabledInfo = Arrays.asList(FileReportDataTypes.values()); - private List fileModules = new ArrayList<>(); - - FileReportsWorker(List enabled) { - enabledInfo = enabled; - for (Entry entry : fileProgress.entrySet()) { - fileModules.add(entry.getKey()); - } - } - - @Override - protected Integer doInBackground() throws Exception { - for (FileReportModule module : fileModules) { - ReportProgressPanel progress = fileProgress.get(module); - if (progress.getStatus() != ReportStatus.CANCELED) { - progress.start(); - progress.updateStatusLabel( + setupProgressPanel(fileReportModule); + ReportWorker worker = new ReportWorker(() -> { + if (progressPanel.getStatus() != ReportStatus.CANCELED) { + progressPanel.start(); + progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.queryingDb.text")); } - } - List files = getFiles(); - int numFiles = files.size(); - for (FileReportModule module : fileModules) { - module.startReport(reportPath); - module.startTable(enabledInfo); - fileProgress.get(module).setIndeterminate(false); - fileProgress.get(module).setMaximumProgress(numFiles); - } - - int i = 0; - // Add files to report. - for (AbstractFile file : files) { - // Check to see if any reports have been cancelled. - if (fileModules.isEmpty()) { - break; + List files = getFiles(); + int numFiles = files.size(); + if (progressPanel.getStatus() != ReportStatus.CANCELED) { + fileReportModule.startReport(reportPath); + fileReportModule.startTable(enabled); } - // Remove cancelled reports, add files to report otherwise. - Iterator iter = fileModules.iterator(); - while (iter.hasNext()) { - FileReportModule module = iter.next(); - ReportProgressPanel progress = fileProgress.get(module); - if (progress.getStatus() == ReportStatus.CANCELED) { - iter.remove(); + progressPanel.setIndeterminate(false); + progressPanel.setMaximumProgress(numFiles); + + int i = 0; + // Add files to report. + for (AbstractFile file : files) { + // Check to see if any reports have been cancelled. + if (progressPanel.getStatus() == ReportStatus.CANCELED) { + return; } else { - module.addRow(file, enabledInfo); - progress.increment(); + fileReportModule.addRow(file, enabled); + progressPanel.increment(); } if ((i % 100) == 0) { - progress.updateStatusLabel( + progressPanel.updateStatusLabel( NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingFile.text", file.getName())); } + i++; } - i++; - } - for (FileReportModule module : fileModules) { - module.endTable(); - module.endReport(); - fileProgress.get(module).complete(ReportStatus.COMPLETE); - } + fileReportModule.endTable(); + fileReportModule.endReport(); + progressPanel.complete(ReportStatus.COMPLETE); + }); + worker.execute(); + displayProgressPanel(); + } + } - return 0; + /** + * Get all files in the image. + * + * @return + */ + private List getFiles() { + List absFiles; + try { + SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); + absFiles = skCase.findAllFilesWhere("meta_type != " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()); //NON-NLS + return absFiles; + } catch (TskCoreException ex) { + MessageNotifyUtil.Notify.show( + NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), + MessageNotifyUtil.MessageType.ERROR); + logger.log(Level.SEVERE, "failed to generate reports. Unable to get all files in the image.", ex); //NON-NLS + return Collections.emptyList(); + } + } + + private void setupProgressPanel(ReportModule module) { + String reportFilePath = module.getRelativeFilePath(); + if (!reportFilePath.isEmpty()) { + this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportPath + reportFilePath); + } else { + this.progressPanel = reportGenerationPanel.addReport(module.getName(), null); + } + } + + private class ReportWorker extends SwingWorker { + + private final Runnable doInBackground; + + private ReportWorker(Runnable doInBackground) { + this.doInBackground = doInBackground; } - /** - * Get all files in the image. - * - * @return - */ - private List getFiles() { - List absFiles; - try { - SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); - absFiles = skCase.findAllFilesWhere("meta_type != " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()); //NON-NLS - return absFiles; - } catch (TskCoreException ex) { - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), - MessageNotifyUtil.MessageType.ERROR); - logger.log(Level.SEVERE, "failed to generate reports. Unable to get all files in the image.", ex); //NON-NLS - return Collections.emptyList(); - } + @Override + protected Void doInBackground() throws Exception { + doInBackground.run(); + return null; } @Override @@ -420,1706 +296,6 @@ class ReportGenerator { errorList.clear(); } } - } - /** - * SwingWorker to run TableReportModules to report on blackboard artifacts, - * content tags, and blackboard artifact tags. - */ - private class TableReportsWorker extends SwingWorker { - - private List tableModules = new ArrayList<>(); - private List artifactTypes = new ArrayList<>(); - private HashSet tagNamesFilter = new HashSet<>(); - - private List images = new ArrayList<>(); - - TableReportsWorker(Map artifactTypeSelections, Map tagNameSelections) { - // Get the report modules selected by the user. - for (Entry entry : tableProgress.entrySet()) { - tableModules.add(entry.getKey()); - } - - // Get the artifact types selected by the user. - for (Entry entry : artifactTypeSelections.entrySet()) { - if (entry.getValue()) { - artifactTypes.add(entry.getKey()); - } - } - - // Get the tag names selected by the user and make a tag names filter. - if (null != tagNameSelections) { - for (Entry entry : tagNameSelections.entrySet()) { - if (entry.getValue() == true) { - tagNamesFilter.add(entry.getKey()); - } - } - } - } - - @Override - protected Integer doInBackground() throws Exception { - // Start the progress indicators for each active TableReportModule. - for (TableReportModule module : tableModules) { - ReportProgressPanel progress = tableProgress.get(module); - if (progress.getStatus() != ReportStatus.CANCELED) { - module.startReport(reportPath); - progress.start(); - progress.setIndeterminate(false); - progress.setMaximumProgress(this.artifactTypes.size() + 2); // +2 for content and blackboard artifact tags - } - } - - // report on the blackboard results - makeBlackboardArtifactTables(); - - // report on the tagged files and artifacts - makeContentTagsTables(); - makeBlackboardArtifactTagsTables(); - - // report on the tagged images - makeThumbnailTable(); - - // finish progress, wrap up - for (TableReportModule module : tableModules) { - tableProgress.get(module).complete(ReportStatus.COMPLETE); - module.endReport(); - } - - return 0; - } - - /** - * Generate the tables for the selected blackboard artifacts - */ - private void makeBlackboardArtifactTables() { - // Make a comment string describing the tag names filter in effect. - StringBuilder comment = new StringBuilder(); - if (!tagNamesFilter.isEmpty()) { - comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text")); - comment.append(makeCommaSeparatedList(tagNamesFilter)); - } - - // Add a table to the report for every enabled blackboard artifact type. - for (BlackboardArtifact.Type type : artifactTypes) { - // Check for cancellaton. - removeCancelledTableReportModules(); - if (tableModules.isEmpty()) { - return; - } - - for (TableReportModule module : tableModules) { - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", - type.getDisplayName())); - } - - // Keyword hits and hashset hit artifacts get special handling. - if (type.getTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - writeKeywordHits(tableModules, comment.toString(), tagNamesFilter); - continue; - } else if (type.getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { - writeHashsetHits(tableModules, comment.toString(), tagNamesFilter); - continue; - } - - List artifactList = getFilteredArtifacts(type, tagNamesFilter); - - if (artifactList.isEmpty()) { - continue; - } - - /* - Gets all of the attribute types of this artifact type by adding - all of the types to a set - */ - Set attrTypeSet = new TreeSet<>((Type o1, Type o2) -> o1.getDisplayName().compareTo(o2.getDisplayName())); - for (ArtifactData data : artifactList) { - List attributes = data.getAttributes(); - for (BlackboardAttribute attribute : attributes) { - attrTypeSet.add(attribute.getAttributeType()); - } - } - // Get the columns appropriate for the artifact type. This is - // used to get the data that will be in the cells below based on - // type, and display the column headers. - List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); - if (columns.isEmpty()) { - continue; - } - ReportGenerator.this.columnHeaderMap.put(type.getTypeID(), columns); - - // The artifact list is sorted now, as getting the row data is - // dependent on having the columns, which is necessary for - // sorting. - Collections.sort(artifactList); - List columnHeaderNames = new ArrayList<>(); - for (Column currColumn : columns) { - columnHeaderNames.add(currColumn.getColumnHeader()); - } - - for (TableReportModule module : tableModules) { - module.startDataType(type.getDisplayName(), comment.toString()); - module.startTable(columnHeaderNames); - } - for (ArtifactData artifactData : artifactList) { - // Add the row data to all of the reports. - for (TableReportModule module : tableModules) { - - // Get the row data for this artifact, and has the - // module add it. - List rowData = artifactData.getRow(); - if (rowData.isEmpty()) { - continue; - } - - module.addRow(rowData); - } - } - // Finish up this data type - for (TableReportModule module : tableModules) { - tableProgress.get(module).increment(); - module.endTable(); - module.endDataType(); - } - } - } - - /** - * Make table for tagged files - */ - @SuppressWarnings("deprecation") - private void makeContentTagsTables() { - // Check for cancellaton. - removeCancelledTableReportModules(); - if (tableModules.isEmpty()) { - return; - } - - // Get the content tags. - List tags; - try { - tags = Case.getCurrentCase().getServices().getTagsManager().getAllContentTags(); - } catch (TskCoreException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetContentTags")); - logger.log(Level.SEVERE, "failed to get content tags", ex); //NON-NLS - return; - } - - // Tell the modules reporting on content tags is beginning. - for (TableReportModule module : tableModules) { - // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. - // @@@ Alos Using the obsolete ARTIFACT_TYPE.TSK_TAG_FILE is also an expedient hack. - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", - ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName())); - ArrayList columnHeaders = new ArrayList<>(Arrays.asList( - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.tag"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.file"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeModified"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeChanged"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeAccessed"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeCreated"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.size"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.hash"))); - - StringBuilder comment = new StringBuilder(); - if (!tagNamesFilter.isEmpty()) { - comment.append( - NbBundle.getMessage(this.getClass(), "ReportGenerator.makeContTagTab.taggedFiles.msg")); - comment.append(makeCommaSeparatedList(tagNamesFilter)); - } - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML) module; - htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); - htmlReportModule.startContentTagsTable(columnHeaders); - } else { - module.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); - module.startTable(columnHeaders); - } - } - - // Give the modules the rows for the content tags. - for (ContentTag tag : tags) { - // skip tags that we are not reporting on - if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { - continue; - } - - String fileName; - try { - fileName = tag.getContent().getUniquePath(); - } catch (TskCoreException ex) { - fileName = tag.getContent().getName(); - } - - ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getName().getDisplayName(), fileName, tag.getComment())); - Content content = tag.getContent(); - if (content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - - // Add metadata about the file to HTML output - rowData.add(file.getMtimeAsDate()); - rowData.add(file.getCtimeAsDate()); - rowData.add(file.getAtimeAsDate()); - rowData.add(file.getCrtimeAsDate()); - rowData.add(Long.toString(file.getSize())); - rowData.add(file.getMd5Hash()); - } - for (TableReportModule module : tableModules) { - // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML) module; - htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); - } else { - module.addRow(rowData); - } - } - - // see if it is for an image so that we later report on it - checkIfTagHasImage(tag); - } - - // The the modules content tags reporting is ended. - for (TableReportModule module : tableModules) { - tableProgress.get(module).increment(); - module.endTable(); - module.endDataType(); - } - } - - @Override - protected void done() { - try { - get(); - } catch (InterruptedException | ExecutionException ex) { - MessageNotifyUtil.Notify.show( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(), - MessageNotifyUtil.MessageType.ERROR); - logger.log(Level.SEVERE, "failed to generate reports", ex.getCause()); //NON-NLS - logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS - } // catch and ignore if we were cancelled - catch (java.util.concurrent.CancellationException ex) { - } finally { - displayReportErrors(); - errorList.clear(); - } - } - - /** - * Generate the tables for the tagged artifacts - */ - @SuppressWarnings("deprecation") - private void makeBlackboardArtifactTagsTables() { - // Check for cancellaton. - removeCancelledTableReportModules(); - if (tableModules.isEmpty()) { - return; - } - - List tags; - try { - tags = Case.getCurrentCase().getServices().getTagsManager().getAllBlackboardArtifactTags(); - } catch (TskCoreException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBArtifactTags")); - logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); //NON-NLS - return; - } - - // Tell the modules reporting on blackboard artifact tags data type is beginning. - // @@@ Using the obsolete ARTIFACT_TYPE.TSK_TAG_ARTIFACT is an expedient hack. - for (TableReportModule module : tableModules) { - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", - ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName())); - StringBuilder comment = new StringBuilder(); - if (!tagNamesFilter.isEmpty()) { - comment.append( - NbBundle.getMessage(this.getClass(), "ReportGenerator.makeBbArtTagTab.taggedRes.msg")); - comment.append(makeCommaSeparatedList(tagNamesFilter)); - } - module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString()); - module.startTable(new ArrayList<>(Arrays.asList( - NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.resultType"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.tag"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.comment"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile")))); - } - - // Give the modules the rows for the content tags. - for (BlackboardArtifactTag tag : tags) { - if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { - continue; - } - - List row; - for (TableReportModule module : tableModules) { - row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName())); - module.addRow(row); - } - - // check if the tag is an image that we should later make a thumbnail for - checkIfTagHasImage(tag); - } - - // The the modules blackboard artifact tags reporting is ended. - for (TableReportModule module : tableModules) { - tableProgress.get(module).increment(); - module.endTable(); - module.endDataType(); - } - } - - /** - * Test if the user requested that this tag be reported on - * - * @param tagName - * - * @return true if it should be reported on - */ - private boolean passesTagNamesFilter(String tagName) { - return tagNamesFilter.isEmpty() || tagNamesFilter.contains(tagName); - } - - void removeCancelledTableReportModules() { - Iterator iter = tableModules.iterator(); - while (iter.hasNext()) { - TableReportModule module = iter.next(); - if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { - iter.remove(); - } - } - } - - /** - * Make a report for the files that were previously found to be images. - */ - private void makeThumbnailTable() { - for (TableReportModule module : tableModules) { - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.createdThumb.text")); - - if (module instanceof ReportHTML) { - ReportHTML htmlModule = (ReportHTML) module; - htmlModule.startDataType( - NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.name"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.desc")); - List emptyHeaders = new ArrayList<>(); - for (int i = 0; i < ReportHTML.THUMBNAIL_COLUMNS; i++) { - emptyHeaders.add(""); - } - htmlModule.startTable(emptyHeaders); - - htmlModule.addThumbnailRows(images); - - htmlModule.endTable(); - htmlModule.endDataType(); - } - } - } - - /** - * Analyze artifact associated with tag and add to internal list if it - * is associated with an image. - * - * @param artifactTag - */ - private void checkIfTagHasImage(BlackboardArtifactTag artifactTag) { - AbstractFile file; - try { - file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID()); - } catch (TskCoreException ex) { - errorList.add( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.errGetContentFromBBArtifact")); - logger.log(Level.WARNING, "Error while getting content from a blackboard artifact to report on.", ex); //NON-NLS - return; - } - - if (file != null) { - checkIfFileIsImage(file); - } - } - - /** - * Analyze file that tag is associated with and determine if it is an - * image and should have a thumbnail reported for it. Images are added - * to internal list. - * - * @param contentTag - */ - private void checkIfTagHasImage(ContentTag contentTag) { - Content c = contentTag.getContent(); - if (c instanceof AbstractFile == false) { - return; - } - checkIfFileIsImage((AbstractFile) c); - } - - /** - * If file is an image file, add it to the internal 'images' list. - * - * @param file - */ - private void checkIfFileIsImage(AbstractFile file) { - - if (file.isDir() - || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS - || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { - return; - } - - if (ImageUtils.thumbnailSupported(file)) { - images.add(file); - } - } - } - - /// @@@ Should move the methods specific to TableReportsWorker into that scope. - private Boolean failsTagFilter(HashSet tagNames, HashSet tagsNamesFilter) { - if (null == tagsNamesFilter || tagsNamesFilter.isEmpty()) { - return false; - } - - HashSet filteredTagNames = new HashSet<>(tagNames); - filteredTagNames.retainAll(tagsNamesFilter); - return filteredTagNames.isEmpty(); - } - - /** - * Get a List of the artifacts and data of the given type that pass the - * given Tag Filter. - * - * @param type The artifact type to get - * @param tagNamesFilter The tag names that should be included. - * - * @return a list of the filtered tags. - */ - private List getFilteredArtifacts(BlackboardArtifact.Type type, HashSet tagNamesFilter) { - List artifacts = new ArrayList<>(); - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(type.getTypeID())) { - List tags = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact); - HashSet uniqueTagNames = new HashSet<>(); - for (BlackboardArtifactTag tag : tags) { - uniqueTagNames.add(tag.getName().getDisplayName()); - } - if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { - continue; - } - try { - artifacts.add(new ArtifactData(artifact, skCase.getBlackboardAttributes(artifact), uniqueTagNames)); - } catch (TskCoreException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBAttribs")); - logger.log(Level.SEVERE, "Failed to get Blackboard Attributes when generating report.", ex); //NON-NLS - } - } - } catch (TskCoreException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBArtifacts")); - logger.log(Level.SEVERE, "Failed to get Blackboard Artifacts when generating report.", ex); //NON-NLS - } - return artifacts; - } - - /** - * Write the keyword hits to the provided TableReportModules. - * - * @param tableModules modules to report on - */ - @SuppressWarnings("deprecation") - private void writeKeywordHits(List tableModules, String comment, HashSet tagNamesFilter) { - - // Query for keyword lists-only so that we can tell modules what lists - // will exist for their index. - // @@@ There is a bug in here. We should use the tags in the below code - // so that we only report the lists that we will later provide with real - // hits. If no keyord hits are tagged, then we make the page for nothing. - String orderByClause; - if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS - } else { - orderByClause = "ORDER BY list ASC"; //NON-NLS - } - String keywordListQuery - = "SELECT att.value_text AS list " + //NON-NLS - "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + //NON-NLS - "WHERE att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + //NON-NLS - "AND art.artifact_type_id = " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " " + //NON-NLS - "AND att.artifact_id = art.artifact_id " + //NON-NLS - "GROUP BY list " + orderByClause; //NON-NLS - - try (CaseDbQuery dbQuery = skCase.executeQuery(keywordListQuery)) { - ResultSet listsRs = dbQuery.getResultSet(); - List lists = new ArrayList<>(); - while (listsRs.next()) { - String list = listsRs.getString("list"); //NON-NLS - if (list.isEmpty()) { - list = NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs"); - } - lists.add(list); - } - - // Make keyword data type and give them set index - for (TableReportModule module : tableModules) { - module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); - module.addSetIndex(lists); - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", - ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName())); - } - } catch (TskCoreException | SQLException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWLists")); - logger.log(Level.SEVERE, "Failed to query keyword lists: ", ex); //NON-NLS - return; - } - - if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att3.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(att1.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(att2.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS - } else { - orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS - } - // Query for keywords, grouped by list - String keywordsQuery - = "SELECT art.artifact_id, art.obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path " + //NON-NLS - "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f " + //NON-NLS - "WHERE (att1.artifact_id = art.artifact_id) " + //NON-NLS - "AND (att2.artifact_id = art.artifact_id) " + //NON-NLS - "AND (att3.artifact_id = art.artifact_id) " + //NON-NLS - "AND (f.obj_id = art.obj_id) " + //NON-NLS - "AND (att1.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + //NON-NLS - "AND (att2.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + //NON-NLS - "AND (att3.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + //NON-NLS - "AND (art.artifact_type_id = " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + //NON-NLS - orderByClause; //NON-NLS - - try (CaseDbQuery dbQuery = skCase.executeQuery(keywordsQuery)) { - ResultSet resultSet = dbQuery.getResultSet(); - - String currentKeyword = ""; - String currentList = ""; - while (resultSet.next()) { - // Check to see if all the TableReportModules have been canceled - if (tableModules.isEmpty()) { - break; - } - Iterator iter = tableModules.iterator(); - while (iter.hasNext()) { - TableReportModule module = iter.next(); - if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { - iter.remove(); - } - } - - // Get any tags that associated with this artifact and apply the tag filter. - HashSet uniqueTagNames = getUniqueTagNames(resultSet.getLong("artifact_id")); //NON-NLS - if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { - continue; - } - String tagsList = makeCommaSeparatedList(uniqueTagNames); - - Long objId = resultSet.getLong("obj_id"); //NON-NLS - String keyword = resultSet.getString("keyword"); //NON-NLS - String preview = resultSet.getString("preview"); //NON-NLS - String list = resultSet.getString("list"); //NON-NLS - String uniquePath = ""; - - try { - AbstractFile f = skCase.getAbstractFileById(objId); - if (f != null) { - uniquePath = skCase.getAbstractFileById(objId).getUniquePath(); - } - } catch (TskCoreException ex) { - errorList.add( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileByID")); - logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS - } - - // If the lists aren't the same, we've started a new list - if ((!list.equals(currentList) && !list.isEmpty()) || (list.isEmpty() && !currentList.equals( - NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs")))) { - if (!currentList.isEmpty()) { - for (TableReportModule module : tableModules) { - module.endTable(); - module.endSet(); - } - } - currentList = list.isEmpty() ? NbBundle - .getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs") : list; - currentKeyword = ""; // reset the current keyword because it's a new list - for (TableReportModule module : tableModules) { - module.startSet(currentList); - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList", - ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), currentList)); - } - } - if (!keyword.equals(currentKeyword)) { - if (!currentKeyword.equals("")) { - for (TableReportModule module : tableModules) { - module.endTable(); - } - } - currentKeyword = keyword; - for (TableReportModule module : tableModules) { - module.addSetElement(currentKeyword); - List columnHeaderNames = new ArrayList<>(); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.preview")); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags")); - module.startTable(columnHeaderNames); - } - } - - String previewreplace = EscapeUtil.escapeHtml(preview); - for (TableReportModule module : tableModules) { - module.addRow(Arrays.asList(new String[]{previewreplace.replaceAll(" tableModules, String comment, HashSet tagNamesFilter) { - String orderByClause; - if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS - } else { - orderByClause = "ORDER BY att.value_text ASC"; //NON-NLS - } - String hashsetsQuery - = "SELECT att.value_text AS list " + //NON-NLS - "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + //NON-NLS - "WHERE att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + //NON-NLS - "AND art.artifact_type_id = " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + " " + //NON-NLS - "AND att.artifact_id = art.artifact_id " + //NON-NLS - "GROUP BY list " + orderByClause; //NON-NLS - - try (CaseDbQuery dbQuery = skCase.executeQuery(hashsetsQuery)) { - // Query for hashsets - ResultSet listsRs = dbQuery.getResultSet(); - List lists = new ArrayList<>(); - while (listsRs.next()) { - lists.add(listsRs.getString("list")); //NON-NLS - } - - for (TableReportModule module : tableModules) { - module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); - module.addSetIndex(lists); - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", - ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName())); - } - } catch (TskCoreException | SQLException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryHashsetLists")); - logger.log(Level.SEVERE, "Failed to query hashset lists: ", ex); //NON-NLS - return; - } - - if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "size ASC NULLS FIRST"; //NON-NLS - } else { - orderByClause = "ORDER BY att.value_text ASC, f.parent_path ASC, f.name ASC, size ASC"; //NON-NLS - } - String hashsetHitsQuery - = "SELECT art.artifact_id, art.obj_id, att.value_text AS setname, f.name AS name, f.size AS size, f.parent_path AS parent_path " + //NON-NLS - "FROM blackboard_artifacts AS art, blackboard_attributes AS att, tsk_files AS f " + //NON-NLS - "WHERE (att.artifact_id = art.artifact_id) " + //NON-NLS - "AND (f.obj_id = art.obj_id) " + //NON-NLS - "AND (att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + //NON-NLS - "AND (art.artifact_type_id = " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + ") " + //NON-NLS - orderByClause; //NON-NLS - - try (CaseDbQuery dbQuery = skCase.executeQuery(hashsetHitsQuery)) { - // Query for hashset hits - ResultSet resultSet = dbQuery.getResultSet(); - String currentSet = ""; - while (resultSet.next()) { - // Check to see if all the TableReportModules have been canceled - if (tableModules.isEmpty()) { - break; - } - Iterator iter = tableModules.iterator(); - while (iter.hasNext()) { - TableReportModule module = iter.next(); - if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { - iter.remove(); - } - } - - // Get any tags that associated with this artifact and apply the tag filter. - HashSet uniqueTagNames = getUniqueTagNames(resultSet.getLong("artifact_id")); //NON-NLS - if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { - continue; - } - String tagsList = makeCommaSeparatedList(uniqueTagNames); - - Long objId = resultSet.getLong("obj_id"); //NON-NLS - String set = resultSet.getString("setname"); //NON-NLS - String size = resultSet.getString("size"); //NON-NLS - String uniquePath = ""; - - try { - AbstractFile f = skCase.getAbstractFileById(objId); - if (f != null) { - uniquePath = skCase.getAbstractFileById(objId).getUniquePath(); - } - } catch (TskCoreException ex) { - errorList.add( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileFromID")); - logger.log(Level.WARNING, "Failed to get Abstract File from ID.", ex); //NON-NLS - return; - } - - // If the sets aren't the same, we've started a new set - if (!set.equals(currentSet)) { - if (!currentSet.isEmpty()) { - for (TableReportModule module : tableModules) { - module.endTable(); - module.endSet(); - } - } - currentSet = set; - for (TableReportModule module : tableModules) { - module.startSet(currentSet); - List columnHeaderNames = new ArrayList<>(); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file")); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.size")); - columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags")); - module.startTable(columnHeaderNames); - tableProgress.get(module).updateStatusLabel( - NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList", - ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), currentSet)); - } - } - - // Add a row for this hit to every module - for (TableReportModule module : tableModules) { - module.addRow(Arrays.asList(new String[]{uniquePath, size, tagsList})); - } - } - - // Finish the current data type - for (TableReportModule module : tableModules) { - tableProgress.get(module).increment(); - module.endDataType(); - } - } catch (TskCoreException | SQLException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryHashsetHits")); - logger.log(Level.SEVERE, "Failed to query hashsets hits: ", ex); //NON-NLS - } - } - - /** - * For a given artifact type ID, return the list of the columns that we are - * reporting on. - * - * @param artifactTypeId artifact type ID - * @param attributeTypeSet The set of attributeTypeSet available for this - * artifact type - * - * @return List row titles - */ - private List getArtifactTableColumns(int artifactTypeId, Set attributeTypeSet) { - ArrayList columns = new ArrayList<>(); - - // Long switch statement to retain ordering of attribute types that are - // attached to pre-defined artifact types. - if (ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TITLE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateCreated"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.value"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_VALUE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.referrer"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_REFERRER))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TITLE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.urlDomainDecoded"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL_DECODED))); - - } else if (ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dest"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.sourceUrl"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.instDateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() == artifactTypeId) { - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.preview"))); - - } else if (ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == artifactTypeId) { - columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"))); - - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.size"))); - - } else if (ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devMake"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devModel"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceId"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.domain"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTaken"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devManufacturer"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devModel"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); - - } else if (ARTIFACT_TYPE.TSK_CONTACT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumHome"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumOffice"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumMobile"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.email"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL))); - - } else if (ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.msgType"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.readStatus"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_READ_STATUS))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromPhoneNum"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromEmail"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_FROM))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toPhoneNum"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toEmail"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_TO))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.subject"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT))); - - } else if (ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromPhoneNum"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toPhoneNum"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION))); - - } else if (ARTIFACT_TYPE.TSK_CALENDAR_ENTRY.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.calendarEntryType"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DESCRIPTION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.startDateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.endDateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.location"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION))); - - } else if (ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.shortCut"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SHORTCUT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME_PERSON))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))); - - } else if (ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceAddress"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.category"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CATEGORY))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userId"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_ID))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.password"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PASSWORD))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appPath"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DESCRIPTION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.replytoAddress"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mailServer"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SERVER_NAME))); - - } else if (ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - } else if (ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID() == artifactTypeId) { - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"))); - - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.extension.text"))); - - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mimeType.text"))); - - columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"))); - - } else if (ARTIFACT_TYPE.TSK_OS_INFO.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.processorArchitecture.text"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osName.text"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osInstallDate.text"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - } else if (ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailTo"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_TO))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailFrom"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_FROM))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSubject"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskDateTimeSent"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskDateTimeRcvd"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailCc"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_CC))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailBcc"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_BCC))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskMsgId"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MSG_ID))); - - } else if (ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSetName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskInterestingFilesCategory"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CATEGORY))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))); - - } else if (ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskGpsRouteCategory"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CATEGORY))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitudeEnd"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitudeEnd"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitudeStart"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitudeStart"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.location"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSetName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.associatedArtifact"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - } else if (ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.associatedArtifact"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.count"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_COUNT))); - - } else if (ARTIFACT_TYPE.TSK_OS_ACCOUNT.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userName"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_NAME))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userId"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_ID))); - - } else if (ARTIFACT_TYPE.TSK_REMOTE_DRIVE.getTypeID() == artifactTypeId) { - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.localPath"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCAL_PATH))); - - columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.remotePath"), - new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_REMOTE_PATH))); - } else { - // This is the case that it is a custom type. The reason an else is - // necessary is to make sure that the source file column is added - for (BlackboardAttribute.Type type : attributeTypeSet) { - columns.add(new AttributeColumn(type.getDisplayName(), type)); - } - columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile"))); - columns.add(new TaggedResultsColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))); - - // Short circuits to guarantee that the attribute types aren't added - // twice. - return columns; - } - // If it is an attribute column, it removes the attribute type of that - // column from the set, so types are not reported more than once. - for (Column column : columns) { - attributeTypeSet = column.removeTypeFromSet(attributeTypeSet); - } - // Now uses the remaining types in the set to construct columns - for (BlackboardAttribute.Type type : attributeTypeSet) { - columns.add(new AttributeColumn(type.getDisplayName(), type)); - } - // Source file column is added here for ordering purposes. - if (artifactTypeId == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_CONTACT.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_CALENDAR_ENTRY.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() - || artifactTypeId == ARTIFACT_TYPE.TSK_OS_INFO.getTypeID()) { - columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile"))); - } - columns.add(new TaggedResultsColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))); - - return columns; - } - - /** - * Converts a collection of strings into a single string of comma-separated - * items - * - * @param items A collection of strings - * - * @return A string of comma-separated items - */ - private String makeCommaSeparatedList(Collection items) { - String list = ""; - for (Iterator iterator = items.iterator(); iterator.hasNext();) { - list += iterator.next() + (iterator.hasNext() ? ", " : ""); - } - return list; - } - - /** - * Given a tsk_file's obj_id, return the unique path of that file. - * - * @param objId tsk_file obj_id - * - * @return String unique path - */ - private String getFileUniquePath(Content content) { - try { - if (content != null) { - return content.getUniquePath(); - } else { - return ""; - } - } catch (TskCoreException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileByID")); - logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS - } - return ""; - - } - - /** - * Container class that holds data about an Artifact to eliminate duplicate - * calls to the Sleuthkit database. - */ - private class ArtifactData implements Comparable { - - private BlackboardArtifact artifact; - private List attributes; - private HashSet tags; - private List rowData = null; - private Content content; - - ArtifactData(BlackboardArtifact artifact, List attrs, HashSet tags) { - this.artifact = artifact; - this.attributes = attrs; - this.tags = tags; - try { - this.content = Case.getCurrentCase().getSleuthkitCase().getContentById(artifact.getObjectID()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not get content from database"); - } - } - - public BlackboardArtifact getArtifact() { - return artifact; - } - - public List getAttributes() { - return attributes; - } - - public HashSet getTags() { - return tags; - } - - public long getArtifactID() { - return artifact.getArtifactID(); - } - - public long getObjectID() { - return artifact.getObjectID(); - } - - /** - * @return the content - */ - public Content getContent() { - return content; - } - - /** - * Compares ArtifactData objects by the first attribute they have in - * common in their List. Should only be used on two - * artifacts of the same type - * - * If all attributes are the same, they are assumed duplicates and are - * compared by their artifact id. Should only be used with attributes of - * the same type. - */ - @Override - public int compareTo(ArtifactData otherArtifactData) { - List thisRow = getRow(); - List otherRow = otherArtifactData.getRow(); - for (int i = 0; i < thisRow.size(); i++) { - int compare = thisRow.get(i).compareTo(otherRow.get(i)); - if (compare != 0) { - return compare; - } - } - return ((Long) this.getArtifactID()).compareTo((Long) otherArtifactData.getArtifactID()); - } - - /** - * Get the values for each row in the table report. - * - * the value types of custom artifacts - * - * @return A list of string representing the data for this artifact. - */ - public List getRow() { - if (rowData == null) { - try { - rowData = getOrderedRowDataAsStrings(); - // If else is done so that row data is not set before - // columns are added to the hash map. - if (rowData.size() > 0) { - // replace null values if attribute was not defined - for (int i = 0; i < rowData.size(); i++) { - if (rowData.get(i) == null) { - rowData.set(i, ""); - } - } - } else { - rowData = null; - return new ArrayList<>(); - } - } catch (TskCoreException ex) { - errorList.add( - NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.coreExceptionWhileGenRptRow")); - logger.log(Level.WARNING, "Core exception while generating row data for artifact report.", ex); //NON-NLS - rowData = Collections.emptyList(); - } - } - return rowData; - } - - /** - * Get a list of Strings with all the row values for the Artifact in the - * correct order to be written to the report. - * - * @return List row values. Values could be null if attribute is - * not defined in artifact - * - * @throws TskCoreException - */ - private List getOrderedRowDataAsStrings() throws TskCoreException { - - List orderedRowData = new ArrayList<>(); - if (ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID() == getArtifact().getArtifactTypeID()) { - if (content != null && content instanceof AbstractFile) { - AbstractFile file = (AbstractFile) content; - orderedRowData.add(file.getName()); - orderedRowData.add(file.getNameExtension()); - String mimeType = file.getMIMEType(); - if (mimeType == null) { - orderedRowData.add(""); - } else { - orderedRowData.add(mimeType); - } - orderedRowData.add(file.getUniquePath()); - } else { - // Make empty rows to make sure the formatting is correct - orderedRowData.add(null); - orderedRowData.add(null); - orderedRowData.add(null); - orderedRowData.add(null); - } - orderedRowData.add(makeCommaSeparatedList(getTags())); - - } else if (ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == getArtifact().getArtifactTypeID()) { - String[] attributeDataArray = new String[3]; - // Array is used so that the order of the attributes is - // maintained. - for (BlackboardAttribute attr : attributes) { - if (attr.getAttributeType().equals(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME))) { - attributeDataArray[0] = attr.getDisplayString(); - } else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CATEGORY))) { - attributeDataArray[1] = attr.getDisplayString(); - } else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))) { - String pathToShow = attr.getDisplayString(); - if (pathToShow.isEmpty()) { - pathToShow = getFileUniquePath(content); - } - attributeDataArray[2] = pathToShow; - } - } - orderedRowData.addAll(Arrays.asList(attributeDataArray)); - orderedRowData.add(makeCommaSeparatedList(getTags())); - - } else { - if (ReportGenerator.this.columnHeaderMap.containsKey(this.artifact.getArtifactTypeID())) { - - for (Column currColumn : ReportGenerator.this.columnHeaderMap.get(this.artifact.getArtifactTypeID())) { - String cellData = currColumn.getCellData(this); - orderedRowData.add(cellData); - } - } - } - - return orderedRowData; - } - - } - - /** - * Get any tags associated with an artifact - * - * @param artifactId - * - * @return hash set of tag display names - * - * @throws SQLException - */ - @SuppressWarnings("deprecation") - private HashSet getUniqueTagNames(long artifactId) throws TskCoreException { - HashSet uniqueTagNames = new HashSet<>(); - - String query = "SELECT display_name, artifact_id FROM tag_names AS tn, blackboard_artifact_tags AS bat " + //NON-NLS - "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS - - try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { - ResultSet tagNameRows = dbQuery.getResultSet(); - while (tagNameRows.next()) { - uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS - } - } catch (TskCoreException | SQLException ex) { - throw new TskCoreException("Error getting tag names for artifact: ", ex); - } - - return uniqueTagNames; - - } - - private interface Column { - - String getColumnHeader(); - - String getCellData(ArtifactData artData); - - Set removeTypeFromSet(Set types); - } - - private class AttributeColumn implements Column { - - private String columnHeader; - private BlackboardAttribute.Type attributeType; - - /** - * Constructs an ArtifactCell - * - * @param columnHeader The header text of this column - * @param attributeType The attribute type associated with this column - */ - AttributeColumn(String columnHeader, BlackboardAttribute.Type attributeType) { - this.columnHeader = Objects.requireNonNull(columnHeader); - this.attributeType = attributeType; - } - - @Override - public String getColumnHeader() { - return this.columnHeader; - } - - @Override - public String getCellData(ArtifactData artData) { - List attributes = artData.getAttributes(); - for (BlackboardAttribute attribute : attributes) { - if (attribute.getAttributeType().equals(this.attributeType)) { - if (attribute.getAttributeType().getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - return attribute.getDisplayString(); - } else { - return ContentUtils.getStringTime(attribute.getValueLong(), artData.getContent()); - } - } - } - return ""; - } - - @Override - public Set removeTypeFromSet(Set types) { - types.remove(this.attributeType); - return types; - } - } - - private class SourceFileColumn implements Column { - - private String columnHeader; - - SourceFileColumn(String columnHeader) { - this.columnHeader = columnHeader; - } - - @Override - public String getColumnHeader() { - return this.columnHeader; - } - - @Override - public String getCellData(ArtifactData artData) { - return getFileUniquePath(artData.getContent()); - /*else if (this.columnHeader.equals(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))) { - return makeCommaSeparatedList(artData.getTags()); - } - return "";*/ - } - - @Override - public Set removeTypeFromSet(Set types) { - // This column doesn't have a type, so nothing to remove - return types; - } - } - - private class TaggedResultsColumn implements Column { - - private String columnHeader; - - TaggedResultsColumn(String columnHeader) { - this.columnHeader = columnHeader; - } - - @Override - public String getColumnHeader() { - return this.columnHeader; - } - - @Override - public String getCellData(ArtifactData artData) { - return makeCommaSeparatedList(artData.getTags()); - } - - @Override - public Set removeTypeFromSet(Set types) { - // This column doesn't have a type, so nothing to remove - return types; - } - } - - private class HeaderOnlyColumn implements Column { - - private String columnHeader; - - HeaderOnlyColumn(String columnHeader) { - this.columnHeader = columnHeader; - } - - @Override - public String getColumnHeader() { - return columnHeader; - } - - @Override - public String getCellData(ArtifactData artData) { - throw new UnsupportedOperationException("Cannot get cell data of unspecified column"); - } - - @Override - public Set removeTypeFromSet(Set types) { - // This column doesn't have a type, so nothing to remove - return types; - } } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index eeca1efe56..54fa7b6b7e 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -838,10 +838,17 @@ class ReportHTML implements TableReportModule { try { indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS StringBuilder index = new StringBuilder(); - index.append("\n").append( //NON-NLS + final String reportTitle = reportBranding.getReportTitle(); + String iconPath = reportBranding.getAgencyLogoPath(); + if (iconPath == null){ + // use default Autopsy icon if custom icon is not set + iconPath = "favicon.ico"; + } + index.append("<head>\n<title>").append(reportTitle).append(" ").append( NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append( "\n"); //NON-NLS - index.append("\n"); //NON-NLS + index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java old mode 100644 new mode 100755 index abdd7dee96..a24850c514 --- a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2016 Basis Technology Corp. * contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,33 +27,56 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.*; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifact; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; -import org.apache.commons.lang.StringEscapeUtils; +import org.jdom2.CDATA; +import org.openide.filesystems.FileUtil; /** - * Generates a KML file based on geo coordinates store in blackboard. + * Generates a KML file based on geospatial information from the BlackBoard. */ class ReportKML implements GeneralReportModule { private static final Logger logger = Logger.getLogger(ReportKML.class.getName()); + private static final String KML_STYLE_FILE = "style.kml"; + private static final String REPORT_KML = "ReportKML.kml"; + private static final String STYLESHEETS_PATH = "/org/sleuthkit/autopsy/report/stylesheets/"; private static ReportKML instance = null; private Case currentCase; private SleuthkitCase skCase; - private String reportPath; + private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); + private Namespace ns; + private final String SEP = "
"; + + private enum FeatureColor { + RED("style.kml#redFeature"), + GREEN("style.kml#greenFeature"), + BLUE("style.kml#blueFeature"), + PURPLE("style.kml#purpleFeature"), + WHITE("style.kml#whiteFeature"), + YELLOW("style.kml#yellowFeature"); + private String color; + + FeatureColor(String color) { + this.color = color; + } + + String getColor() { + return this.color; + } + } // Hidden constructor for the report private ReportKML() { @@ -77,282 +100,727 @@ class ReportKML implements GeneralReportModule { public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { // Start the progress bar and setup the report - progressPanel.setIndeterminate(false); + progressPanel.setIndeterminate(true); progressPanel.start(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying")); - reportPath = baseReportDir + "ReportKML.kml"; //NON-NLS - String reportPath2 = baseReportDir + "ReportKML.txt"; //NON-NLS + String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS currentCase = Case.getCurrentCase(); skCase = currentCase.getSleuthkitCase(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading")); + + ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS + + Element kml = new Element("kml", ns); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS + Document kmlDocument = new Document(kml); + + Element document = new Element("Document", ns); //NON-NLS + kml.addContent(document); + + Element name = new Element("name", ns); //NON-NLS + ReportBranding rb = new ReportBranding(); + name.setText(rb.getReportTitle() + " KML"); //NON-NLS + document.addContent(name); + // Check if ingest has finished - String ingestwarning = ""; if (IngestManager.getInstance().isIngestRunning()) { - ingestwarning = NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text"); + Element ingestwarning = new Element("snippet", ns); //NON-NLS + ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS + document.addContent(ingestwarning); } - progressPanel.setMaximumProgress(5); - progressPanel.increment(); - // @@@ BC: I don't get why we do this in two passes. - // Why not just print the coordinates as we find them and make some utility methods to do the printing? - // Should pull out time values for all of these points and store in TimeSpan element + // Create folder structure + Element gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS + Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS + gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS + + Element gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS + Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS + gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS + + Element gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS + Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS + + Element gpsRouteFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS + gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS + + Element gpsSearchesFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS + Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS + gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS + + Element gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS + + gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS + gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS + gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS + gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS + + document.addContent(gpsExifMetadataFolder); + document.addContent(gpsBookmarksFolder); + document.addContent(gpsLastKnownLocationFolder); + document.addContent(gpsRouteFolder); + document.addContent(gpsSearchesFolder); + document.addContent(gpsTrackpointsFolder); + + ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE; + + /** + * In the following code, nulls are okay, and are handled when we go to + * write out the KML feature. Nulls are expected to be returned from any + * method where the artifact is not found and is handled in the + * individual feature creation methods. This is done because we don't + * know beforehand which attributes will be included for which artifact, + * as anyone could write a module that adds additional attributes to an + * artifact. + * + * If there are any issues reading the database getting artifacts and + * attributes, or any exceptions thrown during this process, a severe + * error is logged, the report is marked as "Incomplete KML Report", and + * we use a best-effort method to generate KML information on everything + * we can successfully pull out of the database. + */ try { - try (BufferedWriter out = new BufferedWriter(new FileWriter(reportPath2))) { + for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { + try { + Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + String desc = getDescriptionFromArtifact(artifact, "EXIF Metadata With Locations"); //NON-NLS + Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - double lat = 0; // temp latitude - double lon = 0; //temp longitude - AbstractFile aFile; - String geoPath = ""; // will hold values of images to put in kml - String imageName = ""; - - File f; - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { - lat = 0; - lon = 0; - geoPath = ""; - String extractedToPath; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - - lat = attribute.getValueDouble(); + if (lat != null && lat != 0.0 && lon != null && lon != 0.0) { + AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + Path path = null; + copyFileUsingStream(abstractFile, Paths.get(baseReportDir, abstractFile.getName()).toFile()); + try { + path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); + } catch (TskCoreException ex) { + path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); + String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); + if (path == null) { + path = Paths.get(abstractFile.getName()); } + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, path, formattedCoordinates)); } - if (lon != 0 && lat != 0) { - aFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - - if (aFile != null) { - extractedToPath = reportPath + aFile.getName(); - geoPath = extractedToPath; - f = new File(extractedToPath); - f.createNewFile(); - copyFileUsingStream(aFile, f); - imageName = aFile.getName(); - } - out.write(String.valueOf(lat)); - out.write(";"); - out.write(String.valueOf(lon)); - out.write(";"); - out.write(String.valueOf(geoPath)); - out.write(";"); - out.write(String.valueOf(imageName)); - out.write("\n"); - // lat lon path name - } + } catch (Exception ex) { + logger.log(Level.SEVERE, "Could not extract photo information.", ex); //NON-NLS + result = ReportProgressPanel.ReportStatus.ERROR; } - - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) { - lat = 0; - lon = 0; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } - } - if (lon != 0 && lat != 0) { - out.write(lat + ";" + lon + "\n"); - } - } - - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) { - lat = 0; - lon = 0; - double destlat = 0; - double destlon = 0; - String name = ""; - String location = ""; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID()) //longitude - { - destlat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID()) //longitude - { - destlon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID()) //longitude - { - name = attribute.getValueString(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()) //longitude - { - location = attribute.getValueString(); - } - } - - // @@@ Should do something more fancy with these in KML and store them as a single point. - String display = name; - if (display.isEmpty()) { - display = location; - } - - if (lon != 0 && lat != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongStartPoint", lat, lon, display)); - } - if (destlat != 0 && destlon != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongEndPoint", destlat, destlon, - display)); - } - } - - out.flush(); - - progressPanel.increment(); - /* - * Step 1: generate XML stub - */ - Namespace ns = Namespace.getNamespace("", "http://earth.google.com/kml/2.2"); //NON-NLS - // kml - Element kml = new Element("kml", ns); //NON-NLS - Document kmlDocument = new Document(kml); - - // Document - Element document = new Element("Document", ns); //NON-NLS - kml.addContent(document); - - // name - Element name = new Element("name", ns); //NON-NLS - name.setText("Java Generated KML Document"); //NON-NLS - document.addContent(name); - - /* - * Step 2: add in Style elements - */ - // Style - Element style = new Element("Style", ns); //NON-NLS - style.setAttribute("id", "redIcon"); //NON-NLS - document.addContent(style); - - // IconStyle - Element iconStyle = new Element("IconStyle", ns); //NON-NLS - style.addContent(iconStyle); - - // color - Element color = new Element("color", ns); //NON-NLS - color.setText("990000ff"); //NON-NLS - iconStyle.addContent(color); - - // Icon - Element icon = new Element("Icon", ns); //NON-NLS - iconStyle.addContent(icon); - - // href - Element href = new Element("href", ns); //NON-NLS - href.setText("http://www.cs.mun.ca/~hoeber/teaching/cs4767/notes/02.1-kml/circle.png"); //NON-NLS - icon.addContent(href); - progressPanel.increment(); - /* - * Step 3: read data from source location and add in a Placemark - * for each data element - */ - - File file = new File(reportPath2); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line = reader.readLine(); - while (line != null) { - String[] lineParts = line.split(";"); - if (lineParts.length > 1) { - String coordinates = lineParts[1].trim() + "," + lineParts[0].trim(); //lat,lon - // Placemark - Element placemark = new Element("Placemark", ns); //NON-NLS - document.addContent(placemark); - - if (lineParts.length == 4) { - // name - Element pmName = new Element("name", ns); //NON-NLS - pmName.setText(lineParts[3].trim()); - placemark.addContent(pmName); - - String savedPath = lineParts[2].trim(); - if (savedPath.isEmpty() == false) { - // Path - Element pmPath = new Element("Path", ns); //NON-NLS - pmPath.setText(savedPath); - placemark.addContent(pmPath); - - // description - Element pmDescription = new Element("description", ns); //NON-NLS - String xml = "
" + featureType + ""); //NON-NLS + + String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (name != null && !name.isEmpty()) { + result.append("Name: ").append(name).append(SEP); //NON-NLS + } + + String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (location != null && !location.isEmpty()) { + result.append("Location: ").append(location).append(SEP); //NON-NLS + } + + Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (timestamp != null) { + result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS + result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS + } + + Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); + if (startingTimestamp != null) { + result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS + result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS + } + + Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); + if (endingTimestamp != null) { + result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS + result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS + } + + Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + if (createdTimestamp != null) { + result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS + result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS + } + + Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + if (latitude != null) { + result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS + } + + Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + if (longitude != null) { + result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS + } + + Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + if (latitudeStart != null) { + result.append("Latitude Start: ").append(latitudeStart).append(SEP); //NON-NLS + } + + Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + if (longitudeStart != null) { + result.append("Longitude Start: ").append(longitudeStart).append(SEP); //NON-NLS + } + + Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + if (latitudeEnd != null) { + result.append("Latitude End: ").append(latitudeEnd).append(SEP); //NON-NLS + } + + Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + if (longitudeEnd != null) { + result.append("Longitude End: ").append(longitudeEnd).append(SEP); //NON-NLS + } + + Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); + if (velocity != null) { + result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS + } + + Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + if (altitude != null) { + result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS + } + + Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); + if (bearing != null) { + result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS + } + + Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); + if (hPrecision != null) { + result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS + } + + Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); + if (vPrecision != null) { + result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS + } + + String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); + if (mapDatum != null && !mapDatum.isEmpty()) { + result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS + } + + String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (programName != null && !programName.isEmpty()) { + result.append("Reported by: ").append(programName).append(SEP); //NON-NLS + } + + String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + if (flag != null && !flag.isEmpty()) { + result.append("Flag: ").append(flag).append(SEP); //NON-NLS + } + + String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); + if (pathSource != null && !pathSource.isEmpty()) { + result.append("Source: ").append(pathSource).append(SEP); //NON-NLS + } + + String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + if (deviceMake != null && !deviceMake.isEmpty()) { + result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS + } + + String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + if (deviceModel != null && !deviceModel.isEmpty()) { + result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS + } + + return result.toString(); + } + + private String getTimeStamp(long timeStamp) { + return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); + } + + /** + * Create a Point for use in a Placemark. Note in this method altitude is + * ignored, as Google Earth apparently has trouble using altitudes for + * LineStrings, though the parameters are still in the call. Also note that + * any null value passed in will be set to 0.0, under the idea that it is + * better to show some data with gaps, than to show nothing at all. + * + * @param latitude point latitude + * @param longitude point longitude + * @param altitude point altitude. Currently ignored. + * + * @return the Point as an Element + */ + private Element makePoint(Double latitude, Double longitude, Double altitude) { + if (latitude == null) { + latitude = 0.0; + } + if (longitude == null) { + longitude = 0.0; + } + if (altitude == null) { + altitude = 0.0; + } + Element point = new Element("Point", ns); //NON-NLS + + // KML uses lon, lat. Deliberately reversed. + Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + altitude); //NON-NLS + + if (altitude != 0) { + /* + Though we are including a non-zero altitude, clamp it to the + ground because inaccuracies from the GPS data can cause the terrain + to occlude points when zoomed in otherwise. Show the altitude, but + keep the point clamped to the ground. We may change this later for + flying GPS sensors. + */ + Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS + point.addContent(altitudeMode); + } + point.addContent(coordinates); + + return point; + } + + /** + * Create a LineString for use in a Placemark. Note in this method, start + * and stop altitudes get ignored, as Google Earth apparently has trouble + * using altitudes for LineStrings, though the parameters are still in the + * call. Also note that any null value passed in will be set to 0.0, under + * the idea that it is better to show some data with gaps, than to show + * nothing at all. + * + * @param startLatitude Starting latitude + * @param startLongitude Starting longitude + * @param startAltitude Starting altitude. Currently ignored. + * @param stopLatitude Ending latitude + * @param stopLongitude Ending longitude + * @param stopAltitude Ending altitude. Currently ignored. + * + * @return the Line as an Element + */ + private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) { + if (startLatitude == null) { + startLatitude = 0.0; + } + if (startLongitude == null) { + startLongitude = 0.0; + } + if (startAltitude == null) { + startAltitude = 0.0; + } + if (stopLatitude == null) { + stopLatitude = 0.0; + } + if (stopLongitude == null) { + stopLongitude = 0.0; + } + if (stopAltitude == null) { + stopAltitude = 0.0; + } + + Element lineString = new Element("LineString", ns); //NON-NLS + lineString.addContent(new Element("extrude", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("tessellate", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("altitudeMode", ns).addContent("clampToGround")); //NON-NLS + // KML uses lon, lat. Deliberately reversed. + lineString.addContent(new Element("coordinates", ns).addContent( + startLongitude + "," + startLatitude + ",0.0," + + stopLongitude + "," + stopLatitude + ",0.0")); //NON-NLS + return lineString; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML placemark + */ + private Element makePlacemark(String name, FeatureColor color, String description, Long timestamp, Element feature, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + } else if (timestamp != null) { + placemark.addContent(new Element("name", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + } else { + placemark.addContent(new Element("name", ns).addContent("")); //NON-NLS + } + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + placemark.addContent(new Element("description", ns).addContent(description)); //NON-NLS + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark file name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param path The path to the file in the source image + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML Placemark, including a picture. + */ + private Element makePlacemarkWithPicture(String name, FeatureColor color, String description, Long timestamp, Element feature, Path path, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + Element desc = new Element("description", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + String image = ""; //NON-NLS + desc.addContent(image); + } + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + if (path != null) { + String pathAsString = path.toString(); + if (pathAsString != null && !pathAsString.isEmpty()) { + desc.addContent(description + "Source Path: " + pathAsString); + } + } + placemark.addContent(desc); + + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Extracts the file to the output folder. + * + * @param inputFile The input AbstractFile to copy + * @param outputFile the output file + * + * @throws IOException + */ + private void copyFileUsingStream(AbstractFile inputFile, File outputFile) throws IOException { + byte[] buffer = new byte[65536]; + int length; + outputFile.createNewFile(); + try (InputStream is = new ReadContentInputStream(inputFile); + OutputStream os = new FileOutputStream(outputFile)) { while ((length = is.read(buffer)) != -1) { os.write(buffer, 0, length); } - - } finally { - is.close(); - os.close(); } } @@ -377,4 +845,36 @@ class ReportKML implements GeneralReportModule { public JPanel getConfigurationPanel() { return null; // No configuration panel } + + /** + * This is a smash-n-grab from AbstractFile.createNonUniquePath(String). + * This method is intended to be removed when img_ and vol_ are no longer + * added to images and volumes respectively, OR when AbstractFile is sorted + * out with respect to this. + * + * @param uniquePath The path to sanitize. + * + * @return path without leading img_/vol_ in position 0 or 1 respectively. + */ + private static String removeLeadingImgAndVol(String uniquePath) { + // split the path into parts + String[] pathSegments = uniquePath.replaceFirst("^/*", "").split("/"); //NON-NLS + + // Replace image/volume name if they exist in specific entries + if (pathSegments.length > 0) { + pathSegments[0] = pathSegments[0].replaceFirst("^img_", ""); //NON-NLS + } + if (pathSegments.length > 1) { + pathSegments[1] = pathSegments[1].replaceFirst("^vol_", ""); //NON-NLS + } + + // Assemble the path + StringBuilder strbuf = new StringBuilder(); + for (String segment : pathSegments) { + if (!segment.isEmpty()) { + strbuf.append("/").append(segment); + } + } + return strbuf.toString(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java index 7c2bc5193c..93acae830a 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java @@ -24,9 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import static java.util.Collections.swap; import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; import javax.swing.JList; import javax.swing.JPanel; @@ -137,8 +135,8 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { // Make sure that the report module has a valid non-null name. private boolean moduleIsValid(ReportModule module) { - return module.getName() != null && !module.getName().isEmpty() - && module.getRelativeFilePath() != null; + return module.getName() != null && !module.getName().isEmpty() + && module.getRelativeFilePath() != null; } private void popupWarning(ReportModule module) { @@ -163,13 +161,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { * * @return */ - Map getTableModuleStates() { - Map reportModuleStates = new LinkedHashMap<>(); + TableReportModule getTableModule() { ReportModule mod = getSelectedModule(); if (tableModules.contains(mod)) { - reportModuleStates.put((TableReportModule) mod, Boolean.TRUE); + return (TableReportModule) mod; } - return reportModuleStates; + return null; } /** @@ -177,13 +174,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { * * @return */ - Map getGeneralModuleStates() { - Map reportModuleStates = new LinkedHashMap<>(); + GeneralReportModule getGeneralModule() { ReportModule mod = getSelectedModule(); if (generalModules.contains(mod)) { - reportModuleStates.put((GeneralReportModule) mod, Boolean.TRUE); + return (GeneralReportModule) mod; } - return reportModuleStates; + return null; } /** @@ -191,13 +187,12 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { * * @return */ - Map getFileModuleStates() { - Map reportModuleStates = new LinkedHashMap<>(); + FileReportModule getFileModule() { ReportModule mod = getSelectedModule(); if (fileModules.contains(mod)) { - reportModuleStates.put((FileReportModule) mod, Boolean.TRUE); + return (FileReportModule) mod; } - return reportModuleStates; + return null; } /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index bf51dfee86..e0a5eb6788 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -1,4 +1,4 @@ - /* +/* * * Autopsy Forensic Browser * @@ -65,13 +65,17 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr wiz.setTitle(NbBundle.getMessage(ReportWizardAction.class, "ReportWizardAction.reportWiz.title")); if (DialogDisplayer.getDefault().notify(wiz) == WizardDescriptor.FINISH_OPTION) { @SuppressWarnings("unchecked") - ReportGenerator generator = new ReportGenerator((Map) wiz.getProperty("tableModuleStates"), //NON-NLS - (Map) wiz.getProperty("generalModuleStates"), //NON-NLS - (Map) wiz.getProperty("fileModuleStates")); //NON-NLS - generator.generateTableReports((Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS - generator.generateFileListReports((Map) wiz.getProperty("fileReportOptions")); //NON-NLS - generator.generateGeneralReports(); - generator.displayProgressPanels(); + ReportGenerator generator = new ReportGenerator(); //NON-NLS + TableReportModule tableReport = (TableReportModule) wiz.getProperty("tableModule"); + GeneralReportModule generalReport = (GeneralReportModule) wiz.getProperty("generalModule"); + FileReportModule fileReport = (FileReportModule) wiz.getProperty("fileModule"); + if (tableReport != null) { + generator.generateTableReport(tableReport, (Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS + } else if (generalReport != null) { + generator.generateGeneralReport(generalReport); + } else if (fileReport != null) { + generator.generateFileListReport(fileReport, (Map) wiz.getProperty("fileReportOptions")); //NON-NLS + } } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardPanel1.java index 9231a9685a..b5e3a80034 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardPanel1.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.report; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collection; -import java.util.Map; import java.util.prefs.Preferences; import javax.swing.JButton; import javax.swing.event.ChangeListener; @@ -108,17 +107,17 @@ class ReportWizardPanel1 implements WizardDescriptor.FinishablePanel tables = getComponent().getTableModuleStates(); - Map generals = getComponent().getGeneralModuleStates(); - wiz.putProperty("tableModuleStates", tables); //NON-NLS - wiz.putProperty("generalModuleStates", generals); //NON-NLS - wiz.putProperty("fileModuleStates", getComponent().getFileModuleStates()); //NON-NLS + TableReportModule module = getComponent().getTableModule(); + GeneralReportModule general = getComponent().getGeneralModule(); + wiz.putProperty("tableModule", module); //NON-NLS + wiz.putProperty("generalModule", general); //NON-NLS + wiz.putProperty("fileModule", getComponent().getFileModule()); //NON-NLS // Store preferences that WizardIterator will use to determine what // panels need to be shown Preferences prefs = NbPreferences.forModule(ReportWizardPanel1.class); - prefs.putBoolean("tableModule", any(tables.values())); //NON-NLS - prefs.putBoolean("generalModule", any(generals.values())); //NON-NLS + prefs.putBoolean("tableModule", module != null); //NON-NLS + prefs.putBoolean("generalModule", general != null); //NON-NLS } /** diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java new file mode 100755 index 0000000000..5dd88f680a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -0,0 +1,1686 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.report; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; +import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +class TableReportGenerator { + + private final List artifactTypes = new ArrayList<>(); + private final HashSet tagNamesFilter = new HashSet<>(); + + private final List images = new ArrayList<>(); + private final ReportProgressPanel progressPanel; + private final TableReportModule tableReport; + private final Map> columnHeaderMap; + private static final Logger logger = Logger.getLogger(TableReportGenerator.class.getName()); + + private final List errorList; + + TableReportGenerator(Map artifactTypeSelections, Map tagNameSelections, ReportProgressPanel progressPanel, TableReportModule tableReport) { + + this.progressPanel = progressPanel; + this.tableReport = tableReport; + this.columnHeaderMap = new HashMap<>(); + errorList = new ArrayList<>(); + // Get the artifact types selected by the user. + for (Map.Entry entry : artifactTypeSelections.entrySet()) { + if (entry.getValue()) { + artifactTypes.add(entry.getKey()); + } + } + + // Get the tag names selected by the user and make a tag names filter. + if (null != tagNameSelections) { + for (Map.Entry entry : tagNameSelections.entrySet()) { + if (entry.getValue() == true) { + tagNamesFilter.add(entry.getKey()); + } + } + } + } + + protected void execute() { + // Start the progress indicators for each active TableReportModule. + + progressPanel.start(); + progressPanel.setIndeterminate(false); + progressPanel.setMaximumProgress(this.artifactTypes.size() + 2); // +2 for content and blackboard artifact tags + // report on the blackboard results + if (progressPanel.getStatus() != ReportProgressPanel.ReportStatus.CANCELED) { + makeBlackboardArtifactTables(); + } + + // report on the tagged files and artifacts + if (progressPanel.getStatus() != ReportProgressPanel.ReportStatus.CANCELED) { + makeContentTagsTables(); + } + + if (progressPanel.getStatus() != ReportProgressPanel.ReportStatus.CANCELED) { + makeBlackboardArtifactTagsTables(); + } + + if (progressPanel.getStatus() != ReportProgressPanel.ReportStatus.CANCELED) { + // report on the tagged images + makeThumbnailTable(); + } + + // finish progress, wrap up + progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE); + } + + /** + * Generate the tables for the selected blackboard artifacts + */ + private void makeBlackboardArtifactTables() { + // Make a comment string describing the tag names filter in effect. + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text")); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + + // Add a table to the report for every enabled blackboard artifact type. + for (BlackboardArtifact.Type type : artifactTypes) { + // Check for cancellaton. + + if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { + return; + } + + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", + type.getDisplayName())); + + // Keyword hits and hashset hit artifacts get special handling. + if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { + writeKeywordHits(tableReport, comment.toString(), tagNamesFilter); + continue; + } else if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { + writeHashsetHits(tableReport, comment.toString(), tagNamesFilter); + continue; + } + + List artifactList = getFilteredArtifacts(type, tagNamesFilter); + + if (artifactList.isEmpty()) { + continue; + } + + /* + Gets all of the attribute types of this artifact type by adding + all of the types to a set + */ + Set attrTypeSet = new TreeSet<>((BlackboardAttribute.Type o1, BlackboardAttribute.Type o2) -> o1.getDisplayName().compareTo(o2.getDisplayName())); + for (ArtifactData data : artifactList) { + List attributes = data.getAttributes(); + for (BlackboardAttribute attribute : attributes) { + attrTypeSet.add(attribute.getAttributeType()); + } + } + // Get the columns appropriate for the artifact type. This is + // used to get the data that will be in the cells below based on + // type, and display the column headers. + List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); + if (columns.isEmpty()) { + continue; + } + columnHeaderMap.put(type.getTypeID(), columns); + + // The artifact list is sorted now, as getting the row data is + // dependent on having the columns, which is necessary for + // sorting. + Collections.sort(artifactList); + List columnHeaderNames = new ArrayList<>(); + for (Column currColumn : columns) { + columnHeaderNames.add(currColumn.getColumnHeader()); + } + + tableReport.startDataType(type.getDisplayName(), comment.toString()); + tableReport.startTable(columnHeaderNames); + for (ArtifactData artifactData : artifactList) { + // Get the row data for this artifact, and has the + // module add it. + List rowData = artifactData.getRow(); + if (rowData.isEmpty()) { + continue; + } + + tableReport.addRow(rowData); + } + // Finish up this data type + progressPanel.increment(); + tableReport.endTable(); + tableReport.endDataType(); + } + } + + /** + * Make table for tagged files + */ + @SuppressWarnings("deprecation") + private void makeContentTagsTables() { + + // Get the content tags. + List tags; + try { + tags = Case.getCurrentCase().getServices().getTagsManager().getAllContentTags(); + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetContentTags")); + logger.log(Level.SEVERE, "failed to get content tags", ex); //NON-NLS + return; + } + + // Tell the modules reporting on content tags is beginning. + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + // @@@ Alos Using the obsolete ARTIFACT_TYPE.TSK_TAG_FILE is also an expedient hack. + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", + BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName())); + ArrayList columnHeaders = new ArrayList<>(Arrays.asList( + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.tag"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.file"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeModified"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeChanged"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeAccessed"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeCreated"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.size"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.hash"))); + + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append( + NbBundle.getMessage(this.getClass(), "ReportGenerator.makeContTagTab.taggedFiles.msg")); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + if (tableReport instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML) tableReport; + htmlReportModule.startDataType(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + htmlReportModule.startContentTagsTable(columnHeaders); + } else { + tableReport.startDataType(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + tableReport.startTable(columnHeaders); + } + + // Give the modules the rows for the content tags. + for (ContentTag tag : tags) { + // skip tags that we are not reporting on + if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { + continue; + } + + String fileName; + try { + fileName = tag.getContent().getUniquePath(); + } catch (TskCoreException ex) { + fileName = tag.getContent().getName(); + } + + ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getName().getDisplayName(), fileName, tag.getComment())); + Content content = tag.getContent(); + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; + + // Add metadata about the file to HTML output + rowData.add(file.getMtimeAsDate()); + rowData.add(file.getCtimeAsDate()); + rowData.add(file.getAtimeAsDate()); + rowData.add(file.getCrtimeAsDate()); + rowData.add(Long.toString(file.getSize())); + rowData.add(file.getMd5Hash()); + } + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + if (tableReport instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML) tableReport; + htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); + } else { + tableReport.addRow(rowData); + } + + // see if it is for an image so that we later report on it + checkIfTagHasImage(tag); + } + + // The the modules content tags reporting is ended. + progressPanel.increment(); + tableReport.endTable(); + tableReport.endDataType(); + } + + /** + * Generate the tables for the tagged artifacts + */ + @SuppressWarnings("deprecation") + private void makeBlackboardArtifactTagsTables() { + + List tags; + try { + tags = Case.getCurrentCase().getServices().getTagsManager().getAllBlackboardArtifactTags(); + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBArtifactTags")); + logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); //NON-NLS + return; + } + + // Tell the modules reporting on blackboard artifact tags data type is beginning. + // @@@ Using the obsolete ARTIFACT_TYPE.TSK_TAG_ARTIFACT is an expedient hack. + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", + BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName())); + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append( + NbBundle.getMessage(this.getClass(), "ReportGenerator.makeBbArtTagTab.taggedRes.msg")); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + tableReport.startDataType(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString()); + tableReport.startTable(new ArrayList<>(Arrays.asList( + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.resultType"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.tag"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.comment"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile")))); + + // Give the modules the rows for the content tags. + for (BlackboardArtifactTag tag : tags) { + if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { + continue; + } + + List row; + row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName())); + tableReport.addRow(row); + + // check if the tag is an image that we should later make a thumbnail for + checkIfTagHasImage(tag); + } + + // The the modules blackboard artifact tags reporting is ended. + progressPanel.increment(); + tableReport.endTable(); + tableReport.endDataType(); + } + + /** + * Test if the user requested that this tag be reported on + * + * @param tagName + * + * @return true if it should be reported on + */ + private boolean passesTagNamesFilter(String tagName) { + return tagNamesFilter.isEmpty() || tagNamesFilter.contains(tagName); + } + + /** + * Make a report for the files that were previously found to be images. + */ + private void makeThumbnailTable() { + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.createdThumb.text")); + + if (tableReport instanceof ReportHTML) { + ReportHTML htmlModule = (ReportHTML) tableReport; + htmlModule.startDataType( + NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.name"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.desc")); + List emptyHeaders = new ArrayList<>(); + for (int i = 0; i < ReportHTML.THUMBNAIL_COLUMNS; i++) { + emptyHeaders.add(""); + } + htmlModule.startTable(emptyHeaders); + + htmlModule.addThumbnailRows(images); + + htmlModule.endTable(); + htmlModule.endDataType(); + } + + } + + /** + * Analyze artifact associated with tag and add to internal list if it is + * associated with an image. + * + * @param artifactTag + */ + private void checkIfTagHasImage(BlackboardArtifactTag artifactTag) { + AbstractFile file; + try { + file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID()); + } catch (TskCoreException ex) { + errorList.add( + NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.errGetContentFromBBArtifact")); + logger.log(Level.WARNING, "Error while getting content from a blackboard artifact to report on.", ex); //NON-NLS + return; + } + + if (file != null) { + checkIfFileIsImage(file); + } + } + + /** + * Analyze file that tag is associated with and determine if it is an image + * and should have a thumbnail reported for it. Images are added to internal + * list. + * + * @param contentTag + */ + private void checkIfTagHasImage(ContentTag contentTag) { + Content c = contentTag.getContent(); + if (c instanceof AbstractFile == false) { + return; + } + checkIfFileIsImage((AbstractFile) c); + } + + /** + * If file is an image file, add it to the internal 'images' list. + * + * @param file + */ + private void checkIfFileIsImage(AbstractFile file) { + + if (file.isDir() + || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS + || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { + return; + } + + if (ImageUtils.thumbnailSupported(file)) { + images.add(file); + } + } + + /** + * Converts a collection of strings into a single string of comma-separated + * items + * + * @param items A collection of strings + * + * @return A string of comma-separated items + */ + private String makeCommaSeparatedList(Collection items) { + String list = ""; + for (Iterator iterator = items.iterator(); iterator.hasNext();) { + list += iterator.next() + (iterator.hasNext() ? ", " : ""); + } + return list; + } + + /** + * Write the keyword hits to the provided TableReportModules. + * + * @param tableModule module to report on + */ + @SuppressWarnings("deprecation") + private void writeKeywordHits(TableReportModule tableModule, String comment, HashSet tagNamesFilter) { + + // Query for keyword lists-only so that we can tell modules what lists + // will exist for their index. + // @@@ There is a bug in here. We should use the tags in the below code + // so that we only report the lists that we will later provide with real + // hits. If no keyord hits are tagged, then we make the page for nothing. + String orderByClause; + if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { + orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS + } else { + orderByClause = "ORDER BY list ASC"; //NON-NLS + } + String keywordListQuery + = "SELECT att.value_text AS list " + + //NON-NLS + "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + + //NON-NLS + "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + + //NON-NLS + "AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " " + + //NON-NLS + "AND att.artifact_id = art.artifact_id " + + //NON-NLS + "GROUP BY list " + orderByClause; //NON-NLS + + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCase().getSleuthkitCase().executeQuery(keywordListQuery)) { + ResultSet listsRs = dbQuery.getResultSet(); + List lists = new ArrayList<>(); + while (listsRs.next()) { + String list = listsRs.getString("list"); //NON-NLS + if (list.isEmpty()) { + list = NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs"); + } + lists.add(list); + } + + // Make keyword data type and give them set index + tableModule.startDataType(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); + tableModule.addSetIndex(lists); + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName())); + } catch (TskCoreException | SQLException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWLists")); + logger.log(Level.SEVERE, "Failed to query keyword lists: ", ex); //NON-NLS + return; + } + + if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { + orderByClause = "ORDER BY convert_to(att3.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(att1.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(f.parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(f.name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(att2.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS + } else { + orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS + } + // Query for keywords, grouped by list + String keywordsQuery + = "SELECT art.artifact_id, art.obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path " + + //NON-NLS + "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f " + + //NON-NLS + "WHERE (att1.artifact_id = art.artifact_id) " + + //NON-NLS + "AND (att2.artifact_id = art.artifact_id) " + + //NON-NLS + "AND (att3.artifact_id = art.artifact_id) " + + //NON-NLS + "AND (f.obj_id = art.obj_id) " + + //NON-NLS + "AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + + //NON-NLS + "AND (att2.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + + //NON-NLS + "AND (att3.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + + //NON-NLS + "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + + //NON-NLS + orderByClause; //NON-NLS + + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCase().getSleuthkitCase().executeQuery(keywordsQuery)) { + ResultSet resultSet = dbQuery.getResultSet(); + + String currentKeyword = ""; + String currentList = ""; + while (resultSet.next()) { + // Check to see if all the TableReportModules have been canceled + if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { + break; + } + + // Get any tags that associated with this artifact and apply the tag filter. + HashSet uniqueTagNames = getUniqueTagNames(resultSet.getLong("artifact_id")); //NON-NLS + if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { + continue; + } + String tagsList = makeCommaSeparatedList(uniqueTagNames); + + Long objId = resultSet.getLong("obj_id"); //NON-NLS + String keyword = resultSet.getString("keyword"); //NON-NLS + String preview = resultSet.getString("preview"); //NON-NLS + String list = resultSet.getString("list"); //NON-NLS + String uniquePath = ""; + + try { + AbstractFile f = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(objId); + if (f != null) { + uniquePath = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(objId).getUniquePath(); + } + } catch (TskCoreException ex) { + errorList.add( + NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileByID")); + logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS + } + + // If the lists aren't the same, we've started a new list + if ((!list.equals(currentList) && !list.isEmpty()) || (list.isEmpty() && !currentList.equals( + NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs")))) { + if (!currentList.isEmpty()) { + tableModule.endTable(); + tableModule.endSet(); + } + currentList = list.isEmpty() ? NbBundle + .getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs") : list; + currentKeyword = ""; // reset the current keyword because it's a new list + tableModule.startSet(currentList); + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList", + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), currentList)); + } + if (!keyword.equals(currentKeyword)) { + if (!currentKeyword.equals("")) { + tableModule.endTable(); + } + currentKeyword = keyword; + tableModule.addSetElement(currentKeyword); + List columnHeaderNames = new ArrayList<>(); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.preview")); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags")); + tableModule.startTable(columnHeaderNames); + } + + String previewreplace = EscapeUtil.escapeHtml(preview); + tableModule.addRow(Arrays.asList(new String[]{previewreplace.replaceAll(" tagNamesFilter) { + String orderByClause; + if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { + orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS + } else { + orderByClause = "ORDER BY att.value_text ASC"; //NON-NLS + } + String hashsetsQuery + = "SELECT att.value_text AS list " + + //NON-NLS + "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + + //NON-NLS + "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + + //NON-NLS + "AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + " " + + //NON-NLS + "AND att.artifact_id = art.artifact_id " + + //NON-NLS + "GROUP BY list " + orderByClause; //NON-NLS + + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCase().getSleuthkitCase().executeQuery(hashsetsQuery)) { + // Query for hashsets + ResultSet listsRs = dbQuery.getResultSet(); + List lists = new ArrayList<>(); + while (listsRs.next()) { + lists.add(listsRs.getString("list")); //NON-NLS + } + + tableModule.startDataType(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); + tableModule.addSetIndex(lists); + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing", + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName())); + } catch (TskCoreException | SQLException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryHashsetLists")); + logger.log(Level.SEVERE, "Failed to query hashset lists: ", ex); //NON-NLS + return; + } + + if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { + orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(f.parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(f.name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "size ASC NULLS FIRST"; //NON-NLS + } else { + orderByClause = "ORDER BY att.value_text ASC, f.parent_path ASC, f.name ASC, size ASC"; //NON-NLS + } + String hashsetHitsQuery + = "SELECT art.artifact_id, art.obj_id, att.value_text AS setname, f.name AS name, f.size AS size, f.parent_path AS parent_path " + + //NON-NLS + "FROM blackboard_artifacts AS art, blackboard_attributes AS att, tsk_files AS f " + + //NON-NLS + "WHERE (att.artifact_id = art.artifact_id) " + + //NON-NLS + "AND (f.obj_id = art.obj_id) " + + //NON-NLS + "AND (att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + + //NON-NLS + "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + ") " + + //NON-NLS + orderByClause; //NON-NLS + + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCase().getSleuthkitCase().executeQuery(hashsetHitsQuery)) { + // Query for hashset hits + ResultSet resultSet = dbQuery.getResultSet(); + String currentSet = ""; + while (resultSet.next()) { + // Check to see if all the TableReportModules have been canceled + if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { + break; + } + + // Get any tags that associated with this artifact and apply the tag filter. + HashSet uniqueTagNames = getUniqueTagNames(resultSet.getLong("artifact_id")); //NON-NLS + if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { + continue; + } + String tagsList = makeCommaSeparatedList(uniqueTagNames); + + Long objId = resultSet.getLong("obj_id"); //NON-NLS + String set = resultSet.getString("setname"); //NON-NLS + String size = resultSet.getString("size"); //NON-NLS + String uniquePath = ""; + + try { + AbstractFile f = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(objId); + if (f != null) { + uniquePath = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(objId).getUniquePath(); + } + } catch (TskCoreException ex) { + errorList.add( + NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileFromID")); + logger.log(Level.WARNING, "Failed to get Abstract File from ID.", ex); //NON-NLS + return; + } + + // If the sets aren't the same, we've started a new set + if (!set.equals(currentSet)) { + if (!currentSet.isEmpty()) { + tableModule.endTable(); + tableModule.endSet(); + } + currentSet = set; + tableModule.startSet(currentSet); + List columnHeaderNames = new ArrayList<>(); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file")); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.size")); + columnHeaderNames.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags")); + tableModule.startTable(columnHeaderNames); + progressPanel.updateStatusLabel( + NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList", + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), currentSet)); + } + + // Add a row for this hit to every module + tableModule.addRow(Arrays.asList(new String[]{uniquePath, size, tagsList})); + } + + // Finish the current data type + progressPanel.increment(); + tableModule.endDataType(); + } catch (TskCoreException | SQLException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryHashsetHits")); + logger.log(Level.SEVERE, "Failed to query hashsets hits: ", ex); //NON-NLS + } + } + + /** + * @return the errorList + */ + List getErrorList() { + return errorList; + } + + /** + * Container class that holds data about an Artifact to eliminate duplicate + * calls to the Sleuthkit database. + */ + private class ArtifactData implements Comparable { + + private BlackboardArtifact artifact; + private List attributes; + private HashSet tags; + private List rowData = null; + private Content content; + + ArtifactData(BlackboardArtifact artifact, List attrs, HashSet tags) { + this.artifact = artifact; + this.attributes = attrs; + this.tags = tags; + try { + this.content = Case.getCurrentCase().getSleuthkitCase().getContentById(artifact.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Could not get content from database"); + } + } + + public BlackboardArtifact getArtifact() { + return artifact; + } + + public List getAttributes() { + return attributes; + } + + public HashSet getTags() { + return tags; + } + + public long getArtifactID() { + return artifact.getArtifactID(); + } + + public long getObjectID() { + return artifact.getObjectID(); + } + + /** + * @return the content + */ + public Content getContent() { + return content; + } + + /** + * Compares ArtifactData objects by the first attribute they have in + * common in their List. Should only be used on two + * artifacts of the same type + * + * If all attributes are the same, they are assumed duplicates and are + * compared by their artifact id. Should only be used with attributes of + * the same type. + */ + @Override + public int compareTo(ArtifactData otherArtifactData) { + List thisRow = getRow(); + List otherRow = otherArtifactData.getRow(); + for (int i = 0; i < thisRow.size(); i++) { + int compare = thisRow.get(i).compareTo(otherRow.get(i)); + if (compare != 0) { + return compare; + } + } + return ((Long) this.getArtifactID()).compareTo(otherArtifactData.getArtifactID()); + } + + /** + * Get the values for each row in the table report. + * + * the value types of custom artifacts + * + * @return A list of string representing the data for this artifact. + */ + public List getRow() { + if (rowData == null) { + try { + rowData = getOrderedRowDataAsStrings(); + // If else is done so that row data is not set before + // columns are added to the hash map. + if (rowData.size() > 0) { + // replace null values if attribute was not defined + for (int i = 0; i < rowData.size(); i++) { + if (rowData.get(i) == null) { + rowData.set(i, ""); + } + } + } else { + rowData = null; + return new ArrayList<>(); + } + } catch (TskCoreException ex) { + errorList.add( + NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.coreExceptionWhileGenRptRow")); + logger.log(Level.WARNING, "Core exception while generating row data for artifact report.", ex); //NON-NLS + rowData = Collections.emptyList(); + } + } + return rowData; + } + + /** + * Get a list of Strings with all the row values for the Artifact in the + * correct order to be written to the report. + * + * @return List row values. Values could be null if attribute is + * not defined in artifact + * + * @throws TskCoreException + */ + private List getOrderedRowDataAsStrings() throws TskCoreException { + + List orderedRowData = new ArrayList<>(); + if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID() == getArtifact().getArtifactTypeID()) { + if (content != null && content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; + orderedRowData.add(file.getName()); + orderedRowData.add(file.getNameExtension()); + String mimeType = file.getMIMEType(); + if (mimeType == null) { + orderedRowData.add(""); + } else { + orderedRowData.add(mimeType); + } + orderedRowData.add(file.getUniquePath()); + } else { + // Make empty rows to make sure the formatting is correct + orderedRowData.add(null); + orderedRowData.add(null); + orderedRowData.add(null); + orderedRowData.add(null); + } + orderedRowData.add(makeCommaSeparatedList(getTags())); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == getArtifact().getArtifactTypeID()) { + String[] attributeDataArray = new String[3]; + // Array is used so that order of the attributes is maintained. + for (BlackboardAttribute attr : attributes) { + if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME))) { + attributeDataArray[0] = attr.getDisplayString(); + } else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY))) { + attributeDataArray[1] = attr.getDisplayString(); + } + } + + attributeDataArray[2] = content.getUniquePath(); + orderedRowData.addAll(Arrays.asList(attributeDataArray)); + + HashSet allTags = getTags(); + try { + List contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(content); + for (ContentTag ct : contentTags) { + allTags.add(ct.getName().getDisplayName()); + } + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetContentTags")); + logger.log(Level.SEVERE, "Failed to get content tags", ex); //NON-NLS + } + orderedRowData.add(makeCommaSeparatedList(allTags)); + + } else if (columnHeaderMap.containsKey(this.artifact.getArtifactTypeID())) { + + for (Column currColumn : columnHeaderMap.get(this.artifact.getArtifactTypeID())) { + String cellData = currColumn.getCellData(this); + orderedRowData.add(cellData); + } + } + + return orderedRowData; + } + + } + + /** + * Get a List of the artifacts and data of the given type that pass the + * given Tag Filter. + * + * @param type The artifact type to get + * @param tagNamesFilter The tag names that should be included. + * + * @return a list of the filtered tags. + */ + private List getFilteredArtifacts(BlackboardArtifact.Type type, HashSet tagNamesFilter) { + List artifacts = new ArrayList<>(); + try { + for (BlackboardArtifact artifact : Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(type.getTypeID())) { + List tags = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact); + HashSet uniqueTagNames = new HashSet<>(); + for (BlackboardArtifactTag tag : tags) { + uniqueTagNames.add(tag.getName().getDisplayName()); + } + if (failsTagFilter(uniqueTagNames, tagNamesFilter)) { + continue; + } + try { + artifacts.add(new ArtifactData(artifact, Case.getCurrentCase().getSleuthkitCase().getBlackboardAttributes(artifact), uniqueTagNames)); + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBAttribs")); + logger.log(Level.SEVERE, "Failed to get Blackboard Attributes when generating report.", ex); //NON-NLS + } + } + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetBBArtifacts")); + logger.log(Level.SEVERE, "Failed to get Blackboard Artifacts when generating report.", ex); //NON-NLS + } + return artifacts; + } + + private Boolean failsTagFilter(HashSet tagNames, HashSet tagsNamesFilter) { + if (null == tagsNamesFilter || tagsNamesFilter.isEmpty()) { + return false; + } + + HashSet filteredTagNames = new HashSet<>(tagNames); + filteredTagNames.retainAll(tagsNamesFilter); + return filteredTagNames.isEmpty(); + } + + /** + * For a given artifact type ID, return the list of the columns that we are + * reporting on. + * + * @param artifactTypeId artifact type ID + * @param attributeTypeSet The set of attributeTypeSet available for this + * artifact type + * + * @return List row titles + */ + private List getArtifactTableColumns(int artifactTypeId, Set attributeTypeSet) { + ArrayList columns = new ArrayList<>(); + + // Long switch statement to retain ordering of attribute types that are + // attached to pre-defined artifact types. + if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateCreated"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.value"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.referrer"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.urlDomainDecoded"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL_DECODED))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dest"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.sourceUrl"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.instDateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() == artifactTypeId) { + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.preview"))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == artifactTypeId) { + columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"))); + + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.size"))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devMake"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devModel"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceId"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.domain"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTaken"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devManufacturer"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devModel"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumHome"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumOffice"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumMobile"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.email"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.msgType"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.readStatus"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromPhoneNum"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromEmail"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toPhoneNum"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toEmail"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.subject"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromPhoneNum"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toPhoneNum"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_CALENDAR_ENTRY.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.calendarEntryType"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.startDateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.endDateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.location"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.shortCut"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SHORTCUT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceAddress"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.category"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userId"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.password"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PASSWORD))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appPath"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.replytoAddress"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mailServer"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SERVER_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID() == artifactTypeId) { + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"))); + + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.extension.text"))); + + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mimeType.text"))); + + columns.add(new HeaderOnlyColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.processorArchitecture.text"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osName.text"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osInstallDate.text"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailTo"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailFrom"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSubject"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskDateTimeSent"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskDateTimeRcvd"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_RCVD))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailCc"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CC))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskEmailBcc"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_BCC))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskMsgId"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MSG_ID))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSetName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskInterestingFilesCategory"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskGpsRouteCategory"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitudeEnd"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitudeEnd"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitudeStart"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitudeStart"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.location"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskSetName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.associatedArtifact"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.associatedArtifact"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.count"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COUNT))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userName"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userId"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID))); + + } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_REMOTE_DRIVE.getTypeID() == artifactTypeId) { + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.localPath"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCAL_PATH))); + + columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.remotePath"), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH))); + } else { + // This is the case that it is a custom type. The reason an else is + // necessary is to make sure that the source file column is added + for (BlackboardAttribute.Type type : attributeTypeSet) { + columns.add(new AttributeColumn(type.getDisplayName(), type)); + } + columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile"))); + columns.add(new TaggedResultsColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))); + + // Short circuits to guarantee that the attribute types aren't added + // twice. + return columns; + } + // If it is an attribute column, it removes the attribute type of that + // column from the set, so types are not reported more than once. + for (Column column : columns) { + attributeTypeSet = column.removeTypeFromSet(attributeTypeSet); + } + // Now uses the remaining types in the set to construct columns + for (BlackboardAttribute.Type type : attributeTypeSet) { + columns.add(new AttributeColumn(type.getDisplayName(), type)); + } + // Source file column is added here for ordering purposes. + if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALENDAR_ENTRY.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID()) { + columns.add(new SourceFileColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile"))); + } + columns.add(new TaggedResultsColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))); + + return columns; + } + + /** + * Given a tsk_file's obj_id, return the unique path of that file. + * + * @param objId tsk_file obj_id + * + * @return String unique path + */ + private String getFileUniquePath(Content content) { + try { + if (content != null) { + return content.getUniquePath(); + } else { + return ""; + } + } catch (TskCoreException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedGetAbstractFileByID")); + logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS + } + return ""; + + } + + /** + * Get any tags associated with an artifact + * + * @param artifactId + * + * @return hash set of tag display names + * + * @throws SQLException + */ + @SuppressWarnings("deprecation") + private HashSet getUniqueTagNames(long artifactId) throws TskCoreException { + HashSet uniqueTagNames = new HashSet<>(); + + String query = "SELECT display_name, artifact_id FROM tag_names AS tn, blackboard_artifact_tags AS bat " + + //NON-NLS + "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS + + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCase().getSleuthkitCase().executeQuery(query)) { + ResultSet tagNameRows = dbQuery.getResultSet(); + while (tagNameRows.next()) { + uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + throw new TskCoreException("Error getting tag names for artifact: ", ex); + } + + return uniqueTagNames; + + } + + private interface Column { + + String getColumnHeader(); + + String getCellData(ArtifactData artData); + + Set removeTypeFromSet(Set types); + } + + private class AttributeColumn implements Column { + + private final String columnHeader; + private final BlackboardAttribute.Type attributeType; + + /** + * Constructs an ArtifactCell + * + * @param columnHeader The header text of this column + * @param attributeType The attribute type associated with this column + */ + AttributeColumn(String columnHeader, BlackboardAttribute.Type attributeType) { + this.columnHeader = Objects.requireNonNull(columnHeader); + this.attributeType = attributeType; + } + + @Override + public String getColumnHeader() { + return this.columnHeader; + } + + @Override + public String getCellData(ArtifactData artData) { + List attributes = artData.getAttributes(); + for (BlackboardAttribute attribute : attributes) { + if (attribute.getAttributeType().equals(this.attributeType)) { + if (attribute.getAttributeType().getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { + return attribute.getDisplayString(); + } else { + return ContentUtils.getStringTime(attribute.getValueLong(), artData.getContent()); + } + } + } + return ""; + } + + @Override + public Set removeTypeFromSet(Set types) { + types.remove(this.attributeType); + return types; + } + } + + private class SourceFileColumn implements Column { + + private final String columnHeader; + + SourceFileColumn(String columnHeader) { + this.columnHeader = columnHeader; + } + + @Override + public String getColumnHeader() { + return this.columnHeader; + } + + @Override + public String getCellData(ArtifactData artData) { + return getFileUniquePath(artData.getContent()); + /*else if (this.columnHeader.equals(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))) { + return makeCommaSeparatedList(artData.getTags()); + } + return "";*/ + } + + @Override + public Set removeTypeFromSet(Set types) { + // This column doesn't have a type, so nothing to remove + return types; + } + } + + private class TaggedResultsColumn implements Column { + + private final String columnHeader; + + TaggedResultsColumn(String columnHeader) { + this.columnHeader = columnHeader; + } + + @Override + public String getColumnHeader() { + return this.columnHeader; + } + + @Override + public String getCellData(ArtifactData artData) { + return makeCommaSeparatedList(artData.getTags()); + } + + @Override + public Set removeTypeFromSet(Set types) { + // This column doesn't have a type, so nothing to remove + return types; + } + } + + private class HeaderOnlyColumn implements Column { + + private final String columnHeader; + + HeaderOnlyColumn(String columnHeader) { + this.columnHeader = columnHeader; + } + + @Override + public String getColumnHeader() { + return columnHeader; + } + + @Override + public String getCellData(ArtifactData artData) { + throw new UnsupportedOperationException("Cannot get cell data of unspecified column"); + } + + @Override + public Set removeTypeFromSet(Set types) { + // This column doesn't have a type, so nothing to remove + return types; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml new file mode 100755 index 0000000000..aeb3992df1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml @@ -0,0 +1,299 @@ + + + + + + normal + #n_YellowPushpin + + + highlight + #h_YellowPushpin + + + + + + + normal + #n_bluePushpin + + + highlight + #h_bluePushpin + + + + + + + normal + #n_redPushpin + + + highlight + #h_redPushpin + + + + + + + normal + #n_greenPushpin + + + highlight + #h_greenPushpin + + + + + + + normal + #n_purplePushpin + + + highlight + #h_purplePushpin + + + + + + + normal + #n_whitePushpin + + + highlight + #h_whitePushpin + + + + + + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties index d23cfbb773..5b74bea711 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties @@ -9,7 +9,7 @@ Timeline.zoomOutButton.text=Zoom Out Timeline.goToButton.text=Go To\: Timeline.yearBarChart.x.years=Years Timeline.resultPanel.loading=Loading... -Timeline.node.root=Root + TimelineFrame.title=Timeline TimelinePanel.jButton1.text=6m TimelinePanel.jButton13.text=all @@ -24,4 +24,4 @@ TimelinePanel.jButton7.text=3d TimelinePanel.jButton2.text=1m TimelinePanel.jButton3.text=3m TimelinePanel.jButton4.text=2w -ProgressWindow.progressHeader.text=\ \ No newline at end of file +ProgressWindow.progressHeader.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties index ba1ee4f94a..17f945778a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties @@ -1,48 +1,48 @@ -CTL_MakeTimeline=\u300C\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u300D -CTL_TimeLineTopComponent=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6 -CTL_TimeLineTopComponentAction=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C8\u30C3\u30D7\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 -HINT_TimeLineTopComponent=\u3053\u308C\u306F\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u3059 -OpenTimelineAction.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3 -Timeline.frameName.text={0} - Autopsy\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3 -Timeline.goToButton.text=\u4E0B\u8A18\u3078\u79FB\u52D5\uFF1A -Timeline.node.root=\u30EB\u30FC\u30C8 -Timeline.pushDescrLOD.confdlg.msg={0}\u30A4\u30D9\u30F3\u30C8\u306E\u8A73\u7D30\u304C\u8868\u793A\u53EF\u80FD\u3067\u3059\u3002\u3053\u306E\u51E6\u7406\u306F\u9577\u6642\u9593\u304B\u304B\u308B\u3082\u3057\u304F\u306FAutopsy\u3092\u30AF\u30E9\u30C3\u30B7\u30E5\u3059\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\n\n\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F -Timeline.resultPanel.loading=\u30ED\u30FC\u30C9\u4E2D\u30FB\u30FB\u30FB -Timeline.resultsPanel.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u7D50\u679C -Timeline.runJavaFxThread.progress.creating=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u4F5C\u6210\u4E2D\u30FB\u30FB\u30FB -Timeline.zoomOutButton.text=\u30BA\u30FC\u30E0\u30A2\u30A6\u30C8 -TimelineFrame.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3 -TimeLineTopComponent.eventsTab.name=\u30A4\u30D9\u30F3\u30C8 -TimeLineTopComponent.filterTab.name=\u30D5\u30A3\u30EB\u30BF\u30FC -OpenTimeLineAction.msgdlg.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093\u3002 -PrompDialogManager.buttonType.continueNoUpdate=\u66F4\u65B0\u305B\u305A\u6B21\u3078 -PrompDialogManager.buttonType.showTimeline=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A -PrompDialogManager.buttonType.update=\u66F4\u65B0 -PromptDialogManager.confirmDuringIngest.contentText=\u6B21\u3078\u9032\u307F\u307E\u3059\u304B\uFF1F -PromptDialogManager.confirmDuringIngest.headerText=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B8C\u4E86\u3059\u308B\u524D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307E\u3059\u3002\n\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u5B8C\u6210\u3057\u3066\u3044\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 -PromptDialogManager.progressDialog.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u3092\u5165\u529B\u4E2D -PromptDialogManager.rebuildPrompt.details=\u8A73\u7D30\uFF1A -PromptDialogManager.rebuildPrompt.headerText=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u4E0D\u5B8C\u5168\u307E\u305F\u306F\u6700\u65B0\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\n \u6B20\u843D\u3057\u3066\u3044\u308B\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u306A\u30A4\u30D9\u30F3\u30C8\u304C\u4E00\u90E8\u3042\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 -Timeline.confirmation.dialogs.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F -Timeline.pushDescrLOD.confdlg.title=\u8AAC\u660E\u306E\u8A18\u8FF0\u30EC\u30D9\u30EB\u3092\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F -TimeLineController.errorTitle=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A8\u30E9\u30FC -TimeLineController.outOfDate.errorMessage=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u66F4\u65B0\u304C\u5FC5\u8981\u3060\u3068\u60F3\u5B9A\u3057\u307E\u3059\u3002 -TimeLineController.rebuildReasons.incompleteOldSchema=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u4E0D\u5B8C\u5168\u306A\u60C5\u5831\u304C\u4EE5\u524D\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u306A\u3044\u3068\u3001\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u3001\u307E\u305F\u306F\u6A5F\u80FD\u3057\u306A\u3044\u304B\u3082\u3057\u308C\u306A\u3044\u3067\u3059\u3002 -TimeLineController.rebuildReasons.ingestWasRunning=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B9F\u884C\u4E2D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u60C5\u5831\u304C\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u304C\u6B20\u3051\u3066\u3044\u308B\u3001\u4E0D\u5B8C\u5168\u3001\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 -TimeLineController.rebuildReasons.outOfDate=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF1A\u898B\u308C\u306A\u3044\u30A4\u30D9\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\u3002 -TimeLineController.rebuildReasons.outOfDateError=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 -TimeLinecontroller.updateNowQuestion=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u4ECA\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F +CTL_MakeTimeline=\u300c\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u300d +CTL_TimeLineTopComponent=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6 +CTL_TimeLineTopComponentAction=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c8\u30c3\u30d7\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 +HINT_TimeLineTopComponent=\u3053\u308c\u306f\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u3059 +OpenTimelineAction.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3 +Timeline.frameName.text={0} - Autopsy\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3 +Timeline.goToButton.text=\u4e0b\u8a18\u3078\u79fb\u52d5\uff1a +Timeline.pushDescrLOD.confdlg.msg={0}\u30a4\u30d9\u30f3\u30c8\u306e\u8a73\u7d30\u304c\u8868\u793a\u53ef\u80fd\u3067\u3059\u3002\u3053\u306e\u51e6\u7406\u306f\u9577\u6642\u9593\u304b\u304b\u308b\u3082\u3057\u304f\u306fAutopsy\u3092\u30af\u30e9\u30c3\u30b7\u30e5\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u5b9f\u884c\u3057\u307e\u3059\u304b\uff1f +Timeline.resultPanel.loading=\u30ed\u30fc\u30c9\u4e2d\u30fb\u30fb\u30fb +Timeline.resultsPanel.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u7d50\u679c +Timeline.runJavaFxThread.progress.creating=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u4f5c\u6210\u4e2d\u30fb\u30fb\u30fb +Timeline.zoomOutButton.text=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8 +TimelineFrame.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3 +TimeLineTopComponent.eventsTab.name=\u30a4\u30d9\u30f3\u30c8 +TimeLineTopComponent.filterTab.name=\u30d5\u30a3\u30eb\u30bf\u30fc +OpenTimeLineAction.msgdlg.text=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +PrompDialogManager.buttonType.continueNoUpdate=\u66f4\u65b0\u305b\u305a\u6b21\u3078 +PrompDialogManager.buttonType.showTimeline=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a +PrompDialogManager.buttonType.update=\u66f4\u65b0 +PromptDialogManager.confirmDuringIngest.contentText=\u6b21\u3078\u9032\u307f\u307e\u3059\u304b\uff1f +PromptDialogManager.confirmDuringIngest.headerText=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u524d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307e\u3059\u3002\n\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u5b8c\u6210\u3057\u3066\u3044\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 +PromptDialogManager.progressDialog.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u3092\u5165\u529b\u4e2d +PromptDialogManager.rebuildPrompt.details=\u8a73\u7d30\uff1a +PromptDialogManager.rebuildPrompt.headerText=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u4e0d\u5b8c\u5168\u307e\u305f\u306f\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\n \u6b20\u843d\u3057\u3066\u3044\u308b\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u306a\u30a4\u30d9\u30f3\u30c8\u304c\u4e00\u90e8\u3042\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 +Timeline.confirmation.dialogs.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f +Timeline.pushDescrLOD.confdlg.title=\u8aac\u660e\u306e\u8a18\u8ff0\u30ec\u30d9\u30eb\u3092\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f +TimeLineController.errorTitle=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a8\u30e9\u30fc +TimeLineController.outOfDate.errorMessage=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u66f4\u65b0\u304c\u5fc5\u8981\u3060\u3068\u60f3\u5b9a\u3057\u307e\u3059\u3002 +TimeLineController.rebuildReasons.incompleteOldSchema=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4e0d\u5b8c\u5168\u306a\u60c5\u5831\u304c\u4ee5\u524d\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u306a\u3044\u3068\u3001\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u3001\u307e\u305f\u306f\u6a5f\u80fd\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u306a\u3044\u3067\u3059\u3002 +TimeLineController.rebuildReasons.ingestWasRunning=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b9f\u884c\u4e2d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u60c5\u5831\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u304c\u6b20\u3051\u3066\u3044\u308b\u3001\u4e0d\u5b8c\u5168\u3001\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 +TimeLineController.rebuildReasons.outOfDate=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff1a\u898b\u308c\u306a\u3044\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u3059\u3002 +TimeLineController.rebuildReasons.outOfDateError=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +TimeLinecontroller.updateNowQuestion=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4eca\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f TimelinePanel.jButton13.text=\u5168\u3066 -Timeline.yearBarChart.x.years=\u5E74 -TimelinePanel.jButton1.text=6\u30F6\u6708 +Timeline.yearBarChart.x.years=\u5e74 +TimelinePanel.jButton1.text=6\u30f6\u6708 TimelinePanel.jButton10.text=1\u6642\u9593 TimelinePanel.jButton9.text=12\u6642\u9593 -TimelinePanel.jButton11.text=5\u5E74 -TimelinePanel.jButton12.text=10\u5E74 +TimelinePanel.jButton11.text=5\u5e74 +TimelinePanel.jButton12.text=10\u5e74 TimelinePanel.jButton6.text=1\u9031\u9593 -TimelinePanel.jButton5.text=1\u5E74 -TimelinePanel.jButton8.text=1\u65E5 -TimelinePanel.jButton7.text=3\u65E5 -TimelinePanel.jButton2.text=1\u30F6\u6708 -TimelinePanel.jButton3.text=3\u30F6\u6708 -TimelinePanel.jButton4.text=2\u9031\u9593 \ No newline at end of file +TimelinePanel.jButton5.text=1\u5e74 +TimelinePanel.jButton8.text=1\u65e5 +TimelinePanel.jButton7.text=3\u65e5 +TimelinePanel.jButton2.text=1\u30f6\u6708 +TimelinePanel.jButton3.text=3\u30f6\u6708 +TimelinePanel.jButton4.text=2\u9031\u9593 +TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java index b766394926..47d7f6940d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/OpenTimelineAction.java @@ -18,8 +18,13 @@ */ package org.sleuthkit.autopsy.timeline; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.io.IOException; import java.util.logging.Level; +import javax.swing.ImageIcon; +import javax.swing.JButton; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; @@ -27,6 +32,7 @@ import org.openide.awt.ActionRegistration; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; +import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.coreutils.Logger; @@ -36,8 +42,9 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @ActionReferences(value = { - @ActionReference(path = "Menu/Tools", position = 100)}) -public class OpenTimelineAction extends CallableSystemAction { + @ActionReference(path = "Menu/Tools", position = 100), + @ActionReference(path = "Toolbars/Case", position = 102)}) +public class OpenTimelineAction extends CallableSystemAction implements Presenter.Toolbar { private static final Logger LOGGER = Logger.getLogger(OpenTimelineAction.class.getName()); @@ -45,10 +52,22 @@ public class OpenTimelineAction extends CallableSystemAction { private static TimeLineController timeLineController = null; + private JButton toolbarButton = new JButton(); + synchronized static void invalidateController() { timeLineController = null; } + public OpenTimelineAction() { + toolbarButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performAction(); + } + }); + this.setEnabled(false); + } + @Override public boolean isEnabled() { /** @@ -102,4 +121,29 @@ public class OpenTimelineAction extends CallableSystemAction { public boolean asynchronous() { return false; // run on edt } + + /** + * Set this action to be enabled/disabled + * + * @param value whether to enable this action or not + */ + @Override + public void setEnabled(boolean value) { + super.setEnabled(value); + toolbarButton.setEnabled(value); + } + + /** + * Returns the toolbar component of this action + * + * @return component the toolbar button + */ + @Override + public Component getToolbarPresenter() { + ImageIcon icon = new ImageIcon("Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_26.png"); //NON-NLS + toolbarButton.setIcon(icon); + toolbarButton.setText(this.getName()); + + return toolbarButton; + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index bbc213de10..ed3f3b307a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.TimeZone; import java.util.concurrent.ExecutionException; @@ -188,7 +189,7 @@ public class TimeLineController { } @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - private TimeLineTopComponent mainFrame; + private TimeLineTopComponent topComponent; //are the listeners currently attached @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -199,11 +200,7 @@ public class TimeLineController { private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener(); @GuardedBy("this") - private final ReadOnlyObjectWrapper visualizationMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS); - - synchronized public ReadOnlyObjectProperty visualizationModeProperty() { - return visualizationMode.getReadOnlyProperty(); - } + private final ReadOnlyObjectWrapper viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS); @GuardedBy("filteredEvents") private final FilteredEventsModel filteredEvents; @@ -223,21 +220,38 @@ public class TimeLineController { @GuardedBy("this") private final ObservableList selectedEventIDs = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + @GuardedBy("this") + private final ReadOnlyObjectWrapper selectedTimeRange = new ReadOnlyObjectWrapper<>(); + + private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true); + + private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); + /** - * @return A list of the selected event ids + * Get an ObservableList of selected event IDs + * + * @return A list of the selected event IDs */ synchronized public ObservableList getSelectedEventIDs() { return selectedEventIDs; } - @GuardedBy("this") - private final ReadOnlyObjectWrapper selectedTimeRange = new ReadOnlyObjectWrapper<>(); + /** + * Get a read only observable view of the selected time range. + * + * @return A read only view of the selected time range. + */ + synchronized public ReadOnlyObjectProperty selectedTimeRangeProperty() { + return selectedTimeRange.getReadOnlyProperty(); + } /** - * @return a read only view of the selected interval. + * Get the selected time range. + * + * @return The selected time range. */ - synchronized public ReadOnlyObjectProperty getSelectedTimeRange() { - return selectedTimeRange.getReadOnlyProperty(); + synchronized public Interval getSelectedTimeRange() { + return selectedTimeRange.get(); } public ReadOnlyBooleanProperty eventsDBStaleProperty() { @@ -282,9 +296,30 @@ public class TimeLineController { synchronized public ReadOnlyBooleanProperty canRetreatProperty() { return historyManager.getCanRetreat(); } - private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true); - private final PromptDialogManager promptDialogManager = new PromptDialogManager(this); + synchronized public ReadOnlyObjectProperty viewModeProperty() { + return viewMode.getReadOnlyProperty(); + } + + /** + * Set a new ViewMode as the active one. + * + * @param viewMode The new ViewMode to set. + */ + synchronized public void setViewMode(ViewMode viewMode) { + if (this.viewMode.get() != viewMode) { + this.viewMode.set(viewMode); + } + } + + /** + * Get the currently active ViewMode. + * + * @return The currently active ViewMode. + */ + synchronized public ViewMode getViewMode() { + return viewMode.get(); + } public TimeLineController(Case autoCase) throws IOException { this.autoCase = autoCase; @@ -310,6 +345,9 @@ public class TimeLineController { filteredEvents.filterProperty().get(), DescriptionLoD.SHORT); historyManager.advance(InitialZoomState); + + //clear the selected events when the view mode changes + viewMode.addListener(observable -> selectEventIDs(Collections.emptySet())); } /** @@ -449,9 +487,9 @@ public class TimeLineController { IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener); IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener); Case.removePropertyChangeListener(caseListener); - if (mainFrame != null) { - mainFrame.close(); - mainFrame = null; + if (topComponent != null) { + topComponent.close(); + topComponent = null; } OpenTimelineAction.invalidateController(); } @@ -582,17 +620,6 @@ public class TimeLineController { pushTimeRange(new Interval(start, end)); } - /** - * Set a new Visualization mode as the active one. - * - * @param visualizationMode The new VisaualizationMode to set. - */ - synchronized public void setVisualizationMode(VisualizationMode visualizationMode) { - if (this.visualizationMode.get() != visualizationMode) { - this.visualizationMode.set(visualizationMode); - } - } - public void selectEventIDs(Collection events) { final LoggedTask selectEventIDsTask = new LoggedTask("Select Event IDs", true) { //NON-NLS @Override @@ -624,16 +651,16 @@ public class TimeLineController { */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) synchronized private void showWindow() { - if (mainFrame == null) { - mainFrame = new TimeLineTopComponent(this); + if (topComponent == null) { + topComponent = new TimeLineTopComponent(this); } - mainFrame.open(); - mainFrame.toFront(); + topComponent.open(); + topComponent.toFront(); /* * Make this top component active so its ExplorerManager's lookup gets * proxied in Utilities.actionsGlobalContext() */ - mainFrame.requestActive(); + topComponent.requestActive(); } synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form index 1f0f367115..a05e7505ec 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form @@ -54,7 +54,7 @@ - + @@ -65,7 +65,7 @@
- + @@ -82,33 +82,45 @@ - - - - - - + - + + + + + + + + + + + + - - - - - - + - + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index f91d2e8bba..1ca594fa14 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,14 @@ */ package org.sleuthkit.autopsy.timeline; -import java.awt.BorderLayout; +import java.beans.PropertyVetoException; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.embed.swing.JFXPanel; +import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; @@ -34,8 +36,15 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javax.swing.SwingUtilities; +import org.controlsfx.control.Notifications; +import org.joda.time.Interval; +import org.joda.time.format.DateTimeFormatter; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.windows.Mode; import org.openide.windows.TopComponent; @@ -44,19 +53,22 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.corecomponents.DataContentPanel; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; +import org.sleuthkit.autopsy.timeline.explorernodes.EventNode; +import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode; import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar; import org.sleuthkit.autopsy.timeline.ui.StatusBar; -import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView; import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel; -import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel; +import org.sleuthkit.autopsy.timeline.ui.ViewFrame; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree; import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; +import org.sleuthkit.datamodel.TskCoreException; /** - * TopComponent for the timeline feature. + * TopComponent for the Timeline feature. */ @TopComponent.Description( preferredID = "TimeLineTopComponent", @@ -67,91 +79,209 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName()); - private final DataContentPanel dataContentPanel; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private final DataContentPanel contentViewerPanel; - private final TimeLineResultView tlrv; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private DataResultPanel dataResultPanel; + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final ExplorerManager em = new ExplorerManager(); private final TimeLineController controller; + /** + * Listener that drives the result viewer or content viewer (depending on + * view mode) according to the controller's selected event IDs + */ + @NbBundle.Messages({"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."}) + private final InvalidationListener selectedEventsListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + ObservableList selectedEventIDs = controller.getSelectedEventIDs(); + + //depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly. + switch (controller.getViewMode()) { + case LIST: + + //make an array of EventNodes for the selected events + EventNode[] childArray = new EventNode[selectedEventIDs.size()]; + try { + for (int i = 0; i < selectedEventIDs.size(); i++) { + childArray[i] = EventNode.createEventNode(selectedEventIDs.get(i), controller.getEventsModel()); + } + Children children = new Children.Array(); + children.add(childArray); + + SwingUtilities.invokeLater(() -> { + //set generic container node as root context + em.setRootContext(new AbstractNode(children)); + try { + //set selected nodes for actions + em.setSelectedNodes(childArray); + } catch (PropertyVetoException ex) { + //I don't know why this would ever happen. + LOGGER.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS + } + //if there is only one event selected push it into content viewer. + if (selectedEventIDs.size() == 1) { + contentViewerPanel.setNode(childArray[0]); + } else { + contentViewerPanel.setNode(null); + } + }); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(jFXViewPanel.getScene().getWindow()) + .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg()) + .showError(); + }); + } + + break; + case COUNTS: + case DETAIL: + //make a root node with nodes for the selected events as children and push it to the result viewer. + EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel()); + SwingUtilities.invokeLater(() -> { + dataResultPanel.setPath(getResultViewerSummaryString()); + dataResultPanel.setNode(rootNode); + }); + break; + default: + throw new UnsupportedOperationException("Unknown view mode: " + controller.getViewMode()); + } + } + }; + + /** + * Constructor + * + * @param controller The TimeLineController for this topcomponent. + */ public TimeLineTopComponent(TimeLineController controller) { initComponents(); - this.controller = controller; associateLookup(ExplorerUtils.createLookup(em, getActionMap())); - setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent")); setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent")); setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application - dataContentPanel = DataContentPanel.createInstance(); - this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER); - tlrv = new TimeLineResultView(controller, dataContentPanel); - DataResultPanel dataResultPanel = tlrv.getDataResultPanel(); - this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER); - dataResultPanel.open(); - customizeFXComponents(); - } + this.controller = controller; - @NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events", - "TimeLineTopComponent.filterTab.name=Filters"}) - void customizeFXComponents() { - Platform.runLater(() -> { + //create linked result and content views + contentViewerPanel = DataContentPanel.createInstance(); + dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel); - //create and wire up jfx componenets that make up the interface - final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller)); - filterTab.setClosable(false); - filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS + //add them to bottom splitpane + horizontalSplitPane.setLeftComponent(dataResultPanel); + horizontalSplitPane.setRightComponent(contentViewerPanel); - final EventsTree eventsTree = new EventsTree(controller); - final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree); - final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree); - eventsTreeTab.setClosable(false); - eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS - eventsTreeTab.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS)); + dataResultPanel.open(); //get the explorermanager - final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab); - VBox.setVgrow(leftTabPane, Priority.ALWAYS); - controller.visualizationModeProperty().addListener((Observable observable) -> { - if (controller.visualizationModeProperty().get().equals(VisualizationMode.COUNTS)) { - //if view mode is counts, make sure events tabd is not active - leftTabPane.getSelectionModel().select(filterTab); - } - }); + Platform.runLater(this::initFXComponents); - HistoryToolBar historyToolBar = new HistoryToolBar(controller); - final TimeZonePanel timeZonePanel = new TimeZonePanel(); - VBox.setVgrow(timeZonePanel, Priority.SOMETIMES); + //set up listeners + TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString())); + controller.getSelectedEventIDs().addListener(selectedEventsListener); - final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller); - - final VBox leftVBox = new VBox(5, timeZonePanel,historyToolBar, zoomSettingsPane, leftTabPane); - SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE); - - final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel); - mainSplitPane.setDividerPositions(0); - - final Scene scene = new Scene(mainSplitPane); - scene.addEventFilter(KeyEvent.KEY_PRESSED, - (KeyEvent event) -> { - if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) { - new Back(controller).handle(null); - } else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) { - new Back(controller).handle(null); - } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) { - new Forward(controller).handle(null); - } else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) { - new Forward(controller).handle(null); + //Listen to ViewMode and adjust GUI componenets as needed. + controller.viewModeProperty().addListener(viewMode -> { + switch (controller.getViewMode()) { + case COUNTS: + case DETAIL: + /* + * For counts and details mode, restore the result table at + * the bottom left. + */ + SwingUtilities.invokeLater(() -> { + splitYPane.remove(contentViewerPanel); + if ((horizontalSplitPane.getParent() == splitYPane) == false) { + splitYPane.setBottomComponent(horizontalSplitPane); + horizontalSplitPane.setRightComponent(contentViewerPanel); } }); - - //add ui componenets to JFXPanels - jFXVizPanel.setScene(scene); - jFXstatusPanel.setScene(new Scene(new StatusBar(controller))); - + break; + case LIST: + /* + * For list mode, remove the result table, and let the + * content viewer expand across the bottom. + */ + SwingUtilities.invokeLater(() -> { + splitYPane.setBottomComponent(contentViewerPanel); + }); + break; + default: + throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode()); + } }); } + /** + * Create and wire up JavaFX components of the interface + */ + @NbBundle.Messages({ + "TimeLineTopComponent.eventsTab.name=Events", + "TimeLineTopComponent.filterTab.name=Filters"}) + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + void initFXComponents() { + /////init componenets of left most column from top to bottom + final TimeZonePanel timeZonePanel = new TimeZonePanel(); + VBox.setVgrow(timeZonePanel, Priority.SOMETIMES); + HistoryToolBar historyToolBar = new HistoryToolBar(controller); + final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller); + + //set up filter tab + final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller)); + filterTab.setClosable(false); + filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS + + //set up events tab + final EventsTree eventsTree = new EventsTree(controller); + final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree); + eventsTreeTab.setClosable(false); + eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS + eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL)); + + final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab); + VBox.setVgrow(leftTabPane, Priority.ALWAYS); + controller.viewModeProperty().addListener(viewMode -> { + if (controller.getViewMode().equals(ViewMode.DETAIL) == false) { + //if view mode is not details, switch back to the filter tab + leftTabPane.getSelectionModel().select(filterTab); + } + }); + + //assemble left column + final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane); + SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE); + + final ViewFrame viewFrame = new ViewFrame(controller, eventsTree); + final SplitPane mainSplitPane = new SplitPane(leftVBox, viewFrame); + mainSplitPane.setDividerPositions(0); + + final Scene scene = new Scene(mainSplitPane); + scene.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> { + if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) { + new Back(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(keyEvent)) { + new Back(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) { + new Forward(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(keyEvent)) { + new Forward(controller).handle(null); + } + }); + + //add ui componenets to JFXPanels + jFXViewPanel.setScene(scene); + jFXstatusPanel.setScene(new Scene(new StatusBar(controller))); + } + @Override public List availableModes(List modes) { return Collections.emptyList(); @@ -165,12 +295,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer // //GEN-BEGIN:initComponents private void initComponents() { - jFXstatusPanel = new JFXPanel(); + jFXstatusPanel = new javafx.embed.swing.JFXPanel(); splitYPane = new javax.swing.JSplitPane(); - jFXVizPanel = new JFXPanel(); - lowerSplitXPane = new javax.swing.JSplitPane(); - resultContainerPanel = new javax.swing.JPanel(); - contentViewerContainerPanel = new javax.swing.JPanel(); + jFXViewPanel = new javafx.embed.swing.JFXPanel(); + horizontalSplitPane = new javax.swing.JSplitPane(); + leftFillerPanel = new javax.swing.JPanel(); + rightfillerPanel = new javax.swing.JPanel(); jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16)); @@ -178,32 +308,47 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer splitYPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); splitYPane.setResizeWeight(0.9); splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400)); - splitYPane.setLeftComponent(jFXVizPanel); + splitYPane.setLeftComponent(jFXViewPanel); - lowerSplitXPane.setDividerLocation(600); - lowerSplitXPane.setResizeWeight(0.5); - lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300)); - lowerSplitXPane.setRequestFocusEnabled(false); + horizontalSplitPane.setDividerLocation(600); + horizontalSplitPane.setResizeWeight(0.5); + horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300)); + horizontalSplitPane.setRequestFocusEnabled(false); - resultContainerPanel.setPreferredSize(new java.awt.Dimension(700, 300)); - resultContainerPanel.setLayout(new java.awt.BorderLayout()); - lowerSplitXPane.setLeftComponent(resultContainerPanel); + javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel); + leftFillerPanel.setLayout(leftFillerPanelLayout); + leftFillerPanelLayout.setHorizontalGroup( + leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 599, Short.MAX_VALUE) + ); + leftFillerPanelLayout.setVerticalGroup( + leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 54, Short.MAX_VALUE) + ); - contentViewerContainerPanel.setPreferredSize(new java.awt.Dimension(500, 300)); - contentViewerContainerPanel.setLayout(new java.awt.BorderLayout()); - lowerSplitXPane.setRightComponent(contentViewerContainerPanel); + horizontalSplitPane.setLeftComponent(leftFillerPanel); - splitYPane.setRightComponent(lowerSplitXPane); + javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel); + rightfillerPanel.setLayout(rightfillerPanelLayout); + rightfillerPanelLayout.setHorizontalGroup( + rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 364, Short.MAX_VALUE) + ); + rightfillerPanelLayout.setVerticalGroup( + rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 54, Short.MAX_VALUE) + ); + + horizontalSplitPane.setRightComponent(rightfillerPanel); + + splitYPane.setRightComponent(horizontalSplitPane); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(0, 0, 0)) + .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -215,11 +360,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JPanel contentViewerContainerPanel; - private javafx.embed.swing.JFXPanel jFXVizPanel; + private javax.swing.JSplitPane horizontalSplitPane; + private javafx.embed.swing.JFXPanel jFXViewPanel; private javafx.embed.swing.JFXPanel jFXstatusPanel; - private javax.swing.JSplitPane lowerSplitXPane; - private javax.swing.JPanel resultContainerPanel; + private javax.swing.JPanel leftFillerPanel; + private javax.swing.JPanel rightfillerPanel; private javax.swing.JSplitPane splitYPane; // End of variables declaration//GEN-END:variables @@ -229,25 +374,34 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer putClientProperty(PROP_UNDOCKING_DISABLED, true); } - @Override - public void componentClosed() { - // TODO add custom code on component closing - } - - void writeProperties(java.util.Properties p) { - // better to version settings since initial version as advocated at - // http://wiki.apidesign.org/wiki/PropertyFiles - p.setProperty("version", "1.0"); - // TODO store your settings - } - - void readProperties(java.util.Properties p) { - String version = p.getProperty("version"); - // TODO read your settings according to their version - } - @Override public ExplorerManager getExplorerManager() { return em; } + + /** + * Get the string that should be used as the label above the result table. + * It displays the time range spanned by the selected events. + * + * @return A String representation of all the events displayed. + */ + @NbBundle.Messages({ + "# {0} - start of date range", + "# {1} - end of date range", + "TimeLineResultView.startDateToEndDate.text={0} to {1}"}) + private String getResultViewerSummaryString() { + Interval selectedTimeRange = controller.getSelectedTimeRange(); + if (selectedTimeRange == null) { + return ""; + } else { + final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter(); + String start = selectedTimeRange.getStart() + .withZone(TimeLineController.getJodaTimeZone()) + .toString(zonedFormatter); + String end = selectedTimeRange.getEnd() + .withZone(TimeLineController.getJodaTimeZone()) + .toString(zonedFormatter); + return Bundle.TimeLineResultView_startDateToEndDate_text(start, end); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java b/Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java similarity index 83% rename from Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java rename to Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java index 8990b792bc..5078c7f260 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,10 @@ package org.sleuthkit.autopsy.timeline; /** - * + * Enumeration of view modes. */ -public enum VisualizationMode { - - COUNTS, DETAIL; +public enum ViewMode { + COUNTS, + DETAIL, + LIST; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index e50e1909a8..7649b35705 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -71,7 +71,7 @@ public class SaveSnapshotAsReport extends Action { "Timeline.ModuleName=Timeline", "SaveSnapShotAsReport.action.dialogs.title=Timeline", "SaveSnapShotAsReport.action.name.text=Snapshot Report", - "SaveSnapShotAsReport.action.longText=Save a screen capture of the visualization as a report.", + "SaveSnapShotAsReport.action.longText=Save a screen capture of the current view of the timeline as a report.", "# {0} - report file path", "SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]", "SaveSnapShotAsReport.Success=Success", diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java new file mode 100644 index 0000000000..71e022e65b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java @@ -0,0 +1,152 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline.datamodel; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; + +/** + * A container for several events that have the same timestamp and description + * and are backed by the same file. Used in the ListView to coalesce the file + * system events for a file when they have the same timestamp. + */ +public class CombinedEvent { + + private final long fileID; + private final long epochMillis; + private final String description; + + /** + * A map from EventType to event ID. + */ + private final Map eventTypeMap = new HashMap<>(); + + /** + * Constructor + * + * @param epochMillis The timestamp for this event, in millis from the Unix + * epoch. + * @param description The full description shared by all the combined events + * @param fileID The ID of the file shared by all the combined events. + * @param eventMap A map from EventType to event ID. + */ + public CombinedEvent(long epochMillis, String description, long fileID, Map eventMap) { + this.epochMillis = epochMillis; + this.description = description; + eventTypeMap.putAll(eventMap); + this.fileID = fileID; + } + + /** + * Get the timestamp of this event as millis from the Unix epoch. + * + * @return The timestamp of this event as millis from the Unix epoch. + */ + public long getStartMillis() { + return epochMillis; + } + + /** + * Get the full description shared by all the combined events. + * + * @return The full description shared by all the combined events. + */ + public String getDescription() { + return description; + } + + /** + * Get the obj ID of the file shared by the combined events. + * + * @return The obj ID of the file shared by the combined events. + */ + public long getFileID() { + return fileID; + } + + /** + * Get the types of the combined events. + * + * @return The types of the combined events. + */ + public Set getEventTypes() { + return eventTypeMap.keySet(); + } + + /** + * Get the event IDs of the combined events. + * + * @return The event IDs of the combined events. + */ + public Collection getEventIDs() { + return eventTypeMap.values(); + } + + /** + * Get the event ID of one event that is representative of all the combined + * events. It can be used to look up a SingleEvent with more details, for + * example. + * + * @return An arbitrary representative event ID for the combined events. + */ + public Long getRepresentativeEventID() { + return eventTypeMap.values().stream().findFirst().get(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + (int) (this.fileID ^ (this.fileID >>> 32)); + hash = 53 * hash + (int) (this.epochMillis ^ (this.epochMillis >>> 32)); + hash = 53 * hash + Objects.hashCode(this.description); + hash = 53 * hash + Objects.hashCode(this.eventTypeMap); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CombinedEvent other = (CombinedEvent) obj; + if (this.fileID != other.fileID) { + return false; + } + if (this.epochMillis != other.epochMillis) { + return false; + } + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.eventTypeMap, other.eventTypeMap)) { + return false; + } + return true; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index ace1dd68ff..9f95d31337 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.datamodel; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; +import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.SortedSet; import javax.annotation.concurrent.Immutable; import org.joda.time.Interval; @@ -108,8 +108,8 @@ public class EventCluster implements MultiEvent { */ private final ImmutableSet hashHits; - private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, - Set hashHits, Set tagged, String description, DescriptionLoD lod, + private EventCluster(Interval spanningInterval, EventType type, Collection eventIDs, + Collection hashHits, Collection tagged, String description, DescriptionLoD lod, EventStripe parent) { this.span = spanningInterval; @@ -122,8 +122,8 @@ public class EventCluster implements MultiEvent { this.parent = parent; } - public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, - Set hashHits, Set tagged, String description, DescriptionLoD lod) { + public EventCluster(Interval spanningInterval, EventType type, Collection eventIDs, + Collection hashHits, Collection tagged, String description, DescriptionLoD lod) { this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 1431673f22..6227256130 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -70,15 +70,15 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * This class acts as the model for a {@link TimeLineView} + * This class acts as the model for a TimelineView * * Views can register listeners on properties returned by methods. * * This class is implemented as a filtered view into an underlying - * {@link EventsRepository}. + * EventsRepository. * * TODO: as many methods as possible should cache their results so as to avoid - * unnecessary db calls through the {@link EventsRepository} -jm + * unnecessary db calls through the EventsRepository -jm * * Concurrency Policy: repo is internally synchronized, so methods that only * access the repo atomically do not need further synchronization @@ -279,7 +279,7 @@ public final class FilteredEventsModel { return repo.getTagCountsByTagName(eventIDsWithTags); } - public Set getEventIDs(Interval timeRange, Filter filter) { + public List getEventIDs(Interval timeRange, Filter filter) { final Interval overlap; final RootFilter intersect; synchronized (this) { @@ -290,6 +290,21 @@ public final class FilteredEventsModel { return repo.getEventIDs(overlap, intersect); } + /** + * Get a representation of all the events, within the given time range, that + * pass the given filter, grouped by time and description such that file + * system events for the same file, with the same timestamp, are combined + * together. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of combined events, sorted by timestamp. + */ + public List getCombinedEvents() { + return repo.getCombinedEvents(requestedTimeRange.get(), requestedFilter.get()); + } + /** * return the number of events that pass the requested filter and are within * the given time range. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 996c35ef3b..8bbfc756af 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; @@ -343,18 +344,29 @@ public class EventDB { return result; } - Set getEventIDs(Interval timeRange, RootFilter filter) { - return getEventIDs(timeRange.getStartMillis() / 1000, timeRange.getEndMillis() / 1000, filter); - } + /** + * Get the IDs of all the events within the given time range that pass the + * given filter. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of event ids, sorted by timestamp of the corresponding + * event.. + */ + List getEventIDs(Interval timeRange, RootFilter filter) { + Long startTime = timeRange.getStartMillis() / 1000; + Long endTime = timeRange.getEndMillis() / 1000; - Set getEventIDs(Long startTime, Long endTime, RootFilter filter) { if (Objects.equals(startTime, endTime)) { - endTime++; + endTime++; //make sure end is at least 1 millisecond after start } - Set resultIDs = new HashSet<>(); + + ArrayList resultIDs = new ArrayList<>(); DBLock.lock(); - final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter); // NON-NLS + final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query)) { while (rs.next()) { @@ -370,6 +382,55 @@ public class EventDB { return resultIDs; } + /** + * Get a representation of all the events, within the given time range, that + * pass the given filter, grouped by time and description such that file + * system events for the same file, with the same timestamp, are combined + * together. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of combined events, sorted by timestamp. + */ + List getCombinedEvents(Interval timeRange, RootFilter filter) { + Long startTime = timeRange.getStartMillis() / 1000; + Long endTime = timeRange.getEndMillis() / 1000; + + if (Objects.equals(startTime, endTime)) { + endTime++; //make sure end is at least 1 millisecond after start + } + + ArrayList results = new ArrayList<>(); + + DBLock.lock(); + final String query = "SELECT full_description, time, file_id, GROUP_CONCAT(events.event_id), GROUP_CONCAT(sub_type)" + + " FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter) + + " GROUP BY time,full_description, file_id ORDER BY time ASC, full_description"; + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query)) { + while (rs.next()) { + + //make a map from event type to event ID + List eventIDs = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(events.event_id)"), Long::valueOf); + List eventTypes = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(sub_type)"), s -> RootEventType.allTypes.get(Integer.valueOf(s))); + Map eventMap = new HashMap<>(); + for (int i = 0; i < eventIDs.size(); i++) { + eventMap.put(eventTypes.get(i), eventIDs.get(i)); + } + results.add(new CombinedEvent(rs.getLong("time") * 1000, rs.getString("full_description"), rs.getLong("file_id"), eventMap)); + } + + } catch (SQLException sqlEx) { + LOGGER.log(Level.SEVERE, "failed to execute query for combined events", sqlEx); // NON-NLS + } finally { + DBLock.unlock(); + } + + return results; + } + /** * this relies on the fact that no tskObj has ID 0 but 0 is the default * value for the datasource_id column in the events table. @@ -583,7 +644,14 @@ public class EventDB { insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)"); //NON-NLS insertTagStmt = prepareStatement("INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)"); //NON-NLS deleteTagStmt = prepareStatement("DELETE FROM tags WHERE tag_id = ?"); //NON-NLS - countAllEventsStmt = prepareStatement("SELECT count(*) AS count FROM events"); //NON-NLS + + /* + * This SQL query is really just a select count(*), but that has + * performance problems on very large tables unless you include + * a where clause see http://stackoverflow.com/a/9338276/4004683 + * for more. + */ + countAllEventsStmt = prepareStatement("SELECT count(event_id) AS count FROM events WHERE event_id IS NOT null"); //NON-NLS dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events"); //NON-NLS dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets"); //NON-NLS @@ -1090,12 +1158,12 @@ public class EventDB { private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLoD descriptionLOD, TagsFilter filter) throws SQLException { Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS String eventIDsString = rs.getString("event_ids");// NON-NLS - Set eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); + List eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD)); EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS - Set hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS - Set tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS + List hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS + List tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); } @@ -1168,7 +1236,6 @@ public class EventDB { return useSubTypes ? "sub_type" : "base_type"; //NON-NLS } - private PreparedStatement prepareStatement(String queryString) throws SQLException { PreparedStatement prepareStatement = con.prepareStatement(queryString); preparedStatements.add(prepareStatement); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index a93bf9dc18..db6ce5949f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.CancellationProgressTask; +import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; @@ -214,10 +215,25 @@ public class EventsRepository { idToEventCache.invalidateAll(); } - public Set getEventIDs(Interval timeRange, RootFilter filter) { + public List getEventIDs(Interval timeRange, RootFilter filter) { return eventDB.getEventIDs(timeRange, filter); } + /** + * Get a representation of all the events, within the given time range, that + * pass the given filter, grouped by time and description such that file + * system events for the same file, with the same timestamp, are combined + * together. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of combined events, sorted by timestamp. + */ + public List getCombinedEvents(Interval timeRange, RootFilter filter) { + return eventDB.getCombinedEvents(timeRange, filter); + } + public Interval getSpanningInterval(Collection eventIDs) { return eventDB.getSpanningInterval(eventIDs); } @@ -581,7 +597,7 @@ public class EventsRepository { timeMap.put(FileSystemTypes.FILE_MODIFIED, f.getMtime()); /* - * if there are no legitimate ( greater tan zero ) time stamps ( eg, + * if there are no legitimate ( greater than zero ) time stamps ( eg, * logical/local files) skip the rest of the event generation: this * should result in droping logical files, since they do not have * legitimate time stamps. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 208ffa1b75..4f6dfa3eb9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.db; import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -82,11 +81,11 @@ class SQLHelper { * @return a Set of X, each element mapped from one element of the original * comma delimited string */ - static Set unGroupConcat(String groupConcat, Function mapper) { - return StringUtils.isBlank(groupConcat) ? Collections.emptySet() + static List unGroupConcat(String groupConcat, Function mapper) { + return StringUtils.isBlank(groupConcat) ? Collections.emptyList() : Stream.of(groupConcat.split(",")) .map(mapper::apply) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java index 47b04eb479..903cb55178 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java @@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.events; /** * A "local" event published by filteredEventsModel to indicate that the user - * requested that the current visualization be refreshed with out changing any + * requested that the current view be refreshed with out changing any * of the parameters ( to include more up to date tag data for example.) * * This event is not intended for use out side of the Timeline module. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties index d431985f98..7096881555 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties @@ -1 +1 @@ -EventRoodNode.tooManyNode.displayName=\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u6570\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u306F{1}\u3042\u308A\u307E\u3059\u3002 \ No newline at end of file +EventRoodNode.tooManyNode.displayName=\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u6570\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u306f{1}\u3042\u308a\u307e\u3059\u3002 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index abb7245eda..ee22cfd295 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,47 +23,60 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; -import javafx.beans.Observable; import javax.swing.Action; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.openide.nodes.Children; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** - * * Explorer Node for {@link SingleEvent}s. + * * Explorer Node for a SingleEvent. */ -class EventNode extends DisplayableItemNode { +public class EventNode extends DisplayableItemNode { + + private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); - private final SingleEvent e; + private final SingleEvent event; - EventNode(SingleEvent eventById, AbstractFile file, BlackboardArtifact artifact) { - super(Children.LEAF, Lookups.fixed(eventById, file, artifact)); - this.e = eventById; - this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS + EventNode(SingleEvent event, AbstractFile file, BlackboardArtifact artifact) { + super(Children.LEAF, Lookups.fixed(event, file, artifact)); + this.event = event; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS } - EventNode(SingleEvent eventById, AbstractFile file) { - super(Children.LEAF, Lookups.fixed(eventById, file)); - this.e = eventById; - this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS + EventNode(SingleEvent event, AbstractFile file) { + super(Children.LEAF, Lookups.fixed(event, file)); + this.event = event; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS } @Override + @NbBundle.Messages({ + "NodeProperty.displayName.icon=Icon", + "NodeProperty.displayName.description=Description", + "NodeProperty.displayName.baseType=Base Type", + "NodeProperty.displayName.subType=Sub Type", + "NodeProperty.displayName.known=Known", + "NodeProperty.displayName.dateTime=Date/Time"}) protected Sheet createSheet() { Sheet s = super.createSheet(); Sheet.Set properties = s.get(Sheet.PROPERTIES); @@ -72,28 +85,25 @@ class EventNode extends DisplayableItemNode { s.put(properties); } - final TimeProperty timePropery = new TimeProperty("time", "Date/Time", "time ", getDateTimeString()); // NON-NLS - - TimeLineController.getTimeZone().addListener((Observable observable) -> { - try { - timePropery.setValue(getDateTimeString()); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS - } - }); - - properties.put(new NodeProperty<>("icon", "Icon", "icon", true)); // NON-NLS //gets overridden with icon - properties.put(timePropery); - properties.put(new NodeProperty<>("description", "Description", "description", e.getFullDescription())); // NON-NLS - properties.put(new NodeProperty<>("eventBaseType", "Base Type", "base type", e.getEventType().getSuperType().getDisplayName())); // NON-NLS - properties.put(new NodeProperty<>("eventSubType", "Sub Type", "sub type", e.getEventType().getDisplayName())); // NON-NLS - properties.put(new NodeProperty<>("Known", "Known", "known", e.getKnown().toString())); // NON-NLS + properties.put(new NodeProperty<>("icon", Bundle.NodeProperty_displayName_icon(), "icon", true)); // NON-NLS //gets overridden with icon + properties.put(new TimeProperty("time", Bundle.NodeProperty_displayName_dateTime(), "time ", getDateTimeString()));// NON-NLS + properties.put(new NodeProperty<>("description", Bundle.NodeProperty_displayName_description(), "description", event.getFullDescription())); // NON-NLS + properties.put(new NodeProperty<>("eventBaseType", Bundle.NodeProperty_displayName_baseType(), "base type", event.getEventType().getSuperType().getDisplayName())); // NON-NLS + properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS + properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS return s; } + /** + * Get the time of this event as a String formated according to the + * controller's time zone setting. + * + * @return The time of this event as a String formated according to the + * controller's time zone setting. + */ private String getDateTimeString() { - return new DateTime(e.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter()); + return new DateTime(event.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter()); } @Override @@ -118,7 +128,7 @@ class EventNode extends DisplayableItemNode { @Override public T accept(DisplayableItemNodeVisitor dinv) { - throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); // NON-NLS } /* @@ -134,7 +144,7 @@ class EventNode extends DisplayableItemNode { * We use TimeProperty instead of a normal NodeProperty to correctly display * the date/time when the user changes the timezone setting. */ - private class TimeProperty extends PropertySupport.ReadWrite { + final private class TimeProperty extends PropertySupport.ReadWrite { private String value; @@ -147,6 +157,14 @@ class EventNode extends DisplayableItemNode { super(name, String.class, displayName, shortDescription); setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS this.value = value; + TimeLineController.getTimeZone().addListener(timeZone -> { + try { + setValue(getDateTimeString()); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + LOGGER.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS + } + }); + } @Override @@ -161,4 +179,32 @@ class EventNode extends DisplayableItemNode { firePropertyChange("time", oldValue, t); // NON-NLS } } + + /** + * Factory method to create an EventNode from the event ID and the events + * model. + * + * @param eventID The ID of the event this node is for. + * @param eventsModel The model that provides access to the events DB. + * + * @return An EventNode with the file (and artifact) backing this event in + * its lookup. + */ + public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, IllegalStateException { + /* + * Look up the event by id and creata an EventNode with the appropriate + * data in the lookup. + */ + final SingleEvent eventById = eventsModel.getEventById(eventID); + + SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); + + if (eventById.getArtifactID().isPresent()) { + BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); + return new EventNode(eventById, file, blackboardArtifact); + } else { + return new EventNode(eventById, file); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 6fd3693514..3d1d44132e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -27,22 +27,19 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Root Explorer node to represent events. + * Root Explorer Node to represent events. */ public class EventRootNode extends DisplayableItemNode { + private static final long serialVersionUID = 1L; + /** * Since the lazy loading seems to be broken if there are more than this * many child events, we don't show them and just show a message showing the @@ -50,18 +47,8 @@ public class EventRootNode extends DisplayableItemNode { */ public static final int MAX_EVENTS_TO_DISPLAY = 5000; - /** - * the number of child events - */ - private final int childCount; - - public EventRootNode(String NAME, Collection fileIds, FilteredEventsModel filteredEvents) { + public EventRootNode(Collection fileIds, FilteredEventsModel filteredEvents) { super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds)); - - super.setName(NAME); - super.setDisplayName(NAME); - - childCount = fileIds.size(); } @Override @@ -74,9 +61,6 @@ public class EventRootNode extends DisplayableItemNode { return null; } - public int getChildCount() { - return childCount; - } /* * TODO (AUT-1849): Correct or remove peristent column reordering code @@ -95,12 +79,12 @@ public class EventRootNode extends DisplayableItemNode { private static final Logger LOGGER = Logger.getLogger(EventNodeChildFactory.class.getName()); /** - * list of event ids that act as keys for the child nodes. + * List of event IDs that act as keys for the child nodes. */ private final Collection eventIDs; /** - * filteredEvents is used to lookup the events from their ids + * filteredEvents is used to lookup the events from their IDs */ private final FilteredEventsModel filteredEvents; @@ -112,8 +96,8 @@ public class EventRootNode extends DisplayableItemNode { @Override protected boolean createKeys(List toPopulate) { /** - * if there are too many events, just add one id (-1) to indicate - * this. + * If there are too many events, just add one dummy ID (-1) to + * indicate this. */ if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) { toPopulate.addAll(eventIDs); @@ -127,34 +111,24 @@ public class EventRootNode extends DisplayableItemNode { protected Node createNodeForKey(Long eventID) { if (eventID < 0) { /* - * if the eventId is a the special value, return a node with a - * warning that their are too many evens + * If the eventId is a the special value ( -1 ), return a node + * with a warning that their are too many evens */ return new TooManyNode(eventIDs.size()); } else { - /* - * look up the event by id and creata an EventNode with the - * appropriate data in the lookup. - */ - final SingleEvent eventById = filteredEvents.getEventById(eventID); try { - SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); - AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); - if (file != null) { - if (eventById.getArtifactID().isPresent()) { - BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); - return new EventNode(eventById, file, blackboardArtifact); - } else { - return new EventNode(eventById, file); - } - } else { - //This should never happen in normal operations - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS - return null; - } - } catch (IllegalStateException | TskCoreException ex) { - //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS + return EventNode.createEventNode(eventID, filteredEvents); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + return null; + } catch (TskCoreException ex) { + /* + * Just log it: There might be lots of these errors, and we + * don't want to flood the user with notifications. It will + * be obvious the UI is broken anyways + */ + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS return null; } } @@ -162,8 +136,7 @@ public class EventRootNode extends DisplayableItemNode { } /** - * A Node that just shows a warning message that their are too many events - * to show + * A Node with a warning message that their are too many events to show. */ private static class TooManyNode extends AbstractNode { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/20140521121247760_easyicon_net_32.png b/Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_32.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/images/20140521121247760_easyicon_net_32.png rename to Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_32.png diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_26.png b/Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_26.png new file mode 100755 index 0000000000..f44034ff01 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_26.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/20140521121247760_easyicon_net_32_colorized.png b/Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_32.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/images/20140521121247760_easyicon_net_32_colorized.png rename to Core/src/org/sleuthkit/autopsy/timeline/images/btn_icon_timeline_colorized_32.png diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_first.png b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_first.png new file mode 100644 index 0000000000..b03eaf8b54 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_first.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_last.png b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_last.png new file mode 100644 index 0000000000..8ec8947847 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_last.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_next.png b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_next.png new file mode 100644 index 0000000000..e252606d3e Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_next.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_previous.png b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_previous.png new file mode 100644 index 0000000000..18f9cc1094 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/resultset_previous.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/table.png b/Core/src/org/sleuthkit/autopsy/timeline/images/table.png new file mode 100644 index 0000000000..0d1e11a834 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/table.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java index 75377ff5bb..917d2a0881 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java @@ -70,7 +70,7 @@ public class SnapShotReportWriter { * @param zoomParams The ZoomParams in effect when the snapshot was * taken. * @param generationDate The generation Date of the report. - * @param snapshot A snapshot of the visualization to include in the + * @param snapshot A snapshot of the view to include in the * report. */ public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java new file mode 100644 index 0000000000..7f6a2efe14 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java @@ -0,0 +1,378 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline.ui; + +import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.Subscribe; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.concurrent.Task; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import org.controlsfx.control.MaskerPane; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.LoggedTask; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.ViewMode; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; + +/** + * Base class for views that can be hosted in the ViewFrame + * + */ +public abstract class AbstractTimeLineView extends BorderPane { + + private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName()); + + /** + * Boolean property that holds true if the view does not show any events + * with the current zoom and filter settings. + */ + private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true); + + /** + * Boolean property that holds true if the view may not represent the + * current state of the DB, because, for example, tags have been updated but + * the view. was not refreshed. + */ + private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false); + + /** + * Listener that is attached to various properties that should trigger a + * view update when they change. + */ + private InvalidationListener updateListener = (Observable any) -> refresh(); + + /** + * Task used to reload the content of this view + */ + private Task updateTask; + + private final TimeLineController controller; + private final FilteredEventsModel filteredEvents; + + /** + * Constructor + * + * @param controller + */ + public AbstractTimeLineView(TimeLineController controller) { + this.controller = controller; + this.filteredEvents = controller.getEventsModel(); + this.filteredEvents.registerForEvents(this); + this.filteredEvents.zoomParametersProperty().addListener(updateListener); + TimeLineController.getTimeZone().addListener(updateListener); + } + + /** + * Handle a RefreshRequestedEvent from the events model by updating the + * view. + * + * @param event The RefreshRequestedEvent to handle. + */ + @Subscribe + public void handleRefreshRequested(RefreshRequestedEvent event) { + refresh(); + } + + /** + * Does the view represent an out-of-date state of the DB. It might if, for + * example, tags have been updated but the view was not refreshed. + * + * @return True if the view does not represent the current state of the DB. + */ + public boolean isOutOfDate() { + return outOfDate.get(); + } + + /** + * Get a ReadOnlyBooleanProperty that holds true if this view does not + * represent the current state of the DB> + * + * @return A ReadOnlyBooleanProperty that holds the out-of-date state for + * this view. + */ + public ReadOnlyBooleanProperty outOfDateProperty() { + return outOfDate.getReadOnlyProperty(); + } + + /** + * Get the TimelineController for this view. + * + * @return The TimelineController for this view. + */ + protected TimeLineController getController() { + return controller; + } + + /** + * Refresh this view based on current state of zoom / filters. Primarily + * this invokes the background ViewRefreshTask returned by getUpdateTask(), + * which derived classes must implement. + * + * TODO: replace this logic with a javafx Service ? -jm + */ + protected final synchronized void refresh() { + if (updateTask != null) { + updateTask.cancel(true); + updateTask = null; + } + updateTask = getNewUpdateTask(); + updateTask.stateProperty().addListener((Observable observable) -> { + switch (updateTask.getState()) { + case CANCELLED: + case FAILED: + case READY: + case RUNNING: + case SCHEDULED: + break; + case SUCCEEDED: + try { + this.hasVisibleEvents.set(updateTask.get()); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception updating view", ex); //NON-NLS + } + break; + } + }); + getController().monitorTask(updateTask); + } + + /** + * Get the FilteredEventsModel for this view. + * + * @return The FilteredEventsModel for this view. + */ + protected FilteredEventsModel getEventsModel() { + return filteredEvents; + } + + /** + * Get a new background Task that fetches the appropriate data and loads it + * into this view. + * + * @return A new task to execute on a background thread to reload this view + * with different data. + */ + protected abstract Task getNewUpdateTask(); + + /** + * Get the ViewMode for this view. + * + * @return The ViewMode for this view. + */ + protected abstract ViewMode getViewMode(); + + /** + * Get a List of Nodes containing settings widgets to insert into top + * ToolBar of the ViewFrame. + * + * @return The List of settings Nodes. + */ + abstract protected ImmutableList getSettingsControls(); + + /** + * Does this view have custom time navigation controls that should replace + * the default ones from the ViewFrame? + * + * @return True if this view have custom time navigation controls. + */ + abstract protected boolean hasCustomTimeNavigationControls(); + + /** + * Get a List of Nodes containing controls to insert into the lower time + * range ToolBar of the ViewFrame. + * + * @return The List of Nodes. + */ + abstract protected ImmutableList getTimeNavigationControls(); + + /** + * Dispose of this view and any resources it holds onto. + */ + final synchronized void dispose() { + //cancel and gc updateTask + if (updateTask != null) { + updateTask.cancel(true); + updateTask = null; + } + //remvoe and gc updateListener + this.filteredEvents.zoomParametersProperty().removeListener(updateListener); + TimeLineController.getTimeZone().removeListener(updateListener); + updateListener = null; + filteredEvents.unRegisterForEvents(this); + } + + /** + * Are there are any events visible in this view with the current view + * parameters? + * + * @return True if there are events visible in this view with the current + * view parameters. + */ + boolean hasVisibleEvents() { + return hasVisibleEventsProperty().get(); + } + + /** + * A property that indicates whether there are any events visible in this + * view with the current view parameters. + * + * @return A property that indicates whether there are any events visible in + * this view with the current view parameters. + */ + ReadOnlyBooleanProperty hasVisibleEventsProperty() { + return hasVisibleEvents.getReadOnlyProperty(); + } + + /** + * Set this view out of date because, for example, tags have been updated + * but the view was not refreshed. + */ + void setOutOfDate() { + outOfDate.set(true); + } + + /** + * Clear all data items from this chart. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + abstract protected void clearData(); + + /** + * Base class for Tasks that refreshes a view when the view settings change. + * + * @param The type of a single object that can represent + * the range of data displayed along the X-Axis. + */ + protected abstract class ViewRefreshTask extends LoggedTask { + + private final Node center; + + /** + * Constructor + * + * @param taskName The name of this task. + * @param logStateChanges Whether or not task state changes should be + * logged. + */ + protected ViewRefreshTask(String taskName, boolean logStateChanges) { + super(taskName, logStateChanges); + this.center = getCenter(); + } + + /** + * Sets initial progress value and message and shows blocking progress + * indicator over the view. Derived Tasks should be sure to call this as + * part of their call() implementation. + * + * @return True + * + * @throws Exception If there is an unhandled exception during the + * background operation + */ + @NbBundle.Messages(value = {"ViewRefreshTask.preparing=Analyzing zoom and filter settings"}) + @Override + protected Boolean call() throws Exception { + updateProgress(-1, 1); + updateMessage(Bundle.ViewRefreshTask_preparing()); + Platform.runLater(() -> { + MaskerPane maskerPane = new MaskerPane(); + maskerPane.textProperty().bind(messageProperty()); + maskerPane.progressProperty().bind(progressProperty()); + setCenter(new StackPane(center, maskerPane)); + setCursor(Cursor.WAIT); + }); + return true; + } + + /** + * Updates the horizontal axis and removes the blocking progress + * indicator. Derived Tasks should be sure to call this as part of their + * succeeded() implementation. + */ + @Override + protected void succeeded() { + super.succeeded(); + outOfDate.set(false); + cleanup(); + } + + /** + * Removes the blocking progress indicator. Derived Tasks should be sure + * to call this as part of their cancelled() implementation. + */ + @Override + protected void cancelled() { + super.cancelled(); + cleanup(); + } + + /** + * Removes the blocking progress indicator. Derived Tasks should be sure + * to call this as part of their failed() implementation. + */ + @Override + protected void failed() { + super.failed(); + cleanup(); + } + + /** + * Removes the blocking progress indicator and reset the cursor to the + * default. + */ + private void cleanup() { + setCenter(center); //clear masker pane installed in call() + setCursor(Cursor.DEFAULT); + } + + /** + * Set the horizontal range that this chart will show. + * + * @param values A single object representing the range that this chart + * will show. + */ + protected abstract void setDateValues(AxisValuesType values); + + /** + * Clears the chart data and sets the horizontal axis range. For use + * within the derived implementation of the call() method. + * + * @param axisValues + */ + @ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI) + protected void resetView(AxisValuesType axisValues) { + Platform.runLater(() -> { + clearData(); + setDateValues(axisValues); + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java similarity index 53% rename from Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java rename to Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java index 4aa137c94f..976c6f3fff 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-16 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,28 +18,16 @@ */ package org.sleuthkit.autopsy.timeline.ui; -import com.google.common.eventbus.Subscribe; -import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; import javafx.beans.binding.DoubleBinding; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.SortedList; -import javafx.concurrent.Task; import javafx.geometry.Pos; -import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.chart.Axis; import javafx.scene.chart.XYChart; @@ -47,7 +35,6 @@ import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; import javafx.scene.control.Tooltip; import javafx.scene.layout.Border; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderStroke; import javafx.scene.layout.BorderStrokeStyle; import javafx.scene.layout.BorderWidths; @@ -55,7 +42,6 @@ import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; @@ -64,18 +50,14 @@ import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang3.StringUtils; -import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; /** - * Abstract base class for TimeLineChart based visualizations. + * Abstract base class for TimeLineChart based views. * * @param The type of data plotted along the x axis * @param The type of data plotted along the y axis @@ -88,36 +70,33 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; * * TODO: pull up common history context menu items out of derived classes? -jm */ -public abstract class AbstractVisualizationPane> extends BorderPane { +public abstract class AbstractTimelineChart> extends AbstractTimeLineView { - private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName()); + private static final Logger LOGGER = Logger.getLogger(AbstractTimelineChart.class.getName()); - @NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.") - private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text()); + @NbBundle.Messages("AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.") + private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractTimelineChart_defaultTooltip_text()); private static final Border ONLY_LEFT_BORDER = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 0, 0, 1))); /** - * Get the tool tip to use for this visualization when no more specific - * Tooltip is needed. + * Get the tool tip to use for this view when no more specific Tooltip is + * needed. * * @return The default Tooltip. */ - public static Tooltip getDefaultTooltip() { + static public Tooltip getDefaultTooltip() { return DEFAULT_TOOLTIP; } /** - * Boolean property that holds true if the visualization may not represent - * the current state of the DB, because, for example, tags have been updated - * but the vis. was not refreshed. + * The nodes that are selected. + * + * @return An ObservableList of the nodes that are selected in + * this view. */ - private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false); - - /** - * Boolean property that holds true if the visualization does not show any - * events with the current zoom and filter settings. - */ - private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true); + protected ObservableList getSelectedNodes() { + return selectedNodes; + } /** * Access to chart data via series @@ -130,123 +109,36 @@ public abstract class AbstractVisualizationPane updateTask; - - final private TimeLineController controller; - final private FilteredEventsModel filteredEvents; - final private ObservableList selectedNodes = FXCollections.observableArrayList(); - /** - * Listener that is attached to various properties that should trigger a vis - * update when they change. - */ - private InvalidationListener updateListener = any -> refresh(); + public Pane getSpecificLabelPane() { + return specificLabelPane; + } - /** - * Does the visualization represent an out-of-date state of the DB. It might - * if, for example, tags have been updated but the vis. was not refreshed. - * - * @return True if the visualization does not represent the curent state of - * the DB. - */ - public boolean isOutOfDate() { - return outOfDate.get(); + public Pane getContextLabelPane() { + return contextLabelPane; + } + + public Region getSpacer() { + return spacer; } /** - * Set this visualization out of date because, for example, tags have been - * updated but the vis. was not refreshed. - */ - void setOutOfDate() { - outOfDate.set(true); - } - - /** - * Get a ReadOnlyBooleanProperty that holds true if this visualization does - * not represent the current state of the DB> + * Get the CharType that implements this view. * - * @return A ReadOnlyBooleanProperty that holds the out-of-date state for - * this visualization. - */ - public ReadOnlyBooleanProperty outOfDateProperty() { - return outOfDate.getReadOnlyProperty(); - } - - /** - * The visualization nodes that are selected. - * - * @return An ObservableList of the nodes that are selected in - * this visualization. - */ - protected ObservableList getSelectedNodes() { - return selectedNodes; - } - - /** - * List of Nodes to insert into the toolbar. This should be set in an - * implementations constructor. - */ - private List settingsNodes; - - /** - * Get a List of nodes containing settings widgets to insert into this - * visualization's header. - * - * @return The List of settings Nodes. - */ - protected List getSettingsNodes() { - return Collections.unmodifiableList(settingsNodes); - } - - /** - * Set the List of nodes containing settings widgets to insert into this - * visualization's header. - * - * - * @param settingsNodes The List of nodes containing settings widgets to - * insert into this visualization's header. - */ - protected void setSettingsNodes(List settingsNodes) { - this.settingsNodes = new ArrayList<>(settingsNodes); - } - - /** - * Get the TimelineController for this visualization. - * - * @return The TimelineController for this visualization. - */ - protected TimeLineController getController() { - return controller; - } - - /** - * Get the CharType that implements this visualization. - * - * @return The CharType that implements this visualization. + * @return The CharType that implements this view. */ protected ChartType getChart() { return chart; } /** - * Get the FilteredEventsModel for this visualization. + * Set the ChartType that implements this view. * - * @return The FilteredEventsModel for this visualization. - */ - protected FilteredEventsModel getEventsModel() { - return filteredEvents; - } - - /** - * Set the ChartType that implements this visualization. - * - * @param chart The ChartType that implements this visualization. + * @param chart The ChartType that implements this view. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) protected void setChart(ChartType chart) { @@ -255,29 +147,7 @@ public abstract class AbstractVisualizationPane getNewUpdateTask(); - /** * Get the label that should be used for a tick mark at the given value. * @@ -342,16 +203,16 @@ public abstract class AbstractVisualizationPane getXAxis(); /** - * Get the Y-Axis of this Visualization's chart + * Get the Y-Axis of this view's chart * - * @return The vertical axis used by this Visualization's chart + * @return The vertical axis used by this view's chart */ abstract protected Axis getYAxis(); @@ -364,74 +225,6 @@ public abstract class AbstractVisualizationPane { - switch (updateTask.getState()) { - case CANCELLED: - case FAILED: - case READY: - case RUNNING: - case SCHEDULED: - break; - case SUCCEEDED: - try { - this.hasVisibleEvents.set(updateTask.get()); - } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NON-NLS - } - break; - } - }); - controller.monitorTask(updateTask); - } - - /** - * Handle a RefreshRequestedEvent from the events model by updating the - * visualization. - * - * @param event The RefreshRequestedEvent to handle. - */ - @Subscribe - public void handleRefreshRequested(RefreshRequestedEvent event) { - refresh(); - } - - /** - * Dispose of this visualization and any resources it holds onto. - */ - final synchronized void dispose() { - - //cancel and gc updateTask - if (updateTask != null) { - updateTask.cancel(true); - updateTask = null; - } - //remvoe and gc updateListener - this.filteredEvents.zoomParametersProperty().removeListener(updateListener); - TimeLineController.getTimeZone().removeListener(updateListener); - updateListener = null; - - filteredEvents.unRegisterForEvents(this); - } - /** * Make a series for each event type in a consistent order. */ @@ -459,23 +252,20 @@ public abstract class AbstractVisualizationPane { - VBox vBox = new VBox(specificLabelPane, contextLabelPane); + VBox vBox = new VBox(getSpecificLabelPane(), getContextLabelPane()); vBox.setFillWidth(false); - HBox hBox = new HBox(spacer, vBox); + HBox hBox = new HBox(getSpacer(), vBox); hBox.setFillHeight(false); setBottom(hBox); DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin()); - spacer.minWidthProperty().bind(spacerSize); - spacer.prefWidthProperty().bind(spacerSize); - spacer.maxWidthProperty().bind(spacerSize); + getSpacer().minWidthProperty().bind(spacerSize); + getSpacer().prefWidthProperty().bind(spacerSize); + getSpacer().maxWidthProperty().bind(spacerSize); }); createSeries(); @@ -487,10 +277,8 @@ public abstract class AbstractVisualizationPane controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : "")); + hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : "")); } @@ -521,7 +309,15 @@ public abstract class AbstractVisualizationPane> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition)); - if (tickMarks.isEmpty() == false) { + if (tickMarks.isEmpty()) { + /* + * Since StackedBarChart does some funky animation/background thread + * stuff, sometimes there are no tick marks even though there is + * data. Dispatching another call to layoutDateLables() allows that + * stuff time to run before we check a gain. + */ + Platform.runLater(this::layoutDateLabels); + } else { //get the spacing between ticks in the underlying axis double spacing = getTickSpacing(); @@ -681,117 +477,4 @@ public abstract class AbstractVisualizationPane The type of a single object that can represent - * the range of data displayed along the X-Axis. - */ - abstract protected class VisualizationRefreshTask extends LoggedTask { - - private final Node center; - - /** - * Constructor - * - * @param taskName The name of this task. - * @param logStateChanges Whether or not task state changes should be - * logged. - */ - protected VisualizationRefreshTask(String taskName, boolean logStateChanges) { - super(taskName, logStateChanges); - this.center = getCenter(); - } - - /** - * Sets initial progress value and message and shows blocking progress - * indicator over the visualization. Derived Tasks should be sure to - * call this as part of their call() implementation. - * - * @return True - * - * @throws Exception If there is an unhandled exception during the - * background operation - */ - @NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"}) - @Override - protected Boolean call() throws Exception { - updateProgress(-1, 1); - updateMessage(Bundle.VisualizationUpdateTask_preparing()); - Platform.runLater(() -> { - MaskerPane maskerPane = new MaskerPane(); - maskerPane.textProperty().bind(messageProperty()); - maskerPane.progressProperty().bind(progressProperty()); - setCenter(new StackPane(center, maskerPane)); - setCursor(Cursor.WAIT); - }); - - return true; - } - - /** - * Updates the horizontal axis and removes the blocking progress - * indicator. Derived Tasks should be sure to call this as part of their - * succeeded() implementation. - */ - @Override - protected void succeeded() { - super.succeeded(); - outOfDate.set(false); - layoutDateLabels(); - cleanup(); - } - - /** - * Removes the blocking progress indicator. Derived Tasks should be sure - * to call this as part of their cancelled() implementation. - */ - @Override - protected void cancelled() { - super.cancelled(); - cleanup(); - } - - /** - * Removes the blocking progress indicator. Derived Tasks should be sure - * to call this as part of their failed() implementation. - */ - @Override - protected void failed() { - super.failed(); - cleanup(); - } - - /** - * Removes the blocking progress indicator and reset the cursor to the - * default. - */ - private void cleanup() { - setCenter(center); //clear masker pane installed in call() - setCursor(Cursor.DEFAULT); - } - - /** - * Clears the chart data and sets the horizontal axis range. For use - * within the derived implementation of the call() method. - * - * @param axisValues - */ - @ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI) - protected void resetChart(AxisValuesType axisValues) { - Platform.runLater(() -> { - clearChartData(); - setDateAxisValues(axisValues); - }); - } - - /** - * Set the horizontal range that this chart will show. - * - * @param values A single object representing the range that this chart - * will show. - */ - abstract protected void setDateAxisValues(AxisValuesType values); - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties index b08bf41e42..43dcee68ea 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties @@ -35,14 +35,7 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years Timeline.ui.ZoomRanges.fiveyears.text=Five Years Timeline.ui.ZoomRanges.tenyears.text=Ten Years Timeline.ui.ZoomRanges.all.text=All -TimeLineResultView.startDateToEndDate.text={0} to {1} -VisualizationPanel.histogramTask.title=Rebuild Histogram -VisualizationPanel.histogramTask.preparing=preparing -VisualizationPanel.histogramTask.resetUI=resetting ui -VisualizationPanel.histogramTask.queryDb=querying db -VisualizationPanel.histogramTask.updateUI2=updating ui -VisualizationPanel.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings. -VisualizationPanel.zoomButton.text=Zoom to events +ViewFrame.zoomButton.text=Zoom to events TimeZonePanel.localRadio.text=Local Time Zone TimeZonePanel.otherRadio.text=GMT / UTC -VisualizationPanel.resetFiltersButton.text=Reset all filters +ViewFrame.resetFiltersButton.text=Reset all filters diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties index 2ba0472b32..5c8dacfed2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties @@ -1,24 +1,24 @@ Timeline.node.root=\u30eb\u30fc\u30c8 Timeline.ui.TimeLineChart.tooltip.text=\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3067\u4e0b\u8a18\u306e\u7bc4\u56f2\u3078\u30ba\u30fc\u30e0\uff1a\n{0}\u301c{1}\n\u53f3\u30af\u30ea\u30c3\u30af\u3067\u5143\u306b\u623b\u308a\u307e\u3059\u3002 Timeline.ui.ZoomRanges.all.text=\u5168\u3066 -TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1} -VisualizationPanel.histogramTask.preparing=\u6e96\u5099\u4e2d -VisualizationPanel.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d -VisualizationPanel.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d -VisualizationPanel.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9 -VisualizationPanel.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d + +ViewFrame.histogramTask.preparing=\u6e96\u5099\u4e2d +ViewFrame.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d +ViewFrame.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d +ViewFrame.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9 +ViewFrame.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d TimeZonePanel.localRadio.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3 -VisualizationPanel.countsToggle.text=\u30ab\u30a6\u30f3\u30c8 -VisualizationPanel.detailsToggle.text=\u8a73\u7d30 -VisualizationPanel.endLabel.text=\u30a8\u30f3\u30c9\uff1a -VisualizationPanel.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002 -VisualizationPanel.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8 -VisualizationPanel.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a -VisualizationPanel.visualizationModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a -VisualizationPanel.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0 -VisualizationPanel.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8 +ViewFrame.countsToggle.text=\u30ab\u30a6\u30f3\u30c8 +ViewFrame.detailsToggle.text=\u8a73\u7d30 +ViewFrame.endLabel.text=\u30a8\u30f3\u30c9\uff1a +ViewFrame.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +ViewFrame.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8 +ViewFrame.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a +ViewFrame.viewModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a +ViewFrame.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0 +ViewFrame.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8 *=Autopsy\u30d5\u30a9\u30ec\u30f3\u30b8\u30c3\u30af\u30d6\u30e9\u30a6\u30b6 -AbstractVisualization.Default_Tooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044 +AbstractTimelineChart.defaultTooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044 IntervalSelector.ClearSelectedIntervalAction.tooltTipText=\u9078\u629e\u3057\u305f\u9593\u9694\u3092\u30af\u30ea\u30a2\u3059\u308b IntervalSelector.ZoomAction.name=\u30ba\u30fc\u30e0 NoEventsDialog.titledPane.text=\u898b\u308c\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093 @@ -40,6 +40,6 @@ Timeline.ui.ZoomRanges.fiveyears.text=5\u5e74 Timeline.ui.ZoomRanges.tenyears.text=10\u5e74 TimeLineChart.zoomHistoryActionGroup.name=\u30ba\u30fc\u30e0\u5c65\u6b74 TimeZonePanel.title=\u6642\u9593\u3092\u6b21\u3067\u8868\u793a\uff1a -VisualizationPanel.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5 -VisualizationPanel.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 -VisualizationUpdateTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d +ViewFrame.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5 +ViewFrame.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 +ViewRefreshTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java deleted file mode 100644 index f61e2b5b28..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.timeline.ui; - -import java.util.HashSet; -import java.util.Set; -import javafx.beans.Observable; -import javax.swing.SwingUtilities; -import org.joda.time.format.DateTimeFormatter; -import org.openide.nodes.Node; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; -import org.sleuthkit.autopsy.corecomponents.DataResultPanel; -import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode; - -/** - * Since it was too hard to derive from {@link DataResultPanel}, this class - * implements {@link TimeLineView}, listens to the events/state of a the - * assigned {@link FilteredEventsModel} and acts appropriately on its - * {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter - * between a FilteredEventsModel instance and a DataResultPanel instance. - */ -public class TimeLineResultView { - - /** - * the {@link DataResultPanel} that is the real view proxied by this class - */ - private final DataResultPanel dataResultPanel; - - private final TimeLineController controller; - - private final FilteredEventsModel filteredEvents; - - private Set selectedEventIDs = new HashSet<>(); - - public DataResultPanel getDataResultPanel() { - return dataResultPanel; - } - - public TimeLineResultView(TimeLineController controller, DataContent dataContent) { - - this.controller = controller; - this.filteredEvents = controller.getEventsModel(); - dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent); - - //set up listeners on relevant properties - TimeLineController.getTimeZone().addListener((Observable observable) -> { - dataResultPanel.setPath(getSummaryString()); - }); - - controller.getSelectedEventIDs().addListener((Observable o) -> { - refresh(); - }); - refresh(); - } - - /** - * @return a String representation of all the Events displayed - */ - private String getSummaryString() { - if (controller.getSelectedTimeRange().get() != null) { - final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter(); - return NbBundle.getMessage(this.getClass(), "TimeLineResultView.startDateToEndDate.text", - controller.getSelectedTimeRange().get().getStart() - .withZone(TimeLineController.getJodaTimeZone()) - .toString(zonedFormatter), - controller.getSelectedTimeRange().get().getEnd() - .withZone(TimeLineController.getJodaTimeZone()) - .toString(zonedFormatter)); - } - return ""; - } - - /** - * refresh this view with the events selected in the controller - */ - public final void refresh() { - - Set newSelectedEventIDs = new HashSet<>(controller.getSelectedEventIDs()); - if (selectedEventIDs.equals(newSelectedEventIDs) == false) { - selectedEventIDs = newSelectedEventIDs; - final EventRootNode root = new EventRootNode( - NbBundle.getMessage(this.getClass(), "Timeline.node.root"), selectedEventIDs, - filteredEvents); - - //this must be in edt or exception is thrown - SwingUtilities.invokeLater(() -> { - dataResultPanel.setPath(getSummaryString()); - dataResultPanel.setNode(root); - }); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml similarity index 88% rename from Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml rename to Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml index 9bb04129a1..5de950392e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml @@ -23,7 +23,7 @@ -
- + @@ -58,8 +58,20 @@ + + + + + + + + + + + + - + @@ -81,7 +93,7 @@ - + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java new file mode 100644 index 0000000000..94fe96916f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java @@ -0,0 +1,770 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline.ui.listvew; + +import com.google.common.collect.Iterables; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.IntegerBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.MenuItem; +import javafx.scene.control.OverrunStyle; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import javax.swing.Action; +import javax.swing.JMenuItem; +import org.apache.commons.lang3.StringUtils; +import org.controlsfx.control.Notifications; +import org.controlsfx.control.action.ActionUtils; +import org.openide.awt.Actions; +import org.openide.util.NbBundle; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.timeline.FXMLConstructor; +import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; +import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes; +import org.sleuthkit.autopsy.timeline.explorernodes.EventNode; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * The inner component that makes up the List view. Manages the TableView. + */ +class ListTimeline extends BorderPane { + + private static final Logger LOGGER = Logger.getLogger(ListTimeline.class.getName()); + + private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS + private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS + private static final Image FIRST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_first.png"); //NON-NLS + private static final Image PREVIOUS = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_previous.png"); //NON-NLS + private static final Image NEXT = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_next.png"); //NON-NLS + private static final Image LAST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_last.png"); //NON-NLS + + /** + * call-back used to wrap the CombinedEvent in a ObservableValue + */ + private static final Callback, ObservableValue> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue()); + private static final List SCROLL_BY_UNITS = Arrays.asList( + ChronoField.YEAR, + ChronoField.MONTH_OF_YEAR, + ChronoField.DAY_OF_MONTH, + ChronoField.HOUR_OF_DAY, + ChronoField.MINUTE_OF_HOUR, + ChronoField.SECOND_OF_MINUTE); + + @FXML + private HBox navControls; + + @FXML + private ComboBox scrollInrementComboBox; + + @FXML + private Button firstButton; + + @FXML + private Button previousButton; + + @FXML + private Button nextButton; + + @FXML + private Button lastButton; + + @FXML + private Label eventCountLabel; + @FXML + private TableView table; + @FXML + private TableColumn idColumn; + @FXML + private TableColumn dateTimeColumn; + @FXML + private TableColumn descriptionColumn; + @FXML + private TableColumn typeColumn; + @FXML + private TableColumn knownColumn; + @FXML + private TableColumn taggedColumn; + @FXML + private TableColumn hashHitColumn; + + /** + * Observable list used to track selected events. + */ + private final ObservableList selectedEventIDs = FXCollections.observableArrayList(); + + private final ConcurrentSkipListSet visibleEvents; + + private final TimeLineController controller; + private final SleuthkitCase sleuthkitCase; + private final TagsManager tagsManager; + + /** + * Constructor + * + * @param controller The controller for this timeline + */ + ListTimeline(TimeLineController controller) { + + this.controller = controller; + sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase(); + tagsManager = controller.getAutopsyCase().getServices().getTagsManager(); + FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS + this.visibleEvents = new ConcurrentSkipListSet<>(Comparator.comparing(table.getItems()::indexOf)); + } + + @FXML + @NbBundle.Messages({ + "# {0} - the number of events", + "ListTimeline.eventCountLabel.text={0} events"}) + void initialize() { + assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert idColumn != null : "fx:id=\"idColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS + + scrollInrementComboBox.setButtonCell(new ChronoFieldListCell()); + scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell()); + scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS); + scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR); + + ActionUtils.configureButton(new ScrollToFirst(), firstButton); + ActionUtils.configureButton(new ScrollToPrevious(), previousButton); + ActionUtils.configureButton(new ScrollToNext(), nextButton); + ActionUtils.configureButton(new ScrollToLast(), lastButton); + + //override default row with one that provides context menus + table.setRowFactory(tableView -> new EventRow()); + + //remove idColumn (can be restored for debugging). + table.getColumns().remove(idColumn); + + //// set up cell and cell-value factories for columns + dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY); + dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> + TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis()))); + + descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY); + descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> + singleEvent.getDescription(DescriptionLoD.FULL))); + + typeColumn.setCellValueFactory(CELL_VALUE_FACTORY); + typeColumn.setCellFactory(col -> new EventTypeCell()); + + knownColumn.setCellValueFactory(CELL_VALUE_FACTORY); + knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent -> + singleEvent.getKnown().getName())); + + taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY); + taggedColumn.setCellFactory(col -> new TaggedCell()); + + hashHitColumn.setCellValueFactory(CELL_VALUE_FACTORY); + hashHitColumn.setCellFactory(col -> new HashHitCell()); + + //bind event count label to number of items in the table + eventCountLabel.textProperty().bind(new StringBinding() { + { + bind(table.getItems()); + } + + @Override + protected String computeValue() { + return Bundle.ListTimeline_eventCountLabel_text(table.getItems().size()); + } + }); + + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + table.getSelectionModel().getSelectedItems().addListener((Observable observable) -> { + //keep the selectedEventsIDs in sync with the table's selection model, via getRepresentitiveEventID(). + selectedEventIDs.setAll(table.getSelectionModel().getSelectedItems().stream() + .filter(Objects::nonNull) + .map(CombinedEvent::getRepresentativeEventID) + .collect(Collectors.toSet())); + }); + } + + /** + * Clear all the events out of the table. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + void clear() { + table.getItems().clear(); + } + + /** + * Set the Collection of CombinedEvents to show in the table. + * + * @param events The Collection of events to sho in the table. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + void setCombinedEvents(Collection events) { + table.getItems().setAll(events); + } + + /** + * Get an ObservableList of IDs of events that are selected in this table. + * + * @return An ObservableList of IDs of events that are selected in this + * table. + */ + ObservableList getSelectedEventIDs() { + return selectedEventIDs; + } + + /** + * Get an ObservableList of combined events that are selected in this table. + * + * @return An ObservableList of combined events that are selected in this + * table. + */ + ObservableList getSelectedEvents() { + return table.getSelectionModel().getSelectedItems(); + } + + /** + * Set the combined events that are selected in this view. + * + * @param selectedEvents The events that should be selected. + */ + void selectEvents(Collection selectedEvents) { + CombinedEvent firstSelected = selectedEvents.stream().min(Comparator.comparing(CombinedEvent::getStartMillis)).orElse(null); + table.getSelectionModel().clearSelection(); + table.scrollTo(firstSelected); + selectedEvents.forEach(table.getSelectionModel()::select); + table.requestFocus(); + } + + List getNavControls() { + return Collections.singletonList(navControls); + } + + private void scrollToAndFocus(Integer index) { + table.requestFocus(); + if (visibleEvents.contains(table.getItems().get(index)) == false) { + table.scrollTo(index); + } + table.getFocusModel().focus(index); + } + + /** + * TableCell to show the (sub) type of an event. + */ + private class EventTypeCell extends EventTableCell { + + @NbBundle.Messages({ + "ListView.EventTypeCell.modifiedTooltip=File Modified ( M )", + "ListView.EventTypeCell.accessedTooltip=File Accessed ( A )", + "ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )", + "ListView.EventTypeCell.changedTooltip=File Changed ( C )" + }) + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + setTooltip(null); + } else { + if (item.getEventTypes().stream().allMatch(eventType -> eventType instanceof FileSystemTypes)) { + String typeString = ""; //NON-NLS + VBox toolTipVbox = new VBox(5); + + for (FileSystemTypes type : Arrays.asList(FileSystemTypes.FILE_MODIFIED, FileSystemTypes.FILE_ACCESSED, FileSystemTypes.FILE_CHANGED, FileSystemTypes.FILE_CREATED)) { + if (item.getEventTypes().contains(type)) { + switch (type) { + case FILE_MODIFIED: + typeString += "M"; //NON-NLS + toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(), new ImageView(type.getFXImage()))); + break; + case FILE_ACCESSED: + typeString += "A"; //NON-NLS + toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(), new ImageView(type.getFXImage()))); + break; + case FILE_CREATED: + typeString += "B"; //NON-NLS + toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(), new ImageView(type.getFXImage()))); + break; + case FILE_CHANGED: + typeString += "C"; //NON-NLS + toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(), new ImageView(type.getFXImage()))); + break; + default: + throw new UnsupportedOperationException("Unknown FileSystemType: " + type.name()); //NON-NLS + } + } else { + typeString += "_"; //NON-NLS + } + } + setText(typeString); + setGraphic(new ImageView(BaseTypes.FILE_SYSTEM.getFXImage())); + Tooltip tooltip = new Tooltip(); + tooltip.setGraphic(toolTipVbox); + setTooltip(tooltip); + + } else { + EventType eventType = Iterables.getOnlyElement(item.getEventTypes()); + setText(eventType.getDisplayName()); + setGraphic(new ImageView(eventType.getFXImage())); + setTooltip(new Tooltip(eventType.getDisplayName())); + }; + } + } + } + + /** + * A TableCell that shows information about the tags applied to a event. + */ + private class TaggedCell extends EventTableCell { + + /** + * Constructor + */ + TaggedCell() { + setAlignment(Pos.CENTER); + } + + @NbBundle.Messages({ + "ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.", + "# {0} - tag names", + "ListTimeline.taggedTooltip.text=Tags:\n{0}"}) + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null || (getEvent().isTagged() == false)) { + setGraphic(null); + setTooltip(null); + } else { + /* + * if the cell is not empty and the event is tagged, show the + * tagged icon, and show a list of tag names in the tooltip + */ + setGraphic(new ImageView(TAG)); + + SortedSet tagNames = new TreeSet<>(); + try { + //get file tags + AbstractFile abstractFileById = sleuthkitCase.getAbstractFileById(getEvent().getFileID()); + tagsManager.getContentTagsByContent(abstractFileById).stream() + .map(tag -> tag.getName().getDisplayName()) + .forEach(tagNames::add); + + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getFileID(), ex); //NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(getScene().getWindow()) + .text(Bundle.ListTimeline_taggedTooltip_error()) + .showError(); + }); + } + getEvent().getArtifactID().ifPresent(artifactID -> { + //get artifact tags, if there is an artifact associated with the event. + try { + BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID); + tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream() + .map(tag -> tag.getName().getDisplayName()) + .forEach(tagNames::add); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(getScene().getWindow()) + .text(Bundle.ListTimeline_taggedTooltip_error()) + .showError(); + }); + } + }); + Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS + tooltip.setGraphic(new ImageView(TAG)); + setTooltip(tooltip); + } + } + } + + /** + * TableCell to show the hash hits if any associated with the file backing + * an event. + */ + private class HashHitCell extends EventTableCell { + + /** + * Constructor + */ + HashHitCell() { + setAlignment(Pos.CENTER); + } + + @NbBundle.Messages({ + "ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.", + "# {0} - hash set names", + "ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"}) + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null || (getEvent().isHashHit() == false)) { + setGraphic(null); + setTooltip(null); + } else { + /* + * if the cell is not empty and the event's file is a hash hit, + * show the hash hit icon, and show a list of hash set names in + * the tooltip + */ + setGraphic(new ImageView(HASH_HIT)); + try { + Set hashSetNames = new TreeSet<>(sleuthkitCase.getAbstractFileById(getEvent().getFileID()).getHashSetNames()); + Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS + tooltip.setGraphic(new ImageView(HASH_HIT)); + setTooltip(tooltip); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getFileID(), ex); //NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(getScene().getWindow()) + .text(Bundle.ListTimeline_hashHitTooltip_error()) + .showError(); + }); + } + } + } + } + + /** + * TableCell to show text derived from a SingleEvent by the given Function. + */ + private class TextEventTableCell extends EventTableCell { + + private final Function textSupplier; + + /** + * Constructor + * + * @param textSupplier Function that takes a SingleEvent and produces a + * String to show in this TableCell. + */ + TextEventTableCell(Function textSupplier) { + this.textSupplier = textSupplier; + setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); + setEllipsisString(" ... "); //NON-NLS + } + + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + setText(textSupplier.apply(getEvent())); + } + } + } + + /** + * Base class for TableCells that represent a MergedEvent by way of a + * representative SingleEvent. + */ + private abstract class EventTableCell extends TableCell { + + private SingleEvent event; + + /** + * Get the representative SingleEvent for this cell. + * + * @return The representative SingleEvent for this cell. + */ + SingleEvent getEvent() { + return event; + } + + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + event = null; + } else { + //stash the event in the cell for derived classed to use. + event = controller.getEventsModel().getEventById(item.getRepresentativeEventID()); + } + } + } + + /** + * TableRow that adds a right-click context menu. + */ + private class EventRow extends TableRow { + + private SingleEvent event; + + /** + * Get the representative SingleEvent for this row . + * + * @return The representative SingleEvent for this row . + */ + SingleEvent getEvent() { + return event; + } + + @NbBundle.Messages({ + "ListChart.errorMsg=There was a problem getting the content for the selected event."}) + @Override + protected void updateItem(CombinedEvent item, boolean empty) { + CombinedEvent oldItem = getItem(); + if (oldItem != null) { + visibleEvents.remove(oldItem); + } + super.updateItem(item, empty); + + if (empty || item == null) { + event = null; + } else { + visibleEvents.add(item); + event = controller.getEventsModel().getEventById(item.getRepresentativeEventID()); + + setOnContextMenuRequested(contextMenuEvent -> { + //make a new context menu on each request in order to include uptodate tag names and hash sets + try { + EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel()); + List menuItems = new ArrayList<>(); + + //for each actions avaialable on node, make a menu item. + for (Action action : node.getActions(false)) { + if (action == null) { + // swing/netbeans uses null action to represent separator in menu + menuItems.add(new SeparatorMenuItem()); + } else { + String actionName = Objects.toString(action.getValue(Action.NAME)); + //for now, suppress properties and tools actions, by ignoring them + if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS + if (action instanceof Presenter.Popup) { + /* + * If the action is really the root of a + * set of actions (eg, tagging). Make a + * menu that parallels the action's + * menu. + */ + JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter(); + menuItems.add(SwingFXMenuUtils.createFXMenu(submenu)); + } else { + menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false))); + } + } + } + }; + + //show new context menu. + new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()])) + .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY()); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); //NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); //NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(getScene().getWindow()) + .text(Bundle.ListChart_errorMsg()) + .showError(); + }); + } + }); + + } + } + } + + private class ChronoFieldListCell extends ListCell { + + @Override + protected void updateItem(ChronoField item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + } else { + String displayName = item.getDisplayName(Locale.getDefault()); + setText(String.join(" ", StringUtils.splitByCharacterTypeCamelCase(displayName))); + } + } + } + + private class ScrollToFirst extends org.controlsfx.control.action.Action { + + ScrollToFirst() { + super("", actionEvent -> scrollToAndFocus(0)); + setGraphic(new ImageView(FIRST)); + disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1)); + } + } + + private class ScrollToLast extends org.controlsfx.control.action.Action { + + ScrollToLast() { + super("", actionEvent -> scrollToAndFocus(table.getItems().size() - 1)); + setGraphic(new ImageView(LAST)); + IntegerBinding size = Bindings.size(table.getItems()); + disabledProperty().bind(size.isEqualTo(0).or( + table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1)))); + } + } + + private class ScrollToNext extends org.controlsfx.control.action.Action { + + ScrollToNext() { + super("", actionEvent -> { + + ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem(); + ZoneId timeZoneID = TimeLineController.getTimeZoneID(); + TemporalUnit selectedUnit = selectedChronoField.getBaseUnit(); + + int focusedIndex = table.getFocusModel().getFocusedIndex(); + CombinedEvent focusedItem = table.getFocusModel().getFocusedItem(); + if (-1 == focusedIndex || null == focusedItem) { + focusedItem = visibleEvents.first(); + focusedIndex = table.getItems().indexOf(focusedItem); + } + + ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID); + ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);// + for (ChronoField field : SCROLL_BY_UNITS) { + if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) { + nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());// + } + } + long nextMillis = nextDateTime.toInstant().toEpochMilli(); + + int nextIndex = table.getItems().size() - 1; + for (int i = focusedIndex; i < table.getItems().size(); i++) { + if (table.getItems().get(i).getStartMillis() >= nextMillis) { + nextIndex = i; + break; + } + } + scrollToAndFocus(nextIndex); + }); + setGraphic(new ImageView(NEXT)); + IntegerBinding size = Bindings.size(table.getItems()); + disabledProperty().bind(size.isEqualTo(0).or( + table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1)))); + } + + } + + private class ScrollToPrevious extends org.controlsfx.control.action.Action { + + ScrollToPrevious() { + super("", actionEvent -> { + ZoneId timeZoneID = TimeLineController.getTimeZoneID(); + ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem(); + TemporalUnit selectedUnit = selectedChronoField.getBaseUnit(); + + int focusedIndex = table.getFocusModel().getFocusedIndex(); + CombinedEvent focusedItem = table.getFocusModel().getFocusedItem(); + if (-1 == focusedIndex || null == focusedItem) { + focusedItem = visibleEvents.last(); + focusedIndex = table.getItems().indexOf(focusedItem); + } + + ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID); + ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);// + + for (ChronoField field : SCROLL_BY_UNITS) { + if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) { + previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());// + } + } + long previousMillis = previousDateTime.toInstant().toEpochMilli(); + + int previousIndex = 0; + for (int i = focusedIndex; i > 0; i--) { + if (table.getItems().get(i).getStartMillis() <= previousMillis) { + previousIndex = i; + break; + } + } + + scrollToAndFocus(previousIndex); + }); + setGraphic(new ImageView(PREVIOUS)); + disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1)); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java new file mode 100644 index 0000000000..a213cdb3f8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -0,0 +1,142 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 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.timeline.ui.listvew; + +import com.google.common.collect.ImmutableList; +import java.util.HashSet; +import java.util.List; +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.concurrent.Task; +import javafx.scene.Node; +import org.joda.time.Interval; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.ViewMode; +import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView; + +/** + * An AbstractTimeLineView that uses a TableView to display events. + */ +public class ListViewPane extends AbstractTimeLineView { + + private final ListTimeline listTimeline; + + /** + * Constructor + * + * @param controller + */ + public ListViewPane(TimeLineController controller) { + super(controller); + listTimeline = new ListTimeline(controller); + + //initialize chart; + setCenter(listTimeline); + + //keep controller's list of selected event IDs in sync with this list's + listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> { + controller.selectEventIDs(listTimeline.getSelectedEventIDs()); + }); + } + + @Override + protected Task getNewUpdateTask() { + return new ListUpdateTask(); + } + + @Override + protected void clearData() { + listTimeline.clear(); + } + + @Override + final protected ViewMode getViewMode() { + return ViewMode.LIST; + } + + @Override + protected ImmutableList getSettingsControls() { + return ImmutableList.of(); + } + + @Override + protected ImmutableList getTimeNavigationControls() { + return ImmutableList.copyOf(listTimeline.getNavControls()); + } + + @Override + protected boolean hasCustomTimeNavigationControls() { + return true; + } + + private class ListUpdateTask extends ViewRefreshTask { + + @NbBundle.Messages({ + "ListViewPane.loggedTask.queryDb=Retreiving event data", + "ListViewPane.loggedTask.name=Updating List View", + "ListViewPane.loggedTask.updateUI=Populating view"}) + ListUpdateTask() { + super(Bundle.ListViewPane_loggedTask_name(), true); + } + + @Override + protected Boolean call() throws Exception { + super.call(); + if (isCancelled()) { + return null; + } + + FilteredEventsModel eventsModel = getEventsModel(); + + //grab the currently selected event + HashSet selectedEvents = new HashSet<>(listTimeline.getSelectedEvents()); + + //clear the chart and set the time range. + resetView(eventsModel.getTimeRange()); + + //get the combined events to be displayed + updateMessage(Bundle.ListViewPane_loggedTask_queryDb()); + List combinedEvents = eventsModel.getCombinedEvents(); + + updateMessage(Bundle.ListViewPane_loggedTask_updateUI()); + Platform.runLater(() -> { + //put the combined events into the table. + listTimeline.setCombinedEvents(combinedEvents); + //restore the selected event + listTimeline.selectEvents(selectedEvents); + }); + + return combinedEvents.isEmpty() == false; + + } + + @Override + protected void cancelled() { + super.cancelled(); + getController().retreat(); + } + + @Override + protected void setDateValues(Interval timeRange) { + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/SwingFXMenuUtils.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/SwingFXMenuUtils.java new file mode 100644 index 0000000000..17241f2a36 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/SwingFXMenuUtils.java @@ -0,0 +1,117 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline.ui.listvew; + +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.MenuElement; +import javax.swing.SwingUtilities; + +/** + * Allows creation of JavaFX menus with the same structure as Swing menus and + * which invoke the same actions. + */ +public class SwingFXMenuUtils extends MenuItem { + + /** + * Factory method that creates a JavaFX MenuItem backed by a MenuElement + * + * @param jMenuElement The MenuElement to create a JavaFX menu for. + * + * @return a MenuItem for the given MenuElement + */ + public static MenuItem createFXMenu(MenuElement jMenuElement) { + if (jMenuElement == null) { + //Since null is sometime used to represenet a seperator, follow that convention. + return new SeparatorMenuItem(); + } else if (jMenuElement instanceof JMenu) { + return new MenuAdapter((JMenu) jMenuElement); + } else if (jMenuElement instanceof JPopupMenu) { + return new MenuAdapter((JPopupMenu) jMenuElement); + } else { + return new MenuItemAdapter((JMenuItem) jMenuElement); + } + } + + /** + * A JavaFX MenuItem that invokes the backing JMenuItem when clicked. + */ + private static class MenuItemAdapter extends MenuItem { + + private MenuItemAdapter(final JMenuItem jMenuItem) { + super(jMenuItem.getText()); + setOnAction(actionEvent -> SwingUtilities.invokeLater(jMenuItem::doClick)); + } + } + + /** + * A JavaFX Menu that has the same structure as a given Swing JMenu or + * JPopupMenu. + */ + private static class MenuAdapter extends Menu { + + /** + * Constructor for JMenu + * + * @param jMenu The JMenu to parallel in this Menu. + */ + MenuAdapter(final JMenu jMenu) { + super(jMenu.getText()); + populateSubMenus(jMenu); + } + + /** + * Constructor for JPopupMenu + * + * @param jPopupMenu The JPopupMenu to parallel in this Menu. + */ + MenuAdapter(JPopupMenu jPopupMenu) { + super(jPopupMenu.getLabel()); + populateSubMenus(jPopupMenu); + } + + /** + * Populate the sub menus of this menu. + * + * @param menu The MenuElement whose sub elements will be used to + * populate the sub menus of this menu. + */ + private void populateSubMenus(MenuElement menu) { + for (MenuElement menuElement : menu.getSubElements()) { + if (menuElement == null) { + //Since null is sometime used to represenet a seperator, follow that convention. + getItems().add(new SeparatorMenuItem()); + + } else if (menuElement instanceof JMenuItem) { + getItems().add(SwingFXMenuUtils.createFXMenu(menuElement)); + + } else if (menuElement instanceof JPopupMenu) { + populateSubMenus(menuElement); + + } else { + throw new UnsupportedOperationException("Unown MenuElement subclass: " + menuElement.getClass().getName()); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java index 9877150e8b..20d75c32db 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java @@ -22,6 +22,7 @@ import java.util.function.Consumer; import java.util.function.Function; import javafx.application.Platform; import javafx.beans.InvalidationListener; +import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.Label; @@ -31,7 +32,7 @@ import javafx.util.StringConverter; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.VisualizationMode; +import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; @@ -102,15 +103,14 @@ public class ZoomSettingsPane extends TitledPane { Function.identity()); descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text()); //the description slider is only usefull in the detail view - descrLODSlider.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS)); + descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(ViewMode.COUNTS)); /** * In order for the selected value in the time unit slider to correspond - * to the amount of time used as units along the x-axis of the - * visualization, and since we don't want to show "forever" as a time - * unit, the range of the slider is restricted, and there is an offset - * of 1 between the "real" value, and what is shown in the slider - * labels. + * to the amount of time used as units along the x-axis of the view, and + * since we don't want to show "forever" as a time unit, the range of + * the slider is restricted, and there is an offset of 1 between the + * "real" value, and what is shown in the slider labels. */ timeUnitSlider.setMax(TimeUnits.values().length - 2); configureSliderListeners(timeUnitSlider, @@ -121,6 +121,12 @@ public class ZoomSettingsPane extends TitledPane { modelTimeRange -> RangeDivisionInfo.getRangeDivisionInfo(modelTimeRange).getPeriodSize().ordinal() - 1, index -> index + 1); //compensate for the -1 above when mapping to the Enum whose displayName will be shown at index timeUnitLabel.setText(Bundle.ZoomSettingsPane_timeUnitLabel_text()); + + //hide the whole panel in list mode + BooleanBinding notListMode = controller.viewModeProperty().isNotEqualTo(ViewMode.LIST); + visibleProperty().bind(notListMode); + managedProperty().bind(notListMode); + } /** @@ -176,7 +182,7 @@ public class ZoomSettingsPane extends TitledPane { //set the tick labels to the enum displayNames slider.setLabelFormatter(new EnumSliderLabelFormatter<>(enumClass, labelIndexMapper)); - //make a listener to responds to slider value changes (by updating the visualization) + //make a listener to responds to slider value changes (by updating the view) final InvalidationListener sliderListener = observable -> { //only process event if the slider value is not changing (user has released slider thumb) if (slider.isValueChanging() == false) { diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index f1e05e398c..22b2a8cfe4 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -14,6 +14,14 @@ 8.25.1
+ + org.openide.util.ui + + + + 9.4.1 + + diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index bff28c8d43..b225bc745e 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -15,6 +15,22 @@ 1.32.1 + + org.netbeans.api.progress.compat8 + + + + 1.46.1 + + + + org.netbeans.api.progress.nb + + + + 1.46.1 + + org.netbeans.modules.options.api @@ -89,6 +105,14 @@ 8.19.1 + + org.openide.util.ui + + + + 9.4.1 + + org.openide.windows diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanelController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanelController.java index 28d507619e..3d893fa88a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanelController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanelController.java @@ -31,7 +31,7 @@ import org.openide.util.Lookup; */ @OptionsPanelController.TopLevelRegistration( categoryName = "#OptionsCategory_Name_Options", - iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png", + iconBase = "org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_32.png", keywords = "#OptionsCategory_Keywords_Options", keywordsCategory = "Options", position = 10 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java old mode 100644 new mode 100755 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/SwingMenuItemAdapter.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/SwingMenuItemAdapter.java index 5f995433da..59f48dc094 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/SwingMenuItemAdapter.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/SwingMenuItemAdapter.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javax.swing.JMenu; @@ -34,9 +33,7 @@ public class SwingMenuItemAdapter extends MenuItem { SwingMenuItemAdapter(final JMenuItem jMenuItem) { super(jMenuItem.getText()); this.jMenuItem = jMenuItem; - setOnAction((ActionEvent t) -> { - jMenuItem.doClick(); - }); + setOnAction(actionEvent -> jMenuItem.doClick()); } public static MenuItem create(MenuElement jmenuItem) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_26.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_26.png new file mode 100755 index 0000000000..eea6d50835 Binary files /dev/null and b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_26.png differ diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_32.png similarity index 100% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_32.png diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_48.png similarity index 100% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/btn_icon_image_gallery_48.png diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index a0c6d6ff4b..38ff90aa1b 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -15,6 +15,22 @@ 1.24.1 + + org.netbeans.api.progress.compat8 + + + + 1.46.1 + + + + org.netbeans.api.progress.nb + + + + 1.46.1 + + org.netbeans.modules.options.api @@ -81,6 +97,14 @@ 8.8.1 + + org.openide.util.ui + + + + 9.4.1 + + org.openide.windows diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java index 8002351ea1..83dac4645b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.keywordsearch; import java.nio.charset.Charset; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form index 2d409701cc..da596bfa71 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.form @@ -187,11 +187,12 @@ + + + - - diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index 5b3364451e..ccafb5b2a5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,6 +45,13 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import static javax.swing.SwingConstants.CENTER; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import org.openide.util.NbBundle.Messages; /** * GlobalEditListPanel widget to manage keywords in lists @@ -52,7 +59,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionListener, OptionsPanel { private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName()); - private KeywordTableModel tableModel; + private static final long serialVersionUID = 1L; + private final KeywordTableModel tableModel; private KeywordList currentKeywordList; private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); @@ -73,8 +81,6 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis saveListButton.setToolTipText(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.saveCurrentWIthNewNameToolTip")); deleteWordButton.setToolTipText(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.removeSelectedMsg")); - keywordTable.setShowHorizontalLines(false); - keywordTable.setShowVerticalLines(false); keywordTable.getParent().setBackground(keywordTable.getBackground()); final int width = jScrollPane1.getPreferredSize().width; keywordTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); @@ -85,7 +91,8 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis column.setPreferredWidth(((int) (width * 0.90))); } else { column.setPreferredWidth(((int) (width * 0.10))); - //column.setCellRenderer(new CheckBoxRenderer()); + column.setCellRenderer(new CheckBoxRenderer()); + column.setHeaderRenderer(new HeaderRenderer(keywordTable)); } } keywordTable.setCellSelectionEnabled(false); @@ -239,9 +246,8 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis keywordTable.setModel(tableModel); keywordTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF); + keywordTable.setGridColor(new java.awt.Color(153, 153, 153)); keywordTable.setMaximumSize(new java.awt.Dimension(30000, 30000)); - keywordTable.setShowHorizontalLines(false); - keywordTable.setShowVerticalLines(false); keywordTable.getTableHeader().setReorderingAllowed(false); jScrollPane1.setViewportView(keywordTable); @@ -686,4 +692,55 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis resync(); } } + + /** + * A cell renderer for boolean cells that shows a center-aligned green check + * mark if true, nothing if false. + */ + private class CheckBoxRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 1L; + final ImageIcon theCheck = new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/checkmark.png")); // NON-NLS + + CheckBoxRenderer() { + setHorizontalAlignment(CENTER); + } + + @Override + @Messages("IsRegularExpression=Keyword is a regular expression") + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + + if ((value instanceof Boolean)) { + if ((Boolean) value) { + setIcon(theCheck); + setToolTipText(Bundle.IsRegularExpression()); + } else { + setIcon(null); + setToolTipText(null); + } + } + return this; + } + } + + /** + * A cell renderer for header cells that center-aligns the header text. + */ + private static class HeaderRenderer implements TableCellRenderer { + + private DefaultTableCellRenderer renderer; + + public HeaderRenderer(JTable table) { + renderer = (DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer(); + renderer.setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int col) { + return renderer.getTableCellRendererComponent( + table, value, isSelected, hasFocus, row, col); + } + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java index 4e03038b39..27e9ccd637 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HtmlTextExtractor.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; +import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.ReadContentInputStream; @@ -48,7 +49,6 @@ class HtmlTextExtractor implements TextExtractor { private static final int MAX_SIZE = 50000000; //private static final String UTF16BOM = "\uFEFF"; disabled prepending of BOM private final char[] textChunkBuf = new char[MAX_EXTR_TEXT_CHARS]; - private KeywordSearchIngestModule module; private AbstractFile sourceFile; private int numChunks = 0; @@ -63,8 +63,7 @@ class HtmlTextExtractor implements TextExtractor { //"application/xml-dtd", ); - HtmlTextExtractor(KeywordSearchIngestModule module) { - this.module = module; + HtmlTextExtractor() { ingester = Server.getIngester(); } @@ -98,7 +97,7 @@ class HtmlTextExtractor implements TextExtractor { } @Override - public boolean index(AbstractFile sourceFile) throws IngesterException { + public boolean index(AbstractFile sourceFile, IngestJobContext context) throws IngesterException { this.sourceFile = sourceFile; numChunks = 0; //unknown until indexing is done @@ -125,6 +124,10 @@ class HtmlTextExtractor implements TextExtractor { boolean eof = false; //we read max 1024 chars at time, this seems to max what this Reader would return while (!eof && (readSize = reader.read(textChunkBuf, 0, SINGLE_READ_CHARS)) != -1) { + if (context.fileIngestIsCancelled()) { + ingester.ingest(this); + return true; + } totalRead += readSize; //consume more bytes to fill entire chunk (leave EXTRA_CHARS to end the word) @@ -203,7 +206,6 @@ class HtmlTextExtractor implements TextExtractor { //after all chunks, ingest the parent file without content itself, and store numChunks ingester.ingest(this); - return success; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index 77568a8916..e7325f776c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -31,6 +31,7 @@ import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TextUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.AbstractFile; @@ -298,6 +299,21 @@ class Ingester { String s = ""; try { s = new String(docChunkContentBuf, 0, read, docContentEncoding); + // Sanitize by replacing non-UTF-8 characters with caret '^' before adding to index + char[] chars = null; + for (int i = 0; i < s.length(); i++) { + if (!TextUtil.isValidSolrUTF8(s.charAt(i))) { + // only convert string to char[] if there is a non-UTF8 character + if (chars == null) { + chars = s.toCharArray(); + } + chars[i] = '^'; + } + } + // check if the string was modified (i.e. there was a non-UTF8 character found) + if (chars != null) { + s = new String(chars); + } } catch (UnsupportedEncodingException ex) { logger.log(Level.SEVERE, "Unsupported encoding", ex); //NON-NLS } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index cda05d86b3..13f69b6998 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -29,7 +29,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; @@ -207,14 +206,14 @@ public final class KeywordSearchIngestModule implements FileIngestModule { } //initialize extractors - stringExtractor = new StringsTextExtractor(this); + stringExtractor = new StringsTextExtractor(); stringExtractor.setScripts(KeywordSearchSettings.getStringExtractScripts()); stringExtractor.setOptions(KeywordSearchSettings.getStringExtractOptions()); textExtractors = new ArrayList<>(); //order matters, more specific extractors first - textExtractors.add(new HtmlTextExtractor(this)); - textExtractors.add(new TikaTextExtractor(this)); + textExtractors.add(new HtmlTextExtractor()); + textExtractors.add(new TikaTextExtractor()); indexer = new Indexer(); initialized = true; @@ -236,15 +235,24 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (KeywordSearchSettings.getSkipKnown() && abstractFile.getKnown().equals(FileKnown.KNOWN)) { //index meta-data only + if (context.fileIngestIsCancelled()) { + return ProcessResult.OK; + } indexer.indexFile(abstractFile, false); return ProcessResult.OK; } //index the file and content (if the content is supported) + if (context.fileIngestIsCancelled()) { + return ProcessResult.OK; + } indexer.indexFile(abstractFile, true); // Start searching if it hasn't started already if (!startedSearching) { + if (context.fileIngestIsCancelled()) { + return ProcessResult.OK; + } List keywordListNames = settings.getNamesOfEnabledKeyWordLists(); SearchRunner.getInstance().startJob(jobId, dataSourceId, keywordListNames); startedSearching = true; @@ -418,7 +426,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { //logger.log(Level.INFO, "Extractor: " + fileExtract + ", file: " + aFile.getName()); //divide into chunks and index - return fileExtract.index(aFile); + return fileExtract.index(aFile, context); } /** @@ -431,7 +439,10 @@ public final class KeywordSearchIngestModule implements FileIngestModule { */ private boolean extractStringsAndIndex(AbstractFile aFile) { try { - if (stringExtractor.index(aFile)) { + if (context.fileIngestIsCancelled()) { + return true; + } + if (stringExtractor.index(aFile, KeywordSearchIngestModule.this.context)) { putIngestStatus(jobId, aFile.getId(), IngestStatus.STRINGS_INGESTED); return true; } else { @@ -480,14 +491,21 @@ public final class KeywordSearchIngestModule implements FileIngestModule { // unallocated and unused blocks can only have strings extracted from them. if ((aType.equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || aType.equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS))) { + if (context.fileIngestIsCancelled()) { + return; + } extractStringsAndIndex(aFile); return; } final long size = aFile.getSize(); //if not to index content, or a dir, or 0 content, index meta data only + if ((indexContent == false || aFile.isDir() || size == 0)) { try { + if (context.fileIngestIsCancelled()) { + return; + } ingester.ingest(aFile, false); //meta-data only putIngestStatus(jobId, aFile.getId(), IngestStatus.METADATA_INGESTED); } catch (IngesterException ex) { @@ -497,9 +515,12 @@ public final class KeywordSearchIngestModule implements FileIngestModule { return; } - String detectedFormat; + String fileType; try { - detectedFormat = fileTypeDetector.getFileType(aFile); + if (context.fileIngestIsCancelled()) { + return; + } + fileType = fileTypeDetector.getFileType(aFile); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Could not detect format using fileTypeDetector for file: %s", aFile), ex); //NON-NLS return; @@ -507,8 +528,11 @@ public final class KeywordSearchIngestModule implements FileIngestModule { // we skip archive formats that are opened by the archive module. // @@@ We could have a check here to see if the archive module was enabled though... - if (TextExtractor.ARCHIVE_MIME_TYPES.contains(detectedFormat)) { + if (TextExtractor.ARCHIVE_MIME_TYPES.contains(fileType)) { try { + if (context.fileIngestIsCancelled()) { + return; + } ingester.ingest(aFile, false); //meta-data only putIngestStatus(jobId, aFile.getId(), IngestStatus.METADATA_INGESTED); } catch (IngesterException ex) { @@ -519,27 +543,29 @@ public final class KeywordSearchIngestModule implements FileIngestModule { } boolean wasTextAdded = false; - if (isTextExtractSupported(aFile, detectedFormat)) { - //extract text with one of the extractors, divide into chunks and index with Solr - try { - //logger.log(Level.INFO, "indexing: " + aFile.getName()); - if (!extractTextAndIndex(aFile, detectedFormat)) { - logger.log(Level.WARNING, "Failed to extract text and ingest, file ''{0}'' (id: {1}).", new Object[]{aFile.getName(), aFile.getId()}); //NON-NLS - putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); - } else { - putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED); - wasTextAdded = true; - } - } catch (IngesterException e) { - logger.log(Level.INFO, "Could not extract text with Tika, " + aFile.getId() + ", " //NON-NLS - + aFile.getName(), e); - putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_INDEXING); - } catch (Exception e) { - logger.log(Level.WARNING, "Error extracting text with Tika, " + aFile.getId() + ", " //NON-NLS - + aFile.getName(), e); - putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); + //extract text with one of the extractors, divide into chunks and index with Solr + try { + //logger.log(Level.INFO, "indexing: " + aFile.getName()); + if (context.fileIngestIsCancelled()) { + return; } + if (!extractTextAndIndex(aFile, fileType)) { + logger.log(Level.WARNING, "Failed to extract text and ingest, file ''{0}'' (id: {1}).", new Object[]{aFile.getName(), aFile.getId()}); //NON-NLS + putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); + } else { + putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED); + wasTextAdded = true; + } + + } catch (IngesterException e) { + logger.log(Level.INFO, "Could not extract text with Tika, " + aFile.getId() + ", " //NON-NLS + + aFile.getName(), e); + putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_INDEXING); + } catch (Exception e) { + logger.log(Level.WARNING, "Error extracting text with Tika, " + aFile.getId() + ", " //NON-NLS + + aFile.getName(), e); + putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); } // if it wasn't supported or had an error, default to strings diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 26c93f21e3..aa0bb5d830 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -25,7 +25,6 @@ import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.apache.solr.common.util.ContentStreamBase.StringStream; import org.openide.util.lookup.ServiceProvider; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java index 7599ce1c85..3bbc97dcfc 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; +import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException; import org.sleuthkit.datamodel.AbstractFile; @@ -44,7 +45,6 @@ class StringsTextExtractor implements TextExtractor { private static final int BOM_LEN = 0; //disabled prepending of BOM private static final Charset INDEX_CHARSET = Server.DEFAULT_INDEXED_TEXT_CHARSET; private static final SCRIPT DEFAULT_SCRIPT = SCRIPT.LATIN_2; - private KeywordSearchIngestModule module; private AbstractFile sourceFile; private int numChunks = 0; private final List