diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 8f8b3b4b42..a2feedc54f 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -36,10 +36,10 @@ GetTagNameDialog.tagNameExistsTskCore.msg=The {0} tag name already exists in the OpenLogFolder.error1=Log File Not Found: {0} OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder CTL_OpenLogFolder=Open Log Folder -CTL_OpenOutputFolder=Open Output Folder -OpenOutputFolder.error1=Output Folder Not Found: {0} -OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available. -OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder +CTL_OpenOutputFolder=Open Case Folder +OpenOutputFolder.error1=Case Folder Not Found: {0} +OpenOutputFolder.noCaseOpen=No open case, therefore no current case folder available. +OpenOutputFolder.CouldNotOpenOutputFolder=Could not open case folder ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED index 506786c42d..a3a13c0cff 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED @@ -79,10 +79,10 @@ GetTagNameDialog.tagNameExistsTskCore.msg=The {0} tag name already exists in the OpenLogFolder.error1=Log File Not Found: {0} OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder CTL_OpenLogFolder=Open Log Folder -CTL_OpenOutputFolder=Open Output Folder -OpenOutputFolder.error1=Output Folder Not Found: {0} -OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available. -OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder +CTL_OpenOutputFolder=Open Case Folder +OpenOutputFolder.error1=Case Folder Not Found: {0} +OpenOutputFolder.noCaseOpen=No open case, therefore no current case folder available. +OpenOutputFolder.CouldNotOpenOutputFolder=Could not open case folder # {0} - old tag name # {1} - artifactID ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.form index bb12ba69d9..5e9ec1f38a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.form @@ -76,6 +76,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index 5f164cd00f..401663056b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,10 @@ import java.io.File; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.JTable; +import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; @@ -51,6 +54,16 @@ class OpenRecentCasePanel extends javax.swing.JPanel { */ private OpenRecentCasePanel() { initComponents(); + imagesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + imagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + //enable the ok button when something is selected + if (!e.getValueIsAdjusting()){ + openButton.setEnabled(imagesTable.getSelectedRowCount() > 0); + } + } + }); } /* @@ -90,9 +103,6 @@ class OpenRecentCasePanel extends javax.swing.JPanel { // If there are any images, let's select the first one if (imagesTable.getRowCount() > 0) { imagesTable.setRowSelectionInterval(0, 0); - openButton.setEnabled(true); - } else { - openButton.setEnabled(false); } } @@ -251,6 +261,7 @@ class OpenRecentCasePanel extends javax.swing.JPanel { cancelButton.setText(org.openide.util.NbBundle.getMessage(OpenRecentCasePanel.class, "OpenRecentCasePanel.cancelButton.text")); // NOI18N openButton.setText(org.openide.util.NbBundle.getMessage(OpenRecentCasePanel.class, "OpenRecentCasePanel.openButton.text")); // NOI18N + openButton.setEnabled(false); openButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { openButtonActionPerformed(evt); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index 5a2521ef8e..31ccf6bac7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -754,9 +754,8 @@ public class FileManager implements Closeable { * the parent local directory. * @param localFile The local/logical file or directory. * @param progressUpdater notifier to receive progress notifications on - * folders added, or null if not used - * @param progressUpdater Called after each file/directory is added to the - * case database. + * folders added, or null if not used. Called after + * each file/directory is added to the case database. * * @return An AbstractFile representation of the local/logical file. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 259831824c..9713a5b47a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -810,6 +810,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi occurrencePanel.getPreferredSize(); detailsPanelScrollPane.setViewportView(occurrencePanel); } else { + String currentCaseName; + try { + currentCaseName = Case.getCurrentCaseThrows().getName(); + } catch (NoCurrentCaseException ex) { + currentCaseName = null; + LOGGER.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex); + } for (CorrelationAttributeInstance corAttr : correlationAttributes) { Map correlatedNodeDataMap = new HashMap<>(0); @@ -823,7 +830,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { dataSourcesTableModel.addNodeData(nodeData); } - } else { + } else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) { dataSourcesTableModel.addNodeData(nodeData); } } catch (EamDbException ex) { @@ -1153,8 +1160,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { tempCaseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); } catch (EamDbException ignored) { - tempCaseUUID = UUID_PLACEHOLDER_STRING; - //place holder value will be used since correlation attribute was unavailble + //non central repo nodeData won't have a correlation case + try { + tempCaseUUID = Case.getCurrentCaseThrows().getName(); + //place holder value will be used since correlation attribute was unavailble + } catch (NoCurrentCaseException ex) { + LOGGER.log(Level.WARNING, "Unable to get current case", ex); + tempCaseUUID = UUID_PLACEHOLDER_STRING; + } } caseUUID = tempCaseUUID; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java index 240826b8b7..76871d074d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java @@ -21,9 +21,13 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; +import java.util.logging.Level; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Model for cells in the data sources section of the other occurrences data @@ -32,6 +36,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OtherOccurrencesDataSourcesTableModel.class.getName()); private final Set dataSourceSet = new LinkedHashSet<>(); /** @@ -140,8 +145,14 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { try { caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); } catch (EamDbException ignored) { - caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); - //place holder value will be used since correlation attribute was unavailble + //non central repo nodeData won't have a correlation case + try { + caseUUID = Case.getCurrentCaseThrows().getName(); + //place holder value will be used since correlation attribute was unavailble + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Unable to get current case", ex); + caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + } } dataSourceSet.add(new DataSourceColumnItem(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName(), caseUUID)); fireTableDataChanged(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java index f759ecfd2a..3344951857 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java @@ -22,10 +22,14 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Model for cells in the files section of the other occurrences data content @@ -34,6 +38,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; public class OtherOccurrencesFilesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(OtherOccurrencesFilesTableModel.class.getName()); private final List nodeKeys = new ArrayList<>(); private final Map> nodeMap = new HashMap<>(); @@ -119,8 +124,14 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { try { caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); } catch (EamDbException ignored) { - caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); - //place holder value will be used since correlation attribute was unavailble + //non central repo nodeData won't have a correlation case + try { + caseUUID = Case.getCurrentCaseThrows().getName(); + //place holder value will be used since correlation attribute was unavailble + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Unable to get current case", ex); + caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + } } return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath() + caseUUID; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java index f6f3b52c6e..08ed088cf1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java @@ -123,7 +123,7 @@ final public class CorrelationAttributeNormalizer { /** * Verify there are no slashes or invalid domain name characters (such as - * '?' or \: ). Normalize to lower case. + * '?'). Normalize to lower case. */ private static String normalizeDomain(String data) throws CorrelationAttributeNormalizationException { DomainValidator validator = DomainValidator.getInstance(true); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form index 82383f135f..6c189d736e 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form @@ -29,7 +29,7 @@ - + @@ -44,7 +44,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java index 6438b399e7..c9ecbfc146 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java @@ -88,7 +88,7 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { .addComponent(flagTaggedNotableItemsCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(flagPreviouslySeenDevicesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(createCorrelationPropertiesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addContainerGap(65, Short.MAX_VALUE)) + .addContainerGap(47, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -101,7 +101,7 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { .addComponent(flagTaggedNotableItemsCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(flagPreviouslySeenDevicesCheckbox) - .addContainerGap(197, Short.MAX_VALUE)) + .addContainerGap(47, Short.MAX_VALUE)) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties index 635f5a558c..0b30e2584f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties @@ -57,7 +57,6 @@ ManageCorrelationPropertiesDialog.cancelButton.text=Cancel ManageCorrelationPropertiesDialog.okButton.text=OK GlobalSettingsPanel.bnManageProperties.text=Manage Correlation Properties EamDbSettingsDialog.lbDatabaseDesc.text=Database File: -EamDbSettingsDialog.lbFullDbPath.text= GlobalSettingsPanel.cbUseCentralRepo.text=Use a Central Repository GlobalSettingsPanel.organizationTextArea.text=Organization information can be tracked in the Central Repository. GlobalSettingsPanel.manageOrganizationButton.text=Manage Organizations @@ -84,4 +83,4 @@ ManageCasesDialog.orgLabel.text=Organization: ManageCasesDialog.closeButton.text=Close ManageCasesDialog.notesLabel.text=Notes: ManageCasesDialog.dataSourcesLabel.text=Data Sources: -ManageCasesDialog.caseInfoLabel.text=Case Info: \ No newline at end of file +ManageCasesDialog.caseInfoLabel.text=Case Info: diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index 454cb29e39..dc42aa3b68 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -115,7 +115,6 @@ ManageCorrelationPropertiesDialog.cancelButton.text=Cancel ManageCorrelationPropertiesDialog.okButton.text=OK GlobalSettingsPanel.bnManageProperties.text=Manage Correlation Properties EamDbSettingsDialog.lbDatabaseDesc.text=Database File: -EamDbSettingsDialog.lbFullDbPath.text= GlobalSettingsPanel.cbUseCentralRepo.text=Use a Central Repository GlobalSettingsPanel.organizationTextArea.text=Organization information can be tracked in the Central Repository. GlobalSettingsPanel.manageOrganizationButton.text=Manage Organizations diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form index 50979ba938..b063773f48 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form @@ -9,6 +9,7 @@ + @@ -35,18 +36,18 @@ - + - - - + + + - + @@ -59,9 +60,9 @@ - + - + @@ -85,6 +86,15 @@ + + + + + + + + + @@ -115,43 +125,37 @@ - + - - + + + - + - - - - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + @@ -171,40 +175,42 @@ - + - + - + - + - + - + - - - - + + + + + + + + - - @@ -215,6 +221,9 @@ + + + @@ -225,6 +234,9 @@ + + + @@ -242,42 +254,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -291,6 +338,9 @@ + + + @@ -298,6 +348,15 @@ + + + + + + + + + @@ -305,12 +364,8 @@ - - - - - - + + @@ -324,6 +379,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 19b1c4c8e5..5c68b51b92 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -142,12 +142,17 @@ public class EamDbSettingsDialog extends JDialog { lbSingleUserSqLite = new javax.swing.JLabel(); lbDatabaseType = new javax.swing.JLabel(); lbDatabaseDesc = new javax.swing.JLabel(); - lbFullDbPath = new javax.swing.JLabel(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); + dataBaseFileScrollPane = new javax.swing.JScrollPane(); + dataBaseFileTextArea = new javax.swing.JTextArea(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setResizable(false); org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.bnCancel.text")); // NOI18N + bnCancel.setMaximumSize(new java.awt.Dimension(79, 23)); + bnCancel.setMinimumSize(new java.awt.Dimension(79, 23)); + bnCancel.setPreferredSize(new java.awt.Dimension(79, 23)); bnCancel.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnCancelActionPerformed(evt); @@ -169,25 +174,30 @@ public class EamDbSettingsDialog extends JDialog { .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bnOk) .addGap(11, 11, 11) - .addComponent(bnCancel) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); + + pnButtonsLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnCancel, bnOk}); + pnButtonsLayout.setVerticalGroup( pnButtonsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(pnButtonsLayout.createSequentialGroup() .addGap(0, 0, 0) .addGroup(pnButtonsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(bnOk) - .addComponent(bnCancel)) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0)) ); pnSQLiteSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); org.openide.awt.Mnemonics.setLocalizedText(lbDatabasePath, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbDatabasePath.text")); // NOI18N + lbDatabasePath.setPreferredSize(new java.awt.Dimension(80, 14)); tfDatabasePath.setText(org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.tfDatabasePath.text")); // NOI18N tfDatabasePath.setToolTipText(org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.tfDatabasePath.toolTipText")); // NOI18N + tfDatabasePath.setPreferredSize(new java.awt.Dimension(420, 23)); org.openide.awt.Mnemonics.setLocalizedText(bnDatabasePathFileOpen, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.bnDatabasePathFileOpen.text")); // NOI18N bnDatabasePathFileOpen.addActionListener(new java.awt.event.ActionListener() { @@ -197,14 +207,27 @@ public class EamDbSettingsDialog extends JDialog { }); org.openide.awt.Mnemonics.setLocalizedText(lbHostName, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbHostName.text")); // NOI18N + lbHostName.setPreferredSize(new java.awt.Dimension(80, 14)); + + tbDbHostname.setPreferredSize(new java.awt.Dimension(509, 20)); org.openide.awt.Mnemonics.setLocalizedText(lbPort, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbPort.text")); // NOI18N + lbPort.setPreferredSize(new java.awt.Dimension(80, 14)); + + tbDbPort.setPreferredSize(new java.awt.Dimension(509, 20)); org.openide.awt.Mnemonics.setLocalizedText(lbUserName, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbUserName.text")); // NOI18N + lbUserName.setPreferredSize(new java.awt.Dimension(80, 14)); + + tbDbUsername.setPreferredSize(new java.awt.Dimension(509, 20)); org.openide.awt.Mnemonics.setLocalizedText(lbUserPassword, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbUserPassword.text")); // NOI18N + lbUserPassword.setPreferredSize(new java.awt.Dimension(80, 14)); + + jpDbPassword.setPreferredSize(new java.awt.Dimension(509, 20)); cbDatabaseType.setModel(new javax.swing.DefaultComboBoxModel<>(new EamDbPlatformEnum[]{EamDbPlatformEnum.POSTGRESQL, EamDbPlatformEnum.SQLITE})); + cbDatabaseType.setPreferredSize(new java.awt.Dimension(120, 20)); cbDatabaseType.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cbDatabaseTypeActionPerformed(evt); @@ -212,12 +235,25 @@ public class EamDbSettingsDialog extends JDialog { }); org.openide.awt.Mnemonics.setLocalizedText(lbSingleUserSqLite, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbSingleUserSqLite.text")); // NOI18N + lbSingleUserSqLite.setPreferredSize(new java.awt.Dimension(381, 14)); org.openide.awt.Mnemonics.setLocalizedText(lbDatabaseType, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbDatabaseType.text")); // NOI18N + lbDatabaseType.setMaximumSize(new java.awt.Dimension(80, 14)); + lbDatabaseType.setMinimumSize(new java.awt.Dimension(80, 14)); + lbDatabaseType.setPreferredSize(new java.awt.Dimension(80, 14)); org.openide.awt.Mnemonics.setLocalizedText(lbDatabaseDesc, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbDatabaseDesc.text")); // NOI18N + lbDatabaseDesc.setPreferredSize(new java.awt.Dimension(80, 14)); - org.openide.awt.Mnemonics.setLocalizedText(lbFullDbPath, org.openide.util.NbBundle.getMessage(EamDbSettingsDialog.class, "EamDbSettingsDialog.lbFullDbPath.text")); // NOI18N + dataBaseFileScrollPane.setBorder(null); + + dataBaseFileTextArea.setEditable(false); + dataBaseFileTextArea.setBackground(new java.awt.Color(240, 240, 240)); + dataBaseFileTextArea.setColumns(20); + dataBaseFileTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + dataBaseFileTextArea.setLineWrap(true); + dataBaseFileTextArea.setRows(3); + dataBaseFileScrollPane.setViewportView(dataBaseFileTextArea); javax.swing.GroupLayout pnSQLiteSettingsLayout = new javax.swing.GroupLayout(pnSQLiteSettings); pnSQLiteSettings.setLayout(pnSQLiteSettingsLayout); @@ -226,34 +262,30 @@ public class EamDbSettingsDialog extends JDialog { .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addContainerGap() .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbHostName) - .addComponent(lbPort) - .addComponent(lbUserName) - .addComponent(lbDatabaseType) + .addComponent(lbHostName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbUserName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(lbDatabasePath, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(lbUserPassword, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addComponent(lbDatabaseDesc)) + .addComponent(lbDatabaseDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbUserPassword, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGap(10, 10, 10) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbFullDbPath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() + .addComponent(tfDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(bnDatabasePathFileOpen)) .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addComponent(cbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.DEFAULT_SIZE, 467, Short.MAX_VALUE) - .addGap(9, 9, 9)) - .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() - .addComponent(tfDatabasePath) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnDatabasePathFileOpen) - .addGap(11, 11, 11)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnSQLiteSettingsLayout.createSequentialGroup() - .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(tbDbHostname, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jpDbPassword, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tbDbUsername) - .addComponent(tbDbPort, javax.swing.GroupLayout.Alignment.LEADING)) - .addGap(10, 10, 10)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jpDbPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(tbDbUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(tbDbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(tbDbHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(dataBaseFileScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 509, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addGap(55, 55, 55) .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -266,35 +298,36 @@ public class EamDbSettingsDialog extends JDialog { .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbSingleUserSqLite)) - .addComponent(lbDatabaseType, javax.swing.GroupLayout.Alignment.TRAILING)) + .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbDatabaseType, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbDatabasePath) - .addComponent(tfDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(tfDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(bnDatabasePathFileOpen)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbHostName)) + .addComponent(lbHostName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbPort)) + .addComponent(lbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbUserName)) + .addComponent(lbUserName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jpDbPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbUserPassword)) + .addComponent(lbUserPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbFullDbPath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(lbDatabaseDesc, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() + .addComponent(lbDatabaseDesc, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(dataBaseFileScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); @@ -312,11 +345,11 @@ public class EamDbSettingsDialog extends JDialog { layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(10, 10, 10) - .addComponent(pnSQLiteSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE) + .addContainerGap() + .addComponent(pnSQLiteSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pnButtons, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(10, 10, 10)) + .addContainerGap()) ); pack(); @@ -357,6 +390,7 @@ public class EamDbSettingsDialog extends JDialog { } try { tfDatabasePath.setText(databaseFile.getCanonicalPath()); + tfDatabasePath.setCaretPosition(tfDatabasePath.getText().length()); valid(); } catch (IOException ex) { logger.log(Level.SEVERE, "Failed to get path of selected database file", ex); // NON-NLS @@ -584,14 +618,15 @@ public class EamDbSettingsDialog extends JDialog { }//GEN-LAST:event_cbDatabaseTypeActionPerformed private void updateFullDbPath() { - lbFullDbPath.setText(tfDatabasePath.getText() + File.separator + CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT); + dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT); + dataBaseFileTextArea.setCaretPosition(dataBaseFileTextArea.getText().length()); } private void displayDatabaseSettings(boolean isPostgres) { lbDatabasePath.setVisible(!isPostgres); tfDatabasePath.setVisible(!isPostgres); lbDatabaseDesc.setVisible(!isPostgres); - lbFullDbPath.setVisible(!isPostgres); + dataBaseFileTextArea.setVisible(!isPostgres); lbSingleUserSqLite.setVisible(!isPostgres); bnDatabasePathFileOpen.setVisible(!isPostgres); lbHostName.setVisible(isPostgres); @@ -855,13 +890,14 @@ public class EamDbSettingsDialog extends JDialog { private javax.swing.ButtonGroup bnGrpDatabasePlatforms; private javax.swing.JButton bnOk; private javax.swing.JComboBox cbDatabaseType; + private javax.swing.JScrollPane dataBaseFileScrollPane; + private javax.swing.JTextArea dataBaseFileTextArea; private javax.swing.JFileChooser fcDatabasePath; private javax.swing.Box.Filler filler1; private javax.swing.JPasswordField jpDbPassword; private javax.swing.JLabel lbDatabaseDesc; private javax.swing.JLabel lbDatabasePath; private javax.swing.JLabel lbDatabaseType; - private javax.swing.JLabel lbFullDbPath; private javax.swing.JLabel lbHostName; private javax.swing.JLabel lbPort; private javax.swing.JLabel lbSingleUserSqLite; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form index 5124d1d85d..a3ab756f64 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form @@ -7,6 +7,7 @@ + @@ -49,16 +50,16 @@ - - - + + + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java index 550244dffe..883efff72b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java @@ -132,6 +132,7 @@ final class ManageCorrelationPropertiesDialog extends javax.swing.JDialog { taInstructions = new javax.swing.JTextArea(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setResizable(false); org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(ManageCorrelationPropertiesDialog.class, "ManageCorrelationPropertiesDialog.okButton.text")); // NOI18N okButton.addActionListener(new java.awt.event.ActionListener() { @@ -220,14 +221,14 @@ final class ManageCorrelationPropertiesDialog extends javax.swing.JDialog { .addGap(20, 20, 20) .addComponent(taInstructions, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lbWarningMsg, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(okButton) .addComponent(cancelButton)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); pack(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.form index 7bccab25f6..33c03ebf9d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.form @@ -3,7 +3,10 @@
- + + + + @@ -27,7 +30,7 @@ - + @@ -35,7 +38,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java index 26a63f03a4..b36b3de413 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java @@ -159,7 +159,8 @@ public final class ManageOrganizationsDialog extends JDialog { editButton = new javax.swing.JButton(); orgDetailsLabel = new javax.swing.JLabel(); - setMinimumSize(new java.awt.Dimension(545, 415)); + setMinimumSize(new java.awt.Dimension(600, 450)); + setPreferredSize(new java.awt.Dimension(600, 450)); manageOrganizationsScrollPane.setMinimumSize(null); manageOrganizationsScrollPane.setPreferredSize(new java.awt.Dimension(535, 415)); @@ -331,13 +332,13 @@ public final class ManageOrganizationsDialog extends JDialog { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, 0) - .addComponent(manageOrganizationsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(manageOrganizationsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 603, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, 0) - .addComponent(manageOrganizationsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(manageOrganizationsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 443, Short.MAX_VALUE)) ); pack(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNodeFactory.java index c0043d9ba1..171cc32cb2 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNodeFactory.java @@ -54,11 +54,9 @@ final class AccountDeviceInstanceNodeFactory extends ChildFactory accountDeviceInstancesWithRelationships = commsManager.getAccountDeviceInstancesWithRelationships(commsFilter); for (AccountDeviceInstance accountDeviceInstance : accountDeviceInstancesWithRelationships) { - //Filter out device accounts, in the table. - if (Account.Type.DEVICE.equals(accountDeviceInstance.getAccount().getAccountType()) ==false) { - long communicationsCount = commsManager.getRelationshipSourcesCount(accountDeviceInstance, commsFilter); - accountDeviceInstanceKeys.add(new AccountDeviceInstanceKey(accountDeviceInstance, commsFilter, communicationsCount)); - } + long communicationsCount = commsManager.getRelationshipSourcesCount(accountDeviceInstance, commsFilter); + accountDeviceInstanceKeys.add(new AccountDeviceInstanceKey(accountDeviceInstance, commsFilter, communicationsCount)); + } } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error getting filtered account device instances", tskCoreException); diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 4b08fd2d45..1b2dd9ae37 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -22,15 +22,13 @@ CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualiz CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize VisualizationPanel.fitGraphButton.text= VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin. -VisualizationPanel.zoomLabel.text=100% -VisualizationPanel.jLabel2.text=Zoom: -VisualizationPanel.fitZoomButton.toolTipText=fit visualization +VisualizationPanel.fitZoomButton.toolTipText=Fit visualization to available space. VisualizationPanel.fitZoomButton.text= -VisualizationPanel.zoomActualButton.toolTipText=reset zoom +VisualizationPanel.zoomActualButton.toolTipText=Reset visualization default zoom state. VisualizationPanel.zoomActualButton.text= -VisualizationPanel.zoomInButton.toolTipText=Zoom in +VisualizationPanel.zoomInButton.toolTipText=Zoom visualization in. VisualizationPanel.zoomInButton.text= -VisualizationPanel.zoomOutButton.toolTipText=Zoom out +VisualizationPanel.zoomOutButton.toolTipText=Zoom visualization out. VisualizationPanel.zoomOutButton.text= VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.backButton.text_1= @@ -40,11 +38,14 @@ VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1= VisualizationPanel.snapshotButton.text_1=Snapshot Report VisualizationPanel.clearVizButton.actionCommand= -VisualizationPanel.backButton.toolTipText=Click to go back -VisualizationPanel.forwardButton.toolTipText=Click to go forward -VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to redraw the chart -VisualizationPanel.clearVizButton.toolTipText=Click to clear the chart +VisualizationPanel.backButton.toolTipText=Click to go back to previous state. +VisualizationPanel.forwardButton.toolTipText=Click to move state forward. +VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to redraw visualization. +VisualizationPanel.clearVizButton.toolTipText=Click to clear visualization. FiltersPanel.limitHeaderLabel.text=Communications Limit: FiltersPanel.mostRecentLabel.text=Most Recent: FiltersPanel.limitErrorMsgLabel.text=Invalid integer value. VisualizationPanel.forwardButton.text= +VisualizationPanel.zoomPercentLabel.text=100% +VisualizationPanel.zoomLabel.text=Zoom: +VisualizationPanel.snapshotButton.toolTipText=Generate Snapshot report. diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index b314af1cb9..0f07f9a084 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -57,6 +57,8 @@ CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualiz CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize VisualizationPanel.fitGraphButton.text= VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin. +VisualizationPanel.fitZoomButton.toolTipText=Fit Visualization +VisualizationPanel.fitZoomButton.text= # {0} - layout name VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout. # {0} - layout name @@ -65,15 +67,11 @@ VisualizationPanel.lockAction.pluralText=Lock Selected Accounts VisualizationPanel.lockAction.singularText=Lock Selected Account VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts VisualizationPanel.unlockAction.singularText=Unlock Selected Account -VisualizationPanel.zoomLabel.text=100% -VisualizationPanel.jLabel2.text=Zoom: -VisualizationPanel.fitZoomButton.toolTipText=fit visualization -VisualizationPanel.fitZoomButton.text= -VisualizationPanel.zoomActualButton.toolTipText=reset zoom +VisualizationPanel.zoomActualButton.toolTipText=Reset Zoom VisualizationPanel.zoomActualButton.text= -VisualizationPanel.zoomInButton.toolTipText=Zoom in +VisualizationPanel.zoomInButton.toolTipText=Zoom In VisualizationPanel.zoomInButton.text= -VisualizationPanel.zoomOutButton.toolTipText=Zoom out +VisualizationPanel.zoomOutButton.toolTipText=Zoom Out VisualizationPanel.zoomOutButton.text= VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.backButton.text_1= @@ -83,14 +81,17 @@ VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1= VisualizationPanel.snapshotButton.text_1=Snapshot Report VisualizationPanel.clearVizButton.actionCommand= -VisualizationPanel.backButton.toolTipText=Click to go back -VisualizationPanel.forwardButton.toolTipText=Click to go forward -VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to redraw the chart -VisualizationPanel.clearVizButton.toolTipText=Click to clear the chart +VisualizationPanel.backButton.toolTipText=Click to Go Back +VisualizationPanel.forwardButton.toolTipText=Click to Go Forward +VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to Redraw Chart +VisualizationPanel.clearVizButton.toolTipText=Click to Clear Chart FiltersPanel.limitHeaderLabel.text=Communications Limit: FiltersPanel.mostRecentLabel.text=Most Recent: FiltersPanel.limitErrorMsgLabel.text=Invalid integer value. VisualizationPanel.forwardButton.text= +VisualizationPanel.zoomPercentLabel.text=100% +VisualizationPanel.zoomLabel.text=Zoom: +VisualizationPanel.snapshotButton.toolTipText=Generate Snapshot Report VisualizationPanel_action_dialogs_title=Communications VisualizationPanel_action_name_text=Snapshot Report VisualizationPanel_module_name=Communications diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form index bc16bb6da2..a7bc5379c3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form @@ -16,61 +16,72 @@ - - - - - - - - - - - + - + - + - + - - - - - - - - - + + - + + + + + + + + + + + - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index 0032793584..2a5dd8c0d1 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; import java.awt.Component; -import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -28,6 +27,7 @@ import java.awt.Insets; import java.util.List; import java.util.stream.Collectors; import javax.swing.ImageIcon; +import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -68,7 +68,6 @@ public final class CVTTopComponent extends TopComponent { Lookup lookup = ((Lookup.Provider)selectedComponent).getLookup(); proxyLookup.setNewLookups(lookup); } - filtersPane.setDeviceAccountTypeEnabled(browseVisualizeTabPane.getSelectedIndex() != 0); }); @@ -80,6 +79,9 @@ public final class CVTTopComponent extends TopComponent { CVTEvents.getCVTEventBus().register(vizPanel); CVTEvents.getCVTEventBus().register(accountsBrowser); CVTEvents.getCVTEventBus().register(filtersPane); + + mainSplitPane.setResizeWeight(0.5); + mainSplitPane.setDividerLocation(0.25); } @Subscribe @@ -96,37 +98,28 @@ public final class CVTTopComponent extends TopComponent { private void initComponents() { GridBagConstraints gridBagConstraints; + mainSplitPane = new JSplitPane(); + filtersPane = new FiltersPanel(); browseVisualizeTabPane = new JTabbedPane(); accountsBrowser = new AccountsBrowser(); vizPanel = new VisualizationPanel(); - filtersPane = new FiltersPanel(); setLayout(new GridBagLayout()); + mainSplitPane.setLeftComponent(filtersPane); + browseVisualizeTabPane.setFont(new Font("Tahoma", 0, 18)); // NOI18N browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = GridBagConstraints.BOTH; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 0.75; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new Insets(15, 0, 15, 15); - add(browseVisualizeTabPane, gridBagConstraints); + mainSplitPane.setRightComponent(browseVisualizeTabPane); browseVisualizeTabPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; gridBagConstraints.fill = GridBagConstraints.BOTH; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 0.25; + gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new Insets(15, 15, 15, 5); - add(filtersPane, gridBagConstraints); + add(mainSplitPane, gridBagConstraints); }// //GEN-END:initComponents @@ -134,6 +127,7 @@ public final class CVTTopComponent extends TopComponent { private AccountsBrowser accountsBrowser; private JTabbedPane browseVisualizeTabPane; private FiltersPanel filtersPane; + private JSplitPane mainSplitPane; private VisualizationPanel vizPanel; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form index 1aba67e76c..33fe6d5228 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form @@ -16,77 +16,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -95,7 +24,7 @@ - + @@ -108,7 +37,7 @@ - + @@ -199,7 +128,7 @@ - + @@ -293,7 +222,7 @@ - + @@ -490,6 +419,77 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 76c2f189ad..445bf25c87 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -322,11 +322,6 @@ final public class FiltersPanel extends JPanel { panel.setSelected(initalState); panel.addItemListener(validationListener); - if (type.equals(Account.Type.DEVICE)) { - //Deveice type filter is enabled based on whether we are in table or graph view. - panel.setEnabled(deviceAccountTypeEnabled); - } - return panel; } @@ -474,54 +469,6 @@ final public class FiltersPanel extends JPanel { setLayout(new java.awt.GridBagLayout()); - topPane.setLayout(new java.awt.GridBagLayout()); - - filtersTitleLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/funnel.png"))); // NOI18N - filtersTitleLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.filtersTitleLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - topPane.add(filtersTitleLabel, gridBagConstraints); - - refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N - refreshButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.refreshButton.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 0; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; - topPane.add(refreshButton, gridBagConstraints); - - applyFiltersButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/tick.png"))); // NOI18N - applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 0; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 5); - topPane.add(applyFiltersButton, gridBagConstraints); - - needsRefreshLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.needsRefreshLabel.text")); // NOI18N - needsRefreshLabel.setForeground(new java.awt.Color(255, 0, 0)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridwidth = 3; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - topPane.add(needsRefreshLabel, gridBagConstraints); - - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_END; - gridBagConstraints.weightx = 1.0; - add(topPane, gridBagConstraints); - scrollPane.setBorder(null); mainPanel.setLayout(new java.awt.GridBagLayout()); @@ -581,7 +528,7 @@ final public class FiltersPanel extends JPanel { gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 3; + gridBagConstraints.gridy = 4; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; @@ -649,7 +596,7 @@ final public class FiltersPanel extends JPanel { gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; + gridBagConstraints.gridy = 3; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; @@ -726,7 +673,7 @@ final public class FiltersPanel extends JPanel { gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; + gridBagConstraints.gridy = 2; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.ipady = 100; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; @@ -808,16 +755,65 @@ final public class FiltersPanel extends JPanel { gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0); mainPanel.add(accountTypesPane, gridBagConstraints); + topPane.setLayout(new java.awt.GridBagLayout()); + + filtersTitleLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/funnel.png"))); // NOI18N + filtersTitleLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.filtersTitleLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + topPane.add(filtersTitleLabel, gridBagConstraints); + + refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N + refreshButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.refreshButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + topPane.add(refreshButton, gridBagConstraints); + + applyFiltersButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/tick.png"))); // NOI18N + applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 5); + topPane.add(applyFiltersButton, gridBagConstraints); + + needsRefreshLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.needsRefreshLabel.text")); // NOI18N + needsRefreshLabel.setForeground(new java.awt.Color(255, 0, 0)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + topPane.add(needsRefreshLabel, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_END; + gridBagConstraints.weightx = 1.0; + mainPanel.add(topPane, gridBagConstraints); + scrollPane.setViewportView(mainPanel); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; + gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 0); add(scrollPane, gridBagConstraints); }// //GEN-END:initComponents @@ -913,21 +909,6 @@ final public class FiltersPanel extends JPanel { return new DateControlState (endDatePicker.getDate(), endCheckBox.isSelected()); } - /** - * Enable or disable the device account type filter. The filter should be - * disabled for the browse/table mode and enabled for the visualization. - * - * @param enable True to enable the device account type filter, False to - * disable it. - */ - void setDeviceAccountTypeEnabled(boolean enable) { - deviceAccountTypeEnabled = enable; - JCheckBox deviceCheckbox = accountTypeMap.get(Account.Type.DEVICE); - if (deviceCheckbox != null) { - deviceCheckbox.setEnabled(deviceAccountTypeEnabled); - } - } - /** * Set the selection state of all the account type check boxes * @@ -949,11 +930,11 @@ final public class FiltersPanel extends JPanel { } /** - * Helper method that sets all the checkboxes in the given map to the given + * Helper method that sets all the check boxes in the given map to the given * selection state. * * @param map A map from anything to JCheckBoxes. - * @param selected The selection state to set all the checkboxes to. + * @param selected The selection state to set all the check boxes to. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void setAllSelected(Map map, boolean selected) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index f2e3052f47..e85901a1ba 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -105,10 +105,10 @@ - - + + @@ -134,8 +134,8 @@ - + @@ -241,13 +241,6 @@
- - - - - - - @@ -255,6 +248,13 @@ + + + + + + + @@ -320,6 +320,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 10732c630f..8a5404bb1a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -213,7 +213,7 @@ final public class VisualizationPanel extends JPanel { lockedVertexModel.registerhandler(this); final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) - -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); + -> zoomPercentLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); graph.getView().addListener(mxEvent.SCALE, scaleListener); graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener); @@ -373,223 +373,220 @@ final public class VisualizationPanel extends JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - splitPane = new javax.swing.JSplitPane(); - borderLayoutPanel = new javax.swing.JPanel(); - placeHolderPanel = new javax.swing.JPanel(); - jTextArea1 = new javax.swing.JTextArea(); - toolbar = new javax.swing.JPanel(); - fastOrganicLayoutButton = new javax.swing.JButton(); - zoomOutButton = new javax.swing.JButton(); - zoomInButton = new javax.swing.JButton(); - zoomActualButton = new javax.swing.JButton(); - fitZoomButton = new javax.swing.JButton(); - jLabel2 = new javax.swing.JLabel(); - zoomLabel = new javax.swing.JLabel(); - clearVizButton = new javax.swing.JButton(); - jSeparator2 = new javax.swing.JToolBar.Separator(); - backButton = new javax.swing.JButton(); - forwardButton = new javax.swing.JButton(); - snapshotButton = new javax.swing.JButton(); - jSeparator3 = new javax.swing.JToolBar.Separator(); - jSeparator4 = new javax.swing.JToolBar.Separator(); - notificationsJFXPanel = new javafx.embed.swing.JFXPanel(); + splitPane = new JSplitPane(); + borderLayoutPanel = new JPanel(); + placeHolderPanel = new JPanel(); + jTextArea1 = new JTextArea(); + toolbar = new JPanel(); + fastOrganicLayoutButton = new JButton(); + zoomOutButton = new JButton(); + zoomInButton = new JButton(); + zoomActualButton = new JButton(); + fitZoomButton = new JButton(); + zoomLabel = new JLabel(); + zoomPercentLabel = new JLabel(); + clearVizButton = new JButton(); + jSeparator2 = new JToolBar.Separator(); + backButton = new JButton(); + forwardButton = new JButton(); + snapshotButton = new JButton(); + jSeparator3 = new JToolBar.Separator(); + jSeparator4 = new JToolBar.Separator(); + notificationsJFXPanel = new JFXPanel(); - setLayout(new java.awt.BorderLayout()); + setLayout(new BorderLayout()); splitPane.setDividerLocation(800); splitPane.setResizeWeight(0.5); - borderLayoutPanel.setLayout(new java.awt.BorderLayout()); + borderLayoutPanel.setLayout(new BorderLayout()); - jTextArea1.setBackground(new java.awt.Color(240, 240, 240)); + jTextArea1.setBackground(new Color(240, 240, 240)); jTextArea1.setColumns(20); jTextArea1.setLineWrap(true); jTextArea1.setRows(5); - jTextArea1.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N + jTextArea1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N - org.jdesktop.layout.GroupLayout placeHolderPanelLayout = new org.jdesktop.layout.GroupLayout(placeHolderPanel); + GroupLayout placeHolderPanelLayout = new GroupLayout(placeHolderPanel); placeHolderPanel.setLayout(placeHolderPanelLayout); - placeHolderPanelLayout.setHorizontalGroup( - placeHolderPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() .addContainerGap(250, Short.MAX_VALUE) - .add(jTextArea1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 424, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE) .addContainerGap(423, Short.MAX_VALUE)) ); - placeHolderPanelLayout.setVerticalGroup( - placeHolderPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() - .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(jTextArea1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 47, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 47, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - borderLayoutPanel.add(placeHolderPanel, java.awt.BorderLayout.CENTER); + borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER); - fastOrganicLayoutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N - fastOrganicLayoutButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N - fastOrganicLayoutButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N + fastOrganicLayoutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N + fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N + fastOrganicLayoutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N fastOrganicLayoutButton.setFocusable(false); - fastOrganicLayoutButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); - zoomOutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N - zoomOutButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N - zoomOutButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N + zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N + zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N + zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N zoomOutButton.setFocusable(false); - zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - zoomOutButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - zoomOutButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER); + zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM); + zoomOutButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { zoomOutButtonActionPerformed(evt); } }); - zoomInButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N - zoomInButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N - zoomInButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N + zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N + zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N + zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N zoomInButton.setFocusable(false); - zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - zoomInButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - zoomInButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER); + zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM); + zoomInButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { zoomInButtonActionPerformed(evt); } }); - zoomActualButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N - zoomActualButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N - zoomActualButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N + zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N + zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N + zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N zoomActualButton.setFocusable(false); - zoomActualButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - zoomActualButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - zoomActualButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER); + zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM); + zoomActualButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { zoomActualButtonActionPerformed(evt); } }); - fitZoomButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N - fitZoomButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N - fitZoomButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N + fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N + fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N + fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N fitZoomButton.setFocusable(false); - fitZoomButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - fitZoomButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - fitZoomButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER); + fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM); + fitZoomButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { fitZoomButtonActionPerformed(evt); } }); - jLabel2.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jLabel2.text")); // NOI18N + zoomLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N - zoomLabel.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N + zoomPercentLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomPercentLabel.text")); // NOI18N - clearVizButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N - clearVizButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N - clearVizButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N - clearVizButton.setActionCommand(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N - clearVizButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + clearVizButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N + clearVizButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N + clearVizButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N + clearVizButton.setActionCommand(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N + clearVizButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { clearVizButtonActionPerformed(evt); } }); - jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); + jSeparator2.setOrientation(SwingConstants.VERTICAL); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N - backButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N - backButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N - backButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + backButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N + backButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N + backButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N + backButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { backButtonActionPerformed(evt); } }); - forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N - forwardButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N - forwardButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N - forwardButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); - forwardButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + forwardButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N + forwardButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N + forwardButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N + forwardButton.setHorizontalTextPosition(SwingConstants.LEADING); + forwardButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { forwardButtonActionPerformed(evt); } }); - snapshotButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N - snapshotButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N - snapshotButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N + snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N + snapshotButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.toolTipText")); // NOI18N + snapshotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { snapshotButtonActionPerformed(evt); } }); - jSeparator3.setOrientation(javax.swing.SwingConstants.VERTICAL); + jSeparator3.setOrientation(SwingConstants.VERTICAL); - jSeparator4.setOrientation(javax.swing.SwingConstants.VERTICAL); + jSeparator4.setOrientation(SwingConstants.VERTICAL); - org.jdesktop.layout.GroupLayout toolbarLayout = new org.jdesktop.layout.GroupLayout(toolbar); + GroupLayout toolbarLayout = new GroupLayout(toolbar); toolbar.setLayout(toolbarLayout); - toolbarLayout.setHorizontalGroup( - toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() .addContainerGap() .add(backButton) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.RELATED) .add(forwardButton) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jSeparator4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.RELATED) + .add(jSeparator4, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) .add(fastOrganicLayoutButton) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.RELATED) .add(clearVizButton) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jSeparator2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jLabel2) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.RELATED) + .add(jSeparator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) .add(zoomLabel) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(zoomOutButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(zoomInButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(zoomActualButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 33, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(fitZoomButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jSeparator3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.RELATED) + .add(zoomPercentLabel) + .addPreferredGap(LayoutStyle.RELATED) + .add(zoomOutButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(zoomInButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) .add(snapshotButton) - .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - toolbarLayout.setVerticalGroup( - toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() .add(3, 3, 3) - .add(toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.CENTER) + .add(toolbarLayout.createParallelGroup(GroupLayout.CENTER) .add(fastOrganicLayoutButton) .add(zoomOutButton) - .add(zoomInButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(zoomActualButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(fitZoomButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(jLabel2) + .add(zoomInButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(zoomActualButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(fitZoomButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(zoomLabel) + .add(zoomPercentLabel) .add(clearVizButton) - .add(jSeparator2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(backButton) .add(forwardButton) .add(snapshotButton) - .add(jSeparator3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(jSeparator4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jSeparator4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .add(3, 3, 3)) ); - borderLayoutPanel.add(toolbar, java.awt.BorderLayout.PAGE_START); - borderLayoutPanel.add(notificationsJFXPanel, java.awt.BorderLayout.PAGE_END); + borderLayoutPanel.add(toolbar, BorderLayout.PAGE_START); + borderLayoutPanel.add(notificationsJFXPanel, BorderLayout.PAGE_END); splitPane.setLeftComponent(borderLayoutPanel); - add(splitPane, java.awt.BorderLayout.CENTER); + add(splitPane, BorderLayout.CENTER); }// //GEN-END:initComponents private void fitZoomButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fitZoomButtonActionPerformed @@ -880,26 +877,26 @@ final public class VisualizationPanel extends JPanel { } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton backButton; - private javax.swing.JPanel borderLayoutPanel; - private javax.swing.JButton clearVizButton; - private javax.swing.JButton fastOrganicLayoutButton; - private javax.swing.JButton fitZoomButton; - private javax.swing.JButton forwardButton; - private javax.swing.JLabel jLabel2; - private javax.swing.JToolBar.Separator jSeparator2; - private javax.swing.JToolBar.Separator jSeparator3; - private javax.swing.JToolBar.Separator jSeparator4; - private javax.swing.JTextArea jTextArea1; - private javafx.embed.swing.JFXPanel notificationsJFXPanel; - private javax.swing.JPanel placeHolderPanel; - private javax.swing.JButton snapshotButton; - private javax.swing.JSplitPane splitPane; - private javax.swing.JPanel toolbar; - private javax.swing.JButton zoomActualButton; - private javax.swing.JButton zoomInButton; - private javax.swing.JLabel zoomLabel; - private javax.swing.JButton zoomOutButton; + private JButton backButton; + private JPanel borderLayoutPanel; + private JButton clearVizButton; + private JButton fastOrganicLayoutButton; + private JButton fitZoomButton; + private JButton forwardButton; + private JToolBar.Separator jSeparator2; + private JToolBar.Separator jSeparator3; + private JToolBar.Separator jSeparator4; + private JTextArea jTextArea1; + private JFXPanel notificationsJFXPanel; + private JPanel placeHolderPanel; + private JButton snapshotButton; + private JSplitPane splitPane; + private JPanel toolbar; + private JButton zoomActualButton; + private JButton zoomInButton; + private JLabel zoomLabel; + private JButton zoomOutButton; + private JLabel zoomPercentLabel; // End of variables declaration//GEN-END:variables /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/defaultContact.png b/Core/src/org/sleuthkit/autopsy/communications/images/defaultContact.png new file mode 100755 index 0000000000..80d697fac5 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/defaultContact.png differ diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/BlackboardArtifactDateComparator.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/BlackboardArtifactDateComparator.java new file mode 100755 index 0000000000..b4279c8193 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/BlackboardArtifactDateComparator.java @@ -0,0 +1,98 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obt ain 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.communications.relationships; + +import java.util.Comparator; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + + /** +* A comparator class for comparing BlackboardArtifacts of type +* TSK_EMAIL_MSG, TSK_MESSAGE, and TSK_CALLLOG by their respective creation +* date-time. +*/ +class BlackboardArtifactDateComparator implements Comparator { + static final int ACCENDING = 1; + static final int DECENDING = -1; + + private static final Logger logger = Logger.getLogger(BlackboardArtifactDateComparator.class.getName()); + + private final int direction; + + BlackboardArtifactDateComparator(int direction) { + this.direction = direction; + } + + @Override + public int compare(BlackboardArtifact bba1, BlackboardArtifact bba2) { + + BlackboardAttribute attribute1 = getTimeAttributeForArtifact(bba1); + BlackboardAttribute attribute2 = getTimeAttributeForArtifact(bba2); + // Inializing to Long.MAX_VALUE so that if a BlackboardArtifact of + // any unexpected type is passed in, it will bubble to the top of + // the list. + long dateTime1 = Long.MAX_VALUE; + long dateTime2 = Long.MAX_VALUE; + + if (attribute1 != null) { + dateTime1 = attribute1.getValueLong(); + } + + if (attribute2 != null) { + dateTime2 = attribute2.getValueLong(); + } + + return Long.compare(dateTime1, dateTime2) * direction; + } + + private BlackboardAttribute getTimeAttributeForArtifact(BlackboardArtifact artifact) { + if(artifact == null) { + return null; + } + + BlackboardAttribute attribute = null; + + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if (fromID != null) { + try { + switch (fromID) { + case TSK_EMAIL_MSG: + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + break; + case TSK_MESSAGE: + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + break; + case TSK_CALLLOG: + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + break; + default: + attribute = null; + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to compare attributes for artifact %d", artifact.getArtifactID()), ex); + } + } + + return attribute; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index f82411bf9d..f2aa0df8e1 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -1,3 +1,8 @@ +CallLogViewer_device_label=Device +CallLogViewer_duration_label=Duration(seconds) +CallLogViewer_noCallLogs= +CallLogViewer_recipient_label=To/From +CallLogViewer_title=Call Logs ContactDetailsPane.nameLabel.text=Placeholder ContactNode_Email=Email Address ContactNode_Home_Number=Home Number diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogNode.java new file mode 100755 index 0000000000..a9e92d6451 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogNode.java @@ -0,0 +1,117 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications.relationships; + +import java.util.logging.Level; +import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.communications.Utils; +import static org.sleuthkit.autopsy.communications.relationships.RelationshipsNodeUtilities.getAttributeDisplayString; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A BlackboardArtifactNode for Calllogs. + */ +final class CallLogNode extends BlackboardArtifactNode { + + private static final Logger logger = Logger.getLogger(CallLogNode.class.getName()); + + final static String DURATION_PROP = "duration"; + + CallLogNode(BlackboardArtifact artifact, String deviceID) { + super(artifact, Utils.getIconFilePath(Account.Type.PHONE)); + setDisplayName(deviceID); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + final BlackboardArtifact artifact = getArtifact(); + + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if (null != fromID && fromID != TSK_CALLLOG) { + return sheet; + } + + String phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM); + if(phoneNumber == null || phoneNumber.isEmpty()) { + phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO); + } + + long duration = -1; + try{ + duration = getCallDuration(artifact); + } catch(TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to get calllog duration for artifact: %d", artifact.getArtifactID()), ex); + } + + sheetSet.put(createNode(TSK_DATETIME_START, artifact)); + sheetSet.put(createNode(TSK_DIRECTION, artifact)); + sheetSet.put(new NodeProperty<>(TSK_PHONE_NUMBER.getLabel(), TSK_PHONE_NUMBER.getDisplayName(), "", phoneNumber)); + if(duration != -1) { + sheetSet.put(new NodeProperty<>("duration", "Duration", "", Long.toString(duration))); + } + + return sheet; + } + + NodeProperty createNode(BlackboardAttribute.ATTRIBUTE_TYPE type, BlackboardArtifact artifact) { + return new NodeProperty<>(type.getLabel(), type.getDisplayName(), type.getDisplayName(), getAttributeDisplayString(artifact, type)); + } + + long getCallDuration(BlackboardArtifact artifact) throws TskCoreException { + BlackboardAttribute startAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_DATETIME_START.getTypeID()))); + BlackboardAttribute endAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_DATETIME_END.getTypeID()))); + + if(startAttribute == null || endAttribute == null) { + return -1; + } + + return endAttribute.getValueLong() - startAttribute.getValueLong(); + } + + /** + * Circumvent DataResultFilterNode's slightly odd delegation to + * BlackboardArtifactNode.getSourceName(). + * + * @return the displayName of this Node, which is the type. + */ + @Override + public String getSourceName() { + return getDisplayName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.form new file mode 100755 index 0000000000..cab67d7f86 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.form @@ -0,0 +1,27 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java new file mode 100755 index 0000000000..cb5d34d7f9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogViewer.java @@ -0,0 +1,146 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications.relationships; + +import javax.swing.JPanel; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.NodeAdapter; +import org.openide.nodes.NodeMemberEvent; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION; + +/** + * + * CallLogViewer Panel + */ +final class CallLogViewer extends javax.swing.JPanel implements RelationshipsViewer { + + private final CallLogsChildNodeFactory nodeFactory; + + @Messages({ + "CallLogViewer_title=Call Logs", + "CallLogViewer_noCallLogs=", + "CallLogViewer_recipient_label=To/From", + "CallLogViewer_duration_label=Duration(seconds)", + "CallLogViewer_device_label=Device" + }) + + /** + * Creates new form CallLogViewer + */ + public CallLogViewer() { + initComponents(); + + nodeFactory = new CallLogsChildNodeFactory(null); + outlineViewPanel.hideOutlineView(Bundle.CallLogViewer_noCallLogs()); + + outlineViewPanel.getOutlineView().setPropertyColumns( + TSK_DIRECTION.getLabel(), TSK_DIRECTION.getDisplayName(), + TSK_PHONE_NUMBER.getLabel(), Bundle.CallLogViewer_recipient_label(), + TSK_DATETIME_START.getLabel(), TSK_DATETIME_START.getDisplayName(), + CallLogNode.DURATION_PROP, Bundle.CallLogViewer_duration_label() + ); + + Outline outline = outlineViewPanel.getOutlineView().getOutline(); + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CallLogViewer_device_label()); + + outlineViewPanel.getExplorerManager().setRootContext( + new TableFilterNode( + new DataResultFilterNode( + new AbstractNode(Children.create(nodeFactory, true)), outlineViewPanel.getExplorerManager()), true)); + + outlineViewPanel.getExplorerManager().getRootContext().addNodeListener(new NodeAdapter(){ + @Override + public void childrenAdded(NodeMemberEvent nme) { + updateOutlineViewPanel(); + } + + @Override + public void childrenRemoved(NodeMemberEvent nme) { + updateOutlineViewPanel(); + } + }); + + } + + /** + * 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() { + java.awt.GridBagConstraints gridBagConstraints; + + outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + + setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(15, 15, 15, 15); + add(outlineViewPanel, gridBagConstraints); + }// //GEN-END:initComponents + + @Override + public String getDisplayName() { + return Bundle.CallLogViewer_title(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + nodeFactory.refresh(info); + } + + @Override + public Lookup getLookup() { + return outlineViewPanel.getLookup(); + } + + private void updateOutlineViewPanel() { + int nodeCount = outlineViewPanel.getExplorerManager().getRootContext().getChildren().getNodesCount(); + if(nodeCount == 0) { + outlineViewPanel.hideOutlineView(Bundle.ContactsViewer_noContacts_message()); + } else { + outlineViewPanel.showOutlineView(); + } + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogsChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogsChildNodeFactory.java new file mode 100755 index 0000000000..5bfd1bdb96 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CallLogsChildNodeFactory.java @@ -0,0 +1,204 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obt ain 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.communications.relationships; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.relationships.CallLogsChildNodeFactory.CallLogNodeKey; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + *A ChildFactory for CallLog artifacts. + */ +final class CallLogsChildNodeFactory extends ChildFactory{ + + private static final Logger logger = Logger.getLogger(CallLogsChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + private final Map deviceIDMap = new HashMap<>(); + + CallLogsChildNodeFactory(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + } + + void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + @Override + protected boolean createKeys(List list) { + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + try { + relationshipSources = selectionInfo.getRelationshipSources(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to load relationship sources.", ex); //NON-NLS + return false; + } + + + for(Content content: relationshipSources) { + if( !(content instanceof BlackboardArtifact)){ + continue; + } + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if ( fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG) { + + String deviceID = ""; + try { + deviceID = getDeviceIDForDataSource(bba.getDataSource().getName()); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to get account for artifact data source: artifactID = %d", bba.getId()), ex); + } + + list.add(new CallLogNodeKey(bba, deviceID)); + } + } + + list.sort(new CallLogComparator(BlackboardArtifactDateComparator.ACCENDING)); + + return true; + } + + @Override + protected Node createNodeForKey(CallLogNodeKey key) { + + return new CallLogNode(key.getArtifact(), key.getDeviceID()); + } + + /** + * Gets the device ID for the given data source. + * + * To reduce lookup calls to the DB unique dataSourceName\deviceID pairs + * are stored in deviceIDMap. + * + * @param dataSourceName String name of data source + * + * @return device ID for given dataSourceName or empty string if non is found. + * + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + private String getDeviceIDForDataSource(String dataSourceName) throws NoCurrentCaseException, TskCoreException{ + + String deviceID = deviceIDMap.get(dataSourceName); + + if(deviceID == null) { + CommunicationsManager manager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + CommunicationsFilter filter = new CommunicationsFilter(); + + List list = new ArrayList<>(); + list.add(dataSourceName); + + List typeList = new ArrayList(); + typeList.add(Account.Type.DEVICE); + + filter.addAndFilter(new CommunicationsFilter.DeviceFilter(list)); + filter.addAndFilter(new CommunicationsFilter.AccountTypeFilter(typeList)); + + + // This list should just have 1 item in it + List adiList = manager.getAccountDeviceInstancesWithRelationships(filter); + + if( adiList != null && !adiList.isEmpty() ) { + deviceID = adiList.get(0).getDeviceId(); + } else { + deviceID = ""; + } + + deviceIDMap.put(dataSourceName, deviceID); + } + + return (deviceID != null ? deviceID : ""); + } + + /** + * ChildFactory key class which contains a BlackboardArtifact and its + * data source deviceID + */ + final class CallLogNodeKey{ + private final BlackboardArtifact artifact; + private final String deviceID; + + private CallLogNodeKey(BlackboardArtifact artifact, String deviceID) { + this.artifact = artifact; + this.deviceID = deviceID; + } + + /** + * Get the BlackboardArtifact for this key + * + * @return BlackboardArtifact instance + */ + BlackboardArtifact getArtifact() { + return artifact; + } + + /** + * Gets the BlackboardArtifact data source device ID. + * + * @return String device id. + */ + String getDeviceID() { + return deviceID; + } + } + + /** + * A comparator for CallLogNodeKey objects + */ + final class CallLogComparator implements Comparator{ + + final BlackboardArtifactDateComparator comparator; + + CallLogComparator(int direction) { + comparator = new BlackboardArtifactDateComparator(direction); + } + + @Override + public int compare(CallLogNodeKey key1, CallLogNodeKey key2) { + return comparator.compare(key1.getArtifact(), key2.getArtifact()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form index 671fac333e..0536cb5b11 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form @@ -1,10 +1,6 @@
- - - - @@ -15,6 +11,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java index ae456e0abb..d8592a0f1d 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java @@ -18,24 +18,40 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; /** * Displays the propertied of a ContactNode in a PropertySheet. */ public final class ContactDetailsPane extends javax.swing.JPanel implements ExplorerManager.Provider { - final private ExplorerManager explorerManager = new ExplorerManager(); + private static final Logger logger = Logger.getLogger(ContactDetailsPane.class.getName()); + + private final static String DEFAULT_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images/defaultContact.png"; + + private final ExplorerManager explorerManager = new ExplorerManager(); + private final ImageIcon defaultImage; /** * Displays the propertied of a ContactNode in a PropertySheet. */ public ContactDetailsPane() { initComponents(); - this.setEnabled(false); - nameLabel.setText(""); + + defaultImage = new ImageIcon(ContactDetailsPane.class.getResource(DEFAULT_IMAGE_PATH)); } /** @@ -46,9 +62,16 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl public void setNode(Node[] nodes) { if (nodes != null && nodes.length == 1) { nameLabel.setText(nodes[0].getDisplayName()); + nameLabel.setIcon(null); propertySheet.setNodes(nodes); + + BlackboardArtifact n = nodes[0].getLookup().lookup(BlackboardArtifact.class); + if(n != null) { + nameLabel.setIcon(getImageFromArtifact(n)); + } } else { nameLabel.setText(""); + nameLabel.setIcon(null); propertySheet.setNodes(null); } } @@ -57,12 +80,40 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl public ExplorerManager getExplorerManager() { return explorerManager; } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - nameLabel.setEnabled(enabled); - propertySheet.setEnabled(enabled); + + public ImageIcon getImageFromArtifact(BlackboardArtifact artifact){ + ImageIcon imageIcon = defaultImage; + + if(artifact == null) { + return imageIcon; + } + + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if(artifactType != BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT) { + return imageIcon; + } + + try { + for(Content content: artifact.getChildren()) { + if(content instanceof AbstractFile) { + AbstractFile file = (AbstractFile)content; + + try { + BufferedImage image = ImageIO.read(new File(file.getLocalAbsPath())); + imageIcon = new ImageIcon(image); + break; + } catch (IOException ex) { + // ImageIO.read will through an IOException if file is not an image + // therefore we don't need to report this exception just try + // the next file. + } + } + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to load image for contact: %d", artifact.getId()), ex); + } + + return imageIcon; } /** @@ -75,7 +126,6 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - messageContentViewer1 = new org.sleuthkit.autopsy.contentviewers.MessageContentViewer(); nameLabel = new javax.swing.JLabel(); propertySheet = new org.openide.explorer.propertysheet.PropertySheet(); @@ -107,7 +157,6 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.contentviewers.MessageContentViewer messageContentViewer1; private javax.swing.JLabel nameLabel; private org.openide.explorer.propertysheet.PropertySheet propertySheet; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java index 9f275ec228..33b9d16724 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.communications.relationships; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.logging.Level; @@ -36,6 +37,8 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBU import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; /** * Extends BlackboardArtifactNode to override createSheet to create a contact @@ -112,6 +115,25 @@ final class ContactNode extends BlackboardArtifactNode { for (BlackboardAttribute bba : otherMap.values()) { sheetSet.put(new NodeProperty<>(bba.getAttributeType().getTypeName(), bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); } + + // Don't need these values to appear in the Contact property sheet. + sheetSet.remove("S"); + sheetSet.remove("C"); + + List children = artifact.getChildren(); + if(children != null) { + int count = 0; + String imageLabelPrefix = "Image"; + for(Content child: children) { + if(child instanceof AbstractFile) { + String imageLabel = imageLabelPrefix; + if(count > 0) { + imageLabel = imageLabelPrefix + "-" + count; + } + sheetSet.put(new NodeProperty<>(imageLabel, imageLabel, imageLabel, child.getName())); + } + } + } } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error getting attribute values.", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java index 4fdb400c87..33afae7251 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -142,9 +142,6 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{ @Override public void setSelectionInfo(SelectionInfo info) { - contactPane.setNode(new Node[]{new AbstractNode(Children.LEAF)}); - contactPane.setEnabled(false); - nodeFactory.refresh(info); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index d34bc084da..1c18ba5952 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.communications.relationships; -import java.util.TimeZone; import java.util.logging.Level; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; @@ -27,20 +26,18 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; -import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; -import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.communications.Utils; +import static org.sleuthkit.autopsy.communications.relationships.RelationshipsNodeUtilities.getAttributeDisplayString; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE; /** * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView @@ -48,7 +45,6 @@ import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; class MessageNode extends BlackboardArtifactNode { public static final String UNTHREADED_ID = ""; - public static final String CALL_LOG_ID = ""; private static final Logger logger = Logger.getLogger(MessageNode.class.getName()); @@ -87,93 +83,46 @@ class MessageNode extends BlackboardArtifactNode { } sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS - - final BlackboardArtifact artifact = getArtifact(); - if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()) { - sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",CALL_LOG_ID)); //NON-NLS - } else { - sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? UNTHREADED_ID : threadID)); //NON-NLS - } - BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - if (null != fromID) { - //Consider refactoring this to reduce boilerplate - switch (fromID) { - case TSK_EMAIL_MSG: - sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS - sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS - sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", - getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS - sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", - getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS - try { - sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS - } - - break; - case TSK_MESSAGE: - sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS - sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS - sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", - getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS - sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", - getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS - try { - sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS - } - break; - case TSK_CALLLOG: - sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS - sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS - sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", - getAttributeDisplayString(artifact, TSK_DATETIME_START))); //NON-NLS - break; - default: - break; - } + + if(fromID == null || + (fromID != TSK_EMAIL_MSG && + fromID != TSK_MESSAGE)) { + return sheet; } - - return sheet; - } - - /** - * - * Get the display string for the attribute of the given type from the given - * artifact. - * - * @param artifact the value of artifact - * @param attributeType the value of TSK_SUBJECT1 - * - * @return The display string, or an empty string if there is no such - * attribute or an an error. - */ - private static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + + sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? UNTHREADED_ID : threadID)); //NON-NLS + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS try { - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); - if (attribute == null) { - return ""; - } else if (attributeType.getValueType() == DATETIME) { - return TimeUtilities.epochToTime(attribute.getValueLong(), - TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); - } else { - return attribute.getDisplayString(); - } - } catch (TskCoreException tskCoreException) { - logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS - return ""; + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } + + switch (fromID) { + case TSK_EMAIL_MSG: + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS + break; + case TSK_MESSAGE: + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS + break; + default: + break; + } + return sheet; } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java index 4c7be60e80..617992dab5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java @@ -229,11 +229,7 @@ public class MessageViewer extends JPanel implements RelationshipsViewer { if (!subject.isEmpty()) { threadNameLabel.setText(subject); } else { - if (threadIDList.contains(MessageNode.CALL_LOG_ID)) { - threadNameLabel.setText(Bundle.MessageViewer_viewMessage_calllogs()); - } else { - threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded()); - } + threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded()); } showMessagesPane(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java index 5039ce225e..baa866e85e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java @@ -18,17 +18,15 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -92,8 +90,7 @@ public class MessagesChildNodeFactory extends ChildFactory{ BlackboardArtifact bba = (BlackboardArtifact) content; BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); - if (fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG - && fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + if (fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG && fromID != BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { continue; } @@ -102,12 +99,8 @@ public class MessagesChildNodeFactory extends ChildFactory{ // To achive this assign any artifact that does not have a threadID // the "UNTHREADED_ID" // All call logs will default to a single call logs thread - String artifactThreadID; - if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG) { - artifactThreadID = MessageNode.CALL_LOG_ID; - } else { - artifactThreadID = MessageNode.UNTHREADED_ID; - } + String artifactThreadID = MessageNode.UNTHREADED_ID; + BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); if(attribute != null) { @@ -116,13 +109,14 @@ public class MessagesChildNodeFactory extends ChildFactory{ if(threadIDs == null || threadIDs.contains(artifactThreadID)) { list.add(bba); - } - + } } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to load artifacts for relationship sources.", ex); //NON-NLS } + + list.sort(new DateComparator()); return true; } @@ -132,4 +126,80 @@ public class MessagesChildNodeFactory extends ChildFactory{ return new MessageNode(key, null, null); } + /** + * A comparator class for comparing BlackboardArtifacts of type + * TSK_EMAIL_MSG, TSK_MESSAGE, and TSK_CALLLOG by their respective creation + * date-time. + */ + class DateComparator implements Comparator { + @Override + public int compare(BlackboardArtifact bba1, BlackboardArtifact bba2) { + + BlackboardAttribute attribute1 = null; + BlackboardAttribute attribute2 = null; + // Inializing to Long.MAX_VALUE so that if a BlackboardArtifact of + // any unexpected type is passed in, it will bubble to the top of + // the list. + long dateTime1 = Long.MAX_VALUE; + long dateTime2 = Long.MAX_VALUE; + + if (bba1 != null) { + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba1.getArtifactTypeID()); + if (fromID != null) { + try { + switch (fromID) { + case TSK_EMAIL_MSG: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + break; + case TSK_MESSAGE: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + break; + case TSK_CALLLOG: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + break; + default: + attribute1 = null; + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to compare attributes for artifact %d", bba1.getArtifactID()), ex); + } + } + } + + if (bba2 != null) { + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba2.getArtifactTypeID()); + if (fromID != null) { + try { + switch (fromID) { + case TSK_EMAIL_MSG: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + break; + case TSK_MESSAGE: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + break; + case TSK_CALLLOG: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + break; + default: + attribute2 = null; + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to compare attributes for artifact %d", bba2.getArtifactID()), ex); + } + } + } + + if (attribute1 != null) { + dateTime1 = attribute1.getValueLong(); + } + + if (attribute2 != null) { + dateTime2 = attribute2.getValueLong(); + } + + return Long.compare(dateTime1, dateTime2); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form index ef5a5d1357..3289340e2c 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form @@ -11,6 +11,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java index 1431bb9fa1..f1ef86d244 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java @@ -30,12 +30,6 @@ import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; public final class RelationshipBrowser extends JPanel implements Lookup.Provider { private SelectionInfo currentSelection; - - private final MessageViewer messagesViewer; - private final ContactsViewer contactsViewer; - private final SummaryViewer summaryViewer; - private final MediaViewer mediaViewer; - private final ModifiableProxyLookup proxyLookup; /** @@ -43,15 +37,18 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider */ public RelationshipBrowser() { initComponents(); - messagesViewer = new MessageViewer(); - contactsViewer = new ContactsViewer(); - summaryViewer = new SummaryViewer(); - mediaViewer = new MediaViewer(); + + MessageViewer messagesViewer = new MessageViewer(); + ContactsViewer contactsViewer = new ContactsViewer(); + SummaryViewer summaryViewer = new SummaryViewer(); + MediaViewer mediaViewer = new MediaViewer(); + CallLogViewer callLogViewer = new CallLogViewer(); proxyLookup = new ModifiableProxyLookup(messagesViewer.getLookup()); tabPane.add(summaryViewer.getDisplayName(), summaryViewer); tabPane.add(messagesViewer.getDisplayName(), messagesViewer); + tabPane.add(callLogViewer.getDisplayName(), callLogViewer); tabPane.add(contactsViewer.getDisplayName(), contactsViewer); tabPane.add(mediaViewer.getDisplayName(), mediaViewer); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java new file mode 100755 index 0000000000..db2f6f6ebd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.communications.relationships; + +import java.util.TimeZone; +import java.util.logging.Level; +import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; +import org.sleuthkit.datamodel.TimeUtilities; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A set of reusable utility functions for the Relationships package. + * + */ +final class RelationshipsNodeUtilities { + + private static final Logger logger = Logger.getLogger(RelationshipsNodeUtilities.class.getName()); + + // Here to make codacy happy + private RelationshipsNodeUtilities(){ + } + + /** + * + * Get the display string for the attribute of the given type from the given + * artifact. + * + * @param artifact the value of artifact + * @param attributeType the value of TSK_SUBJECT1 + * + * @return The display string, or an empty string if there is no such + * attribute or an an error. + */ + static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); + if (attribute == null) { + return ""; + } else if (attributeType.getValueType() == DATETIME) { + return TimeUtilities.epochToTime(attribute.getValueLong(), + TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); + } else { + return attribute.getDisplayString(); + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS + return ""; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java index 1cf987d132..111f1c6630 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadChildNodeFactory.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,13 +30,10 @@ import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -120,19 +118,13 @@ final class ThreadChildNodeFactory extends ChildFactory { BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG - || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { // We want email and message artifacts that do not have "threadIDs" to appear as one thread in the UI // To achive this assign any artifact that does not have a threadID // the "UNTHREADED_ID" - // All call logs will default to a single call logs thread - String threadID; - if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG) { - threadID = MessageNode.CALL_LOG_ID; - } else { - threadID = MessageNode.UNTHREADED_ID; - } + String threadID = MessageNode.UNTHREADED_ID; + BlackboardAttribute attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID)); if(attribute != null) { @@ -144,15 +136,41 @@ final class ThreadChildNodeFactory extends ChildFactory { rootMessageMap.put(threadID, bba); } else { // Get the date of the message - BlackboardAttribute tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); - attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); - - // put the earliest message into the table - if(tableAttribute != null - && attribute != null - && tableAttribute.getValueLong() > attribute.getValueLong()) { - rootMessageMap.put(threadID, bba); + BlackboardAttribute tableAttribute = null; + switch(fromID) { + case TSK_EMAIL_MSG: + tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + // put the earliest message into the table + if(tableAttribute != null + && attribute != null + && tableAttribute.getValueLong() > attribute.getValueLong()) { + rootMessageMap.put(threadID, bba); + } + break; + case TSK_MESSAGE: + tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + // put the earliest message into the table + if(tableAttribute != null + && attribute != null + && tableAttribute.getValueLong() < attribute.getValueLong()) { + rootMessageMap.put(threadID, bba); + } + break; + case TSK_CALLLOG: + tableAttribute = tableArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + attribute = bba.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + // put the earliest message into the table + if(tableAttribute != null + && attribute != null + && tableAttribute.getValueLong() > attribute.getValueLong()) { + rootMessageMap.put(threadID, bba); + } + break; } + + } } } @@ -160,6 +178,8 @@ final class ThreadChildNodeFactory extends ChildFactory { for(BlackboardArtifact bba: rootMessageMap.values()) { list.add(bba); } + + list.sort(new ThreadDateComparator()); return true; } @@ -176,44 +196,11 @@ final class ThreadChildNodeFactory extends ChildFactory { if (attribute != null) { return new ThreadNode(bba, attribute.getValueString(), preferredAction); } else { - if (bba.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()) { - return new CallLogNode(); - } else { - // Only one of these should occur. - return new UnthreadedNode(); - } + // Only one of these should occur. + return new UnthreadedNode(); } } - /** - * This node represents the "call log" thread. - */ - final class CallLogNode extends AbstractNode { - /** - * Construct an instance of a CallLogNode. - */ - CallLogNode() { - super(Children.LEAF); - setDisplayName("Call Logs"); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/unthreaded.png" ); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - // Give this node a threadID of "CALL_LOG_ID" - sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",MessageNode.CALL_LOG_ID)); - - return sheet; - } - } - /** * This node represents the "unthreaded" thread. */ @@ -242,4 +229,80 @@ final class ThreadChildNodeFactory extends ChildFactory { return sheet; } } + + /** + * A comparator class for comparing BlackboardArtifacts of type + * TSK_EMAIL_MSG, TSK_MESSAGE, and TSK_CALLLOG by their respective creation + * date-time. + * + * Nodes will be sorted newest to oldest. + */ + class ThreadDateComparator implements Comparator { + + @Override + public int compare(BlackboardArtifact bba1, BlackboardArtifact bba2) { + BlackboardAttribute attribute1 = null; + BlackboardAttribute attribute2 = null; + // Inializing to Long.MAX_VALUE so that if a BlackboardArtifact of + // any unexpected type is passed in, it will bubble to the top of + // the list. + long dateTime1 = Long.MAX_VALUE; + long dateTime2 = Long.MAX_VALUE; + + if (bba1 != null) { + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba1.getArtifactTypeID()); + if (fromID != null) { + try { + switch (fromID) { + case TSK_EMAIL_MSG: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + + break; + case TSK_MESSAGE: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + + break; + case TSK_CALLLOG: + attribute1 = bba1.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to compare attributes for artifact %d", bba1.getArtifactID()), ex); + } + } + } + + if (bba1 != null) { + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba2.getArtifactTypeID()); + if (fromID != null) { + try { + switch (fromID) { + case TSK_EMAIL_MSG: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT)); + break; + case TSK_MESSAGE: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + break; + case TSK_CALLLOG: + attribute2 = bba2.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START)); + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to compare attributes for artifact %d", bba2.getArtifactID()), ex); + } + } + } + + if (attribute1 != null) { + dateTime1 = attribute1.getValueLong(); + } + + if (attribute2 != null) { + dateTime2 = attribute2.getValueLong(); + } + + return Long.compare(dateTime1, dateTime2) * -1; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java index 013730a097..43e6e82308 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ThreadNode.java @@ -18,11 +18,18 @@ */ package org.sleuthkit.autopsy.communications.relationships; +import java.util.logging.Level; import javax.swing.Action; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT; +import org.sleuthkit.datamodel.TskCoreException; /** * An AbstractNode subclass which wraps a MessageNode object. Doing this allows @@ -31,6 +38,10 @@ import org.sleuthkit.datamodel.BlackboardArtifact; */ final class ThreadNode extends AbstractNode{ + private static final Logger logger = Logger.getLogger(ThreadNode.class.getName()); + + private final static int MAX_SUBJECT_LENGTH = 120; + final private MessageNode messageNode; ThreadNode(BlackboardArtifact artifact, String threadID, Action preferredAction) { @@ -41,7 +52,36 @@ final class ThreadNode extends AbstractNode{ @Override protected Sheet createSheet() { - return messageNode.createSheet(); + BlackboardArtifact artifact = messageNode.getArtifact(); + if(artifact == null) { + return messageNode.createSheet() ; + } + + Sheet sheet = messageNode.createSheet(); + BlackboardArtifact.ARTIFACT_TYPE artifactTypeID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + // If its a text message, replace the subject node which is probably + // an empty string with the firest 120 characters of the text message + if(artifactTypeID != null && artifactTypeID == TSK_MESSAGE) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_TEXT.getTypeID()))); + if(attribute != null) { + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + sheetSet.remove("Subject"); + + String msg = attribute.getDisplayString(); + if(msg != null && msg.length() > MAX_SUBJECT_LENGTH) { + msg = msg.substring(0, MAX_SUBJECT_LENGTH) + "..."; + } + + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", msg)); //NON-NLS + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to get the text message from message artifact %d", artifact.getId()), ex); + } + } + + return sheet; } String getThreadID() { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index ead4199941..dddf0f6296 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -143,7 +143,7 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer viewer.setFile(file); this.removeAll(); this.add(viewer.getComponent()); - this.repaint(); + this.validate(); } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java index 10708f9d1a..bc249a94ab 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java @@ -48,6 +48,8 @@ public final class ImageTag extends Group { // Used to tell the 8 edit handles to hide if this tag is no longer selected private final EventDispatchChainImpl ALL_CHILDREN; + private final PhysicalTag physicalTag; + //Notifies listeners that the user has editted the tag boundaries private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); @@ -64,7 +66,7 @@ public final class ImageTag extends Group { }); ImageTagRegion details = contentViewerTag.getDetails(); - PhysicalTag physicalTag = new PhysicalTag(details); + physicalTag = new PhysicalTag(details); //Defines the max allowable boundary that a user may drag any given handle. Boundary dragBoundary = (x, y) -> { @@ -131,6 +133,14 @@ public final class ImageTag extends Group { public void subscribeToEditEvents(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } + + public double getWidth() { + return physicalTag.getWidth(); + } + + public double getHeight() { + return physicalTag.getHeight(); + } /** * Get the content viewer tag that this class represents. diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java index a9f23304a3..69d2a73c4c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java @@ -23,6 +23,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javafx.event.Event; +import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.input.MouseEvent; @@ -65,13 +66,8 @@ public final class ImageTagsGroup extends Group { return; } - //Pull out the logical image tag that this node is associated with - Node topLevelChild = e.getPickResult().getIntersectedNode(); - while (!this.getChildren().contains(topLevelChild)) { - topLevelChild = topLevelChild.getParent(); - } - - requestFocus((ImageTag) topLevelChild); + ImageTag selection = getTagToSelect(new Point2D(e.getX(), e.getY())); + requestFocus(selection); }); } @@ -102,6 +98,31 @@ public final class ImageTagsGroup extends Group { currentFocus = null; } } + + /** + * Find which tag to select on a user mouse press. If multiple tags are + * overlapping, pick the smallest one that is determined by the L + W of + * the tag sides. + * + * @param coordinate User mouse press location + * @return The tag to give focus + */ + private ImageTag getTagToSelect(Point2D coordinate) { + ImageTag tagToSelect = null; + double minTagSize = Double.MAX_VALUE; + + //Find all intersecting tags, select the absolute min based on L + W. + for (Node node : this.getChildren()) { + ImageTag tag = (ImageTag) node; + double tagSize = tag.getWidth() + tag.getHeight(); + if (tag.contains(coordinate) && tagSize < minTagSize) { + tagToSelect = tag; + minTagSize = tagSize; + } + } + + return tagToSelect; + } /** * Notifies the logical image tag that it is no longer in focus. @@ -119,7 +140,7 @@ public final class ImageTagsGroup extends Group { * @param n */ private void requestFocus(ImageTag n) { - if (n.equals(currentFocus)) { + if (n == null || n.equals(currentFocus)) { return; } else if (currentFocus != null && !currentFocus.equals(n)) { resetFocus(currentFocus); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 98fb254131..15ee654347 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -214,7 +214,7 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n -DataResultViewerTable.exportCSVButton.text=Save table as CSV -ViewPreferencesPanel.scoColumnsCheckbox.text=S(core), C(omments), and O(ccurrences) +DataResultViewerTable.exportCSVButton.text=Save Table as CSV +ViewPreferencesPanel.scoColumnsCheckbox.text=S(core), C(omments), and O(ccurences) ViewPreferencesPanel.scoColumnsWrapAroundText.text=to reduce loading times ViewPreferencesPanel.scoColumnsLabel.text=Do not add columns for: diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index d60a979fe3..9f363b7723 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -268,7 +268,7 @@ DataResultViewerTable.pageNumLabel.text= DataResultViewerTable.pageLabel.text=Page: ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table: ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n
Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n -DataResultViewerTable.exportCSVButton.text=Save table as CSV -ViewPreferencesPanel.scoColumnsCheckbox.text=S(core), C(omments), and O(ccurrences) +DataResultViewerTable.exportCSVButton.text=Save Table as CSV +ViewPreferencesPanel.scoColumnsCheckbox.text=S(core), C(omments), and O(ccurences) ViewPreferencesPanel.scoColumnsWrapAroundText.text=to reduce loading times ViewPreferencesPanel.scoColumnsLabel.text=Do not add columns for: diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index e1feecf807..b99a94af96 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -33,8 +33,10 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; @@ -149,7 +151,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * OutlineView to the actions global context. * * @param explorerManager The explorer manager of the ancestor top - * component. + * component. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); @@ -162,8 +164,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * in the OutlineView to the actions global context. * * @param explorerManager The explorer manager of the ancestor top - * component. - * @param title The title. + * component. + * @param title The title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); @@ -177,7 +179,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { initComponents(); initializePagingSupport(); - + /* * Disable the CSV export button for the common properties results */ @@ -700,7 +702,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * order. * * @return a List> of the properties in the persisted - * order. + * order. */ private synchronized List> loadColumnOrder() { @@ -721,7 +723,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * property at the end. */ int offset = props.size(); - boolean noPreviousSettings = true; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); @@ -729,23 +730,51 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1); if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) { propertiesMap.put(value, prop); - noPreviousSettings = false; } else { propertiesMap.put(offset, prop); offset++; } } - // If none of the properties had previous settings, we should decrement - // each value by the number of properties to make the values 0-indexed. - if (noPreviousSettings) { - ArrayList keys = new ArrayList<>(propertiesMap.keySet()); - for (int key : keys) { - propertiesMap.put(key - props.size(), propertiesMap.remove(key)); + /* + NOTE: it is possible to have "discontinuities" in the keys (i.e. column numbers) + of the map. This happens when some of the columns had a previous setting, and + other columns did not. We need to make the keys 0-indexed and continuous. + */ + compactPropertiesMap(); + + return new ArrayList<>(propertiesMap.values()); + } + + /** + * Makes properties map 0-indexed and re-arranges elements to make sure the + * indexes are continuous. + */ + private void compactPropertiesMap() { + + // check if there are discontinuities in the map keys. + int size = propertiesMap.size(); + Queue availablePositions = new LinkedList<>(); + for (int i = 0; i < size; i++) { + if (!propertiesMap.containsKey(i)) { + availablePositions.add(i); } } - return new ArrayList<>(propertiesMap.values()); + // if there are no discontinuities, we are done + if (availablePositions.isEmpty()) { + return; + } + + // otherwise, move map elements into the available positions. + // we don't want to just move down all elements, as we want to preserve the order + // of the ones that had previous setting (i.e. ones that have key < size) + ArrayList keys = new ArrayList<>(propertiesMap.keySet()); + for (int key : keys) { + if (key >= size) { + propertiesMap.put(availablePositions.remove(), propertiesMap.remove(key)); + } + } } /** @@ -1420,7 +1449,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { }//GEN-LAST:event_gotoPageTextFieldActionPerformed @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export" - }) + }) private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed Node currentRoot = this.getExplorerManager().getRootContext(); if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 41ecad0340..8f7f753db2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -23,25 +23,34 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.logging.Level; import javax.swing.Action; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; /** * Node for layout file */ public class LayoutFileNode extends AbstractAbstractFileNode { + + private static final Logger logger = Logger.getLogger(LayoutFileNode.class.getName()); @Deprecated public static enum LayoutContentPropertyType { @@ -91,9 +100,14 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } @Override + @NbBundle.Messages({ + "LayoutFileNode.getActions.viewFileInDir.text=View File in Directory"}) public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(true))); + actionsList.add(new ViewContextAction(Bundle.LayoutFileNode_getActions_viewFileInDir_text(), this)); + actionsList.add(null); // Creates an item separator + actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "LayoutFileNode.getActions.viewInNewWin.text"), this)); final Collection selectedFilesList @@ -104,6 +118,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } else { actionsList.add(ExternalViewerShortcutAction.getInstance()); } + actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent())); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); actionsList.add(ExportCSVAction.getInstance()); @@ -115,6 +130,15 @@ public class LayoutFileNode extends AbstractAbstractFileNode { } actionsList.addAll(ContextMenuExtensionPoint.getActions()); + if (FileTypeExtensions.getArchiveExtensions().contains("." + this.content.getNameExtension().toLowerCase())) { + try { + if (this.content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0) { + actionsList.add(new ExtractArchiveWithPasswordAction(this.getContent())); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); + } + } return actionsList.toArray(new Action[actionsList.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java index f8ff559afd..e85f1ac3d5 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/AddRawImageTask.java @@ -25,7 +25,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; @@ -34,8 +36,6 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskFileRange; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /* * A runnable that adds a raw data source to a case database. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index 959b2c6653..e84dd16da6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -44,7 +44,6 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskException; -import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; /** @@ -140,8 +139,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { try { for (Content c : vol.getChildren()) { - if (!(c instanceof LayoutFile - || c instanceof VirtualDirectory)) { + if (!(c instanceof LayoutFile)) { ret = false; break; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 62c57f7118..ef8adfa319 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo; @@ -75,7 +76,7 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param artifactNode The artifact node for the artifact. */ public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { @@ -97,15 +98,30 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param fileSystemContentNode The file system content node for the - * content. + * content. */ public ViewContextAction(String displayName, AbstractFsContentNode fileSystemContentNode) { super(displayName); this.content = fileSystemContentNode.getLookup().lookup(Content.class); } + /** + * An action that displays the context for abstract file by expanding the + * data sources branch of the tree view to the level of the parent of the + * content, selecting the parent in the tree view, then selecting the + * content in the results view. + * + * @param displayName The display name for the action. + * @param abstractAbstractFileNode The AbstractAbstractFileNode node for the + * content. + */ + public ViewContextAction(String displayName, AbstractAbstractFileNode abstractAbstractFileNode) { + super(displayName); + this.content = abstractAbstractFileNode.getLookup().lookup(Content.class); + } + /** * An action that displays the context for some content by expanding the * data sources branch of the tree view to the level of the parent of the @@ -113,7 +129,7 @@ public class ViewContextAction extends AbstractAction { * content in the results view. * * @param displayName The display name for the action. - * @param content The content. + * @param content The content. */ public ViewContextAction(String displayName, Content content) { super(displayName); @@ -136,12 +152,29 @@ public class ViewContextAction extends AbstractAction { }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { + + /* + * Get the parent content for the content to be selected in the + * results view. If the parent content is null, then the specified + * content is a data source, and the parent tree view node is the + * "Data Sources" node. Otherwise, the tree view needs to be + * searched to find the parent treeview node. + */ + Content parentContent = null; + try { + parentContent = content.getParent(); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); + logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS + return; + } + /* * Get the "Data Sources" node from the tree view. */ DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - Node parentTreeViewNode; + Node parentTreeViewNode = null; if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { // 'Group by Data Source' view SleuthkitCase skCase; @@ -152,13 +185,44 @@ public class ViewContextAction extends AbstractAction { long contentDSObjid = content.getDataSource().getId(); DataSource datasource = skCase.getDataSource(contentDSObjid); dsname = datasource.getName(); - Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - Node datasourceGroupingNode = rootChildren.findChild(dsname); - if (!Objects.isNull(datasourceGroupingNode)) { - Children dsChildren = datasourceGroupingNode.getChildren(); - parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + + if (null != parentContent) { + // the tree view needs to be searched to find the parent treeview node. + /* NOTE: we can't do a lookup by data source name here, becase if there + are multiple data sources with the same name, then "getChildren().findChild(dsname)" + simply returns the first one that it finds. Instead we have to loop over all + data sources with that name, and make sure we find the correct one. + */ + for (int i = 0; i < rootChildren.getNodesCount(); i++) { + // in the root, look for a data source node with the name of interest + Node treeNode = rootChildren.getNodeAt(i); + if (!(treeNode.getName().equals(dsname))) { + continue; + } + + // for this data source, get the "Data Sources" child node + Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourcesNode.NAME); + + // check whether this is the data source we are looking for + parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); + if (parentTreeViewNode != null) { + // found the data source node + break; + } + } } else { + /* If the parent content is null, then the specified + * content is a data source, and the parent tree view node is the + * "Data Sources" node. */ + Node datasourceGroupingNode = rootChildren.findChild(dsname); + if (!Objects.isNull(datasourceGroupingNode)) { + Children dsChildren = datasourceGroupingNode.getChildren(); + parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + } + } + + if (parentTreeViewNode == null) { MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS return; @@ -171,65 +235,12 @@ public class ViewContextAction extends AbstractAction { } else { // Classic view // Start the search at the DataSourcesNode parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); - } - /* - * Get the parent content for the content to be selected in the - * results view. If the parent content is null, then the specified - * content is a data source, and the parent tree view node is the - * "Data Sources" node. Otherwise, the tree view needs to be - * searched to find the parent treeview node. - */ - Content parentContent = null; - - try { - parentContent = content.getParent(); - } catch (TskCoreException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); - logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS - return; - } - if (null != parentContent) { - /* - * Get an ordered list of the ancestors of the specified - * content, starting with its data source. - * - */ - AncestorVisitor ancestorVisitor = new AncestorVisitor(); - List contentBranch = parentContent.accept(ancestorVisitor); - Collections.reverse(contentBranch); - - /** - * Convert the list of ancestors into a list of tree nodes. - * - * IMPORTANT: The "dummy" root node used to create this single - * layer of children needs to be wrapped in a - * DirectoryTreeFilterNode so that its child nodes will also be - * wrapped in DirectoryTreeFilterNodes, via - * DirectoryTreeFilterNodeChildren. Otherwise, the display names - * of the nodes in the branch will not have child node counts - * and will not match the display names of the corresponding - * nodes in the actual tree view. - */ - Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true); - Children ancestorChildren = dummyRootNode.getChildren(); - - /* - * Search the tree for the parent node. Note that this algorithm - * simply discards "extra" ancestor nodes not shown in the tree, - * such as the root directory of the file system for file system - * content. - */ - Children treeNodeChildren = parentTreeViewNode.getChildren(); - for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { - Node ancestorNode = ancestorChildren.getNodeAt(i); - for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) { - Node treeNode = treeNodeChildren.getNodeAt(j); - if (ancestorNode.getName().equals(treeNode.getName())) { - parentTreeViewNode = treeNode; - treeNodeChildren = treeNode.getChildren(); - break; - } + if (null != parentContent) { + // the tree view needs to be searched to find the parent treeview node. + Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, parentTreeViewNode); + if (potentialParentTreeViewNode != null) { + parentTreeViewNode = potentialParentTreeViewNode; } } } @@ -275,6 +286,60 @@ public class ViewContextAction extends AbstractAction { }); } + /** + * Searches tree for parent node by getting an ordered list of the ancestors + * of the specified content. + * + * @param parentContent parent content for the content to be searched for + * @param node Node tree to search + * @return Node object of the matching parent, NULL if not found + */ + private Node findParentNodeInTree(Content parentContent, Node node) { + /* + * Get an ordered list of the ancestors of the specified + * content, starting with its data source. + * + */ + AncestorVisitor ancestorVisitor = new AncestorVisitor(); + List contentBranch = parentContent.accept(ancestorVisitor); + Collections.reverse(contentBranch); + + /** + * Convert the list of ancestors into a list of tree nodes. + * + * IMPORTANT: The "dummy" root node used to create this single layer of + * children needs to be wrapped in a DirectoryTreeFilterNode so that its + * child nodes will also be wrapped in DirectoryTreeFilterNodes, via + * DirectoryTreeFilterNodeChildren. Otherwise, the display names of the + * nodes in the branch will not have child node counts and will not + * match the display names of the corresponding nodes in the actual tree + * view. + */ + Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true); + Children ancestorChildren = dummyRootNode.getChildren(); + + /* + * Search the tree for the parent node. Note that this algorithm + * simply discards "extra" ancestor nodes not shown in the tree, + * such as the root directory of the file system for file system + * content. + */ + Children treeNodeChildren = node.getChildren(); + Node parentTreeViewNode = null; + for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { + Node ancestorNode = ancestorChildren.getNodeAt(i); + for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) { + Node treeNode = treeNodeChildren.getNodeAt(j); + if (ancestorNode.getName().equals(treeNode.getName())) { + parentTreeViewNode = treeNode; + treeNodeChildren = treeNode.getChildren(); + break; + } + } + } + return parentTreeViewNode; + } + /** * A ContentVisitor that returns a list of content objects by starting with * a given content and following its chain of ancestors to the root content diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.form index 4ea5377716..5629803cd2 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.form @@ -19,7 +19,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java index 22a7cb3e2f..0d54172cf7 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java @@ -81,7 +81,7 @@ public class SampleIngestModuleIngestJobSettingsPanel extends IngestModuleIngest .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(skipKnownFilesCheckBox) - .addContainerGap(255, Short.MAX_VALUE)) + .addContainerGap(155, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index b59e0a6ccb..e345db3d65 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -491,6 +491,18 @@ public class IngestManager implements IngestProgressSnapshotProvider { public void addIngestJobEventListener(final PropertyChangeListener listener) { jobEventPublisher.addSubscriber(INGEST_JOB_EVENT_NAMES, listener); } + + /** + * Adds an ingest job event property change listener for the given event types. + * + * @param eventTypes The event types to listen for + * @param listener The PropertyChangeListener to be added + */ + public void addIngestJobEventListener(Set eventTypes, final PropertyChangeListener listener) { + eventTypes.forEach((IngestJobEvent event) -> { + jobEventPublisher.addSubscriber(event.toString(), listener); + }); + } /** * Removes an ingest job event property change listener. @@ -500,6 +512,18 @@ public class IngestManager implements IngestProgressSnapshotProvider { public void removeIngestJobEventListener(final PropertyChangeListener listener) { jobEventPublisher.removeSubscriber(INGEST_JOB_EVENT_NAMES, listener); } + + /** + * Removes an ingest job event property change listener. + * + * @param eventTypes The event types to stop listening for + * @param listener The PropertyChangeListener to be removed. + */ + public void removeIngestJobEventListener(Set eventTypes, final PropertyChangeListener listener) { + eventTypes.forEach((IngestJobEvent event) -> { + jobEventPublisher.removeSubscriber(event.toString(), listener); + }); + } /** * Adds an ingest module event property change listener. @@ -510,6 +534,18 @@ public class IngestManager implements IngestProgressSnapshotProvider { moduleEventPublisher.addSubscriber(INGEST_MODULE_EVENT_NAMES, listener); } + /** + * Adds an ingest module event property change listener for given event types. + * + * @param eventTypes The event types to listen for + * @param listener The PropertyChangeListener to be removed. + */ + public void addIngestModuleEventListener(Set eventTypes, final PropertyChangeListener listener) { + eventTypes.forEach((IngestModuleEvent event) -> { + moduleEventPublisher.addSubscriber(event.toString(), listener); + }); + } + /** * Removes an ingest module event property change listener. * @@ -518,6 +554,16 @@ public class IngestManager implements IngestProgressSnapshotProvider { public void removeIngestModuleEventListener(final PropertyChangeListener listener) { moduleEventPublisher.removeSubscriber(INGEST_MODULE_EVENT_NAMES, listener); } + + /** + * Removes an ingest module event property change listener. + * + * @param eventTypes The event types to stop listening for + * @param listener The PropertyChangeListener to be removed. + */ + public void removeIngestModuleEventListener(Set eventTypes, final PropertyChangeListener listener) { + moduleEventPublisher.removeSubscriber(INGEST_MODULE_EVENT_NAMES, listener); + } /** * Publishes an ingest job event signifying an ingest job started. diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleIngestJobSettingsPanel.java index cb321f35cb..f356b4d04f 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleIngestJobSettingsPanel.java @@ -22,7 +22,8 @@ import javax.swing.JPanel; /** * Abstract base class for panels that allow users to specify per ingest job - * settings for ingest modules. + * settings for ingest modules. The max recommended dimensions for these panels + * is 300 width by 300 height. */ public abstract class IngestModuleIngestJobSettingsPanel extends JPanel { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java index 386326df10..8108bba1b2 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java @@ -170,8 +170,6 @@ public final class IngestServices { * Sets all of the global configuration settings for an ingest module. * * @param moduleName A unique identifier for the module. - * - * @param moduleName moduleName identifier unique to that module * @param settings A mapping of setting names to setting values. * */ diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties index e6c8663dfa..65fa3dd72d 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties @@ -46,8 +46,8 @@ ConfigVisualPanel1.jRadioButton2.text=Open existing configuration ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: ConfigVisualPanel2.folderNamesLabel.text=Folder names: ConfigVisualPanel2.filenamesLabel.text=File names: -ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches -ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule +ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console +ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file ConfigVisualPanel2.deleteRuleButton.text=Delete Rule ConfigVisualPanel2.editRuleButton.text=Edit Rule ConfigVisualPanel2.newRuleButton.text=New Rule @@ -77,15 +77,15 @@ EditRulePanel.jTable1.columnModel.title1=Title 2 EditRulePanel.shouldAlertCheckBox.actionCommand= EditFullPathsRulePanel.ruleNameLabel.text=Rule name: EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a path matches -EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a path +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file EditFullPathsRulePanel.fullPathsLabel.text=Full paths: EditFullPathsRulePanel.fullPathsLabel.toolTipText= EditNonFullPathsRulePanel.ruleNameLabel.text=Rule name: -EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a condition +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a condition matches +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console ConfigVisualPanel1.browseButton.text=Browse ConfigVisualPanel2.fullPathsTable.columnModel.title0= ConfigVisualPanel2.folderNamesTable.columnModel.title0= @@ -120,3 +120,5 @@ ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption progr ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitive. EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. +ConfigVisualPanel2.promptBeforeExit.text=Prompt before exiting imager +ConfigVisualPanel2.promptBeforeExit.actionCommand= diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED index 7ef61a8452..3e6aad34de 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED @@ -37,6 +37,9 @@ ConfigVisualPanel2.editConfiguration=Configure imager ConfigVisualPanel2.editRuleError=Edit rule error ConfigVisualPanel2.editRuleSet=Edit Rule ConfigVisualPanel2.newRule.name=New Rule +# {0} - ruleName +ConfigVisualPanel2.newRuleError.duplicateName=A rule with name "{0}" already exists. Please enter a different rule name +ConfigVisualPanel2.newRuleError.title=New rule error ConfigVisualPanel2.ok=OK ConfigVisualPanel2.rulesTable.columnModel.title0=Rule Name ConfigVisualPanel2.rulesTable.columnModel.title1=Description @@ -76,7 +79,7 @@ EditNonFullPathsRulePanel.modifiedDaysNotPositiveException=Modified days must be EditNonFullPathsRulePanel.units.bytes=Bytes EditNonFullPathsRulePanel.units.gigabytes=Gigabytes EditNonFullPathsRulePanel.units.kilobytes=Kilobytes -EditNonFullPathsRulePanel.units.megabytes=MegaBytes +EditNonFullPathsRulePanel.units.megabytes=Megabytes # {0} - fieldName EditRulePanel.blankLineException={0} cannot have a blank line EditRulePanel.emptyRuleName.message=Rule name cannot be empty @@ -106,8 +109,8 @@ ConfigVisualPanel1.jRadioButton2.text=Open existing configuration ConfigVisualPanel2.modifiedDateLabel.text=Modified Within: ConfigVisualPanel2.folderNamesLabel.text=Folder names: ConfigVisualPanel2.filenamesLabel.text=File names: -ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console if rule matches -ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file if it matches a rule +ConfigVisualPanel2.shouldAlertCheckBox.text=Alert in imager console +ConfigVisualPanel2.shouldSaveCheckBox.text=Extract file ConfigVisualPanel2.deleteRuleButton.text=Delete Rule ConfigVisualPanel2.editRuleButton.text=Edit Rule ConfigVisualPanel2.newRuleButton.text=New Rule @@ -137,15 +140,15 @@ EditRulePanel.jTable1.columnModel.title1=Title 2 EditRulePanel.shouldAlertCheckBox.actionCommand= EditFullPathsRulePanel.ruleNameLabel.text=Rule name: EditFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a path matches -EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a path +EditFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console +EditFullPathsRulePanel.shouldSaveCheckBox.text=Extract file EditFullPathsRulePanel.fullPathsLabel.text=Full paths: EditFullPathsRulePanel.fullPathsLabel.toolTipText= EditNonFullPathsRulePanel.ruleNameLabel.text=Rule name: -EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file if it matches a condition +EditNonFullPathsRulePanel.shouldSaveCheckBox.text=Extract file EditNonFullPathsRulePanel.daysIncludedLabel.text=day(s) EditNonFullPathsRulePanel.shouldAlertCheckBox.actionCommand= -EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console if a condition matches +EditNonFullPathsRulePanel.shouldAlertCheckBox.text=Alert in imager console ConfigVisualPanel1.browseButton.text=Browse ConfigVisualPanel2.fullPathsTable.columnModel.title0= ConfigVisualPanel2.folderNamesTable.columnModel.title0= @@ -186,6 +189,8 @@ ConfigVisualPanel2.flagEncryptionProgramsCheckBox.text=Alert if encryption progr ConfigVisualPanel2.finalizeImageWriter.text=Continue imaging after searches are performed EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitive. EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. +ConfigVisualPanel2.promptBeforeExit.text=Prompt before exiting imager +ConfigVisualPanel2.promptBeforeExit.actionCommand= NewRuleSetPanel.attributeRule.description=Search for files based on one or more attributes or metadata fields. NewRuleSetPanel.attributeRule.name=Attribute NewRuleSetPanel.fullPathRule.description=Search for files based on full exact match path. diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java index 1be96b52a2..2765bb1856 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java @@ -36,9 +36,9 @@ import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.filechooser.FileSystemView; @@ -251,6 +251,9 @@ final class ConfigVisualPanel1 extends JPanel { int firstRemovableDrive = -1; int i = 0; for (File root : roots) { + if (DriveListUtils.isNetworkDrive(root.toString().replace(":\\", ""))) { + continue; + } String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); long spaceInBytes = root.getTotalSpace(); String sizeWithUnit = DriveListUtils.humanReadableByteCount(spaceInBytes, false); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form index 9fd42600c8..b5725173df 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form @@ -26,7 +26,7 @@ - + @@ -41,42 +41,19 @@ - - - - - - - - - - - - - - - - - - - - + + - + - - + - - - - - + @@ -85,15 +62,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + @@ -107,16 +121,17 @@ - + - - + + + @@ -140,30 +155,19 @@ - - - - + - + - - - - + - + - - - - - - - - + + + @@ -171,25 +175,27 @@ - + - + - + - - - + + + - + + + + - @@ -220,7 +226,6 @@ - @@ -563,5 +568,19 @@ + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java index 5b2468efdc..65fe93fcd3 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java @@ -110,6 +110,7 @@ final class ConfigVisualPanel2 extends JPanel { minSizeTextField = new javax.swing.JFormattedTextField(); maxSizeLabel = new javax.swing.JLabel(); maxSizeTextField = new javax.swing.JFormattedTextField(); + promptBeforeExit = new javax.swing.JCheckBox(); org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.modifiedDateLabel.text")); // NOI18N @@ -120,7 +121,6 @@ final class ConfigVisualPanel2 extends JPanel { modifiedWithinTextField.setPreferredSize(new java.awt.Dimension(60, 20)); org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.daysIncludedLabel.text")); // NOI18N - daysIncludedLabel.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(fullPathsLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.fullPathsLabel.text")); // NOI18N @@ -255,6 +255,15 @@ final class ConfigVisualPanel2 extends JPanel { maxSizeTextField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#,###; ")))); maxSizeTextField.setEnabled(false); + promptBeforeExit.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(promptBeforeExit, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.promptBeforeExit.text")); // NOI18N + promptBeforeExit.setActionCommand(org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.promptBeforeExit.actionCommand")); // NOI18N + promptBeforeExit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + promptBeforeExitActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -268,7 +277,7 @@ final class ConfigVisualPanel2 extends JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newRuleButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(editRuleButton) @@ -281,48 +290,57 @@ final class ConfigVisualPanel2 extends JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(flagEncryptionProgramsCheckBox) - .addComponent(finalizeImageWriter) - .addComponent(shouldSaveCheckBox) - .addComponent(shouldAlertCheckBox) .addComponent(extensionsLabel) .addComponent(filenamesLabel) .addComponent(descriptionLabel) .addComponent(ruleNameLabel) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(modifiedDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(fileSizeLabel) - .addComponent(fullPathsLabel) - .addComponent(folderNamesLabel)) - .addGap(4, 4, 4) - .addComponent(minSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(maxSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(107, 107, 107)) + .addComponent(fullPathsLabel) + .addComponent(folderNamesLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() - .addGap(129, 129, 129) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(daysIncludedLabel)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(129, 129, 129) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(ruleNameEditTextField, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(descriptionEditTextField, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(extensionsTextField, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(filenamesScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addContainerGap()))))) + .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(shouldSaveCheckBox) + .addComponent(shouldAlertCheckBox) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(modifiedDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(fileSizeLabel)) + .addGap(4, 4, 4) + .addComponent(minSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(maxSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(maxSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addGap(129, 129, 129) + .addComponent(modifiedWithinTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(daysIncludedLabel))) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()))) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSeparator1)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(flagEncryptionProgramsCheckBox) + .addComponent(finalizeImageWriter) + .addComponent(promptBeforeExit)) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jSeparator1))))) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteRuleButton, editRuleButton, newRuleButton}); @@ -337,12 +355,13 @@ final class ConfigVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 479, Short.MAX_VALUE) + .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 478, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(newRuleButton) .addComponent(editRuleButton) - .addComponent(deleteRuleButton))) + .addComponent(deleteRuleButton)) + .addContainerGap()) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -360,23 +379,16 @@ final class ConfigVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(filenamesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(filenamesLabel) - .addGap(0, 0, Short.MAX_VALUE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(filenamesLabel)) + .addGap(16, 16, 16) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(folderNamesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(folderNamesLabel) - .addGap(0, 0, Short.MAX_VALUE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(folderNamesLabel)) + .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(fullPathsLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGap(11, 11, 11))) + .addComponent(fullPathsLabel) + .addComponent(fullPathsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(minSizeLabel) .addComponent(minSizeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -392,13 +404,15 @@ final class ConfigVisualPanel2 extends JPanel { .addComponent(shouldSaveCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(shouldAlertCheckBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(flagEncryptionProgramsCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(finalizeImageWriter))) - .addContainerGap()) + .addComponent(finalizeImageWriter) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(promptBeforeExit) + .addGap(21, 21, 21)))) ); }// //GEN-END:initComponents @@ -426,6 +440,13 @@ final class ConfigVisualPanel2 extends JPanel { if (option == JOptionPane.OK_OPTION) { try { ImmutablePair ruleMap = editPanel.toRule(); + if (!ruleName.equals(ruleMap.getKey()) && ruleExists(ruleMap)) { + JOptionPane.showMessageDialog(this, + Bundle.ConfigVisualPanel2_newRuleError_duplicateName(ruleMap.getKey()), + Bundle.ConfigVisualPanel2_editRuleError(), + JOptionPane.ERROR_MESSAGE); + continue; + } updateRow(row, ruleMap); break; } catch (IOException | NumberFormatException ex) { @@ -442,7 +463,10 @@ final class ConfigVisualPanel2 extends JPanel { } }//GEN-LAST:event_editRuleButtonActionPerformed - @Messages({"ConfigVisualPanel2.newRule.name=New Rule"}) + @Messages({"ConfigVisualPanel2.newRule.name=New Rule", + "ConfigVisualPanel2.newRuleError.title=New rule error", + "# {0} - ruleName", + "ConfigVisualPanel2.newRuleError.duplicateName=A rule with name \"{0}\" already exists. Please enter a different rule name"}) private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed NewRulePanel panel; panel = new NewRulePanel(okButton, cancelButton); @@ -455,14 +479,22 @@ final class ConfigVisualPanel2 extends JPanel { null, new Object[]{okButton, cancelButton}, okButton); if (option == JOptionPane.OK_OPTION) { try { - // Save the new rule + ImmutablePair ruleMap = panel.toRule(); + if (ruleExists(ruleMap)) { + JOptionPane.showMessageDialog(this, + Bundle.ConfigVisualPanel2_newRuleError_duplicateName(ruleMap.getKey()), + Bundle.ConfigVisualPanel2_newRuleError_title(), + JOptionPane.ERROR_MESSAGE); + continue; + } + // Save the new rule appendRow(ruleMap); break; } catch (IOException | NumberFormatException ex) { JOptionPane.showMessageDialog(this, ex.getMessage(), - "New rule error", + Bundle.ConfigVisualPanel2_newRuleError_title(), JOptionPane.ERROR_MESSAGE); // let user fix the error } @@ -510,6 +542,10 @@ final class ConfigVisualPanel2 extends JPanel { config.setFinalizeImageWriter(finalizeImageWriter.isSelected()); }//GEN-LAST:event_finalizeImageWriterActionPerformed + private void promptBeforeExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_promptBeforeExitActionPerformed + config.setPromptBeforeExit(promptBeforeExit.isSelected()); + }//GEN-LAST:event_promptBeforeExitActionPerformed + /** * Set the whether the a rule for detecting encryption programs will be * added to the rules in this config @@ -579,6 +615,7 @@ final class ConfigVisualPanel2 extends JPanel { private javax.swing.JLabel modifiedDateLabel; private javax.swing.JTextField modifiedWithinTextField; private javax.swing.JButton newRuleButton; + private javax.swing.JCheckBox promptBeforeExit; private javax.swing.JTextField ruleNameEditTextField; private javax.swing.JLabel ruleNameLabel; private javax.swing.JLabel ruleSetFileLabel; @@ -607,6 +644,7 @@ final class ConfigVisualPanel2 extends JPanel { private void updatePanel(String configFilePath, LogicalImagerConfig config, String rowSelectionkey) { configFileTextField.setText(configFilePath); finalizeImageWriter.setSelected(config.isFinalizeImageWriter()); + promptBeforeExit.setSelected(config.isPromptBeforeExit()); LogicalImagerRuleSet ruleSet = getRuleSetFromCurrentConfig(); flagEncryptionProgramsCheckBox.setSelected(ruleSet.find(EncryptionProgramsRule.getName()) != null); RulesTableModel rulesTableModel = new RulesTableModel(); @@ -767,6 +805,22 @@ final class ConfigVisualPanel2 extends JPanel { updatePanel(configFilename, config, ruleMap.getKey()); } + /** + * Check if a rule with the same name as this rule already exists + * + * @param ruleMap the rule to check the name of + * + * @return true if it exists, false otherwise + */ + private boolean ruleExists(ImmutablePair ruleMap) { + for (LogicalImagerRule rule : getRuleSetFromCurrentConfig().getRules()) { + if (rule.getName().equals(ruleMap.getKey())) { + return true; + } + } + return false; + } + private void appendRow(ImmutablePair ruleMap) { getRuleSetFromCurrentConfig().getRules().add(ruleMap.getValue()); updatePanel(configFilename, config, ruleMap.getKey()); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java index af65e8eacb..c40d05cffc 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditFullPathsRulePanel.java @@ -54,10 +54,6 @@ final class EditFullPathsRulePanel extends javax.swing.JPanel { EditFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule, boolean editing) { initComponents(); - if (editing) { - ruleNameTextField.setEnabled(!editing); - } - this.setRule(ruleName, rule); this.setButtons(okButton, cancelButton); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java index a48db9b2c4..8b9ead7efb 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/EditNonFullPathsRulePanel.java @@ -66,14 +66,11 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { "EditNonFullPathsRulePanel.example=Example: ", "EditNonFullPathsRulePanel.units.bytes=Bytes", "EditNonFullPathsRulePanel.units.kilobytes=Kilobytes", - "EditNonFullPathsRulePanel.units.megabytes=MegaBytes", + "EditNonFullPathsRulePanel.units.megabytes=Megabytes", "EditNonFullPathsRulePanel.units.gigabytes=Gigabytes" }) EditNonFullPathsRulePanel(JButton okButton, JButton cancelButton, String ruleName, LogicalImagerRule rule, boolean editing) { initComponents(); - if (editing) { - ruleNameTextField.setEnabled(!editing); - } this.setRule(ruleName, rule); this.setButtons(okButton, cancelButton); @@ -710,9 +707,9 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { return (extensionsCheckbox.isSelected() && !StringUtils.isBlank(extensionsTextField.getText()) && !validateExtensions(extensionsTextField).isEmpty()) || (fileNamesCheckbox.isSelected() && !StringUtils.isBlank(fileNamesTextArea.getText())) || (folderNamesCheckbox.isSelected() && !StringUtils.isBlank(folderNamesTextArea.getText())) - || (minSizeCheckbox.isSelected() && !StringUtils.isBlank(minSizeTextField.getText()) && isNonZeroLong(minSizeTextField.getText())) - || (maxSizeCheckbox.isSelected() && !StringUtils.isBlank(maxSizeTextField.getText()) && isNonZeroLong(maxSizeTextField.getText())) - || (modifiedWithinCheckbox.isSelected() && !StringUtils.isBlank(modifiedWithinTextField.getText())); + || (minSizeCheckbox.isSelected() && !StringUtils.isBlank(minSizeTextField.getText()) && isNonZeroLong(minSizeTextField.getValue())) + || (maxSizeCheckbox.isSelected() && !StringUtils.isBlank(maxSizeTextField.getText()) && isNonZeroLong(maxSizeTextField.getValue())) + || (modifiedWithinCheckbox.isSelected() && !StringUtils.isBlank(modifiedWithinTextField.getText()) && isNonZeroLong(modifiedWithinTextField.getValue())); } catch (IOException ex) { logger.log(Level.WARNING, "Invalid contents of extensionsTextField", ex); return false; @@ -722,14 +719,16 @@ final class EditNonFullPathsRulePanel extends javax.swing.JPanel { /** * Check that value could be a non zero long * - * @param numberString the string to check + * @param numberObject the object to check * * @return true if the value is a non-zero long */ - private boolean isNonZeroLong(String numberString) { + private boolean isNonZeroLong(Object numberObject) { Long value = 0L; try { - value = Long.parseLong(numberString); + if (numberObject instanceof Number) { + value = ((Number) numberObject).longValue(); + } } catch (NumberFormatException ignored) { //The string was not a number, this method will return false becaue the value is still 0L } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java index 685a2c8d35..9d1c175de3 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java @@ -38,6 +38,10 @@ class LogicalImagerConfig { @Expose(serialize = true) private boolean finalizeImageWriter; + @SerializedName("prompt-before-exit") + @Expose(serialize = true) + private boolean promptBeforeExit; + @SerializedName("rule-sets") @Expose(serialize = true) private List ruleSets; @@ -45,6 +49,7 @@ class LogicalImagerConfig { LogicalImagerConfig() { this.version = CURRENT_VERSION; this.finalizeImageWriter = false; + this.promptBeforeExit = true; this.ruleSets = new ArrayList<>(); } @@ -54,6 +59,7 @@ class LogicalImagerConfig { ) { this.version = CURRENT_VERSION; this.finalizeImageWriter = finalizeImageWriter; + this.promptBeforeExit = true; this.ruleSets = ruleSets; } @@ -64,6 +70,19 @@ class LogicalImagerConfig { ) { this.version = version; this.finalizeImageWriter = finalizeImageWriter; + this.promptBeforeExit = true; + this.ruleSets = ruleSets; + } + + LogicalImagerConfig( + String version, + boolean finalizeImageWriter, + boolean promptBeforeExit, + List ruleSets + ) { + this.version = version; + this.finalizeImageWriter = finalizeImageWriter; + this.promptBeforeExit = promptBeforeExit; this.ruleSets = ruleSets; } @@ -87,6 +106,14 @@ class LogicalImagerConfig { this.finalizeImageWriter = finalizeImageWriter; } + boolean isPromptBeforeExit() { + return promptBeforeExit; + } + + void setPromptBeforeExit(boolean promptBeforeExit) { + this.promptBeforeExit = promptBeforeExit; + } + List getRuleSets() { return ruleSets; } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java index a9c049ce08..dd433b68cf 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java @@ -45,6 +45,7 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer parseRules(JsonArray asJsonArray) { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java index 19ff3079e1..9070dfc197 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java @@ -88,7 +88,7 @@ final class AddLogicalImageTask extends AddMultipleImageTask { // Copy directory failed String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString()); errorList.add(msg); - logger.log(Level.SEVERE, String.format("Failed to copy directory {0} to {1}", src.toString(), dest.toString())); + logger.log(Level.SEVERE, String.format("Failed to copy directory %s to %s", src.toString(), dest.toString()), ex); callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); return; } @@ -136,7 +136,7 @@ final class AddLogicalImageTask extends AddMultipleImageTask { return null; } catch (TskCoreException ex) { String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage()); - logger.log(Level.SEVERE, String.format("Failed to add report {0}. Reason= {1}", reportPath.toString(), ex.getMessage())); + logger.log(Level.SEVERE, String.format("Failed to add report %s. Reason= %s", reportPath.toString(), ex.getMessage()), ex); return msg; } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java index 60f2e43a4f..ea09ee0e2e 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java @@ -18,26 +18,23 @@ */ package org.sleuthkit.autopsy.logicalimager.dsp; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.TskFileRange; /** * @@ -51,12 +48,15 @@ class AddMultipleImageTask implements Runnable { private static final Logger LOGGER = Logger.getLogger(AddMultipleImageTask.class.getName()); public static final String TSK_FS_TYPE_UNKNOWN_ERR_MSG = Bundle.AddMultipleImageTask_fsTypeUnknownErr(); + private static final long TWO_GB = 2000000000L; private final String deviceId; private final List imageFilePaths; private final String timeZone; + private final long chunkSize = TWO_GB; private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; private final Case currentCase; + private boolean criticalErrorOccurred; private volatile boolean cancelled; @@ -79,7 +79,7 @@ class AddMultipleImageTask implements Runnable { * @throws NoCurrentCaseException The exception if there is no open case. */ @Messages({ - "# {0} - file", "AddMultipleImageTask.addingFileAsLogicalFile=Adding: {0} as logical file", + "# {0} - file", "AddMultipleImageTask.addingFileAsLogicalFile=Adding: {0} as an unallocated space file.", "# {0} - deviceId", "# {1} - exceptionMessage", "AddMultipleImageTask.errorAddingImgWithoutFileSystem=Error adding images without file systems for device %s: %s",}) AddMultipleImageTask(String deviceId, List imageFilePaths, String timeZone, @@ -98,14 +98,14 @@ class AddMultipleImageTask implements Runnable { * Try to add the input image files as images. */ List newDataSources = new ArrayList<>(); - List localFileDataSourcePaths = new ArrayList<>(); + List corruptedImageFilePaths = new ArrayList<>(); List errorMessages = new ArrayList<>(); currentCase.getSleuthkitCase().acquireSingleUserCaseWriteLock(); try { progressMonitor.setIndeterminate(true); for (String imageFilePath : imageFilePaths) { if (!cancelled) { - addImageToCase(imageFilePath, newDataSources, localFileDataSourcePaths, errorMessages); + addImageToCase(imageFilePath, newDataSources, corruptedImageFilePaths, errorMessages); } } } finally { @@ -114,20 +114,42 @@ class AddMultipleImageTask implements Runnable { /* * Try to add any input image files that did not have file systems as a - * single local/logical files set with the device id as the root virtual + * single an unallocated space file with the device id as the root virtual * directory name. */ - if (!cancelled && !localFileDataSourcePaths.isEmpty()) { - FileManager fileManager = currentCase.getServices().getFileManager(); - FileManager.FileAddProgressUpdater progressUpdater = (final AbstractFile newFile) -> { - progressMonitor.setProgressText(Bundle.AddMultipleImageTask_addingFileAsLogicalFile(Paths.get(newFile.getParentPath(), newFile.getName()))); - }; + if (!cancelled && !corruptedImageFilePaths.isEmpty()) { + SleuthkitCase caseDatabase; + caseDatabase = currentCase.getSleuthkitCase(); try { - LocalFilesDataSource localFilesDataSource = fileManager.addLocalFilesDataSource(deviceId, "", timeZone, localFileDataSourcePaths, progressUpdater); - newDataSources.add(localFilesDataSource); - } catch (TskCoreException | TskDataException ex) { + progressMonitor.setProgressText(Bundle.AddMultipleImageTask_addingFileAsLogicalFile(corruptedImageFilePaths.toString())); + + caseDatabase.acquireSingleUserCaseWriteLock(); + + Image dataSource = caseDatabase.addImageInfo(0, corruptedImageFilePaths, timeZone); + newDataSources.add(dataSource); + List fileRanges = new ArrayList<>(); + + long imageSize = dataSource.getSize(); + int sequence = 0; + //start byte and end byte + long start = 0; + if (chunkSize > 0 && imageSize >= TWO_GB) { + for (double size = TWO_GB; size < dataSource.getSize(); size += TWO_GB) { + fileRanges.add(new TskFileRange(start, TWO_GB, sequence)); + start += TWO_GB; + sequence++; + } + + } + double leftoverSize = imageSize - sequence * TWO_GB; + fileRanges.add(new TskFileRange(start, (long)leftoverSize, sequence)); + + caseDatabase.addLayoutFiles(dataSource, fileRanges); + } catch (TskCoreException ex) { errorMessages.add(Bundle.AddMultipleImageTask_errorAddingImgWithoutFileSystem(deviceId, ex.getLocalizedMessage())); criticalErrorOccurred = true; + } finally { + caseDatabase.releaseSingleUserCaseWriteLock(); } } @@ -169,11 +191,10 @@ class AddMultipleImageTask implements Runnable { * @param newDataSources If the image is added, a data source is * added to this list for eventual return to * the caller via the callback. - * @param localFileDataSourcePaths If the image cannot be added because + * @param corruptedImageFilePaths If the image cannot be added because * Sleuth Kit cannot detect a filesystem, * the image file path is added to this list - * for later addition as a part of a - * local/logical files data source. + * for later addition as an unallocated space file. * @param errorMessages If there are any error messages, the * error messages are added to this list for * eventual return to the caller via the @@ -184,7 +205,7 @@ class AddMultipleImageTask implements Runnable { "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorAdding=Critical error adding {0} for device {1}: {2}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorReverting=Critical error reverting add image process for {0} for device {1}: {2}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.nonCriticalErrorAdding=Non-critical error adding {0} for device {1}: {2}",}) - private void addImageToCase(String imageFilePath, List newDataSources, List localFileDataSourcePaths, List errorMessages) { + private void addImageToCase(String imageFilePath, List newDataSources, List corruptedImageFilePaths, List errorMessages) { /* * Try to add the image to the case database as a data source. */ @@ -198,10 +219,10 @@ class AddMultipleImageTask implements Runnable { /* * If Sleuth Kit failed to add the image because it did not find * a file system, save the image path so it can be added to the - * case as part of a local/logical files data source. All other + * case as an unallocated space file. All other * errors are critical. */ - localFileDataSourcePaths.add(imageFilePath); + corruptedImageFilePaths.add(imageFilePath); } else { errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorAdding(imageFilePath, deviceId, ex.getLocalizedMessage())); criticalErrorOccurred = true; diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties index d6cc1be0e7..665b9f3f2e 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties @@ -5,10 +5,7 @@ LogicalImagerPanel.selectFromDriveLabel.text=Select Acquisition From Drive LogicalImagerPanel.selectDriveLabel.text=Select Drive LogicalImagerPanel.selectFolderLabel.text=Selected Folder: -LogicalImagerPanel.messageTextArea.text= -LogicalImagerPanel.pathTextField.text= LogicalImagerPanel.manualRadioButton.text=Manually Choose Folder -LogicalImagerPanel.importRadioButton.toolTipText= LogicalImagerPanel.importRadioButton.text=Import From External Drive LogicalImagerPanel.browseButton.text=Browse LogicalImagerPanel.refreshButton.text=Refresh diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED index 76b0cf6c23..32bd3c7868 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED @@ -19,7 +19,7 @@ AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1} # {0} - imageFilePath AddMultipleImageTask.adding=Adding: {0} # {0} - file -AddMultipleImageTask.addingFileAsLogicalFile=Adding: {0} as logical file +AddMultipleImageTask.addingFileAsLogicalFile=Adding: {0} as an unallocated space file. # {0} - imageFilePath # {1} - deviceId # {2} - exceptionMessage @@ -48,6 +48,7 @@ LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been LogicalImagerDSProcessor.noCurrentCase=No current case LogicalImagerPanel.imageTable.columnModel.title0=Hostname LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date +LogicalImagerPanel.imageTable.columnModel.title2=Path # {0} - sparseImageDirectory LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain any images # {0} - invalidFormatDirectory @@ -60,10 +61,7 @@ LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from LogicalImagerPanel.selectFromDriveLabel.text=Select Acquisition From Drive LogicalImagerPanel.selectDriveLabel.text=Select Drive LogicalImagerPanel.selectFolderLabel.text=Selected Folder: -LogicalImagerPanel.messageTextArea.text= -LogicalImagerPanel.pathTextField.text= LogicalImagerPanel.manualRadioButton.text=Manually Choose Folder -LogicalImagerPanel.importRadioButton.toolTipText= LogicalImagerPanel.importRadioButton.text=Import From External Drive LogicalImagerPanel.browseButton.text=Browse LogicalImagerPanel.refreshButton.text=Refresh diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java index 12b10d3000..d033d0fdce 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/DriveListUtils.java @@ -18,6 +18,12 @@ */ package org.sleuthkit.autopsy.logicalimager.dsp; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + /** * Utility class for displaying a list of drives */ @@ -48,4 +54,33 @@ public final class DriveListUtils { private DriveListUtils() { //empty private constructor for util class } + + /** Use the command net to determine what this drive is. + * net use will return an error for anything which isn't a share. + */ + public static boolean isNetworkDrive(String driveLetter) { + List cmd = Arrays.asList("cmd", "/c", "net", "use", driveLetter + ":"); + + try { + Process p = new ProcessBuilder(cmd) + .redirectErrorStream(true) + .start(); + + p.getOutputStream().close(); + + StringBuilder consoleOutput = new StringBuilder(); + + String line; + try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + while ((line = in.readLine()) != null) { + consoleOutput.append(line).append("\r\n"); + } + } + + int rc = p.waitFor(); + return rc == 0; + } catch(IOException | InterruptedException e) { + return false; // assume not a network drive + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form index 43a8e0f1ca..8f864e1f20 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.form @@ -89,7 +89,7 @@ - + @@ -133,9 +133,7 @@ - - - + @@ -156,9 +154,6 @@ - - - @@ -231,6 +226,7 @@ + @@ -246,7 +242,6 @@ - @@ -280,9 +275,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java index 880259f95a..06722722d6 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java @@ -39,8 +39,8 @@ import javax.swing.ListSelectionModel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileSystemView; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; @@ -60,12 +60,14 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { private static final long serialVersionUID = 1L; private static final String NO_IMAGE_SELECTED = Bundle.LogicalImagerPanel_messageLabel_noImageSelected(); private static final String DRIVE_HAS_NO_IMAGES = Bundle.LogicalImagerPanel_messageLabel_driveHasNoImages(); + private static final int COLUMN_TO_SORT_ON_INDEX = 1; + private static final int NUMBER_OF_VISIBLE_COLUMNS = 2; private static final String[] EMPTY_LIST_DATA = {}; private final JFileChooser fileChooser = new JFileChooser(); private final Pattern regex = Pattern.compile("Logical_Imager_(.+)_(\\d{4})(\\d{2})(\\d{2})_(\\d{2})_(\\d{2})_(\\d{2})"); - private Path choosenImageDirPath; - private TableModel imageTableModel; + private Path manualImageDirPath; + private DefaultTableModel imageTableModel; /** * Creates new form LogicalImagerPanel @@ -75,10 +77,28 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { */ private LogicalImagerPanel(String context) { initComponents(); + configureImageTable(); jScrollPane1.setBorder(null); clearImageTable(); } + /** + * Perform the Image Table configuration necessary when a new table model is + * set. + */ + private void configureImageTable() { + //hide path column while leaving it in model + if (imageTable.getColumnCount() > NUMBER_OF_VISIBLE_COLUMNS) { + TableColumn columnToHide = imageTable.getColumn(imageTableModel.getColumnName(NUMBER_OF_VISIBLE_COLUMNS)); + if (columnToHide != null) { + imageTable.removeColumn(columnToHide); + } + //sort on specified column in decending order, the first call will toggle to ascending order, the second to descending order + imageTable.getRowSorter().toggleSortOrder(COLUMN_TO_SORT_ON_INDEX); + imageTable.getRowSorter().toggleSortOrder(COLUMN_TO_SORT_ON_INDEX); + } + } + /** * Creates and returns an instance of a LogicalImagerPanel. * @@ -133,7 +153,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { buttonGroup1.add(importRadioButton); importRadioButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(importRadioButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.importRadioButton.text")); // NOI18N - importRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.importRadioButton.toolTipText")); // NOI18N + importRadioButton.setToolTipText(""); importRadioButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { importRadioButtonActionPerformed(evt); @@ -148,7 +168,6 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { } }); - pathTextField.setText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.pathTextField.text")); // NOI18N pathTextField.setDisabledTextColor(java.awt.Color.black); pathTextField.setEnabled(false); @@ -180,6 +199,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { imageScrollPane.setPreferredSize(new java.awt.Dimension(346, 402)); + imageTable.setAutoCreateRowSorter(true); imageTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { @@ -193,7 +213,6 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { imageTable.setShowHorizontalLines(false); imageTable.setShowVerticalLines(false); imageTable.getTableHeader().setReorderingAllowed(false); - imageTable.setUpdateSelectionOnSort(false); imageTable.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseReleased(java.awt.event.MouseEvent evt) { imageTableMouseReleased(evt); @@ -214,7 +233,6 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { messageTextArea.setForeground(java.awt.Color.red); messageTextArea.setLineWrap(true); messageTextArea.setRows(3); - messageTextArea.setText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.messageTextArea.text")); // NOI18N messageTextArea.setBorder(null); messageTextArea.setDisabledTextColor(java.awt.Color.red); messageTextArea.setEnabled(false); @@ -272,7 +290,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 106, Short.MAX_VALUE)) + .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 23, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(refreshButton) .addGap(18, 18, 18) @@ -299,7 +317,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { }) private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed imageTable.clearSelection(); - choosenImageDirPath = null; + manualImageDirPath = null; setErrorMessage(NO_IMAGE_SELECTED); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int retval = fileChooser.showOpenDialog(this); @@ -319,7 +337,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); return; } - choosenImageDirPath = Paths.get(path); + manualImageDirPath = Paths.get(path); setNormalMessage(path); firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } else { @@ -334,11 +352,9 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { private void imageTableSelect() { int index = imageTable.getSelectedRow(); if (index != -1) { - choosenImageDirPath = Paths.get((String) imageTableModel.getValueAt(index, 2)); - setNormalMessage(choosenImageDirPath.toString()); + setNormalMessage((String) imageTableModel.getValueAt(imageTable.convertRowIndexToModel(index), 2)); firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } else { - choosenImageDirPath = null; setErrorMessage(NO_IMAGE_SELECTED); firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); } @@ -365,7 +381,6 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { if (fList != null) { imageTableModel = new ImageTableModel(); - int row = 0; // Find all directories with name like Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS // and has vhd files in it for (File file : fList) { @@ -383,10 +398,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { String second = m.group(7); String extractDate = year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second; - imageTableModel.setValueAt(hostname, row, 0); - imageTableModel.setValueAt(extractDate, row, 1); - imageTableModel.setValueAt(imageDirPath, row, 2); - row++; + imageTableModel.addRow(new Object[]{hostname, extractDate, imageDirPath}); } } } @@ -394,18 +406,17 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { + " " + driveLetter); imageTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); imageTable.setModel(imageTableModel); + configureImageTable(); fixImageTableColumnWidth(); // If there are any images, select the first one if (imageTable.getRowCount() > 0) { imageTable.setRowSelectionInterval(0, 0); imageTableSelect(); } else { - choosenImageDirPath = null; setErrorMessage(DRIVE_HAS_NO_IMAGES); } } else { clearImageTable(); - choosenImageDirPath = null; setErrorMessage(DRIVE_HAS_NO_IMAGES); } } @@ -430,6 +441,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { private void clearImageTable() { imageTableModel = new ImageTableModel(); imageTable.setModel(imageTableModel); + configureImageTable(); fixImageTableColumnWidth(); } @@ -448,7 +460,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { refreshButton.setEnabled(false); - choosenImageDirPath = null; + manualImageDirPath = null; setNormalMessage(""); firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); }//GEN-LAST:event_manualRadioButtonActionPerformed @@ -463,7 +475,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { refreshButton.setEnabled(true); - choosenImageDirPath = null; + manualImageDirPath = null; setNormalMessage(""); refreshButton.doClick(); }//GEN-LAST:event_importRadioButtonActionPerformed @@ -481,6 +493,9 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { int firstRemovableDrive = -1; int i = 0; for (File root : roots) { + if (DriveListUtils.isNetworkDrive(root.toString().replace(":\\", ""))) { + continue; + } String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); long spaceInBytes = root.getTotalSpace(); String sizeWithUnit = DriveListUtils.humanReadableByteCount(spaceInBytes, false); @@ -558,7 +573,7 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { void reset() { //reset the UI elements to default - choosenImageDirPath = null; + manualImageDirPath = null; setNormalMessage(""); driveList.setListData(EMPTY_LIST_DATA); clearImageTable(); @@ -573,11 +588,24 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { * @return true if a proper image has been selected, false otherwise */ boolean validatePanel() { - return choosenImageDirPath != null && choosenImageDirPath.toFile().exists(); + if (manualRadioButton.isSelected()) { + return manualImageDirPath != null && manualImageDirPath.toFile().exists(); + } else if (imageTable.getSelectedRow() != -1) { + Path path = Paths.get((String) imageTableModel.getValueAt(imageTable.convertRowIndexToModel(imageTable.getSelectedRow()), 2)); + return path != null && path.toFile().exists(); + } else { + return false; + } } Path getImageDirPath() { - return choosenImageDirPath; + if (manualRadioButton.isSelected()) { + return manualImageDirPath; + } else if (imageTable.getSelectedRow() != -1) { + return Paths.get((String) imageTableModel.getValueAt(imageTable.convertRowIndexToModel(imageTable.getSelectedRow()), 2)); + } else { + return null; + } } @Override @@ -598,25 +626,19 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { /** * Image Table Model */ - private class ImageTableModel extends AbstractTableModel { + private class ImageTableModel extends DefaultTableModel { - private final List hostnames = new ArrayList<>(); - private final List extractDates = new ArrayList<>(); - private final List imageDirPaths = new ArrayList<>(); - - @Override - public int getRowCount() { - return hostnames.size(); - } + private static final long serialVersionUID = 1L; @Override public int getColumnCount() { - return 2; + return 3; } @Messages({ "LogicalImagerPanel.imageTable.columnModel.title0=Hostname", - "LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date" + "LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date", + "LogicalImagerPanel.imageTable.columnModel.title2=Path" }) @Override public String getColumnName(int column) { @@ -628,55 +650,18 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { case 1: colName = Bundle.LogicalImagerPanel_imageTable_columnModel_title1(); break; + case 2: + colName = Bundle.LogicalImagerPanel_imageTable_columnModel_title2(); + break; default: break; } return colName; } - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - Object ret = null; - switch (columnIndex) { - case 0: - ret = hostnames.get(rowIndex); - break; - case 1: - ret = extractDates.get(rowIndex); - break; - case 2: - ret = imageDirPaths.get(rowIndex); - break; - default: - throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS - } - return ret; - } - @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } - - @Override - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - hostnames.add((String) aValue); - break; - case 1: - extractDates.add((String) aValue); - break; - case 2: - imageDirPaths.add((String) aValue); - break; - default: - throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS - } - // Only show the hostname and extractDates column - if (columnIndex < 2) { - super.setValueAt(aValue, rowIndex, columnIndex); - } - } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.form index b9fc6f08b1..8c970ef7d9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.form @@ -1,6 +1,11 @@
+ + + + + @@ -24,7 +29,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.java index 30e3b11b05..9fa465739f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestSettingsPanel.java @@ -64,6 +64,8 @@ final class DataSourceIntegrityIngestSettingsPanel extends IngestModuleIngestJob jLabel3 = new javax.swing.JLabel(); jLabel1 = new javax.swing.JLabel(); + setPreferredSize(new java.awt.Dimension(300, 155)); + org.openide.awt.Mnemonics.setLocalizedText(computeHashesCheckbox, org.openide.util.NbBundle.getMessage(DataSourceIntegrityIngestSettingsPanel.class, "DataSourceIntegrityIngestSettingsPanel.computeHashesCheckbox.text")); // NOI18N computeHashesCheckbox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -89,7 +91,7 @@ final class DataSourceIntegrityIngestSettingsPanel extends IngestModuleIngestJob .addComponent(verifyHashesCheckbox) .addComponent(computeHashesCheckbox) .addComponent(jLabel3)) - .addContainerGap(47, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 1fb60cf0cb..d8d0f882da 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -185,7 +185,11 @@ class SevenZipExtractor { //As a result, many corrupted files have wonky compression ratios and could flood the UI //with false zip bomb notifications. The decision was made to skip compression ratio checks //for unallocated zip files. Instead, we let the depth be an indicator of a zip bomb. - if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) { + //Gzip archives compress a single file. They may have a sparse file, + //and that file could be much larger, however it won't be the exponential growth seen with more dangerous zip bombs. + //In addition a fair number of browser cache files will be gzip archives, + //and their file sizes are frequently retrieved incorrectly so ignoring gzip files is a reasonable decision. + if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) { return false; } diff --git a/Core/src/org/sleuthkit/autopsy/report/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/PortableCaseReportModule.java index ae2fc88766..eab436771b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/PortableCaseReportModule.java @@ -983,6 +983,9 @@ class PortableCaseReportModule implements ReportModule { enum ChunkSize { NONE("Do not split", ""), // NON-NLS + ONE_HUNDRED_MB("Split into 100 MB chunks", "100m"), + CD("Split into 700 MB chunks (CD)", "700m"), + ONE_GB("Split into 1 GB chunks", "1000m"), DVD("Split into 4.5 GB chunks (DVD)", "4500m"); // NON-NLS private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index f11f5c0a33..45b4182b0e 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,7 +56,8 @@ class ReportGenerator { private static final Logger logger = Logger.getLogger(ReportGenerator.class.getName()); /** - * Progress reportGenerationPanel that can be used to check for cancellation. + * Progress reportGenerationPanel that can be used to check for + * cancellation. */ private ReportProgressPanel progressPanel; @@ -89,7 +90,6 @@ class ReportGenerator { this.errorList = new ArrayList<>(); } - /** * Display the progress panels to the user, and add actions to close the * parent dialog. @@ -138,9 +138,9 @@ class ReportGenerator { * Run the TableReportModules using a SwingWorker. * * @param artifactTypeSelections the enabled/disabled state of the artifact - * types to be included in the report - * @param tagSelections the enabled/disabled state of the tag names - * to be included in the report + * types to be included in the report + * @param tagSelections the enabled/disabled state of the tag names to be + * included in the report */ void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) throws IOException { if (tableReport != null && null != artifactTypeSelections) { @@ -164,7 +164,7 @@ class ReportGenerator { * Run the FileReportModules using a SwingWorker. * * @param enabledInfo the Information that should be included about each - * file in the report. + * file in the report. */ void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) throws IOException { if (fileReportModule != null && null != enabledInfo) { @@ -219,7 +219,7 @@ class ReportGenerator { displayProgressPanel(); } } - + /** * Run the Portable Case Report Module */ @@ -258,10 +258,12 @@ class ReportGenerator { private void setupProgressPanel(ReportModule module, String reportDir) { String reportFilePath = module.getRelativeFilePath(); - if (!reportFilePath.isEmpty()) { - this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportDir + reportFilePath); - } else { + if (reportFilePath == null) { this.progressPanel = reportGenerationPanel.addReport(module.getName(), null); + } else if (reportFilePath.isEmpty()) { + this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportDir); + } else { + this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportDir + reportFilePath); } } @@ -283,9 +285,9 @@ class ReportGenerator { } catch (IOException ex) { throw new IOException("Failed to make report folder, unable to generate reports.", ex); } - return reportPath; + return reportPath; } - + private class ReportWorker extends SwingWorker { private final Runnable doInBackground; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportModule.java b/Core/src/org/sleuthkit/autopsy/report/ReportModule.java index 2109d8a526..8796041474 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportModule.java @@ -1,8 +1,8 @@ - /* +/* * * Autopsy Forensic Browser * - * Copyright 2012-2018 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -45,8 +45,9 @@ interface ReportModule { * module. The path should be relative to the location that gets passed in * to generateReport() (or similar). * - * @return Relative path to where report will be stored. May be null if the - * module does not produce a report file. + * @return Relative path to where report will be stored. Return an empty + * string if the location passed to generateReport() is the output location. + * Return null to indicate that there is no report file. */ public String getRelativeFilePath(); @@ -55,7 +56,7 @@ interface ReportModule { * report configuration step of the report wizard. * * @return Configuration panel or null if the module does not need - * configuration. + * configuration. */ public default JPanel getConfigurationPanel() { return new DefaultReportConfigurationPanel(); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java index 8c4c14aff0..2d12230e65 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2018 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -147,8 +147,7 @@ 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(); } private void popupWarning(ReportModule module) { diff --git a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java index fd81e69b10..3c1111bf7a 100644 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDb.java @@ -65,7 +65,7 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule { @Override public String getRelativeFilePath() { - return ""; + return null; } @Messages({ diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/TextExtractorFactory.java b/Core/src/org/sleuthkit/autopsy/textextractors/TextExtractorFactory.java index 86b96194c0..2c8316ba60 100755 --- a/Core/src/org/sleuthkit/autopsy/textextractors/TextExtractorFactory.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/TextExtractorFactory.java @@ -73,8 +73,7 @@ public class TextExtractorFactory { throw new NoTextExtractorFound( String.format("Could not find a suitable reader for " - + "content with name [%s] and id=[%d]. Try using " - + "the strings extractor instead.", + + "content with name [%s] and id=[%d].", content.getName(), content.getId()) ); } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 9cc6ae560c..4d8dea3d4a 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -29,7 +29,7 @@ import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; import java.util.logging.Level; -import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.commons.lang3.StringUtils; diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java index 8289010828..887da71c8d 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java @@ -31,7 +31,7 @@ import java.io.InputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.logging.Level; -import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.Logger; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index 39c244be05..b81ce9fdb1 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -33,7 +33,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; -import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JFileChooser; import javax.swing.filechooser.FileNameExtensionFilter; import org.apache.commons.lang3.StringUtils; diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index b111f431d9..562fa98ddd 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -53,7 +53,7 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException; import org.sleuthkit.datamodel.Content; import java.util.List; import java.util.logging.Level; -import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions; diff --git a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java index 3e61fac4a1..0a46afd8f9 100644 --- a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java +++ b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java @@ -22,57 +22,73 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.opencv.core.Core; +/** + * A utility class that loads the core OpenCV library and allows clients to + * verify that the library was loaded. + */ public final class OpenCvLoader { - private static final Logger LOGGER = Logger.getLogger(OpenCvLoader.class.getName()); + private static final Logger logger = Logger.getLogger(OpenCvLoader.class.getName()); private static boolean openCvLoaded; - private static UnsatisfiedLinkError exception = null; + private static UnsatisfiedLinkError exception = null; // Deprecated static { + openCvLoaded = false; try { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); openCvLoaded = true; } catch (UnsatisfiedLinkError ex) { - LOGGER.log(Level.WARNING, "Unable to load OpenCV", ex); - exception = ex; //save relevant error for throwing at appropriate time - openCvLoaded = false; - } catch (SecurityException ex) { - LOGGER.log(Level.WARNING, "Unable to load OpenCV", ex); - openCvLoaded = false; + logger.log(Level.WARNING, "Failed to load core OpenCV library", ex); + /* + * Save exception to rethrow later (deprecated). + */ + exception = ex; + } catch (Exception ex) { + /* + * Exception firewall to ensure that runtime exceptions do not cause + * the loading of this class by the Java class loader to fail. + */ + logger.log(Level.WARNING, "Failed to load core OpenCV library", ex); } - + } /** - * Return whether or not the OpenCV library has been loaded. + * Indicates whether or not the core OpenCV library has been loaded. * - * @return - true if the opencv library is loaded or false if it is not - * @throws UnsatisfiedLinkError - A COPY of the exception that prevented - * OpenCV from loading. Note that the stack trace in the exception can be - * confusing because it refers to a past invocation. + * @return True or false. + */ + public static boolean hasOpenCvLoaded() { + return openCvLoaded; + } + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private OpenCvLoader() { + } + + /** + * Indicates whether or not the core OpenCV library has been loaded. + * + * @return True or false. + * + * @throws UnsatisfiedLinkError if this error was thrown during the loading + * of the core OpenCV library during static + * initialization of this class. * * @deprecated Use hasOpenCvLoaded instead. */ @Deprecated public static boolean isOpenCvLoaded() throws UnsatisfiedLinkError { if (!openCvLoaded) { - //exception should never be null if the open cv isn't loaded but just in case if (exception != null) { throw exception; } else { throw new UnsatisfiedLinkError("OpenCV native library failed to load"); } - } return openCvLoaded; } - /** - * Return whether OpenCV library has been loaded. - * - * @return true if OpenCV library was loaded, false if not. - */ - public static boolean hasOpenCvLoaded() { - return openCvLoaded; - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java index 31dbfe3286..c5497d31af 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java @@ -189,7 +189,7 @@ class GroupCellFactory { @Override public String getGroupName() { return Optional.ofNullable(getItem()) - .map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName())) + .map(treeNode -> StringUtils.defaultIfBlank(treeNode.getDisplayName(), DrawableGroup.getBlankGroupName())) .orElse(""); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 42fbfbec0b..b51124e391 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -158,6 +158,11 @@ final public class GroupTree extends NavPanel> { String path = g.getGroupByValueDislpayName(); if (g.getGroupByAttribute() == DrawableAttribute.PATH) { String[] cleanPathTokens = StringUtils.stripStart(path, "/").split("/"); + + // Append obj id to the top level data source name to allow for duplicate data source names + if (g.getGroupKey().getDataSourceObjId() > 0) { + cleanPathTokens[0] = cleanPathTokens[0].concat(String.format("(Id: %d)", g.getGroupKey().getDataSourceObjId())); + } return Arrays.asList(cleanPathTokens); } else { String stripStart = StringUtils.strip(path, "/"); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeNode.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeNode.java index 8cb32336c2..9101d89147 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeNode.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeNode.java @@ -27,6 +27,7 @@ class GroupTreeNode { private final String path; private DrawableGroup group; + private final String dispName; public String getPath() { return path; @@ -36,9 +37,20 @@ class GroupTreeNode { return group; } - GroupTreeNode(String path, DrawableGroup group) { + public String getDisplayName() { + return dispName; + } + + GroupTreeNode(String path, DrawableGroup group) { this.path = path; this.group = group; + + // If the path has a obj id, strip it for display purpose. + if (path.toLowerCase().contains(("(Id: ").toLowerCase())) { + dispName = path.substring(0, path.indexOf("(Id: ")); + } else { + dispName = path; + } } void setGroup(DrawableGroup g) { diff --git a/InternalPythonModules/android/textmessage.py b/InternalPythonModules/android/textmessage.py index ef8fa498c8..803cfa5b96 100644 --- a/InternalPythonModules/android/textmessage.py +++ b/InternalPythonModules/android/textmessage.py @@ -29,6 +29,7 @@ from java.sql import SQLException from java.sql import Statement from java.util.logging import Level from java.util import ArrayList +from java.util import UUID from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Blackboard from org.sleuthkit.autopsy.casemodule.services import FileManager @@ -95,6 +96,7 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) deviceID = ds.getDeviceId() deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile) + uuid = UUID.randomUUID().toString() resultSet = None try: @@ -106,7 +108,7 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): read = resultSet.getInt("read") # may be unread = 0, read = 1 subject = resultSet.getString("subject") # message subject body = resultSet.getString("body") # message body - thread_id = "{0}_{1}".format(abstractFile.getId(), resultSet.getInt("thread_id")) + thread_id = "{0}-{1}".format(uuid, resultSet.getInt("thread_id")) attributes = ArrayList() artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); #create Message artifact and then add attributes from result set. if resultSet.getString("type") == "1": diff --git a/InternalPythonModules/android/wwfmessage.py b/InternalPythonModules/android/wwfmessage.py index faf62bb4e2..bf00ea54c0 100644 --- a/InternalPythonModules/android/wwfmessage.py +++ b/InternalPythonModules/android/wwfmessage.py @@ -27,6 +27,7 @@ from java.sql import SQLException from java.sql import Statement from java.util.logging import Level from java.util import ArrayList +from java.util import UUID from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Blackboard from org.sleuthkit.autopsy.casemodule.services import FileManager @@ -99,6 +100,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId) deviceID = ds.getDeviceId() deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile) + uuid = UUID.randomUUID().toString() resultSet = None try: @@ -110,6 +112,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): created_at = resultSet.getLong("datetime") user_id = resultSet.getString("user_id") # the ID of the user who sent the message. game_id = resultSet.getString("game_id") # ID of the game which the the message was sent. + thread_id = "{0}-{1}".format(uuid, user_id) attributes = ArrayList() artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) # create a call log and then add attributes from result set. @@ -118,7 +121,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MSG_ID, general.MODULE_NAME, game_id)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, general.MODULE_NAME, message)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "Words With Friends Message")) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID, general.MODULE_NAME, user_id)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID, general.MODULE_NAME, thread_id)) artifact.addAttributes(attributes) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index 2ba6856d7e..f5e139fbd3 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -36,7 +36,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search @@ -303,6 +303,7 @@ KeywordSearchModuleFactory.createFileIngestModule.exception.msg=Expected setting SearchRunner.Searcher.done.err.msg=Error performing keyword search KeywordSearchGlobalSearchSettingsPanel.timeRadioButton5.toolTipText=Fastest overall, but no results until the end KeywordSearchGlobalSearchSettingsPanel.timeRadioButton5.text=No periodic searches +Server.status.failed.msg=Local Solr server did not respond to status request. This may be because the server failed to start or is taking too long to initialize. SolrConnectionCheck.HostnameOrPort=Invalid hostname and/or port number. SolrConnectionCheck.Hostname=Invalid hostname. SolrConnectionCheck.MissingHostname=Missing hostname. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 7664e60f8c..c9c5563af5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -356,6 +356,13 @@ final class RegexQuery implements KeywordSearchQuery { String hit = hitMatcher.group(); + /** + * No need to continue on if the the string is "" nothing to find or do. + */ + if ("".equals(hit)) { + break; + } + offset = hitMatcher.end(); final ATTRIBUTE_TYPE artifactAttributeType = originalKeyword.getArtifactAttributeType(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 0234ac7f73..7259f29966 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -42,6 +42,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -428,6 +429,8 @@ public class Server { * immediately (probably before the server is ready) and doesn't check * whether it was successful. */ + @NbBundle.Messages({ + "Server.status.failed.msg=Local Solr server did not respond to status request. This may be because the server failed to start or is taking too long to initialize.",}) void start() throws KeywordSearchModuleException, SolrServerNoPortException { if (isRunning()) { // If a Solr server is running we stop it. @@ -469,16 +472,27 @@ public class Server { Arrays.asList("-Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf", //NON-NLS "-Dcollection.configName=AutopsyConfig"))); //NON-NLS - try { - //block for 10 seconds, give time to fully start the process - //so if it's restarted solr operations can be resumed seamlessly - Thread.sleep(10 * 1000); - } catch (InterruptedException ex) { - logger.log(Level.WARNING, "Timer interrupted"); //NON-NLS + // Wait for the Solr server to start and respond to a status request. + for (int numRetries = 0; numRetries < 6; numRetries++) { + if (isRunning()) { + final List pids = this.getSolrPIDs(); + logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS + return; + } + + // Local Solr server did not respond so we sleep for + // 5 seconds before trying again. + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException ex) { + logger.log(Level.WARNING, "Timer interrupted"); //NON-NLS + } } - final List pids = this.getSolrPIDs(); - logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS + // If we get here the Solr server has not responded to connection + // attempts in a timely fashion. + logger.log(Level.WARNING, "Local Solr server failed to respond to status requests."); + throw new KeywordSearchModuleException(Bundle.Server_status_failed_msg()); } catch (SecurityException ex) { logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS throw new KeywordSearchModuleException( diff --git a/docs/doxygen-user/advanced.dox b/docs/doxygen-user/advanced.dox deleted file mode 100644 index cdfb09406b..0000000000 --- a/docs/doxygen-user/advanced.dox +++ /dev/null @@ -1,9 +0,0 @@ -/*! \page advanced_page Advanced Settings - -If you are behind a proxy and need access to a network with Autopsy or one of the modules, you may set your proxy information in the _Tools_, _Options_, _General_ tab as shown in the screenshot below. - -

-\image html proxySettings.PNG -

- -*/ \ No newline at end of file diff --git a/docs/doxygen-user/auto_ingest.dox b/docs/doxygen-user/auto_ingest.dox index 9674249f6f..3957e7c922 100644 --- a/docs/doxygen-user/auto_ingest.dox +++ b/docs/doxygen-user/auto_ingest.dox @@ -71,7 +71,7 @@ When auto ingest mode is enabled, Autopsy will open with a different UI than nor \image html AutoIngest/auto_ingest_in_progress.png -The user must press the "Start" button to being the auto ingest process. Note that if the computer running Autopsy in auto ingest mode is restarted, someone must log into it to restart Autopsy. It does not start by itself. When "Start" is pressed, the node will scan through the Shared Images folder looking for manifest files. This scan happens periodically when ingest is running. It can also be started manually using the "Refresh" button. +The user must press the "Start" button to begin the auto ingest process. Note that if the computer running Autopsy in auto ingest mode is restarted, someone must log into it to restart Autopsy. It does not start by itself. When "Start" is pressed, the node will scan through the Shared Images folder looking for manifest files. This scan happens periodically when ingest is running. It can also be started manually using the "Refresh" button. The UI for the auto ingest node will display what images are scheduled for analysis, what is currently running, and what has been completed. If a newly added image should be the highest priority, then you can select it and choose "Prioritize Case". This will prioritize all images within the same case to be top priority. You may also prioritize only a single data source (job) using the "Prioritize Job" button in the same manner. If you have prioritized something by mistake, the "Deprioritize" buttons will undo it. diff --git a/docs/doxygen-user/auto_ingest_setup.dox b/docs/doxygen-user/auto_ingest_setup.dox index 35000028c6..c49beee662 100644 --- a/docs/doxygen-user/auto_ingest_setup.dox +++ b/docs/doxygen-user/auto_ingest_setup.dox @@ -90,6 +90,11 @@ Some notes on shared configuration:
  • Shared copies of the hash databases are also not currently supported. Each node will download its own copy of each database. +\subsection auto_ingest_test_button Testing + +Once everything is configured, you can use the "Test" button near the bottom of the panel to test if everything is set up correctly. The button will test whether the services are available, whether a case can be created, and if the ingest settings are valid. If the test passes you'll see a green check. If it fails you'll see a message giving a short description of what error occurred. Depending on the error you may also see a pop-up message. You can check the logs for additional information (close the Options panel and click on "Help" then "Open Log Folder"). + +\image html AutoIngest/test_button_failure.png \subsection auto_ingest_error_suppression Error Suppression diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox index b08ef7dcad..5583971bfc 100644 --- a/docs/doxygen-user/central_repo.dox +++ b/docs/doxygen-user/central_repo.dox @@ -38,7 +38,7 @@ Once a database has been configured, the lower two buttons on the main panel wil Setting up PostgreSQL Deployment -If needed, see the \ref install_postgresql for help setting up your PostgreSQL server. +If needed, see the \ref install_postgresql_page for help setting up your PostgreSQL server. For PostgreSQL all values are required, but some defaults are provided for convenience. @@ -161,33 +161,24 @@ properties from the central repository. If the selected file or artifact is asso to one or more properties in the database, the associated properties will be displayed. Note: the Content Viewer will display ALL associated properties available in the database. It ignores the user's enabled/disabled Correlation Properties. -The other occurrences are grouped by case and then data source. The rows in the content viewer have background colors to indicate if they are known to be of interest. Properties that are notable -will have a Red background, all others will have a White background. The notable status will also be displayed in the "Known" column. +The other occurrences are grouped by case and then data source. Selecting one of the results brings up information on it in the right column. If a file or artifact was previously marked as notable, you will see "notable" in red next to "Known Status". \image html central_repo_content_viewer.png The user can click on any column heading to sort by the values in that column. -If the user selects a row and then right-clicks, a menu will be displayed. +If the user selects an entry in the third column and then right-clicks, a menu will be displayed. This menu has several options. --# Select All --# Export Selected Rows to CSV +-# Export All Other Occurrences to CSV -# Show Case Details -# Show Frequency -Select All +Export All Other Occurrences to CSV -This option will select all rows in the Content Viewer table. - -Export Selected Rows to CSV - -This option will save ALL SELECTED rows in the Content Viewer table to a CSV file. +This option will save every other occurrence in the Content Viewer table to a CSV file. By default, the CSV file is saved into the Export directory inside the currently open Autopsy case, but the user is free to select a different location. -Note: if you want to copy/paste rows, it is usually possible to use CTRL+C to copy the -selected rows and then CTRL+V to paste them into a file, but it will not be CSV formatted. - Show Case Details This option will open a dialog that displays all of the relevant details for the selected case. The @@ -230,4 +221,4 @@ then contain each instance of that notable phone number. -*/ \ No newline at end of file +*/ diff --git a/docs/doxygen-user/communications.dox b/docs/doxygen-user/communications.dox index 1e89a28d8e..94b46e9d30 100644 --- a/docs/doxygen-user/communications.dox +++ b/docs/doxygen-user/communications.dox @@ -14,15 +14,38 @@ The Communications Visualization Tool is loaded through the Tools->Communication \image html cvt_main.png -From the left hand column, you can choose which devices to display, which types of data to display, and optionally select a time range. After any changes to the filters, use the Apply button to update the tables. +From the left hand column, you can choose which devices to display, which types of data to display, and optionally select a time range. You can also choose to limit the display to only the most recent communications. After any changes to the filters, use the Apply button to update the tables. -The middle column displays each account, its device and type, and the number of associated messages (emails, call logs, etc.). By default it will be sorted in descending order of frequency. +The middle column displays each account, its device and type, and the number of associated messages (emails, call logs, etc.). By default it will be sorted in descending order of frequency. The middle column and the right hand column both have a \ref ui_quick_search feature which can be used to quickly find a visible item in their section's table. -Selecting an account in the middle column will bring up the messages for that account in the right hand column. Here data about each message is displayed in the top section, and the messages itself can be seen in the bottom section (if applicable). +Selecting an account in the middle column will bring up the data for that account in the right hand column. There are four tabs that show information about the selected account. -\image html cvt_messages.png +
      +
    • The Summary tab displays counts of how many times the account has appeared in different data types in the top section. In the middle it displays the files this account was found in. If the \ref central_repo_page is enabled, the bottom section will show any other cases that contained this account. -The middle column and the right hand column both have a \ref ui_quick_search feature which can be used to quickly find a visible item in their section's table. +\image html cvt_summary_tab.png + +
    • The Messages tab displays any messages or call logs associated with the account. The Messages will either be in a thread, or listed under a node called "Unthreaded". Clicking on the "Unthreaded" node will show all the messages that are not "Threaded". Call logs will all be under a node named "Call Logs". + +\image html cvt_messages_threaded.png + +You can use the "All Messages" button at the bottom of the panel to show all messages. Clicking on a threaded message will show you all messages in that thread. You can click on an individual message to view it in the lower panel. Click the "Threads" button to return to the original screen. + +\image html cvt_message_email.png + +If the message has attachments, you can view them on the Attachments tab. If you select an attachment you can choose to open it in a new window, or you can look at it in the Thumbnails tab. + +\image html cvt_message_attach.png + +
    • The Contacts tab shows any information on this account that was found in a contacts file. + +\image html cvt_contacts.png + +
    • The Media tab shows thumbnails of any media files in messages for that account. If you click on one, it will show the message the media file came from. + +\image html cvt_media.png + +
    \section cvt_viz Visualization @@ -42,8 +65,12 @@ After selecting either option, the middle tab will switch to the Visualize view \image html cvt_visualize.png -The options at the top allow you to clear the graph, try different graph layouts, and resize the graph. The nodes in the graph can be dragged around and nodes and edges can be selected to display their messages or relationships in the right side tab. For example, in the image below the link between two email addresses has been selected so the Messages viewer is displaying the single email between those two email addresses. +The options at the top allow you to clear the graph and resize the graph. The nodes in the graph can be dragged around and nodes and edges can be selected to display their messages or relationships in the right side tab. For example, in the image below only one node has been selected so the Messages viewer is displaying only messages involving that email address. \image html cvt_links.png +If you click the "Snapshot Report" button, you can generate a report similar to the HTML \ref reporting_page "report module". Select a name for your report, which will be saved to the "Reports" folder in the current case. The Snapshot Report will contain two pages. The first will have a summary of the case, and second will contain the current graph along with your filter settings. + +\image html cvt_snapshot.png + */ \ No newline at end of file diff --git a/docs/doxygen-user/data_sources.dox b/docs/doxygen-user/data_sources.dox index d0b9f12753..cd48474f41 100644 --- a/docs/doxygen-user/data_sources.dox +++ b/docs/doxygen-user/data_sources.dox @@ -8,6 +8,7 @@ Autopsy supports four types of data sources: - Local Disk: Local storage device (local drive, USB-attached drive, etc.). (see \ref ds_local) - Logical Files: Local files or folders. (see \ref ds_log) - Unallocated Space Image Files: Any type of file that does not contain a file system but you want to run through ingest (see \ref ds_unalloc) +- Autopsy Logical Imager Results: The results from running the logical imager. (see \ref ds_logical_imager) \section ds_add Adding a Data Source @@ -111,4 +112,8 @@ To add unallocated space image files: -# Browse to the file. -# Choose whether to break the image up into chunks. Breaking the image up will give better performance since the chunks can be processed in parallel, but there is a chance that keywords or carved files that span chunk boundaries will be missed. +\section ds_logical_imager Adding an Autopsy Logical Imager Result + +This option allows you to add the results of a logical imager collection. See the \ref logical_imager_page page for details. + */ \ No newline at end of file diff --git a/docs/doxygen-user/experimental.dox b/docs/doxygen-user/experimental.dox index c5fcc742a8..1eee7831e4 100644 --- a/docs/doxygen-user/experimental.dox +++ b/docs/doxygen-user/experimental.dox @@ -10,10 +10,4 @@ To start, go to Tools->Plugins and select the "Installed" tab, then check the bo \image html experimental_plugins_menu.png -\section exp_features Current Experimental Features - -- \ref auto_ingest_page -- \ref object_detection_page -- \ref volatility_dsp_page - */ \ No newline at end of file diff --git a/docs/doxygen-user/images/AutoIngest/auto_ingest_mode_setup.png b/docs/doxygen-user/images/AutoIngest/auto_ingest_mode_setup.png index e11db06246..b9875d6245 100644 Binary files a/docs/doxygen-user/images/AutoIngest/auto_ingest_mode_setup.png and b/docs/doxygen-user/images/AutoIngest/auto_ingest_mode_setup.png differ diff --git a/docs/doxygen-user/images/AutoIngest/test_button_failure.png b/docs/doxygen-user/images/AutoIngest/test_button_failure.png new file mode 100644 index 0000000000..b4410d8df1 Binary files /dev/null and b/docs/doxygen-user/images/AutoIngest/test_button_failure.png differ diff --git a/docs/doxygen-user/images/LogicalImager/command_prompt.png b/docs/doxygen-user/images/LogicalImager/command_prompt.png new file mode 100644 index 0000000000..d4c33ebd13 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/command_prompt.png differ diff --git a/docs/doxygen-user/images/LogicalImager/config_flag.png b/docs/doxygen-user/images/LogicalImager/config_flag.png new file mode 100644 index 0000000000..0cecc4bedb Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/config_flag.png differ diff --git a/docs/doxygen-user/images/LogicalImager/configure_drive.png b/docs/doxygen-user/images/LogicalImager/configure_drive.png new file mode 100644 index 0000000000..94b03beeed Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/configure_drive.png differ diff --git a/docs/doxygen-user/images/LogicalImager/dsp_select.png b/docs/doxygen-user/images/LogicalImager/dsp_select.png new file mode 100644 index 0000000000..e05f677af8 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/dsp_select.png differ diff --git a/docs/doxygen-user/images/LogicalImager/exe_folder.png b/docs/doxygen-user/images/LogicalImager/exe_folder.png new file mode 100644 index 0000000000..3891f14f80 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/exe_folder.png differ diff --git a/docs/doxygen-user/images/LogicalImager/full_path_rule.png b/docs/doxygen-user/images/LogicalImager/full_path_rule.png new file mode 100644 index 0000000000..2471c02e4f Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/full_path_rule.png differ diff --git a/docs/doxygen-user/images/LogicalImager/image_flag.png b/docs/doxygen-user/images/LogicalImager/image_flag.png new file mode 100644 index 0000000000..cdc0b2a611 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/image_flag.png differ diff --git a/docs/doxygen-user/images/LogicalImager/import.png b/docs/doxygen-user/images/LogicalImager/import.png new file mode 100644 index 0000000000..2e3155b26e Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/import.png differ diff --git a/docs/doxygen-user/images/LogicalImager/main_config_panel.png b/docs/doxygen-user/images/LogicalImager/main_config_panel.png new file mode 100644 index 0000000000..6e3c1f15ce Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/main_config_panel.png differ diff --git a/docs/doxygen-user/images/LogicalImager/new_attr_rule.png b/docs/doxygen-user/images/LogicalImager/new_attr_rule.png new file mode 100644 index 0000000000..827ff7d19d Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/new_attr_rule.png differ diff --git a/docs/doxygen-user/images/LogicalImager/output_folder.png b/docs/doxygen-user/images/LogicalImager/output_folder.png new file mode 100644 index 0000000000..ff24f8ba87 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/output_folder.png differ diff --git a/docs/doxygen-user/images/LogicalImager/select_folder.png b/docs/doxygen-user/images/LogicalImager/select_folder.png new file mode 100644 index 0000000000..84b0ed66e0 Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/select_folder.png differ diff --git a/docs/doxygen-user/images/LogicalImager/tools_menu.png b/docs/doxygen-user/images/LogicalImager/tools_menu.png new file mode 100644 index 0000000000..1a203de29a Binary files /dev/null and b/docs/doxygen-user/images/LogicalImager/tools_menu.png differ diff --git a/docs/doxygen-user/images/central_repo_content_viewer.png b/docs/doxygen-user/images/central_repo_content_viewer.png index 7c0a137a86..55ef8e2398 100644 Binary files a/docs/doxygen-user/images/central_repo_content_viewer.png and b/docs/doxygen-user/images/central_repo_content_viewer.png differ diff --git a/docs/doxygen-user/images/credentialManager.PNG b/docs/doxygen-user/images/credentialManager.PNG deleted file mode 100644 index 21acb8779b..0000000000 Binary files a/docs/doxygen-user/images/credentialManager.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/cvt_contacts.png b/docs/doxygen-user/images/cvt_contacts.png new file mode 100644 index 0000000000..d085873a16 Binary files /dev/null and b/docs/doxygen-user/images/cvt_contacts.png differ diff --git a/docs/doxygen-user/images/cvt_links.png b/docs/doxygen-user/images/cvt_links.png index 1aa4db9033..5765d5c087 100644 Binary files a/docs/doxygen-user/images/cvt_links.png and b/docs/doxygen-user/images/cvt_links.png differ diff --git a/docs/doxygen-user/images/cvt_main.png b/docs/doxygen-user/images/cvt_main.png index be2c390f66..56d565bab3 100644 Binary files a/docs/doxygen-user/images/cvt_main.png and b/docs/doxygen-user/images/cvt_main.png differ diff --git a/docs/doxygen-user/images/cvt_media.png b/docs/doxygen-user/images/cvt_media.png new file mode 100644 index 0000000000..5fbc03d785 Binary files /dev/null and b/docs/doxygen-user/images/cvt_media.png differ diff --git a/docs/doxygen-user/images/cvt_message_attach.png b/docs/doxygen-user/images/cvt_message_attach.png new file mode 100644 index 0000000000..3eee863ba2 Binary files /dev/null and b/docs/doxygen-user/images/cvt_message_attach.png differ diff --git a/docs/doxygen-user/images/cvt_message_email.png b/docs/doxygen-user/images/cvt_message_email.png new file mode 100644 index 0000000000..335a90013b Binary files /dev/null and b/docs/doxygen-user/images/cvt_message_email.png differ diff --git a/docs/doxygen-user/images/cvt_messages.png b/docs/doxygen-user/images/cvt_messages.png deleted file mode 100644 index 956e8a87c6..0000000000 Binary files a/docs/doxygen-user/images/cvt_messages.png and /dev/null differ diff --git a/docs/doxygen-user/images/cvt_messages_threaded.png b/docs/doxygen-user/images/cvt_messages_threaded.png new file mode 100644 index 0000000000..4399f8d3a6 Binary files /dev/null and b/docs/doxygen-user/images/cvt_messages_threaded.png differ diff --git a/docs/doxygen-user/images/cvt_select_account.png b/docs/doxygen-user/images/cvt_select_account.png index 3a0874e081..f12d8df325 100644 Binary files a/docs/doxygen-user/images/cvt_select_account.png and b/docs/doxygen-user/images/cvt_select_account.png differ diff --git a/docs/doxygen-user/images/cvt_snapshot.png b/docs/doxygen-user/images/cvt_snapshot.png new file mode 100644 index 0000000000..82dae4e80e Binary files /dev/null and b/docs/doxygen-user/images/cvt_snapshot.png differ diff --git a/docs/doxygen-user/images/cvt_summary_tab.png b/docs/doxygen-user/images/cvt_summary_tab.png new file mode 100644 index 0000000000..c5e634c8a0 Binary files /dev/null and b/docs/doxygen-user/images/cvt_summary_tab.png differ diff --git a/docs/doxygen-user/images/cvt_visualize.png b/docs/doxygen-user/images/cvt_visualize.png index e5d56950b5..a7301abaee 100644 Binary files a/docs/doxygen-user/images/cvt_visualize.png and b/docs/doxygen-user/images/cvt_visualize.png differ diff --git a/docs/doxygen-user/images/getHostname.PNG b/docs/doxygen-user/images/getHostname.PNG deleted file mode 100644 index 9497077beb..0000000000 Binary files a/docs/doxygen-user/images/getHostname.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/grantAccessToComputer.PNG b/docs/doxygen-user/images/grantAccessToComputer.PNG new file mode 100644 index 0000000000..a155a20ea7 Binary files /dev/null and b/docs/doxygen-user/images/grantAccessToComputer.PNG differ diff --git a/docs/doxygen-user/images/hostname.PNG b/docs/doxygen-user/images/hostname.PNG deleted file mode 100644 index d97f78ae42..0000000000 Binary files a/docs/doxygen-user/images/hostname.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/multi-user-network.png b/docs/doxygen-user/images/multi-user-network.png new file mode 100644 index 0000000000..bdc6ce7ca7 Binary files /dev/null and b/docs/doxygen-user/images/multi-user-network.png differ diff --git a/docs/doxygen-user/images/objectTypesComputers.PNG b/docs/doxygen-user/images/objectTypesComputers.PNG new file mode 100644 index 0000000000..10b6db1444 Binary files /dev/null and b/docs/doxygen-user/images/objectTypesComputers.PNG differ diff --git a/docs/doxygen-user/images/portable_case_report_panel.png b/docs/doxygen-user/images/portable_case_report_panel.png index 9b46e610c4..e0a8a00513 100644 Binary files a/docs/doxygen-user/images/portable_case_report_panel.png and b/docs/doxygen-user/images/portable_case_report_panel.png differ diff --git a/docs/doxygen-user/images/postgresqlinstall1.PNG b/docs/doxygen-user/images/postgresqlinstall1.PNG deleted file mode 100644 index 4223962bd4..0000000000 Binary files a/docs/doxygen-user/images/postgresqlinstall1.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/postgresqlinstall2.PNG b/docs/doxygen-user/images/postgresqlinstall2.PNG deleted file mode 100644 index 7a417966ca..0000000000 Binary files a/docs/doxygen-user/images/postgresqlinstall2.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/select-data-source-type.PNG b/docs/doxygen-user/images/select-data-source-type.PNG index 5ddac0b812..b997bf9f0f 100644 Binary files a/docs/doxygen-user/images/select-data-source-type.PNG and b/docs/doxygen-user/images/select-data-source-type.PNG differ diff --git a/docs/doxygen-user/images/sharedStoragePermissions.PNG b/docs/doxygen-user/images/sharedStoragePermissions.PNG new file mode 100644 index 0000000000..bea1aa0fd4 Binary files /dev/null and b/docs/doxygen-user/images/sharedStoragePermissions.PNG differ diff --git a/docs/doxygen-user/images/tagging_image_one_tag.png b/docs/doxygen-user/images/tagging_image_one_tag.png new file mode 100644 index 0000000000..4aa206bf49 Binary files /dev/null and b/docs/doxygen-user/images/tagging_image_one_tag.png differ diff --git a/docs/doxygen-user/images/toConnect.PNG b/docs/doxygen-user/images/toConnect.PNG deleted file mode 100644 index 4bfecbc4b6..0000000000 Binary files a/docs/doxygen-user/images/toConnect.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/view_options_options_panel.png b/docs/doxygen-user/images/view_options_options_panel.png index 3a917e166e..40b5f6cf8a 100644 Binary files a/docs/doxygen-user/images/view_options_options_panel.png and b/docs/doxygen-user/images/view_options_options_panel.png differ diff --git a/docs/doxygen-user/installMultiUser.dox b/docs/doxygen-user/installMultiUser.dox deleted file mode 100644 index 38ec15c79c..0000000000 --- a/docs/doxygen-user/installMultiUser.dox +++ /dev/null @@ -1,31 +0,0 @@ -/*! \page install_multiuser_page Setting Up Multi-user Environment - -\section multiuser_install Multi-user Installation - -Autopsy can be setup to work in an environment where multiple users on different computers can have the same case open at the same time. To set up this type of environment, you will need to configure additional (free and open source) network-based services. - -\subsection multiuser_install_services Network-based Services - -You will need the following that all Autopsy clients can access: -- Centralized storage that all clients running Autopsy have access to. The central storage should be either mounted at the same Windows drive letter or UNC paths should be used everywhere. All clients need to be able to access data using the same path. -- A central PostgreSQL database. A database will be created for each case and will be stored on the local drive of the database server. Installation and configuration is explained in \ref install_postgresql. -- A central Solr text index. A Solr core will be created for each case and will be stored in the case folder (not on the local drive of the Solr server). We recommend using Bitnami Solr. This is explained in \ref install_solr. -- An ActiveMQ messaging server to allow the various clients to communicate with each other. This service has minimal storage requirements. This is explained in \ref install_activemq. - -When you setup the above services, write down the addresses, user names, and passwords or each so that you can configure each of the client systems afterwards. - -We recommend using at least 2 dedicated computers for this additional infrastructure. Spreading the services out across several machines can improve throughput. -If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers. - -Ensure that the central storage and PostgreSQL servers are regularly backed up. - -\subsection multiuser_install_clients Autopsy Clients - -Once the infrastructure is in place, you will need to configure Autopsy to use them. -- Install Autopsy on each client system as normal using the steps from \ref installation_page. -- Start Autopsy and open the multi-user settings panel from "Tools", "Options", "Multi-user". As shown in the screenshot below, you can then enter all of the address and authentication information for the network-based services. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct. - -\image html multiuser_settings.PNG - - -*/ diff --git a/docs/doxygen-user/installPostgres.dox b/docs/doxygen-user/installPostgres.dox deleted file mode 100644 index 07650aee24..0000000000 --- a/docs/doxygen-user/installPostgres.dox +++ /dev/null @@ -1,104 +0,0 @@ -/*! \page install_postgresql Install and Configure PostgreSQL -To install PostgreSQL, perform the following steps: - -1. Download a 64-bit PostgreSQL installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_. Autopsy has been tested with PostgreSQL version 9.5. - -2. Run the installer. The name will be similar to _postgresql-9.5.3-1-windows-x64.exe_. - -3. You may accept defaults for all items except for the password as you work through the wizard. Do not lose the password you enter in. This is the PostgreSQL administrator login password. - -4. You do not need to launch the StackBuilder nor acquire any more software from it. Uncheck the option to use StackBuilder and press _Finish_. - -5. Create a regular user account to use while running Autopsy. You can do this with either of two methods, graphically, or command line. We cover graphically first. - - - Graphically: - - Using the PostgreSQL administrator login and the pgAdmin III tool, create a regular user account to use while running Autopsy. - - Right click on "Login Roles" and select "New Login Role..." as shown below: -

    -\image html pgAdmin.PNG -

    - - Enter the user name you would like to use in the "Role name" field. -

    -\image html newLoginRole.PNG -

    - - Enter the password on the "Definition" tab. -

    -\image html newPassword.PNG -

    - - Check "Can create databases" on the "Role Privileges" tab. -

    -\image html newRights.PNG -

    - - Click "OK". -

    - - Command line: -
    - Use the _psql_ tool. To start _psql_, press _Start_, type _psql_, and press _Enter_ a few times until it prompts you for a password. Type in the password you gave it when installing PostgreSQL. You should see a prompt that looks like the screenshot below. -

    -\image html postgresqlinstall1.PNG -

    -If you want your user account name to be "Autopsy" and your password to be "myPassword", use the following command to create a new user, noting that the password is enclosed in single quotes, __not backticks nor double quotes__. Also note that it is important to type this command in from the keyboard directly, as copying and pasting can sometimes yield different characters for single quotes that can confuse _psql_. -

    -The command is: -
    -> CREATE    USER    Autopsy    WITH    PASSWORD    'myPassword'    CREATEDB; -
    -When you see the _CREATE ROLE_ output as shown in the screenshot below, the new user has been created. You can close the _psql_ window now. -
    -
    -\image html postgresqlinstall2.PNG -
    - -6. Edit C:\\Program Files\\PostgreSQL\\9.5\\data\\pg_hba.conf to add an entry to allow external computers to connect via the network. -

    -First, find your machine's IPv4 address and Subnet Mask (Press _Start_, type _cmd_, type _ipconfig_ and parse the results. The IP address is shown in yellow below. -
    -\image html postgresqlinstall3.PNG -
    -The following is an example rule that allows all clients on the 10.10.192.x subnet to connect using md5 authentication. -
    -> host      all      all      10.10.192.0/24      md5 -
    -__Subnet Mask Rules of thumb:__ - - If your Subnet Mask is 255.255.0.0, your rule should look like this: A.B.0.0/16, where A is the first octet in your IP address and B is the second octet. -
    - - If your Subnet Mask is 255.255.255.0, your rule should look like this: A.B.C.0/24, where A is the first octet in your IP address, B is the second octet, and C is the third octet. -

    -Add the line highlighted in yellow below, formatted with spaces between the entries, adjusting the IP address to an appropriate value as described above. -

    -\image html postgresqlinstall4.PNG -
    -

    -If you intend to use PostgreSQL from machines on a different subnet, you need an entry in the _pg_hba.conf_ file for each subnet. -

    - -7. Uncomment the following entires in the configuration file located at C:\\Program Files\\PostgreSQL\\9.5\\data\\postgresql.conf by removing the leading "#", and change their values "off" as shown below. -
    -> fsync = off
    -> synchronous_commit = off
    -> full_page_writes = off
    -
    -Pictorially, change the following, from this: -

    -\image html postgresqlinstall5.PNG -

    -To this: -

    -\image html postgresqlinstall6.PNG -

    -Note the removal of the leading number symbol-this uncomments that entry. -

    - -8. Still in "C:\Program Files\PostgreSQL\9.5\data\postgresql.conf", find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. More information is available at 5.1.1. See the screenshot below. -

    -\image html maxConnections.PNG -

    - -9. Press _Start_, type _services.msc_, and press _Enter_. Select _postgresql-x64-9.5_ in the services list and click the link that says _Stop the service_ then click the link that says _Start the service_ as shown in the screenshot below. -

    -\image html postgresqlinstall7.PNG -

    -PostgreSQL should now be up and running. You can verify by using either the _pgAdmin_ tool or the _psql_ tool to connect to the database server from another machine on the network . -


    - -*/ diff --git a/docs/doxygen-user/installSolr.dox b/docs/doxygen-user/installSolr.dox deleted file mode 100644 index 494e24588d..0000000000 --- a/docs/doxygen-user/installSolr.dox +++ /dev/null @@ -1,108 +0,0 @@ -/*! \page install_solr Install and Configure Solr -A central Solr server is needed to store keyword indexes, and its embedded Zookeeper is used as a coordination service for Autopsy. To install Solr, perform the following steps: - -\section install_solr_prereq Prerequisites - -You will need: -- A 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. -- The Apache Solr 4.10.3-0 installation package from https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr or Direct Download Link -- Access to an installed version of Autopsy so that you can copy files from it. -- A network-accessible machine to install Solr on. Note that the Solr process will need to write data out to the main shared storage drive, and needs adequate permissions to write to this location, which may be across a network. - -\section install_solr_install Installation - -\subsection install_solr_install_java JRE Installation -1. JREs are normally installed under "C:\Program Files\Java\jre(version)", so check there to see if you have one installed already. If not, get the installer from the link in the \ref install_solr_prereq and install it with the default settings. - -\subsection install_solr_install_solr Solr Installation - -The following steps will configure Solr to run using an account that will have access to the network storage. - -1. Run the Bitnami installer, "bitnami-solr-4.10.3-0-windows-installer.exe" -2. If Windows prompts with User Account Control, click _Yes_ -3. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box. -4. If you see an error dialog like the following, you may safely ignore it. -

    -\image html apachebadmessage.PNG -
    -5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_. - -\subsection install_solr_config Solr Configuration -1. Stop the _solrJetty_ service by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrJetty_ Windows service. Select the service and press _Stop the service_. If the service is already stopped and there is no _Stop the service_ available, this is okay. -2. Edit the "C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat" script. You need administrator rights to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file: -
    -
    - - Add the following options in the line that begins with "C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe" : - + ++JvmOptions=-Dcollection.configName=AutopsyConfig - + ++JvmOptions=-Dbootstrap_confdir="C:\Bitnami\solr-4.10.3-0\apache-solr\solr\configsets\AutopsyConfig\conf" - + ++JvmOptions=-DzkRun -
    - - Replace the path to JavaHome with the path to your 64-bit version of the JRE. If you do not know the path, the correct JavaHome path can be obtained by running the command "where java" from the Windows command line. An example is shown below. The text in yellow is what we are interested in. Do not include the "bin" folder in the path you place into the JavaHome variable. A correct example of the final result will look something like this: –-JavaHome="C:\Program Files\Java\jre1.8.0_111" -

    - A portion of an updated _serviceinstall.bat_ is shown below, with the changes marked in yellow. -

    - \image html serviceinstall.PNG -

    -3. Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml" to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is - \10\ -

    - The added part is highlighted in yellow below. Ensure that it is inside the \ tag as follows: -
    - \image html transientcache.PNG -

    -4. Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties" to configure Solr log settings: - - Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB. - - Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line. -

    - The log file should end up looking like this (modified lines are highlighted in yellow -

    - \image html log4j.PNG -

    -5. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\configsets" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". -6. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\lib" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". -7. From an Autopsy installation, copy the file "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\zoo.cfg" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". -8. Stop the solrJetty service by pressing Start, typing services.msc, pressing Enter, and locating the solrJetty Windows service. Select the service and press Stop the service. If the service is already stopped and there is no Stop the service available, this is okay. -9. Start a Windows command prompt as administrator by pressing Start, typing command, right clicking on Command Prompt, and clicking on Run as administrator. Then run the following command to uninstall the solrJetty service: - - cmd /c C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat UNINSTALL - - You will very likely see a result that says "The solrJetty service is not started." This is okay. -10. Start a Windows command prompt as administrator by pressing Start, typing command, right clicking on Command Prompt, and clicking on Run as administrator. Then run the following command to install the solrJetty service: - - cmd /c C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat INSTALL -
    Note the argument "INSTALL" is case sensitive. Your command prompt should look like the screenshot below. Very likely your command prompt will say "The solrJetty service could not be started." This is okay. -

    - \image html solrinstall1.PNG -

    - -\subsection running_solr Running Solr - -To run Solr, first you'll need to choose which account should run the service. Once you've decided, you'll start the service through the Windows Service Manager. Both of these steps are described below. - -\subsubsection solr_user Choosing the User Account for the Solr Service - -Solr will run as a Windows service as a specific user. If the wrong user account is used, Solr will not have permissions to write indexes. - -Generally, you'll want to run Solr as a Windows Domain account. You will NOT want to use the default setup, which is to run Solr as a "Local Service Account", because this account does not have access to network-based shared storage. The one exception is if the computer running Solr is also the computer that hosts the shared storage. In that is the case, then the "Local Service Account" will still be able to access the shared storage since the network is not required. - -\subsubsection start_solr Starting Solr -
      -
    1. You should be able to see the Solr service in a web browser via the URL http://localhost:8983/solr/#/ as shown in the screenshot below. If you can, you should skip the next step. If you cannot, proceed to the next step. - -
    2. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. Refer to the \ref solr_user section to see which account type is appropriate for your case. - -If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values. - -\image html solrinstall2.PNG - -If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right." - -
    3. You should be able to see the Solr service in a web browser via the URL http://localhost:8983/solr/#/ as shown in the screenshot below. -

      - \image html solrinstall3.PNG -

      -If the service is appropriately started and you are unable to see the screenshot above, contact your network administrator to open ports in the firewall. -

      -Warning: The Solr process must have adequate permissions to write data to the main shared storage drive where case output will be stored. -
    -*/ diff --git a/docs/doxygen-user/installation.dox b/docs/doxygen-user/installation.dox index aa67e55d7a..1577806f54 100644 --- a/docs/doxygen-user/installation.dox +++ b/docs/doxygen-user/installation.dox @@ -2,30 +2,44 @@ [TOC] -\section prereqs Prerequisites -It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them. -

    \section install Deployment Types -Starting with Autopsy 4.0, there are two ways to deploy Autopsy: -- **Single-User**: Cases can be open by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process. -- **Multi-User**: Cases can be open by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page. -\section sysreqs System Memory Requirements -The 64 bit version of Autopsy requires a minimum of 8GB RAM (16 GB recommended). -When the 64 bit version of Autopsy is installed on Windows it will be limited to a maximum heap size of 4GB leaving the remaining memory for the operating system, the internal Solr text indexing service and other applications. If you wish to change the maximum heap size you can do so after installation by changing the Maximum JVM Memory value in the Runtime section under Tools -> Options -> Application. +There are two ways to deploy Autopsy: +- **Single-User**: Cases can be opened by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process. +- **Multi-User**: Cases can be opened by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page. -\image html runtime_settings.PNG \section download Download -Download Autopsy from the website: +Regardless of the deployment type, you can download Autopsy from the website: http://sleuthkit.org/autopsy/download.php -The current version of Autopsy 4 is distributed on sleuthkit.org only as a Windows installer. It can run on Linux and OS X, but requires some manual setup. +We distribute a Windows installer and ZIP files to run on Linux and OS X. -\section install_standalone Installation + +\section install_reqs System Requirements + +\subsection prereqs_av AntiVirus + +You should consider either disabling AntiVirus software that runs on your computers that will be running Autopsy or configuring your AntiVirus software to ignore the contents of your case output directory. AntiVirus software may quarantine or even delete some of your results before you get a chance to look at them. Autopsy encodes some of the files that it extracts so that they can not be executed or scanned, but some modules (such as our ZIP extraction module) will directly write files to disk in an unencoded format. + +Of course, the risk with disabling AntiVirus software is that your computer could be infected with malware that came from your media. + + + +\subsection sysreqs Memory + +We recommend a minimum of 16GB of RAM. + +By default, Autopsy will use a maximum of 4GB of RAM (not including memory that the Solr text indexing server uses). You can increase this size after installation by changing the Maximum JVM Memory value in the Runtime section under Tools -> Options -> Application. + +\image html runtime_settings.PNG + + + +\section install_standalone Single-User Installation To install Autopsy, perform the following steps: 1. Run the Autopsy _msi_ file 2. If Windows prompts with User Account Control, click _Yes_ @@ -33,4 +47,12 @@ To install Autopsy, perform the following steps: 4. Autopsy should now be fully installed +\section install_proxy Proxies + +If you are behind a proxy and need access to a network with Autopsy or one of the modules, you may set your proxy information in the _Tools_, _Options_, _General_ tab as shown in the screenshot below. + +

    +\image html proxySettings.PNG +

    + */ diff --git a/docs/doxygen-user/logical_imager.dox b/docs/doxygen-user/logical_imager.dox new file mode 100644 index 0000000000..5f46068418 --- /dev/null +++ b/docs/doxygen-user/logical_imager.dox @@ -0,0 +1,124 @@ +/*! \page logical_imager_page Logical Imager + +\section logical_imager_overview Overview + +The logical imager allows you to collect files from a live Windows computer. The imager is configured with rules that specify what files to collect. Rules can be based on file attributes such as folder names, extensions, and sizes. You can use this feature when you do not have time or authorization to perform a full drive acquisition. + +The logical imager produces one or more sparse VHD images that contain all of the file system data that was read. These VHD images can be imported into Autopsy or mounted by Windows. The imager also enumerates the user accounts on the system and can generate alerts if encryption programs exist. + +The general workflow is: +
      +
    • Configure logical imager using Autopsy. This will copy a configuration file specifying which files to collect and the logical imager executable to the target drive. +
    • Insert the drive into the target system and run logical imager. This will give you a folder containing the sparse VHD copy of the target system (or multiple VHDs if more than one drive was analyzed), a file containing user account information, and a record of which files generated alerts. +
    • Load the result of running logical imager into Autopsy to browse any matching files and see user account information. +
    + +\section logical_imager_config Configuration + +To start, open Autopsy and go to Tools->Create Logical Imager. + +\image html LogicalImager/tools_menu.png + +
      +
    • Configuring an external drive + +The normal use case is to select a drive from the list under "Configure selected external drive." This will put the logical imager executable and a configuration file into the root directory of that drive once you finish the configuration. It is important to run the executable from the root of your drive because its presence on the drive makes the imager skip that drive during processing. MOVE THIS?? + +\image html LogicalImager/configure_drive.png + +
    • Configuring in a folder + +If you're not ready to set up your drive yet, or if you want to create a different configuration file, you can use the second option to browse to a folder or an existing configuration file. If you're creating a new file, browse to the folder you want to create it in. +Notice that the configuration file has the default name "logical-imager-config.json". You can change this, but if you do you'll need to rename it after you copy it to your drive or use the command prompt to run the imager. See the section on \ref logical_imager_custom_run. + +\image html LogicalImager/select_folder.png +
    + +In either case you can now configure your imager. If the configuration file already exists, this screen will be loaded with the current settings from the file. + +\image html LogicalImager/main_config_panel.png + +On the left side you can see each rule in the configuration file. Each of these rules will be applied against the live system. A rule has a name, an optional description, one or more conditions, and settings for what should happen when a file matching the rule is found. When you select a rule you'll see all the settings for that rule on the right side of the panel. You can edit or delete rules once you select them. There are also two global settings in the bottom right that apply to the configuration file as a whole: +
      +
    • Alert if encryption programs are found - This will add a predefined rule to find encryption programs and alert and export any that are found. You will not be able to edit this rule. +
    • Continue imaging after searches are performed - By default, the logical imager will only copy sectors that it uses or that are part of matching files being exported. If this option is selected, logical imager will go back through the image after the rule matching is complete and copy over any remaining sectors. This will take longer to run and result in much larger VHD images. +
    + +To make a new rule, click on the "New Rule" button. + +\image html LogicalImager/new_attr_rule.png + +There are two rule types to choose from: +
      +
    • Attribute rules allow you enter multiple conditions that must be true for a file to match +
    • Full path rules allow you to enter one or more full paths (path and file name) which must match exactly +
    + +For either rule type, you start by entering a rule name and optional description. You will also need to choose at least one action to take when a match is found. +
      +
    • Alert in Imager console if rule matches - this will display the file data in the console and add it to the "alerts.txt" output file. +
    • Extract file if it matches a rule - this will ensure that the matching file's contents will be copied to the sparse VHD +
    + +Attribute rules can have one or more conditions. All conditions must be true for a rule to match. +
      +
    • Extensions - File must match one of the given extensions (comma-separated). Extensions are case-insensitive. +
    • File names - File must match one of the given file names (new line-separated). File names should include extensions and are case-insensitive. +
    • Folder names - File must match one of the given paths (new line-separated). The given path may be a substring of the file path. You can use "[USER_FOLDER]" to match any user folder on the system. For example, "[USER_FOLDER]/Downloads" will match the downloads folder in any user folder, such as "Users/username/Downloads". +
    • Minimum size / Maximum size - File must be in the given range. You can either both fields to specify a range or use just one to match all files larger or smaller than the given size. +
    • Modified Within - File must have been changed within the specified last number of days +
    + +Full path rules have a single condition. +
      +
    • Full paths: File must exactly match one of the given full paths (new line-separated) +
    + +\image html LogicalImager/full_path_rule.png + +\section logical_imager_running Running Logical Imager + +\subsection logical_imager_default_run Running with the Default Configuration + +Using the defaults in the configuration process will create a drive with the config file (named "logical-imager-config.json") and the logical imager executable in the root folder of your drive. + +\image html LogicalImager/exe_folder.png + +The default case is to run the logical imager on every drive except the one containing it. Note that the logical imager executable must be in the root directory for the drive to be skipped. To run the imager, right-click on "tsk_logical_imager.exe" and select "Run as administrator". This will open a console window where you'll see some information about the processing and if you set any rules to create alerts, you'll see matches in the console window as well. The window will close automatically when the processing is complete. + +The logical imager will start writing the sparse VHD(s) and any other data to a directory next to the executable. + +\image html LogicalImager/output_folder.png + +\subsection logical_imager_custom_run Running from a Command Prompt + +To run the logical imager with custom settings, you'll need to first open a command prompt in administrator mode (right-click and then select "Run as administrator"). Then switch to the drive where logical imager is located. You can run using the default configuration by simply typing "tsk_logical_imager.exe". + +\image html LogicalImager/command_prompt.png + +If your configuration file is not named "logical-imager-config.json" (for example, if you have multiple configuration files for different situations), you'll need to specify the file name using the "-c" flag. + +\image html LogicalImager/config_flag.png + +If you want to specify the drive to run on, you can use the "-i" flag. This can be helpful for testing your configuration file - you can create a small USB drive with files that should match your rules to ensure that everything is working correctly before using it on a real system. The following example shows how to only run on the "G" drive on this system: + +\image html LogicalImager/image_flag.png + +\section logical_imager_results Viewing Results + +The logical imager results can be added to an Autopsy case as a \ref ds_page "data source". This brings in the sparse VHD(s) as a disk image and also adds the other files created by the logical imager. Select the "Autopsy Imager" option and proceed to the next page. + +\image html LogicalImager/dsp_select.png + +In the top section, you can see all the logical imager result folders in the root folder of each drive. Select the one you want to add and then hit the "Next" button. + +\image html LogicalImager/import.png + +If your logical imager results are in a different location, select "Manually Choose Folder" and use the "Browse" button to locate your results. + +In either case you'll get to configure the \ref ingest_page "ingest modules" to run. You can run any of them, but since your disk image may not be complete you may see more errors than normal. For example, the sparse VHD may contain the entire file allocation table but the actual data that goes with the files will be missing. + +The alert and user files created by the logical imager can be found under the Reports section of the Tree Viewer. + + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index 3a4c6d96fc..7087c42ec2 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -17,7 +17,7 @@ The following topics are available here: - \subpage cases_page - \subpage ds_page - \subpage uilayout_page -- Automated Analysis (Modules) +- Ingest Modules - \subpage ingest_page "Ingest Modules" - \subpage recent_activity_page - \subpage hash_db_page @@ -34,7 +34,7 @@ The following topics are available here: - \subpage cr_ingest_module - \subpage encryption_page - \subpage vm_extractor_page -- Manual Analysis +- Reviewing the Results - \subpage tree_viewer_page - \subpage result_viewer_page - \subpage content_viewer_page @@ -55,20 +55,20 @@ The following topics are available here: - \subpage reporting_page - \subpage module_install_page - \subpage performance_page -- Multi-user Collaborative Deployments +- Multi-user Cluster - \subpage install_multiuser_page - - \subpage install_activemq - - \subpage install_postgresql - - \subpage install_solr - - \subpage windows_authentication - \subpage multiuser_sec_page - \subpage multiuser_page - Triage - \subpage triage_page - \subpage live_triage_page -- \subpage advanced_page -- \subpage experimental_page +- Experimental Module + - \subpage experimental_page + - \ref auto_ingest_page + - \ref object_detection_page + - \ref volatility_dsp_page - \subpage command_line_ingest_page +- \subpage logical_imager_page - \subpage translations_page If the topic you need is not listed, refer to the Autopsy Wiki or join the SleuthKit User List at SourceForge. diff --git a/docs/doxygen-user/multiuser.dox b/docs/doxygen-user/multi-user/createMultiUserCase.dox similarity index 98% rename from docs/doxygen-user/multiuser.dox rename to docs/doxygen-user/multi-user/createMultiUserCase.dox index 99141d0a22..e0e46d156e 100644 --- a/docs/doxygen-user/multiuser.dox +++ b/docs/doxygen-user/multi-user/createMultiUserCase.dox @@ -7,7 +7,7 @@ Multi-user cases allow multiple instances of Autopsy to have the same case open \image html case-newcase.PNG To create a multi-user case, the following must occur: -- The network services must be installed, configured, and running. See \ref multiuser_install_services. +- The network services must be installed, configured, and running. See \ref multiuser_install_install. - The Case folder needs to be in a shared folder that all other clients can also access at the same path (UNC or drive letter). - The data sources that are added with the Add Data Source wizard must be in a shared folder that all clients can access at the same path. diff --git a/docs/doxygen-user/installActiveMQ.dox b/docs/doxygen-user/multi-user/installActiveMQ.dox similarity index 76% rename from docs/doxygen-user/installActiveMQ.dox rename to docs/doxygen-user/multi-user/installActiveMQ.dox index fec69c00de..4d7ab37fe5 100644 --- a/docs/doxygen-user/installActiveMQ.dox +++ b/docs/doxygen-user/multi-user/installActiveMQ.dox @@ -1,5 +1,9 @@ -/*! \page install_activemq Install and Configure ActiveMQ -To install ActiveMQ, perform the following steps: +/*! \page install_activemq_page Install and Configure ActiveMQ + +\section install_activemq_overview Overview + +ActiveMQ is a messaging service that allows the Autopsy clients to communicate with each other. This allows each client to get real-time updates. This service has minimal storage requirements. + \section install_activemq_prereq Prerequisites @@ -32,15 +36,29 @@ If you need the JRE, install it with the default settings. 4. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link. -5. ActiveMQ should now be installed and configured using the default credentials. You should go to the next section to change the default passwords. To test your installation, you can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by following the instructions below. If you can see a page that looks like the following, it is ready to function. +5. ActiveMQ should now be installed and configured using the default credentials. + + +\subsection install_activemq_test Testing + +To test your installation, you can access the admin pages in your web browser via a URL like this: http://localhost:8161/admin. + +The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by following the instructions below. + +If you can see a page that looks like the following, it confirms that the ActiveMQ service is running locally but it does not necessarily mean that the service is visible to other computers on the network.

    \image html activemq.PNG

    -If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication. +You can confirm that your ActiveMQ installation is visible to other computers on the network by attempting to connect to a URL like the following (replacing the host name with that of the ActiveMQ computer) in a web browser: http://activemq-computer:61616 + +If you are unable to connect to this address: +- Double check that the ActiveMQ service is running +- Check that the port (61616) is not being blocked by a firewall. -\subsection install_activemq_install_pw Configuring Authentication + +\section install_activemq_install_pw Configuring Authentication You can optionally add authentication to your ActiveMQ server. The ActiveMQ communications are not encrypted and contain basic messages between the systems about when new data has been found. @@ -60,6 +78,7 @@ When complete, the file should look like this:

    \image html groups.properties.after.PNG

    + 2. Copy and paste the following text to the file "conf\users.properties", overwriting the text highlighted in yellow in the screenshot below:
    system=manager
    @@ -74,6 +93,7 @@ When complete, the file should look like this:

    \image html users.properties.after.PNG

    + 3. Copy and paste the following text to the file "conf\activemq.xml", inserting the text at the line shown in yellow in the screenshot below. @@ -111,4 +131,8 @@ To add a new user or change the password: \image html StartActiveMQService.PNG

    +\section install_mq_backup Backing Up + +There is nothing to backup for ActiveMQ. It does not store any case-related data in files. + */ diff --git a/docs/doxygen-user/multi-user/installMultiUser.dox b/docs/doxygen-user/multi-user/installMultiUser.dox new file mode 100644 index 0000000000..a70d2b5deb --- /dev/null +++ b/docs/doxygen-user/multi-user/installMultiUser.dox @@ -0,0 +1,35 @@ +/*! \page install_multiuser_page Setting Up Multi-user Cluster + +\section multiuser_install_overview Overview + +Autopsy can be setup to work in an environment where multiple users on different computers can have the same case open at the same time. To set up this type of environment, you will need to configure additional (free and open source) network-based services. + +The basic concept is that you'll have a central: +- Database +- Keyword search index +- Storage + +Each Autopsy client will then use those shared resources instead of the embedded versions that are used for single-user cases. + +\image html multi-user-network.png + + +\section multiuser_install_install Cluster Installation and Configuration + +Let's now step through the process of setting up an Autopsy cluster. When you setup the network services, write down the addresses, user names, and passwords for each so that you can more easily configure each of the client systems afterwards. + +Step 1: \ref install_multiuser_systems_page + +Step 2: \ref install_multiuseruser_page + +Step 3: \ref install_multiuser_storage_page + +Step 4: \ref install_postgresql_page + +Step 5: \ref install_solr_page + +Step 6: \ref install_activemq_page + +Step 7: \ref install_multiuserclient_page + +*/ diff --git a/docs/doxygen-user/multi-user/installMultiUserClient.dox b/docs/doxygen-user/multi-user/installMultiUserClient.dox new file mode 100644 index 0000000000..171875e4a3 --- /dev/null +++ b/docs/doxygen-user/multi-user/installMultiUserClient.dox @@ -0,0 +1,22 @@ +/*! \page install_multiuserclient_page Install Autopsy Clients + + +\section multiuser_install_clients Overview + +Once the infrastructure is in place, you can configure Autopsy clients to use them. +- Install Autopsy on each client system. Use the normal installer and pick the defaults. +- Test that the user has access to the shared storage by opening the shared storage folders using Windows Explorer. If a password prompt is given, then enter the password and store the credentials (see \ref multiuser_users_store). +- Start Autopsy and open the multi-user settings panel from "Tools", "Options", "Multi-user". As shown in the screenshot below, you can then enter all of the address and authentication information for the network-based services. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct. + +\image html multiuser_settings.PNG + +- For each setting, press the "Test" button to ensure that Autopsy can communicate with each service. If any fail, then refer to the specific setup page for testing options. Also check that a firewall is not blocking the communications. + + - NOTE: None of these tests are for permissions on the shared storage because Autopsy does not know about the shared storage. It can't test that until you make a case. + +- Make a test case (see \ref creating_multi_user_cases). You can add a single file in as a logical data source. The key concept is to look for errors. + - If you find errors, look for errors in the log file on the Autopsy client. + - If you followed all of the previous steps in all of the previous pages, then a common error at this point is that Solr cannot access the shared storage and it is running as a Service account. When this happens, you'll see an error message about Solr not being able to create or access a "core". If this happens, review what user Solr should be running as (see \ref multiuser_users_solr) and change the shared storage configuration or ensure that credentials are stored. + + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/multi-user/installPostgres.dox b/docs/doxygen-user/multi-user/installPostgres.dox new file mode 100644 index 0000000000..fd7753281b --- /dev/null +++ b/docs/doxygen-user/multi-user/installPostgres.dox @@ -0,0 +1,119 @@ +/*! \page install_postgresql_page Install and Configure PostgreSQL + + +\section install_post_over Overview + +In a multi-user case, a central PostgreSQL database server is used instead of the embedded SQLite databases. + +A new database will be created for each case and the database will be stored in a location you choose during installation. It is recommended that you choose a drive that is local to the machine and is not the system drive. + +You should ensure that the database folder is backed up. + +\section install_post_install Installation + +To install PostgreSQL, perform the following steps: + +1. Download a 64-bit PostgreSQL installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_. Autopsy has been tested with PostgreSQL version 9.5. + +2. Run the installer. The name will be similar to _postgresql-9.5.3-1-windows-x64.exe_. + +3. You may accept defaults for all items except for the password and the database storage location as you work through the wizard. Do not lose the password you enter in. This is the PostgreSQL administrator login password. + +4. You do not need to launch the StackBuilder nor acquire any more software from it. Uncheck the option to use StackBuilder and press _Finish_. + + +\section install_post_config Configuration + +1. Create a regular database user account that Autopsy will use. You can do this with either of two methods, graphically, or command line. We cover graphically here. + + +- Use the pgAdmin III tool and login with the PostgreSQL administrator login. +- Right click on "Login Roles" and select "New Login Role..." as shown below: +

    +\image html pgAdmin.PNG +

    +- Enter the user name you would like to use in the "Role name" field. +

    +\image html newLoginRole.PNG +

    +- Enter the password on the "Definition" tab. +

    +\image html newPassword.PNG +

    +- Check "Can create databases" on the "Role Privileges" tab. +

    +\image html newRights.PNG +

    +- Click "OK". + +2. Edit C:\\Program Files\\PostgreSQL\\9.5\\data\\pg_hba.conf to add an entry to allow external computers to connect via the network. +

    +First, find your machine's IPv4 address and Subnet Mask (Press _Start_, type _cmd_, type _ipconfig_ and parse the results. The IP address is shown in yellow below. +
    +\image html postgresqlinstall3.PNG +
    +The following is an example rule that allows all clients on the 10.10.192.x subnet to connect using md5 authentication. +
    +> host      all      all      10.10.192.0/24      md5 +
    +__Subnet Mask Rules of thumb:__ + - If your Subnet Mask is 255.255.0.0, your rule should look like this: A.B.0.0/16, where A is the first octet in your IP address and B is the second octet. +
    + - If your Subnet Mask is 255.255.255.0, your rule should look like this: A.B.C.0/24, where A is the first octet in your IP address, B is the second octet, and C is the third octet. +

    +Add the line highlighted in yellow below, formatted with spaces between the entries, adjusting the IP address to an appropriate value as described above. +

    +\image html postgresqlinstall4.PNG +
    +

    +If you intend to use PostgreSQL from machines on a different subnet, you need an entry in the _pg_hba.conf_ file for each subnet. +

    + +3. Uncomment the following entires in the configuration file located at C:\\Program Files\\PostgreSQL\\9.5\\data\\postgresql.conf by removing the leading "#", and change their values "off" as shown below. +
    +> fsync = off
    +> synchronous_commit = off
    +> full_page_writes = off
    +
    +Pictorially, change the following, from this: +

    +\image html postgresqlinstall5.PNG +

    +To this: +

    +\image html postgresqlinstall6.PNG +

    +Note the removal of the leading number symbol-this uncomments that entry. +

    + +4. Still in "C:\Program Files\PostgreSQL\9.5\data\postgresql.conf", find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. See the screenshot below. +

    +\image html maxConnections.PNG +

    + + + +5. Restart the service via the Services panel by pressing _Start_, type _services.msc_, and press _Enter_. Select _postgresql-x64-9.5_ in the services list and click the link that says _Stop the service_. If you want PostgreSQL to run as a different user (you don't need to), then make that change now. When done, click the link that says _Start the service_ as shown in the screenshot below. +

    +\image html postgresqlinstall7.PNG +

    + + +\section install_post_test Testing + +You can verify that PostgreSQL is running by using either the _pgAdmin_ tool or the _psql_ tool to connect to the database server from another machine on the network. + +Common problems are typically the result of: +- Firewall blocking the port (default: 5432) on the PostgreSQL server. +- Incorrectly configured database user account or incorrect credentials. +- Incorrectly configured IP address range in pg_hba.conf file. + + +\section install_post_backup Backing Up + +The databases and configuration files are stored at the location you chose during PostgreSQL installation (not shared storage). So, you should backup that directory periodically. + +For an installation where the default options were chosen, the directory can be found at C:\\Program Files\\PostgreSQL\\9.5\\data. + + +*/ diff --git a/docs/doxygen-user/multi-user/installSharedStorage.dox b/docs/doxygen-user/multi-user/installSharedStorage.dox new file mode 100644 index 0000000000..6651d6ab69 --- /dev/null +++ b/docs/doxygen-user/multi-user/installSharedStorage.dox @@ -0,0 +1,42 @@ +/*! \page install_multiuser_storage_page Set Up Shared Storage + +\section multiuser_storage Overview + +The cluster will need shared storage that can be accessed from: +- Autopsy clients +- Solr server + +This shared storage will be used for both data sources and case outputs, so you will need lots of space. + +Specific configuration of the shared storage will depend on what type of file share you have. Examples include Windows file sharing, Linux Samba, or a NAS using FibreChannel. + +Below are some general guidelines to use during the setup and configuration. + +\subsection multiuser_storage_req Requirements +- All computers will need to access the shared storage at the exact same path. So, all must have the same drive letters or be able to resolve the same host names (for UNC paths). +- If Solr is running as a Windows service, you will likely need to use UNC paths. The service will not have access to drive letters for network mounted drives (such as Windows file sharing). If you have a hardware-based NAS, then you may be able to use drive letters. +- The user accounts that Autopsy and Solr are running as will need permissions to read and write to the shared storage. See \ref multiuser_users for tips on picking user accounts and saving credentials. + + +\subsection multiuser_storage_con Considerations +- You will probably get better performance by having different drives for inputs (disk images) and outputs (case folders). +- If you separate the shares, the "inputs" share can be provided as read-only if you do not want clients to modify the data sources. + + +\subsection multiuser_storage_ex Example +- Windows Server +- Dedicated SSD drives for inputs (data sources) and outputs (case folder). +- Each drive is shared with names "DataSources" and "Cases". +- If the server is not part of a domain, local accounts are created on it for each user that will be running Autopsy or Solr. Each account will have the same password on all systems. +- If Solr will be running as the NetworkService account, grant access to the shares for the computer running Solr (i.e. not just a specific user). + + +\subsection multiuser_storage_test Testing +- Before you proceed to setup any other services, you should test that the computers can access the share. Permission problems with the shares are the most common configuration challenge. +- Log into a computer that will eventually be an Autopsy client using an account that Autopsy will run as. +- Access the share, such as \\\\autopsy_storage\\Cases. +- If you get prompted for a password, then either: + - Store the credentials, as outlined in \ref multiuser_users_store. You'll need to repeat this on all of the clients and Solr server. + - Reconfigure the shared storage server if the prompt was because of an error. This may include ensuring that they both have the same password. + +*/ diff --git a/docs/doxygen-user/multi-user/installSolr.dox b/docs/doxygen-user/multi-user/installSolr.dox new file mode 100644 index 0000000000..9ef8e3c907 --- /dev/null +++ b/docs/doxygen-user/multi-user/installSolr.dox @@ -0,0 +1,142 @@ +/*! \page install_solr_page Install and Configure Solr + +\section install_solr_overview Overview + +Autopsy uses Apache Solr to store keyword text indexes. A central server is needed in a multi-user cluster to maintain and search the indexes. + +A new text index is created for each case and is stored in the case folder on shared storage (not on the local drive of the Solr server). + +Solr's embedded ZooKeeper is also used as a coordination service for Autopsy. + +\section install_solr_prereq Prerequisites + +We use Bitnami Solr, which packages Solr as a Windows service. + +You will need: +- A 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. +- The Apache Solr 4.10.3-0 installation package. This is no longer available from its original source, but you can find it on our site: https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr. +-- NOTE: We tested Solr 6 at one point, but ran into stability problems when loading and unloading cores. For now, you need to use Solr 4. +- An installed version of Autopsy so that you can copy files from it. You can install Autopsy on one of the planned client systems. You do not need to install it on the Solr server. +- A network-accessible machine to install Solr on. Note that the Solr process will need to write data out to the main shared storage drive, and needs adequate permissions to write to this location, which may be across a network. + + + +\section install_solr_install Installation + +\subsection install_solr_install_java JRE Installation +1. JREs are normally installed under "C:\Program Files\Java\jre(version)", so check there to see if you have one installed already. If not, get the installer as listed in the above Prerequisites section and install it with the default settings. + +\subsection install_solr_install_solr Solr Installation + +The following steps will configure Solr to run using an account that will have access to the network storage. + +1. Run the Bitnami installer, "bitnami-solr-4.10.3-0-windows-installer.exe" +2. If Windows prompts with User Account Control, click _Yes_ +3. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box. +4. If you see an error dialog like the following, you may safely ignore it. +

    +\image html apachebadmessage.PNG +
    +5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_. + + + +\subsection install_solr_config Solr Configuration + +1. Stop the _solrJetty_ service by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrJetty_ Windows service. Select the service and press _Stop the service_. If the service is already stopped and there is no _Stop the service_ available, this is okay. +2. Service Configuration: Edit the "C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat" script. You need administrator rights to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file: +
    +
    + - Add the following options in the line that begins with "C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe" : + + ++JvmOptions=-Dcollection.configName=AutopsyConfig + + ++JvmOptions=-Dbootstrap_confdir="C:\Bitnami\solr-4.10.3-0\apache-solr\solr\configsets\AutopsyConfig\conf" + + ++JvmOptions=-DzkRun +
    + - Replace the path to JavaHome with the path to your 64-bit version of the JRE. If you do not know the path, the correct JavaHome path can be obtained by running the command "where java" from the Windows command line. An example is shown below. The text in yellow is what we are interested in. Do not include the "bin" folder in the path you place into the JavaHome variable. A correct example of the final result will look something like this: –-JavaHome="C:\Program Files\Java\jre1.8.0_111" +

    + A portion of an updated _serviceinstall.bat_ is shown below, with the changes marked in yellow. +

    + \image html serviceinstall.PNG +

    +3. Solr Configuration: Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml" to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is + \10\ +

    + The added part is highlighted in yellow below. Ensure that it is inside the \ tag as follows: +
    + \image html transientcache.PNG +

    +4. Log Configuration: Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties" to configure Solr log settings: + - Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB. + - Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line. +

    + The log file should end up looking like this (modified lines are highlighted in yellow +

    + \image html log4j.PNG +

    +5. Schema Configuration: From an Autopsy installation, copy the following into "C:\Bitnami\solr-4.10.3-0\apache-solr\solr": +- The folder "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\configsets" +- The folder "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\lib" +- The file "C:\Program Files\Autopsy-XXX(current version)\autopsy\solr\solr\zoo.cfg" + + +\subsection install_solr_reinstall Reinstall Service + +Because we made changes to the service configuration, we need to reinstall it. + +1. Start a Windows command prompt as administrator by pressing Start, typing command, right clicking on Command Prompt, and clicking on Run as administrator. Then run the following command to uninstall the solrJetty service: + + cmd /c C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat UNINSTALL + + You will very likely see a result that says "The solrJetty service is not started." This is okay. +2. In the same prompt, run the following command to install the solrJetty service: + + cmd /c C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat INSTALL +
    Note the argument "INSTALL" is case sensitive. Your command prompt should look like the screenshot below. Very likely your command prompt will say "The solrJetty service could not be started." This is okay. +

    + \image html solrinstall1.PNG +

    + + +At this point you should be able to access the Solr admin panel in a web browser via the URL http://localhost:8983/solr/#/ + + +\subsection install_solr_service_user Configure Service User + +Back in \ref install_multiuseruser_page, you should have decided what user to run Solr as. To configure Solr to run as that user, you'll use Windows Service Manager. + + +- Press _Start_, type _services.msc_, and press _Enter_. +- Find _solrJetty_. If the service is running, press _Stop the service_, +- Double click the service and switch to the _Log On_ tab to change the logon credentials to the chosen user who will have access to the shared storage. + - If you specify a domain account, the account name will be in the form of _DOMAINNAME\\username_ as shown in the example below + +\image html solrinstall2.PNG + + +- Start the service again. + +\section install_solr_test Testing + +There are two tests that you should perform to confirm that the Solr machine is configured correctly. + +- Web Interface: You should attempt to access the Solr admin panel in a web browser from another machine on the network. Replace the IP address in the following URL with the IP address or the host name that the Solr service is running on: http://172.16.12.61:8983/solr/#/. +

    + \image html solrinstall3.PNG +

    + +If the service is appropriately started but you are unable to see the screenshot above, then it could be that port 8983 for Solr and port 9983 for ZooKeeper are blocked by your firewall. Contact your network administrator to open these ports. + +- Shared Storage: Log into the Solr computer as the user you decided to run the Solr service as and attempt to access the shared storage paths. Ensure that you can access the UNC paths (or drive letters if you have hardware NAS). If everything is configured correctly you should be able to access the storage paths without having to provide credentials. +If you are prompted for a password to access the shared storage, then either enter the password and choose to save the credentials or reconfigure the setup so that the same passwords are used, etc. See \ref multiuser_users_store for steps on storing credentials. If you needed to store the credentials, then you should restart the service or reboot the computer (we have observed that a running service does not get the updated credentials). + +NOTE: You can not do a full test of permissions until you make a test case after all of the services are configured. + +\section install_solr_backup Backing Up + +Solr creates two types of data that need to be backed up: +- Text Indexes: These are stored in the case folder on the shared storage. +- ZooKeeper Data: Autopsy uses a service called ZooKeeper embedded in Solr that stores data about what cases exist and who has them open. This data needs to be backed up so that you can have a list of all available multi-user cases. + - In a default installation that data is stored in C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr\\zoo_data. + + +*/ diff --git a/docs/doxygen-user/multi-user/installSystems.dox b/docs/doxygen-user/multi-user/installSystems.dox new file mode 100644 index 0000000000..e171539245 --- /dev/null +++ b/docs/doxygen-user/multi-user/installSystems.dox @@ -0,0 +1,47 @@ +/*! \page install_multiuser_systems_page Pick Your Hardware / VM Configuration + +\section multiuser_system Overview + +The first step in setting up a multi-user cluster is picking how many computers or VMs you'll use to run the various services. + +From a service perspective, you'll need to run: +- PostgeSQL database server +- Apache Solr text indexing server +- ActiveMQ messaging server +- Network storage + +You can run each of these on their own dedicated VM, but that is not necessary. + +Apache Solr uses a lot of memory, so we recommend keeping it by itself. The exception is if you are using Windows File Sharing for shared storage. You can get better Solr performance if it is writing to local storage instead of over the network. So, you can consider using the same computer for both Solr and shared storage. + +Also note that because all computers need to access the shared storage at the same path, you cannot mix operating systems. A Linux system running Solr will not be able to access the shared storage at the same path as a Windows Autopsy client. + + + +We recommend: + +- Server 1: PostgreSQL and ActiveMQ + - Both of these are low overhead services. +- Server 2: Apache Solr and Shared Storage (if you are using Windows File Sharing). + + +\subsection multiuser_system_hw Suggested Hardware + +TODO + +- PostgreSQL/ActiveMQ (server 1): + - RAM: + - Local Storage: Enough for databases +- Solr (server 2): + - RAM: + - Local Storage: Minimal + +\subsection multiuser_system_back Backups + +You will have lots of important data on the system. Ensure that the following are regularly backed up: +- Shared storage (which contains case data and text indexes) +- Databases on the PostgreSQL server (see \ref install_post_backup) +- Zookeeper data on the Solr server (see \ref install_solr_backup) + + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/multi-user/installUsers.dox b/docs/doxygen-user/multi-user/installUsers.dox new file mode 100644 index 0000000000..d1e989a58f --- /dev/null +++ b/docs/doxygen-user/multi-user/installUsers.dox @@ -0,0 +1,83 @@ +/*! \page install_multiuseruser_page Pick Your User Accounts + +\section multiuser_users Overview + +Before you get into configuring any computers, you should have an understanding about what user accounts will be used. User account permissions are one of the most common challenges people have when setting up a cluster. + +There are two major decisions to make about users: +- The user for Autopsy. +- The user for the Solr service. + +These users are important because they will need access to the shared storage without needing to be prompted for a password. Other services, such as PostgreSQL and ActiveMQ, can run as the default service account because they use only local storage. + +The choice you make here will depend on what type of shared storage platform you are using and what kind of Windows-based infrastructure you have. + + +\subsection multiuser_users_autopsy Autopsy User + +The user account that Autopsy runs as will need access to the shared storage. There are three general options: + +- Domain Accounts: If the cluster is on a Windows domain, then Autopsy can be run with a domain account. + - If your shared storage is a Windows file share, then users should be able to access it without needing a password. + - If your shared storage is another platform, then you'll likely need to force Windows to store the shared storage credentials (as described below). +- Unique Local Accounts: Some clusters are not on a Windows domain and have unique accounts for each analyst / user. + - If your shared storage is a Windows file share, then users will not need to enter their password IF the same user name and password exists on the file share server. + - Otherwise, you'll need to force Windows to store the credentials. +- Shared Local Account: Lastly, some clusters use a single local account, such as one named "autopsy" for all users. This is not recommended because Autopsy uses the login name to track who did certain actions, such as tagging files. + - The same password rules apply here as with the previous scenario. Either have the same password on all systems or force Windows to store the passwords. + + +\subsection multiuser_users_solr Solr Service + +Solr will run as a Windows service and will need access to the shared storage. The default user, which is "LocalService", will not have access to network-based storage. + +So, if you have network-based shared storage, you have three options: +- NetworkService: If you are on a domain, you may be able to run Solr as the "NetworkService" account. This account has access to the network, but the challenge can be granting access for this account to the shared storage. + - If your shared storage is a Windows file share, you'll need to grant access to the computer account running Solr as follows: +
    1. Right click on the shared storage folder choose "Properties" and select the "Security" tab. +
    2. Click the "Edit..." button and then click the "Add..." button. +
    3. Click on the "Object Types" button and confirm that the "Computers" object type has a check mark. + \image html objectTypesComputers.PNG +
    4. Enter the name of the computer and click the "Check Names" button to confirm that it is correct. + \image html grantAccessToComputer.PNG +
    5. Ensure that the computer account has both read and write access to shared storage. + \image html sharedStoragePermissions.PNG
    + - For other shared storage, you may not be able to access the data from the NetworkService account. +- Normal User: If you are not on a domain or cannot grant access to the computer for shared storage, then run Solr as a normal user (local or domain). + - If you do this, refer to the scenarios as outlined above for picking an Autopsy user. The same rules will apply with respect to passwords and saving credentials. + - The main downside of this is that the service needs to be updated when the account password changes and it may require letting others know about the password. +- LocalService: Lastly, if you are using the same server for both Solr and shared storage, then it is possible to run Solr as the default "LocalService" because it doesn't need network access. + + + + +\section multiuser_users_store Storing Credentials + +Based on your shared storage and your above choice for user accounts, you may need to force each Windows computer to store credentials for the shared storage. For example, if your shared storage is a Linux-based system. + + +To store the credentials on a given computer, we simply access the shared storage. Windows will prompt us for a password and we choose the option to save the credentials. We will repeat this on each computer for each user account and using both the hostname and IP address of the storage. If two examiners will be using the same Autopsy client computer and they have their own accounts, you'll need to do this for both users. + +- Launch Windows Explorer and type the UNC path of the shared storage using the IP address, such as "\\10.10.152.211\Cases". Press Enter. +

    +\image html urlInAddressbar.PNG +

    + +- If the folder opens up without prompting for a password, then you are OK. If your credentials are needed, you will see a dialog similar to the following: + +

    +\image html credentialsWithDomain.PNG +

    + +- If your account is part of a Windows domain, add the domain in the top box before the "\". Follow the slash with your username. If you have no domain name, just use your username with no slashes. Add your password in the next box down and place a check mark in "Remember my credentials", then click "OK". + + +Next, repeat with the hostname of the shared storage. For example "\\autopsy_storage\Cases". Again enter your credentials and choose "Remember my credentials". + + +Do these steps for each machine that will be accessing the shared drive. + + +Also note that you will need to repeat this process when the password for the shared storage changes. + +*/ diff --git a/docs/doxygen-user/multiuser-security.dox b/docs/doxygen-user/multi-user/multiuser-security.dox similarity index 100% rename from docs/doxygen-user/multiuser-security.dox rename to docs/doxygen-user/multi-user/multiuser-security.dox diff --git a/docs/doxygen-user/result_viewer.dox b/docs/doxygen-user/result_viewer.dox index b5b349682b..fae9ebb360 100644 --- a/docs/doxygen-user/result_viewer.dox +++ b/docs/doxygen-user/result_viewer.dox @@ -29,7 +29,7 @@ These columns display the following information:
  • (O)ther occurrences column - indicates how many data sources in the Central Repository contain this item. The count will include the selected item. -To display more information about why an icon has appeared, you can hover over it. The Comment and Other occurrences columns query the Central Repository. If this seems to be having a performance impact, it can be disabled through the \ref view_options_page. This will remove the Other occurrences column entirely and the Comment column will be based only on tags. +To display more information about why an icon has appeared, you can hover over it. These columns query the Central Repository as well as the case database. If this seems to be having a performance impact, you can disable them through the \ref view_options_page. This will remove the Other occurrences column entirely, the Comment column will be based only on tags, and the Score column will no longer be able to reflect Notable items. \subsection export_csv Exporting to CSV diff --git a/docs/doxygen-user/tagging.dox b/docs/doxygen-user/tagging.dox index b6cba720ae..ada9f5d0c5 100644 --- a/docs/doxygen-user/tagging.dox +++ b/docs/doxygen-user/tagging.dox @@ -1,6 +1,6 @@ /*! \page tagging_page Tagging -Tagging (or Bookmarking) allows you to create a reference to a file or object and easily find it later or include it in a \ref reporting_page "report". For images, you can select a portion of the image to tag. Tagging is also used by the \ref central_repo_page "central repository" to mark items as notable. +Tagging (or Bookmarking) allows you to create a reference to a file or object and easily find it later or include it in a \ref reporting_page "report". Tagging is also used by the \ref central_repo_page "central repository" to mark items as notable. \section tagging_items Tagging items @@ -43,13 +43,13 @@ Tagged results are shown in the "Results" portion of the tree under "Tags". Tagg \section image_tagging Image tagging -When you have an image selected in the \ref result_viewer_page, you'll see a "Tags Menu" option in the upper right of the "Application" \ref content_viewer_page "Content Viewer". This allows you to tag only a selected area of the image. +When you have an image selected in the \ref result_viewer_page, you'll see a "Tags Menu" option in the upper right of the "Application" \ref content_viewer_page "Content Viewer". This allows you to tag only a selected area of the image. Image tagging is currently only enabled on Windows. \image html tagging_image_menu.png -\subsection image_tagging_creation Creating and editing an image tag +\subsection image_tagging_creation Creating an image tag -To start, select the "Create" option on the tags menu. Then left-click and hold to draw a box around part of the image. When you release the mouse, you'll be able to choose which tag name to use. +To start, select the "Create" option on the tags menu. You may then left-click and drag on the image to create a rectangle (which will be your 'tag'). When you release the mouse, you'll be able to apply a tag name (and optionally a comment) to your image tag. \image html tagging_image_select.png @@ -57,9 +57,9 @@ You can add a new tag name using the "New Tag" button. \image html tagging_image_create_tag.png -Once you choose the tag name you'll see a red outline in the image around the section you chose. If you need to edit the tag, right click on the red border to select it. From here you can drag the edges to resize the box, or delete it by selecting "Delete" from the tags menu. +Once you choose the tag name you'll see a red outline in the image around the section you chose. -\image html tagging_image_edit_tag.png +\image html tagging_image_one_tag.png You can create multiple tags in the same image. @@ -67,6 +67,14 @@ You can create multiple tags in the same image. If you want to temporarily hide the tag outlines, select "Hide" from the tags menu. You can then select "Show" to see them again. The outlines will also reappear if you switch to a different item in the Result Viewer and come back. +\subsection image_tagging_editing Selecting, resizing and deleting an image tag + +In order to resize or delete an image tag, you will need to first select it. You can do this by left clicking anywhere inside (or on) the image tag. Selected tags can be resized by dragging any of the 8 handles that appear. The resized dimensions will automatically be saved when the mouse is released. + +\image html tagging_image_edit_tag.png + +Selecting a tag will also enable the "Delete" option in the tags menu. Deleting a tag is an irreversible operation, so please use caution. + \subsection image_tagging_report Exporting and reporting on image tags If you want to save the image with the tag outline, select "Export" from the tags menu. The result will always be a PNG file. Note that using the "Extract File(s)" option from the right-click menu in the Result Viewer will export the original image. diff --git a/docs/doxygen-user/triage.dox b/docs/doxygen-user/triage.dox index 6f27b02683..3750f92434 100644 --- a/docs/doxygen-user/triage.dox +++ b/docs/doxygen-user/triage.dox @@ -48,7 +48,11 @@ In a triage situation, there is generally not time to make a full image of the s \subsubsection triage_vhd Making a Sparse Image -With any of the above methods for analyzing live systems and devices there is still the problem that your Autopsy case won't be very useful after you disconnect from the drive. To solve this problem you can choose to make a "sparse VHD" as Autopsy is processing the device. This is a file format used by Microsoft Virtual Machines that is readable by Windows and other forensic tools. Instead of copying each sector sequentially, sparse VHDs allow us to copy sectors in any order. This lets us copy each sector as Autopsy reads it, so the sparse VHD will contain all of the files that have been processed so far. We will also have the data associated with volumes and file systems since Autopsy has to process those in the course of analyzing the system. +With any of the above methods for analyzing live systems and devices there is still the problem that your Autopsy case won't be very useful after you disconnect from the drive. It will refer to a device that no longer exists and, more importantly, you may not have a copy of any files of interest that you observed during the triage. + +To solve this problem you can choose to make a "sparse VHD" as Autopsy is processing the device. This will save a copy of every sector that Autopsy reads, which will include file system structures (such as Master File Tables) and files that passed the ingest filters (such as all pictures). + +VHD is a file format used by Microsoft Virtual Machines that is readable by Windows and other forensic tools. The VHD will grow in size as Autopsy reads more data from the target drive. To create a sparse VHD, check the box for "Make a VHD image..." when selecting the disk to analyze. @@ -60,7 +64,7 @@ To create a sparse VHD, check the box for "Make a VHD image..." when selecting t In this scenario, you are trying to answer whether child exploitation images exist in a knock and talk type situation where you will have a limited amount of time with the target system. -Preparaton at the office: +Preparation at the office:
    • Create a \ref live_triage_page "live triage drive" on your USB drive
    • Launch Autopsy from that USB drive and create an \ref ingest_profiles "ingest profile" that: @@ -75,7 +79,7 @@ In this scenario, you are trying to answer whether child exploitation images exi
      • Start the analysis:
          -
        • Plus the live triage drive you made at the office into their laptop +
        • Plug the live triage drive you made at the office into their laptop
        • Launch Autopsy from the .bat file
        • \ref cases_page "Create a case" (saving to your USB drive)
        • Add a \ref ds_local "local drive data source" diff --git a/docs/doxygen-user/view_options.dox b/docs/doxygen-user/view_options.dox index 4516a035ef..14e14d76a4 100644 --- a/docs/doxygen-user/view_options.dox +++ b/docs/doxygen-user/view_options.dox @@ -35,9 +35,9 @@ Similarly, the option to hide slack in the views area will prevent slack files f This option allows you to hide tags from other users in the Tagging section of the tree. See \ref user_tags for more details. -\subsection view_options_cr_columns Do not use the Central Repository to populate columns +\subsection view_options_cr_columns Do not add columns for S(core), C(omments) and (O)ccurrences -By default, the first three columns in the result viewer after the file name in the results viewer are named "S", "C" and "O". These are described in more detail on the \ref result_viewer_page page. The Comment and Other occurrences columns query the Central Repository. If this seems to be having a performance impact, it can be disabled using the checkbox. This will remove the Other occurrences column entirely and the Comment column will be based only on tags. +By default, the first three columns in the result viewer after the file name in the results viewer are named "S", "C" and "O". Populating these columns can increase loading times. See the \ref result_viewer_sco section for additional information. \subsection view_options_paging Paging diff --git a/docs/doxygen-user/windows_authentication.dox b/docs/doxygen-user/windows_authentication.dox deleted file mode 100644 index d86d0249cd..0000000000 --- a/docs/doxygen-user/windows_authentication.dox +++ /dev/null @@ -1,57 +0,0 @@ -/*! \page windows_authentication Shared Drive Authentication - -

          -If your shared drive is a Windows-hosted shared drive, you will likely need to provide authentication for each machine that connects to the shared drive. This guide only covers Windows-hosted shared drives. - -To authenticate with Windows and allow access to a shared drive, you will need: -- A username -- A password -- The domain name (if the machine hosting the shared drive is on a domain) -- The IP address of the machine hosting the shared drive -- The hostname of the machine hosting the shared drive - -Using Windows Explorer, in the address bar enter two slashes "\\" followed by the storage machine's IP address and press _Enter_. An example is shown below with the text "\\10.10.152.211" entered. -

          -\image html urlInAddressbar.PNG -

          - -You will see a dialog similar to the following, asking for your credentials. - -

          -\image html credentialsWithDomain.PNG -

          - -If you have a domain name, add it in the top box before the "\". Follow the slash with your username. If you have no domain name, just use your username with no slashes. Add your password in the next box down and place a check mark in "Remember my credentials", then click "OK". - -Next, we will do the same steps over again, using the hostname of the machine. This is necessary to authenticate with both IP address access and hostname access. If you do not know the hostname, you may find it by pinging the IP address with the "-a" flag set. It will look something like the screenshot below, where we find the hostname associated with the IP address 10.10.142.56   is   win-kmort-4863.basistech.net. - -

          -\image html getHostname.PNG -

          - -In Windows Explorer, use this hostname preceded by two slashes, "\\", in the address bar as shown below and press enter. - -

          -\image html hostname.PNG -

          - -You will see a screen similar to the screenshot below. Do the same steps with domain, username, and password as you did above. - -

          -\image html toConnect.PNG -

          - -Do these steps for each machine that will be accessing the shared drive. -


          -


          -- - - - - -Note that if you are familiar with the Windows Credential Manager, you may use this tool to manage credentials. These credentials can also be managed from the command line using the "net use" command. To get to Credential Manager click on to Start, and typing "Credential Manager" and pressing enter. A screenshot of the Windows Credential Manager with some domain names intentionally blanked out is shown below. - -

          -\image html credentialManager.PNG -

          - -Also note that authentication and access can be an issue when passwords change. When passwords change, for every computer using a credential that is no longer valid, you will need to redo the above steps. One indicator this is a problem is seeing the text: "The system detected a possible attempt to compromise security. Please ensure that you can contact the server that authenticated you."   Do not forget to re-authenticate with both the IP address and the hostname. -


          - -*/ diff --git a/thunderbirdparser/ivy.xml b/thunderbirdparser/ivy.xml index b66def4489..d2833c64c1 100644 --- a/thunderbirdparser/ivy.xml +++ b/thunderbirdparser/ivy.xml @@ -13,5 +13,6 @@ + diff --git a/thunderbirdparser/nbproject/project.properties b/thunderbirdparser/nbproject/project.properties index af5e0794ae..ea9d0786eb 100644 --- a/thunderbirdparser/nbproject/project.properties +++ b/thunderbirdparser/nbproject/project.properties @@ -1,13 +1,19 @@ +file.reference.apache-mime4j-core-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar +file.reference.apache-mime4j-dom-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar +file.reference.apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar file.reference.commons-lang3-3.8.1.jar=release/modules/ext/commons-lang3-3.8.1.jar file.reference.apache-mime4j-core-0.8.0.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar file.reference.apache-mime4j-dom-0.8.0.jar=release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar file.reference.apache-mime4j-mbox-iterator-0.8.0.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar +file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar file.reference.java-libpst-1.0-SNAPSHOT.jar=release/modules/ext/java-libpst-1.0-SNAPSHOT.jar file.reference.ez-vcard-0.10.5.jar=release/modules/ext/ez-vcard-0.10.5.jar file.reference.vinnie-2.0.2.jar=release/modules/ext/vinnie-2.0.2.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial +javadoc.reference.guava-19.0.jar=release/modules/ext/guava-19.0-javadoc.jar license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/autopsy/ nbm.needs.restart=true +source.reference.guava-19.0.jar=release/modules/ext/guava-19.0-sources.jar spec.version.base=4.0 diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index d6e024f08c..c67ed7679f 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -54,18 +54,6 @@ ext/commons-lang3-3.8.1.jar release/modules/ext/commons-lang3-3.8.1.jar - - ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - - - ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar - - - ext/java-libpst-1.0-SNAPSHOT.jar - release/modules/ext/java-libpst-1.0-SNAPSHOT.jar - ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar @@ -74,6 +62,22 @@ ext/ez-vcard-0.10.5.jar release/modules/ext/ez-vcard-0.10.5.jar + + ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar + release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar + + + ext/java-libpst-1.0-SNAPSHOT.jar + release/modules/ext/java-libpst-1.0-SNAPSHOT.jar + + + ext/guava-19.0.jar + release/modules/ext/guava-19.0.jar + + + ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar + release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar + ext/vinnie-2.0.2.jar release/modules/ext/vinnie-2.0.2.jar diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java index 6e003a54a6..431c7cc74c 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessageThreader.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; /** * Given a list of email messages arranges the message into threads using the @@ -41,7 +42,7 @@ final class EmailMessageThreader { private EmailMessageThreader(){} - public static void threadMessages(List emailMessages, String threadIDPrefix) { + public static void threadMessages(List emailMessages) { EmailMessageThreader instance = new EmailMessageThreader(); Map id_table = instance.createIDTable(emailMessages); @@ -51,7 +52,7 @@ final class EmailMessageThreader { Set finalRootSet = instance.groupBySubject(rootSet); - instance.assignThreadIDs(finalRootSet, threadIDPrefix); + instance.assignThreadIDs(finalRootSet); } /** @@ -413,12 +414,10 @@ final class EmailMessageThreader { * * @param IDPrefix A string to make the threadIDs unique. */ - private void assignThreadIDs(Set containerSet, String IDPrefix) { - int threadCounter = 0; - + private void assignThreadIDs(Set containerSet) { for(EmailContainer container: containerSet) { // Generate a threadID - String threadID = String.format("%s-%d", IDPrefix, threadCounter++); + String threadID = UUID.randomUUID().toString(); // Add the IDs to this thread addThreadID(container, threadID); } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java index 42d74bcc2a..5be33551ef 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.logging.Level; @@ -57,22 +58,17 @@ import org.apache.tika.parser.txt.CharsetDetector; import org.apache.tika.parser.txt.CharsetMatch; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.EncodedFileOutputStream; /** - * A parser that extracts information about email messages and attachments from - * a mbox file. - * - * @author jwallace + * An Iterator for parsing mbox files. Wraps an instance of MBoxEmailIterator. */ -class MboxParser { +class MboxParser implements Iterator { private static final Logger logger = Logger.getLogger(MboxParser.class.getName()); - private DefaultMessageBuilder messageBuilder; - private IngestServices services; - private StringBuilder errors; + private final DefaultMessageBuilder messageBuilder; + private final List errorList = new ArrayList<>(); /** * The mime type string for html text. @@ -84,14 +80,14 @@ class MboxParser { */ private String localPath; - MboxParser(IngestServices services, String localPath) { - this.services = services; + private Iterator emailIterator = null; + + private MboxParser(String localPath) { this.localPath = localPath; messageBuilder = new DefaultMessageBuilder(); MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).build(); // disable line length checks. messageBuilder.setMimeEntityConfig(config); - errors = new StringBuilder(); } static boolean isValidMimeTypeMbox(byte[] buffer) { @@ -99,65 +95,86 @@ class MboxParser { } /** - * Parse the mbox file and get the email messages. + * Returns an instance of MBoxParser that will iterate and return + * EMailMessage objects with only the information needed for threading + * emails. * - * @param mboxFile + * @param localPath String path to the mboxFile + * @param mboxFile The mboxFile to parse * - * @return a list of the email messages in the mbox file. + * @return Instance of MboxParser */ - List parse(File mboxFile, long fileID) { + static MboxParser getThreadInfoIterator(String localPath, File mboxFile) { + MboxParser parser = new MboxParser(localPath); + parser.createIterator(mboxFile, 0, false); + return parser; + } + + /** + * Returns an instance of MBoxParser that will iterate "whole" + * EmailMessages. + * + * @param localPath String path to the mboxFile + * @param mboxFile The mboxFile to parse + * @param fileID The fileID of the abstractFile that mboxFile was found + * + * @return Instance of MboxParser + */ + static MboxParser getEmailIterator(String localPath, File mboxFile, long fileID) { + MboxParser parser = new MboxParser(localPath); + parser.createIterator(mboxFile, fileID, true); + + return parser; + } + + /** + * Creates the real Iterator object instance. + * + * @param mboxFile The mboxFile to parse + * @param fileID The fileID of the abstractFile that mboxFile was found + * @param wholeMsg True if EmailMessage should have the whole message, not + * just the thread information. + */ + private void createIterator(File mboxFile, long fileID, boolean wholeMsg) { // Detect possible charsets List encoders = getPossibleEncoders(mboxFile); - CharsetEncoder theEncoder = null; - Iterable mboxIterator = null; // Loop through the possible encoders and find the first one that works. // That will usually be one of the first ones. for (CharsetEncoder encoder : encoders) { try { - mboxIterator = MboxIterator.fromFile(mboxFile).charset(encoder.charset()).build(); - theEncoder = encoder; + Iterable mboxIterable = MboxIterator.fromFile(mboxFile).charset(encoder.charset()).build(); + if (mboxIterable != null) { + emailIterator = new MBoxEmailIterator(mboxIterable.iterator(), encoder, fileID, wholeMsg); + } break; } catch (CharConversionException | UnsupportedCharsetException ex) { // Not the right encoder } catch (IllegalArgumentException ex) { // Not the right encoder } catch (IOException ex) { - logger.log(Level.WARNING, "couldn't find mbox file.", ex); //NON-NLS + logger.log(Level.WARNING, String.format("Failed to open mbox file: %s %d", mboxFile.getName(), fileID), ex); //NON-NLS addErrorMessage(NbBundle.getMessage(this.getClass(), "MboxParser.parse.errMsg.failedToReadFile")); - return new ArrayList<>(); } } + } - // If no encoders work, post an error message and return. - if (mboxIterator == null || theEncoder == null) { - addErrorMessage(NbBundle.getMessage(this.getClass(), "MboxParser.parse.errMsg.couldntFindCharset")); - return new ArrayList<>(); - } + @Override + public boolean hasNext() { + return emailIterator != null && emailIterator.hasNext(); + } - List emails = new ArrayList<>(); - long failCount = 0; - - // Parse each message and extract an EmailMessage structure - for (CharBufferWrapper message : mboxIterator) { - try { - Message msg = messageBuilder.parseMessage(message.asInputStream(theEncoder.charset())); - emails.add(extractEmail(msg, fileID)); - } catch (RuntimeException | IOException ex) { - logger.log(Level.WARNING, "Failed to get message from mbox: {0}", ex.getMessage()); //NON-NLS - failCount++; - } - } - - if (failCount > 0) { - addErrorMessage( - NbBundle.getMessage(this.getClass(), "MboxParser.parse.errMsg.failedToParseNMsgs", failCount)); - } - return emails; + @Override + public EmailMessage next() { + return emailIterator != null ? emailIterator.next() : null; } String getErrors() { - return errors.toString(); + String result = ""; + for (String msg: errorList) { + result += "
        • " + msg + "
        • "; + } + return result; } /** @@ -179,7 +196,7 @@ class MboxParser { email.setSentDate(msg.getDate()); email.setLocalPath(localPath); email.setMessageID(msg.getMessageId()); - + Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS String inReplyTo = null; @@ -208,7 +225,45 @@ class MboxParser { } else { handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields()); } - + + return email; + } + + /** + * Extract the subject, inReplyTo, message-ID and references from the + * Message object and returns them in a new EmailMessage object. + * + * @param msg Message object + * + * @return EmailMessage instance with only some of the message information + */ + private EmailMessage extractPartialEmail(Message msg) { + EmailMessage email = new EmailMessage(); + email.setSubject(msg.getSubject()); + email.setMessageID(msg.getMessageId()); + + Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS + String inReplyTo = null; + + if (field != null) { + inReplyTo = field.getBody(); + email.setInReplyToID(inReplyTo); + } + + field = msg.getHeader().getField("references"); + if (field != null) { + List references = new ArrayList<>(); + for (String id : field.getBody().split(">")) { + references.add(id.trim() + ">"); + } + + if (!references.contains(inReplyTo)) { + references.add(inReplyTo); + } + + email.setReferences(references); + } + return email; } @@ -246,7 +301,7 @@ class MboxParser { * * @param email * @param tb - * @param type The Mime type of the body. + * @param type The Mime type of the body. */ private void handleTextBody(EmailMessage email, TextBody tb, String type, List fields) { BufferedReader r; @@ -258,16 +313,16 @@ class MboxParser { while ((line = r.readLine()) != null) { bodyString.append(line).append("\n"); } - + headersString.append("\n-----HEADERS-----\n"); - for(Field field: fields) { + for (Field field : fields) { String nextLine = field.getName() + ": " + field.getBody(); headersString.append("\n").append(nextLine); } headersString.append("\n\n---END HEADERS--\n\n"); email.setHeaders(headersString.toString()); - + switch (type) { case ContentTypeField.TYPE_TEXT_PLAIN: email.setTextBody(bodyString.toString()); @@ -291,7 +346,7 @@ class MboxParser { * @param email * @param e */ - @NbBundle.Messages ({"MboxParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."}) + @NbBundle.Messages({"MboxParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."}) private void handleAttachment(EmailMessage email, Entity e, long fileID, int index) { String outputDirPath; String relModuleOutputPath; @@ -301,7 +356,7 @@ class MboxParser { } catch (NoCurrentCaseException ex) { addErrorMessage(Bundle.MboxParser_handleAttch_noOpenCase_errMsg()); logger.log(Level.SEVERE, Bundle.MboxParser_handleAttch_noOpenCase_errMsg(), ex); //NON-NLS - return; + return; } String filename = e.getFilename(); @@ -333,7 +388,7 @@ class MboxParser { addErrorMessage( NbBundle.getMessage(this.getClass(), "MboxParser.handleAttch.errMsg.failedToCreateOnDisk", outPath)); - logger.log(Level.INFO, "Failed to create file output stream for: " + outPath, ex); //NON-NLS + logger.log(Level.WARNING, "Failed to create file output stream for: " + outPath, ex); //NON-NLS return; } @@ -346,14 +401,14 @@ class MboxParser { // This could potentially be other types. Only seen this once. } } catch (IOException ex) { - logger.log(Level.INFO, "Failed to write mbox email attachment to disk.", ex); //NON-NLS + logger.log(Level.WARNING, "Failed to write mbox email attachment to disk.", ex); //NON-NLS addErrorMessage(NbBundle.getMessage(this.getClass(), "MboxParser.handleAttch.failedWriteToDisk", filename)); return; } finally { try { fos.close(); } catch (IOException ex) { - logger.log(Level.INFO, "Failed to close file output stream", ex); //NON-NLS + logger.log(Level.WARNING, "Failed to close file output stream", ex); //NON-NLS } } @@ -441,12 +496,53 @@ class MboxParser { try { is.close(); } catch (IOException ex) { - logger.log(Level.INFO, "Failed to close input stream"); //NON-NLS + logger.log(Level.WARNING, "Failed to close input stream"); //NON-NLS } } } private void addErrorMessage(String msg) { - errors.append("
        • ").append(msg).append("
        • "); //NON-NLS + errorList.add(msg); + } + + /** + * An Interator for mbox email messages. + */ + final class MBoxEmailIterator implements Iterator { + + private final Iterator mboxIterator; + private final CharsetEncoder encoder; + private final long fileID; + private final boolean wholeMsg; + + MBoxEmailIterator(Iterator mboxIter, CharsetEncoder encoder, long fileID, boolean wholeMsg) { + mboxIterator = mboxIter; + this.encoder = encoder; + this.fileID = fileID; + this.wholeMsg = wholeMsg; + } + + @Override + public boolean hasNext() { + return (mboxIterator != null && encoder != null) && mboxIterator.hasNext(); + } + + @Override + public EmailMessage next() { + CharBufferWrapper messageBuffer = mboxIterator.next(); + + try { + Message msg = messageBuilder.parseMessage(messageBuffer.asInputStream(encoder.charset())); + if (wholeMsg) { + return extractEmail(msg, fileID); + } else { + return extractPartialEmail(msg); + } + } catch (RuntimeException | IOException ex) { + logger.log(Level.WARNING, "Failed to get message from mbox: {0}", ex.getMessage()); //NON-NLS + } + return null; + } + } } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java index 704e3b2e14..f3b357502f 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.thunderbirdparser; +import com.google.common.collect.Iterables; import com.pff.PSTAttachment; import com.pff.PSTException; import com.pff.PSTFile; @@ -29,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Scanner; import java.util.logging.Level; @@ -55,18 +57,18 @@ class PstParser { * First four bytes of a pst file. */ private static int PST_HEADER = 0x2142444E; - private IngestServices services; - /** - * A map of PSTMessages to their Local path within the file's internal - * directory structure. - */ - private List results; - private StringBuilder errors; + + private final IngestServices services; + + private PSTFile pstFile; + private long fileID; + + private int failureCount = 0; + + private final List errorList = new ArrayList<>(); PstParser(IngestServices services) { - results = new ArrayList<>(); this.services = services; - errors = new StringBuilder(); } enum ParseResult { @@ -75,99 +77,191 @@ class PstParser { } /** - * Parse and extract email messages from the pst/ost file. + * Create an instance of PSTFile for the given File object. * - * @param file A pst or ost file. + * The constructor for PSTFile object will throw a generic PSTException if + * the file is encrypted. + * + * PSTFile.java * - * @return ParseResult: OK on success, ERROR on an error, ENCRYPT if failed - * because the file is encrypted. + * @param file File to open + * @param fileID File id for use when creating the EmailMessage objects + * + * @return ParserResult value OK if the PSTFile was successfully created, + * ENCRYPT will be returned for PSTExceptions that matches at + * specific message or IllegalArgumentExceptions */ - ParseResult parse(File file, long fileID) { - PSTFile pstFile; - long failures; + ParseResult open(File file, long fileID) { + if (file == null) { + return ParseResult.ERROR; + } + try { pstFile = new PSTFile(file); - failures = processFolder(pstFile.getRootFolder(), "\\", true, fileID); - if (failures > 0) { - addErrorMessage( - NbBundle.getMessage(this.getClass(), "PstParser.parse.errMsg.failedToParseNMsgs", failures)); + } catch (PSTException ex) { + // This is the message thrown from the PSTFile constructor if it + // detects that the file is encrypted. + if (ex.getMessage().equals("Only unencrypted and compressable PST files are supported at this time")) { //NON-NLS + logger.log(Level.INFO, "Found encrypted PST file."); //NON-NLS + return ParseResult.ENCRYPT; } - return ParseResult.OK; - } catch (PSTException | IOException ex) { String msg = file.getName() + ": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage(); //NON-NLS - logger.log(Level.WARNING, msg); + logger.log(Level.WARNING, msg, ex); return ParseResult.ERROR; - } catch (IllegalArgumentException ex) { + } catch (IOException ex) { + String msg = file.getName() + ": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage(); //NON-NLS + logger.log(Level.WARNING, msg, ex); + return ParseResult.ERROR; + } catch (IllegalArgumentException ex) { // Not sure if this is true, was in previous version of code. logger.log(Level.INFO, "Found encrypted PST file."); //NON-NLS return ParseResult.ENCRYPT; } + + return ParseResult.OK; } /** - * Get the results of the parsing. + * Creates an EmailMessage iterator for pstFile. These Email objects will be + * complete and with all available information. * - * @return + * @return A instance of an EmailMessage Iterator */ - List getResults() { - return results; - } + Iterator getEmailMessageIterator() { + if (pstFile == null) { + return null; + } - String getErrors() { - return errors.toString(); + Iterable iterable = null; + + try { + iterable = getEmaiMessageIterator(pstFile.getRootFolder(), "\\", fileID, true); + } catch (PSTException | IOException ex) { + logger.log(Level.WARNING, String.format("Exception thrown while parsing fileID: %d", fileID), ex); + } + + if (iterable == null) { + return null; + } + + return iterable.iterator(); } /** - * Process this folder and all subfolders, adding every email found to - * results. Accumulates the folder hierarchy path as it navigates the folder - * structure. + * Get a List of EmailMessages which contain only the information needed for + * threading the emails. * - * @param folder The folder to navigate and process - * @param path The path to the folder within the pst/ost file's directory - * structure + * @return A list of EmailMessage or an empty list if non were found. + */ + List getPartialEmailMessages() { + List messages = new ArrayList<>(); + Iterator iterator = getPartialEmailMessageIterator(); + if (iterator != null) { + while (iterator.hasNext()) { + messages.add(iterator.next()); + } + } + + return messages; + } + + /** + * Returns string containing the list of the current parse errors + * + * @return String error list, empty string if no errors exist. + */ + String getErrors() { + String result = ""; + for (String msg: errorList) { + result += "
        • " + msg + "
        • "; + } + return result; + } + + /** + * Returns the count of parse errors. + * + * @return Integer count of parse errors. + */ + int getFailureCount() { + return failureCount; + } + + /** + * Get an Iterator which will iterate over the PSTFile, but return + * EmailMessages with only the information needed for putting the emails + * into threads. + * + * @return A EmailMessage iterator or null if no messages where found + */ + private Iterator getPartialEmailMessageIterator() { + if (pstFile == null) { + return null; + } + + Iterable iterable = null; + + try { + iterable = getEmaiMessageIterator(pstFile.getRootFolder(), "\\", fileID, false); + } catch (PSTException | IOException ex) { + logger.log(Level.WARNING, String.format("Exception thrown while parsing fileID: %d", fileID), ex); + } + + if (iterable == null) { + return null; + } + + return iterable.iterator(); + } + + /** + * Creates an Iterable object of Email messages for the given folder. + * + * @param folder PSTFolder to process + * @param path String path to folder + * @param fileID FileID of the AbstractFile folder was found in + * @param partialEmail Whether or not fill the EMailMessage with all data + * + * @return An Iterable for iterating email message, or null if there were no + * messages or children in folder. * * @throws PSTException * @throws IOException */ - private long processFolder(PSTFolder folder, String path, boolean root, long fileID) { - String newPath = (root ? path : path + "\\" + folder.getDisplayName()); - long failCount = 0L; // Number of emails that failed + private Iterable getEmaiMessageIterator(PSTFolder folder, String path, long fileID, boolean wholeMsg) throws PSTException, IOException { + Iterable iterable = null; + + if (folder.getContentCount() > 0) { + iterable = new PstEmailIterator(folder, path, fileID, wholeMsg).getIterable(); + } + if (folder.hasSubfolders()) { - List subFolders; - try { - subFolders = folder.getSubFolders(); - } catch (PSTException | IOException ex) { - subFolders = new ArrayList<>(); - logger.log(Level.INFO, "java-libpst exception while getting subfolders: {0}", ex.getMessage()); //NON-NLS - } - - for (PSTFolder f : subFolders) { - failCount += processFolder(f, newPath, false, fileID); - } - } - - if (folder.getContentCount() != 0) { - PSTMessage email; - // A folder's children are always emails, never other folders. - try { - while ((email = (PSTMessage) folder.getNextChild()) != null) { - results.add(extractEmailMessage(email, newPath, fileID)); + List subFolders = folder.getSubFolders(); + for (PSTFolder subFolder : subFolders) { + String newpath = path + "\\" + subFolder.getDisplayName(); + Iterable subIterable = getEmaiMessageIterator(subFolder, newpath, fileID, wholeMsg); + if (subIterable == null) { + continue; } - } catch (PSTException | IOException ex) { - failCount++; - logger.log(Level.INFO, "java-libpst exception while getting emails from a folder: {0}", ex.getMessage()); //NON-NLS + + if (iterable != null) { + iterable = Iterables.concat(iterable, subIterable); + } else { + iterable = subIterable; + } + } } - return failCount; + return iterable; } /** * Create an EmailMessage from a PSTMessage. * - * @param msg - * @param localPath + * @param msg PSTMessage object to parse + * @param localPath Path to local file * - * @return + * @return EmailMessage object. */ private EmailMessage extractEmailMessage(PSTMessage msg, String localPath, long fileID) { EmailMessage email = new EmailMessage(); @@ -177,10 +271,10 @@ class PstParser { email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress())); email.setSentDate(msg.getMessageDeliveryTime()); email.setTextBody(msg.getBody()); - if(false == msg.getTransportMessageHeaders().isEmpty()) { + if (false == msg.getTransportMessageHeaders().isEmpty()) { email.setHeaders("\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() + "\n\n---END HEADERS--\n\n"); - } - + } + email.setHtmlBody(msg.getBodyHTML()); String rtf = ""; try { @@ -193,14 +287,42 @@ class PstParser { email.setSubject(msg.getSubject()); email.setId(msg.getDescriptorNodeId()); email.setMessageID(msg.getInternetMessageId()); - + String inReplyToID = msg.getInReplyToId(); email.setInReplyToID(inReplyToID); if (msg.hasAttachments()) { extractAttachments(email, msg, fileID); } - + + List references = extractReferences(msg.getTransportMessageHeaders()); + if (inReplyToID != null && !inReplyToID.isEmpty()) { + if (references == null) { + references = new ArrayList<>(); + references.add(inReplyToID); + } else if (!references.contains(inReplyToID)) { + references.add(inReplyToID); + } + } + email.setReferences(references); + + return email; + } + + /** + * Create an EmailMessage from a PSTMessage with only the information needed + * for threading emails. + * + * @return EmailMessage object with only some information, not all of the + * msg. + */ + private EmailMessage extractPartialEmailMessage(PSTMessage msg) { + EmailMessage email = new EmailMessage(); + email.setSubject(msg.getSubject()); + email.setId(msg.getDescriptorNodeId()); + email.setMessageID(msg.getInternetMessageId()); + String inReplyToID = msg.getInReplyToId(); + email.setInReplyToID(inReplyToID); List references = extractReferences(msg.getTransportMessageHeaders()); if (inReplyToID != null && !inReplyToID.isEmpty()) { if (references == null) { @@ -218,8 +340,8 @@ class PstParser { /** * Add the attachments within the PSTMessage to the EmailMessage. * - * @param email - * @param msg + * @param email EmailMessage object to have attachment added + * @param msg PSTMessage object with the attachments */ @NbBundle.Messages({"PstParser.noOpenCase.errMsg=Exception while getting open case."}) private void extractAttachments(EmailMessage email, PSTMessage msg, long fileID) { @@ -228,8 +350,8 @@ class PstParser { try { outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator; } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return; } for (int x = 0; x < numberOfAttachments; x++) { String filename = ""; @@ -280,16 +402,14 @@ class PstParser { /** * Extracts a PSTAttachment to the module output directory. * - * @param attach - * @param outPath - * - * @return + * @param attach PSTAttachment object to be parsed + * @param outPath Location to write attachments * * @throws IOException * @throws PSTException */ private void saveAttachmentToDisk(PSTAttachment attach, String outPath) throws IOException, PSTException { - try (InputStream attachmentStream = attach.getFileInputStream(); + try (InputStream attachmentStream = attach.getFileInputStream(); EncodedFileOutputStream out = new EncodedFileOutputStream(new FileOutputStream(outPath), TskData.EncodingType.XOR1)) { // 8176 is the block size used internally and should give the best performance int bufferSize = 8176; @@ -354,10 +474,15 @@ class PstParser { } } + /** + * Adds passed in string to the error message with formatting. + * + * @param msg String message to add + */ private void addErrorMessage(String msg) { - errors.append("
        • ").append(msg).append("
        • "); //NON-NLS + errorList.add(msg); } - + /** * Returns the references value from the email header. * @@ -388,5 +513,96 @@ class PstParser { } return null; - } + } + + /** + * A iterator for processing the PST email folder structure and returning + * instances of the EmailMessage object. + */ + private final class PstEmailIterator implements Iterator { + + private final PSTFolder folder; + private EmailMessage currentMsg; + private EmailMessage nextMsg; + + private final String currentPath; + private final long fileID; + private final boolean wholeMsg; + + /** + * Class constructor, initializes the "next" message; + * + * @param folder PSTFolder object to iterate across + * @param path String path value to the location of folder + * @param fileID Long fileID of the abstract file this PSTFolder was + * found + */ + PstEmailIterator(PSTFolder folder, String path, long fileID, boolean wholeMsg) { + this.folder = folder; + this.fileID = fileID; + this.currentPath = path; + this.wholeMsg = wholeMsg; + + if (folder.getContentCount() > 0) { + try { + PSTMessage message = (PSTMessage) folder.getNextChild(); + if (message != null) { + if (wholeMsg) { + nextMsg = extractEmailMessage(message, currentPath, fileID); + } else { + nextMsg = extractPartialEmailMessage(message); + } + } + } catch (PSTException | IOException ex) { + failureCount++; + logger.log(Level.WARNING, String.format("Unable to extract emails for path: %s file ID: %d ", path, fileID), ex); + } + } + } + + @Override + public boolean hasNext() { + return nextMsg != null; + } + + @Override + public EmailMessage next() { + + currentMsg = nextMsg; + + try { + PSTMessage message = (PSTMessage) folder.getNextChild(); + if (message != null) { + if (wholeMsg) { + nextMsg = extractEmailMessage(message, currentPath, fileID); + } else { + nextMsg = extractPartialEmailMessage(message); + } + } else { + nextMsg = null; + } + } catch (PSTException | IOException ex) { + logger.log(Level.WARNING, String.format("Unable to extract emails for path: %s file ID: %d ", currentPath, fileID), ex); + failureCount++; + nextMsg = null; + } + + return currentMsg; + } + + /** + * Get a wrapped Iterable version of PstEmailIterator + * + * @return Iterable wrapping this class + */ + Iterable getIterable() { + return new Iterable() { + @Override + public Iterator iterator() { + return PstEmailIterator.this; + } + }; + } + + } } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 33da281def..ba0986a021 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Level; @@ -183,52 +184,47 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } PstParser parser = new PstParser(services); - PstParser.ParseResult result = parser.parse(file, abstractFile.getId()); + PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - if (result == PstParser.ParseResult.OK) { - // parse success: Process email and add artifacts - processEmails(parser.getResults(), abstractFile); - - } else if (result == PstParser.ParseResult.ENCRYPT) { - // encrypted pst: Add encrypted file artifact - try { - BlackboardArtifact artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); - artifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, EmailParserModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.encryptionFileLevel"))); + switch( result) { + case OK: + processEmails(parser.getPartialEmailMessages(), parser.getEmailMessageIterator(), abstractFile); + break; + case ENCRYPT: + // encrypted pst: Add encrypted file artifact try { - // index the artifact for keyword search - blackboard.indexArtifact(artifact); - } catch (Blackboard.BlackboardException ex) { - MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_processPst_indexError_message(), artifact.getDisplayName()); - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS - } + BlackboardArtifact artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + artifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, EmailParserModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.encryptionFileLevel"))); - services.fireModuleDataEvent(new ModuleDataEvent(EmailParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED)); - } catch (TskCoreException ex) { - logger.log(Level.INFO, "Failed to add encryption attribute to file: {0}", abstractFile.getName()); //NON-NLS - } - } else { - // parsing error: log message - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", - abstractFile.getName()), - NbBundle.getMessage(this.getClass(), - "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); - logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS - return ProcessResult.ERROR; + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_processPst_indexError_message(), artifact.getDisplayName()); + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + services.fireModuleDataEvent(new ModuleDataEvent(EmailParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED)); + } catch (TskCoreException ex) { + logger.log(Level.INFO, "Failed to add encryption attribute to file: {0}", abstractFile.getName()); //NON-NLS + } + break; + default: + // parsing error: log message + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", + abstractFile.getName()), + NbBundle.getMessage(this.getClass(), + "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); + logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS + return ProcessResult.ERROR; } if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS } - String errors = parser.getErrors(); - if (errors.isEmpty() == false) { - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg2", - abstractFile.getName()), errors); - } - return ProcessResult.OK; } @@ -281,21 +277,29 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - MboxParser parser = new MboxParser(services, emailFolder); - List emails = parser.parse(file, abstractFile.getId()); - processEmails(emails, abstractFile); + MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); + List emails = new ArrayList<>(); + if(emailIterator != null) { + while(emailIterator.hasNext()) { + EmailMessage emailMessage = emailIterator.next(); + if(emailMessage != null) { + emails.add(emailMessage); + } + } + + String errors = emailIterator.getErrors(); + if (!errors.isEmpty()) { + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2", + abstractFile.getName()), errors); + } + } + processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS } - String errors = parser.getErrors(); - if (errors.isEmpty() == false) { - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2", - abstractFile.getName()), errors); - } - return ProcessResult.OK; } @@ -404,25 +408,42 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * Take the extracted information in the email messages and add the * appropriate artifacts and derived files. * - * @param emails + * @param partialEmailsForThreading + * @param fileMessageIterator * @param abstractFile */ - private void processEmails(List emails, AbstractFile abstractFile) { - List derivedFiles = new ArrayList<>(); - + private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. try{ - EmailMessageThreader.threadMessages(emails, String.format("%d", abstractFile.getId())); + EmailMessageThreader.threadMessages(partialEmailsForThreading); } catch(Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - for (EmailMessage email : emails) { - BlackboardArtifact msgArtifact = addEmailArtifact(email, abstractFile); - - if ((msgArtifact != null) && (email.hasAttachment())) { - derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile, msgArtifact )); + List derivedFiles = new ArrayList<>(); + + int msgCnt = 0; + while(fullMessageIterator.hasNext()) { + EmailMessage current = fullMessageIterator.next(); + + if(current == null) { + continue; + } + + if(partialEmailsForThreading.size() > msgCnt) { + EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); + + if(threaded.getMessageID().equals(current.getMessageID()) && + threaded.getSubject().equals(current.getSubject())) { + current.setMessageThreadID(threaded.getMessageThreadID()); + } + } + + BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); + + if ((msgArtifact != null) && (current.hasAttachment())) { + derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); } } @@ -434,7 +455,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { context.addFilesToJob(derivedFiles); services.fireModuleDataEvent(new ModuleDataEvent(EmailParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG)); } - /** * Add the given attachments as derived files and reschedule them for * ingest. diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index 7e5a321313..a0fa8260aa 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -164,8 +164,6 @@ final class VcardParser { private BlackboardArtifact addContactArtifact(VCard vcard, AbstractFile abstractFile) throws NoCurrentCaseException { List attributes = new ArrayList<>(); List accountInstances = new ArrayList<>(); - - extractPhotos(vcard, abstractFile); String name = ""; if (vcard.getFormattedName() != null) { @@ -229,6 +227,8 @@ final class VcardParser { List blackboardArtifacts = new ArrayList<>(); blackboardArtifacts.add(artifact); + extractPhotos(vcard, abstractFile, artifact); + // Add account relationships. if (deviceAccountInstance != null) { try { @@ -269,7 +269,7 @@ final class VcardParser { * * @throws NoCurrentCaseException if there is no open case. */ - private void extractPhotos(VCard vcard, AbstractFile abstractFile) throws NoCurrentCaseException { + private void extractPhotos(VCard vcard, AbstractFile abstractFile, BlackboardArtifact artifact) throws NoCurrentCaseException { String parentFileName = getUniqueName(abstractFile); // Skip files that already have been extracted. try { @@ -306,7 +306,7 @@ final class VcardParser { writeExtractedImage(extractedFilePath, data); derivedFilesCreated.add(fileManager.addDerivedFile(extractedFileName, getFileRelativePath(parentFileName, extractedFileName), data.length, abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getAtime(), - true, abstractFile, null, EmailParserModuleFactory.getModuleName(), null, null, TskData.EncodingType.NONE)); + true, artifact, null, EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", TskData.EncodingType.NONE)); } catch (IOException | TskCoreException ex) { logger.log(Level.WARNING, String.format("Could not write image to '%s' (id=%d).", extractedFilePath, abstractFile.getId()), ex); //NON-NLS }