diff --git a/Core/build.xml b/Core/build.xml
index 2c461a5017..9b6fb0b2af 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -65,8 +65,8 @@
-
+
release/modules/ext/tika-parsers-1.17.jar
- ext/sqlite-jdbc-3.8.11.jar
- release/modules/ext/sqlite-jdbc-3.8.11.jar
+ ext/sqlite-jdbc-3.25.2.jar
+ release/modules/ext/sqlite-jdbc-3.25.2.jar
ext/json-simple-1.1.1.jar
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index c580309cba..46e9dad2bc 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -67,6 +67,7 @@ import org.sleuthkit.autopsy.appservices.AutopsyService;
import org.sleuthkit.autopsy.appservices.AutopsyService.CaseContext;
import static org.sleuthkit.autopsy.casemodule.Bundle.*;
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
+import org.sleuthkit.autopsy.casemodule.datasourceSummary.DataSourceSummaryAction;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
@@ -1165,6 +1166,7 @@ public class Case {
CallableSystemAction.get(AddImageAction.class).setEnabled(true);
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
+ CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
@@ -1218,6 +1220,7 @@ public class Case {
CallableSystemAction.get(AddImageAction.class).setEnabled(false);
CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
+ CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
index 6a9d1d2f2f..2a70c91483 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java
@@ -55,7 +55,7 @@ final class CasePropertiesAction extends CallableSystemAction {
casePropertiesDialog = new JDialog(mainWindow, title, true);
CaseInformationPanel caseInformationPanel = new CaseInformationPanel();
caseInformationPanel.addCloseButtonAction((ActionEvent e) -> {
- casePropertiesDialog.setVisible(false);
+ casePropertiesDialog.dispose();
});
casePropertiesDialog.add(caseInformationPanel);
casePropertiesDialog.setResizable(true);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
index 940f6a9cd5..e274245edd 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
@@ -61,7 +61,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
private void customizeComponents() {
refresh();
this.ingestJobTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
- this.ingestModuleTableModel = new IngestModuleTableModel(this.ingestJobs.get(ingestJobTable.getSelectedRow()));
+ IngestJobInfo currJob = (ingestJobTable.getSelectedRow() < 0 ? null : this.ingestJobs.get(ingestJobTable.getSelectedRow()));
+ this.ingestModuleTableModel = new IngestModuleTableModel(currJob);
this.ingestModuleTable.setModel(this.ingestModuleTableModel);
});
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties
new file mode 100644
index 0000000000..855f75bd20
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties
@@ -0,0 +1,5 @@
+DataSourceSummaryPanel.fileCountsLabel.text=Files (based on mimetype)
+DataSourceSummaryPanel.opperatingSystemLabel.text=OS:
+DataSourceSummaryPanel.ingestJobsLabel.text=Ingest Jobs
+DataSourceSummaryPanel.closeButton.text=Close
+DataSourceSummaryPanel.gotoDataSourceButton.text=Goto Data Source
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryAction.java
new file mode 100644
index 0000000000..5dc3035867
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryAction.java
@@ -0,0 +1,69 @@
+/*
+ * 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.casemodule.datasourceSummary;
+
+import java.awt.event.ActionEvent;
+import java.beans.PropertyChangeEvent;
+import java.util.EnumSet;
+import javax.swing.Action;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.actions.CallableSystemAction;
+import org.sleuthkit.autopsy.casemodule.Case;
+
+@ActionID(category = "Case", id = "org.sleuthkit.autopsy.casemodule.datasourceSummary.DataSourceSummaryAction")
+@ActionRegistration(displayName = "#CTL_DataSourceSummaryAction", lazy = false)
+@Messages({"CTL_DataSourceSummaryAction=Data Source Summary"})
+/**
+ * DataSourceSummaryAction action for the Case menu to activate a ViewSummaryInformationAction selecting the first data source.
+ */
+public class DataSourceSummaryAction extends CallableSystemAction {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a datasource summary action which will be disabled when no case is
+ * open.
+ */
+ DataSourceSummaryAction() {
+ putValue(Action.NAME, Bundle.CTL_DataSourceSummaryAction());
+ this.setEnabled(false);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> {
+ setEnabled(null != evt.getNewValue());
+ });
+ }
+
+ @Override
+ public void performAction() {
+ //perform the action of a ViewSummaryInformationAction with a ActionEvent which will not be used
+ new ViewSummaryInformationAction(null).actionPerformed(new ActionEvent(Boolean.TRUE, 0, ""));
+ }
+
+ @Override
+ public String getName() {
+ return Bundle.CTL_DataSourceSummaryAction();
+ }
+
+ @Override
+ public HelpCtx getHelpCtx() {
+ return HelpCtx.DEFAULT_HELP;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.form
new file mode 100644
index 0000000000..255af69ffa
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.form
@@ -0,0 +1,182 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.java
new file mode 100644
index 0000000000..6fc2ca56e5
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/DataSourceSummaryPanel.java
@@ -0,0 +1,698 @@
+/*
+ * 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.casemodule.datasourceSummary;
+
+import java.awt.Rectangle;
+import java.awt.event.ActionListener;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JOptionPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.table.AbstractTableModel;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.datamodel.utils.FileTypeUtils;
+import org.sleuthkit.autopsy.directorytree.ViewContextAction;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
+import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
+import org.sleuthkit.datamodel.OSInfo;
+import org.sleuthkit.datamodel.OSUtility;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.TskData;
+
+final class DataSourceSummaryPanel extends javax.swing.JPanel {
+
+ private static final long serialVersionUID = 1L;
+ private final List allIngestJobs = new ArrayList<>();
+ private List ingestJobs = new ArrayList<>();
+ private DataSourceTableModel dataSourceTableModel = new DataSourceTableModel();
+ private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel();
+ private FilesTableModel filesTableModel = new FilesTableModel(null);
+ private final List dataSources = new ArrayList<>();
+ private final DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+ private static final Logger logger = Logger.getLogger(DataSourceSummaryPanel.class.getName());
+ private List osInfoList;
+
+ /**
+ * Creates new form DataSourceSummaryPanel for displaying a summary of the
+ * data sources for the fcurrent case and the contents found for each
+ * datasource.
+ */
+ @Messages({"DataSourceSummaryPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
+ "DataSourceSummaryPanel.getDataSources.error.title=Load Failure"})
+ DataSourceSummaryPanel() {
+ initComponents();
+ ingestJobsTable.getTableHeader().setReorderingAllowed(false);
+ fileCountsTable.getTableHeader().setReorderingAllowed(false);
+ dataSourcesTable.getTableHeader().setReorderingAllowed(false);
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ allIngestJobs.addAll(skCase.getIngestJobs());
+ dataSources.addAll(skCase.getDataSources());
+ osInfoList = OSUtility.getOSInfo(skCase);
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
+ JOptionPane.showMessageDialog(this, Bundle.DataSourceSummaryPanel_getDataSources_error_text(), Bundle.DataSourceSummaryPanel_getDataSources_error_title(), JOptionPane.ERROR_MESSAGE);
+ }
+ dataSourcesTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
+ if (!e.getValueIsAdjusting()) {
+ DataSource selectedDataSource = (dataSourcesTable.getSelectedRow() < 0 ? null : dataSources.get(dataSourcesTable.getSelectedRow()));
+ gotoDataSourceButton.setEnabled(selectedDataSource != null);
+ updateIngestJobs(selectedDataSource);
+ filesTableModel = new FilesTableModel(selectedDataSource);
+ fileCountsTable.setModel(filesTableModel);
+ opperatingSystemValueLabel.setText(getOSName(selectedDataSource));
+ this.repaint();
+ }
+ });
+ }
+
+ /**
+ * Get the name of the operating system if it is available. Otherwise get
+ * and empty string.
+ *
+ * @param selectedDataSource the datasource to get the OS information for
+ *
+ * @return the name of the opperating system on the specified datasource,
+ * empty string if no opperating system info found
+ */
+ private String getOSName(DataSource selectedDataSource) {
+ String osName = "";
+ if (selectedDataSource != null) {
+ for (OSInfo osInfo : osInfoList) {
+ try {
+ //assumes only one Opperating System per datasource
+ //get the datasource id from the OSInfo's first artifact if it has artifacts
+ if (!osInfo.getArtifacts().isEmpty() && osInfo.getArtifacts().get(0).getDataSource().getId() == selectedDataSource.getId()) {
+ osName = osInfo.getOSName();
+ //if this OSInfo object has a name use it otherwise keep checking OSInfo objects
+ if (!osName.isEmpty()) {
+ break;
+ }
+ }
+ } catch (TskCoreException ignored) {
+ //unable to get datasource for the OSInfo Object
+ //continue checking for OSInfo objects to try and get get the desired information
+ }
+ }
+ }
+ return osName;
+ }
+
+ /**
+ * Update the ingestJobs list with the ingest jobs for the
+ * selectedDataSource
+ *
+ * @param selectedDataSource the datasource to find the ingest jobs for
+ */
+ @Messages({"DataSourceSummaryPanel.loadIngestJob.error.text=Failed to load ingest jobs.",
+ "DataSourceSummaryPanel.loadIngestJob.error.title=Load Failure"})
+ private void updateIngestJobs(DataSource selectedDataSource) {
+ ingestJobs.clear();
+ if (selectedDataSource != null) {
+ for (IngestJobInfo ingestJob : allIngestJobs) {
+ if (ingestJob.getObjectId() == selectedDataSource.getId()) {
+ ingestJobs.add(ingestJob);
+ }
+ }
+ }
+ ingestJobTableModel = new IngestJobTableModel();
+ ingestJobsTable.setModel(ingestJobTableModel);
+ }
+
+ /**
+ * 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() {
+
+ jSeparator1 = new javax.swing.JSeparator();
+ dataSourcesScrollPane = new javax.swing.JScrollPane();
+ dataSourcesTable = new javax.swing.JTable();
+ ingestJobsScrollPane = new javax.swing.JScrollPane();
+ ingestJobsTable = new javax.swing.JTable();
+ fileCountsScrollPane = new javax.swing.JScrollPane();
+ fileCountsTable = new javax.swing.JTable();
+ opperatingSystemLabel = new javax.swing.JLabel();
+ opperatingSystemValueLabel = new javax.swing.JLabel();
+ fileCountsLabel = new javax.swing.JLabel();
+ ingestJobsLabel = new javax.swing.JLabel();
+ closeButton = new javax.swing.JButton();
+ gotoDataSourceButton = new javax.swing.JButton();
+
+ dataSourcesTable.setModel(dataSourceTableModel);
+ dataSourcesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ dataSourcesScrollPane.setViewportView(dataSourcesTable);
+
+ ingestJobsTable.setModel(ingestJobTableModel);
+ ingestJobsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ ingestJobsScrollPane.setViewportView(ingestJobsTable);
+
+ fileCountsTable.setModel(filesTableModel);
+ fileCountsScrollPane.setViewportView(fileCountsTable);
+
+ org.openide.awt.Mnemonics.setLocalizedText(opperatingSystemLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.opperatingSystemLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(fileCountsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.fileCountsLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(ingestJobsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.ingestJobsLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.closeButton.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(gotoDataSourceButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.gotoDataSourceButton.text")); // NOI18N
+ gotoDataSourceButton.setEnabled(false);
+ gotoDataSourceButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ gotoDataSourceButtonActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(dataSourcesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, Short.MAX_VALUE)
+ .addComponent(gotoDataSourceButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(closeButton))
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(fileCountsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(fileCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(ingestJobsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(ingestJobsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 474, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addComponent(opperatingSystemLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(opperatingSystemValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
+ .addContainerGap())
+ );
+
+ layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {closeButton, gotoDataSourceButton});
+
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(8, 8, 8)
+ .addComponent(dataSourcesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 120, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 5, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(fileCountsLabel)
+ .addComponent(ingestJobsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(fileCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(ingestJobsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(opperatingSystemLabel, javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(opperatingSystemValueLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGap(10, 10, 10)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(closeButton)
+ .addComponent(gotoDataSourceButton))
+ .addContainerGap())
+ );
+ }// //GEN-END:initComponents
+
+ /**
+ * Adds an action listener to the Close button of the panel.
+ *
+ * @param action
+ */
+ void addCloseButtonAction(ActionListener action) {
+ this.closeButton.addActionListener(action);
+ //the gotoDataSourceButton should also close the dialog
+ this.gotoDataSourceButton.addActionListener(action);
+ }
+
+ /**
+ * Select the data source with the specicied data source id. If no data
+ * source matches the dataSourceID it will select the first datasource.
+ *
+ * @param dataSourceID the ID of the datasource to select, null will cause
+ * the first datasource to be selected
+ */
+ void selectDataSource(Long dataSourceID) {
+ if (dataSourceID != null) {
+ for (int i = 0; i < dataSources.size(); i++) {
+ if (dataSources.get(i).getId() == dataSourceID) {
+ dataSourcesTable.setRowSelectionInterval(i, i);
+ //scroll down from top of table to where selected datasource is
+ dataSourcesTable.scrollRectToVisible(new Rectangle(dataSourcesTable.getCellRect(i, 0, true)));
+ return;
+ }
+ }
+ }
+ //if there are data sources in the list and none were found that matched the specied dataSourceID select the first one
+ if (!dataSources.isEmpty()) {
+ dataSourcesTable.setRowSelectionInterval(0, 0);
+ }
+ }
+
+ /**
+ * Performed when the Goto Data Source button is clicked, will cause the
+ * window to be closed and the data source which was selected to be
+ * navigated to in the tree.
+ */
+ private void gotoDataSourceButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoDataSourceButtonActionPerformed
+ //the dialog will be closed due to the action listener added in addCloseButtonAction
+ DataSource selectedDataSource = (dataSourcesTable.getSelectedRow() < 0 ? null : dataSources.get(dataSourcesTable.getSelectedRow()));
+ if (selectedDataSource != null) {
+ new ViewContextAction("", selectedDataSource).actionPerformed(evt);
+ }
+ }//GEN-LAST:event_gotoDataSourceButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton closeButton;
+ private javax.swing.JScrollPane dataSourcesScrollPane;
+ private javax.swing.JTable dataSourcesTable;
+ private javax.swing.JLabel fileCountsLabel;
+ private javax.swing.JScrollPane fileCountsScrollPane;
+ private javax.swing.JTable fileCountsTable;
+ private javax.swing.JButton gotoDataSourceButton;
+ private javax.swing.JLabel ingestJobsLabel;
+ private javax.swing.JScrollPane ingestJobsScrollPane;
+ private javax.swing.JTable ingestJobsTable;
+ private javax.swing.JSeparator jSeparator1;
+ private javax.swing.JLabel opperatingSystemLabel;
+ private javax.swing.JLabel opperatingSystemValueLabel;
+ // End of variables declaration//GEN-END:variables
+
+ /**
+ * Table model for the Data source table, to display all data sources for
+ * the current case.
+ */
+ @Messages({"DataSourceSummaryPanel.DataSourceTableModel.dataSourceName.header=Data Source Name",
+ "DataSourceSummaryPanel.DataSourceTableModel.type.header=Type",
+ "DataSourceSummaryPanel.DataSourceTableModel.files.header=Files",
+ "DataSourceSummaryPanel.DataSourceTableModel.results.header=Results",
+ "DataSourceSummaryPanel.DataSourceTableModel.tags.header=Tags"})
+ private class DataSourceTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List columnHeaders = new ArrayList<>();
+ private final Map fileCountsMap;
+ private final Map artifactCountsMap;
+ private final Map tagCountsMap;
+
+ /**
+ * Create a new DataSourceTableModel for the current case.
+ */
+ DataSourceTableModel() {
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_dataSourceName_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_type_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_files_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_results_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_tags_header());
+ fileCountsMap = getCountsOfFiles();
+ artifactCountsMap = getCountsOfArtifacts();
+ tagCountsMap = getCountsOfTags();
+
+ }
+
+ @Override
+ public int getRowCount() {
+ return dataSources.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnHeaders.size();
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ DataSource currentDataSource = dataSources.get(rowIndex);
+ Long count;
+ switch (columnIndex) {
+ case 0:
+ return currentDataSource.getName();
+ case 1:
+ return "";
+ case 2:
+ //display 0 if no count is found
+ count = fileCountsMap.get(currentDataSource.getId());
+ return count == null ? 0 : count;
+ case 3:
+ //display 0 if no count is found
+ count = artifactCountsMap.get(currentDataSource.getId());
+ return count == null ? 0 : count;
+ case 4:
+ //display 0 if no count is found
+ count = tagCountsMap.get(currentDataSource.getId());
+ return count == null ? 0 : count;
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /**
+ * Get a map containing the number of files in each data source in the
+ * current case.
+ *
+ * @return Collection which maps datasource id to a count for the number
+ * of files in the datasource, will only contain entries for
+ * datasources which have at least 1 file
+ */
+ private Map getCountsOfFiles() {
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ DataSourceCountsCallback callback = new DataSourceCountsCallback();
+ final String countFilesQuery = "data_source_obj_id, COUNT(*) AS count"
+ + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
+ skCase.getCaseDbAccessManager().select(countFilesQuery, callback);
+ return callback.getMapOfCounts();
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Unable to get counts of files for all datasources, providing empty results", ex);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Get a map containing the number of artifacts in each data source in
+ * the current case.
+ *
+ * @return Collection which maps datasource id to a count for the number
+ * of artifacts in the datasource, will only contain entries for
+ * datasources which have at least 1 artifact
+ */
+ private Map getCountsOfArtifacts() {
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ DataSourceCountsCallback callback = new DataSourceCountsCallback();
+ final String countArtifactsQuery = "data_source_obj_id, COUNT(*) AS count"
+ + " FROM blackboard_artifacts WHERE review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID()
+ + " GROUP BY data_source_obj_id"; //NON-NLS
+ skCase.getCaseDbAccessManager().select(countArtifactsQuery, callback);
+ return callback.getMapOfCounts();
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Unable to get counts of artifacts for all datasources, providing empty results", ex);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Get a map containing the number of tags which have been applied in
+ * each data source in the current case. Not necessarily the same as the
+ * number of items tagged, as an item can have any number of tags.
+ *
+ * @return Collection which maps datasource id to a count for the number
+ * of tags which have been applied in the datasource, will only
+ * contain entries for datasources which have at least 1 item
+ * tagged.
+ */
+ private Map getCountsOfTags() {
+ try {
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ DataSourceCountsCallback fileCountcallback = new DataSourceCountsCallback();
+ final String countFileTagsQuery = "data_source_obj_id, COUNT(*) AS count"
+ + " FROM content_tags as content_tags, tsk_files as tsk_files"
+ + " WHERE content_tags.obj_id = tsk_files.obj_id"
+ + " GROUP BY data_source_obj_id"; //NON-NLS
+ skCase.getCaseDbAccessManager().select(countFileTagsQuery, fileCountcallback);
+ Map tagCountMap = new HashMap<>(fileCountcallback.getMapOfCounts());
+ DataSourceCountsCallback artifactCountcallback = new DataSourceCountsCallback();
+ final String countArtifactTagsQuery = "data_source_obj_id, COUNT(*) AS count"
+ + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts"
+ + " WHERE artifact_tags.artifact_id = arts.artifact_id"
+ + " GROUP BY data_source_obj_id"; //NON-NLS
+ skCase.getCaseDbAccessManager().select(countArtifactTagsQuery, artifactCountcallback);
+ //combine the results from the count artifact tags query into the copy of the mapped results from the count file tags query
+ artifactCountcallback.getMapOfCounts().forEach((key, value) -> tagCountMap.merge(key, value, (value1, value2) -> value1 + value2));
+ return tagCountMap;
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Unable to get counts of tags for all datasources, providing empty results", ex);
+ return Collections.emptyMap();
+ }
+
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnHeaders.get(column);
+ }
+
+ /**
+ * Get the map of Data Source ID to counts of items found for a query
+ * which selects data_source_obj_id and count(*) with a group by
+ * data_source_obj_id clause.
+ */
+ private class DataSourceCountsCallback implements CaseDbAccessQueryCallback {
+
+ Map dataSourceObjIdCounts = new HashMap<>();
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ while (rs.next()) {
+ try {
+ dataSourceObjIdCounts.put(rs.getLong("data_source_obj_id"), rs.getLong("count"));
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Unable to get data_source_obj_id or count from result set", ex);
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Failed to get next result for counts by datasource", ex);
+ }
+ }
+
+ /**
+ * Get the processed results
+ *
+ * @return Collection which maps datasource id to a count for the
+ * number of items found with that datasource id, only
+ * contains entries for datasources with at least 1 item
+ * found.
+ */
+ Map getMapOfCounts() {
+ return Collections.unmodifiableMap(dataSourceObjIdCounts);
+ }
+
+ }
+
+ }
+
+ /**
+ * Table model for the Ingest Job table to display ingest jobs for the
+ * selected datasource.
+ */
+ @Messages({"DataSourceSummaryPanel.IngestJobTableModel.StartTime.header=Start Time",
+ "DataSourceSummaryPanel.IngestJobTableModel.EndTime.header=End Time",
+ "DataSourceSummaryPanel.IngestJobTableModel.IngestStatus.header=Ingest Status"})
+ private class IngestJobTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List columnHeaders = new ArrayList<>();
+
+ /**
+ * Create a new IngestJobTableModel
+ */
+ IngestJobTableModel() {
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_StartTime_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_EndTime_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_IngestStatus_header());
+ }
+
+ @Override
+ public int getRowCount() {
+ return ingestJobs.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnHeaders.size();
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ IngestJobInfo currIngestJob = ingestJobs.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ return datetimeFormat.format(currIngestJob.getStartDateTime());
+ case 1:
+ Date endDate = currIngestJob.getEndDateTime();
+ if (endDate.getTime() == 0) {
+ return "N/A";
+ }
+ return datetimeFormat.format(currIngestJob.getEndDateTime());
+ case 2:
+ return currIngestJob.getStatus().getDisplayName();
+ default:
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnHeaders.get(column);
+ }
+
+ }
+
+ /**
+ * Table model for the files table model to display counts of specific file
+ * types found in the currently selected data source.
+ */
+ @Messages({"DataSourceSummaryPanel.FilesTableModel.type.header=File Type",
+ "DataSourceSummaryPanel.FilesTableModel.count.header=Count"})
+ private class FilesTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+ private final DataSource currentDataSource;
+ private final List columnHeaders = new ArrayList<>();
+
+ /**
+ * Create a FilesTableModel for the speicified datasource.
+ *
+ * @param selectedDataSource the datasource which this filesTablemodel
+ * will represent
+ */
+ FilesTableModel(DataSource selectedDataSource) {
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_FilesTableModel_type_header());
+ columnHeaders.add(Bundle.DataSourceSummaryPanel_FilesTableModel_count_header());
+ currentDataSource = selectedDataSource;
+ }
+
+ @Override
+ public int getRowCount() {
+ //should be kept equal to the number of types we are displaying in the tables
+ return 5;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnHeaders.size();
+ }
+
+ @Messages({
+ "DataSourceSummaryPanel.FilesTableModel.images.row=Images",
+ "DataSourceSummaryPanel.FilesTableModel.videos.row=Videos",
+ "DataSourceSummaryPanel.FilesTableModel.audio.row=Audio",
+ "DataSourceSummaryPanel.FilesTableModel.documents.row=Documents",
+ "DataSourceSummaryPanel.FilesTableModel.executables.row=Executables"
+ })
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ if (columnIndex == 0) {
+ switch (rowIndex) {
+ case 0:
+ return Bundle.DataSourceSummaryPanel_FilesTableModel_images_row();
+ case 1:
+ return Bundle.DataSourceSummaryPanel_FilesTableModel_videos_row();
+ case 2:
+ return Bundle.DataSourceSummaryPanel_FilesTableModel_audio_row();
+ case 3:
+ return Bundle.DataSourceSummaryPanel_FilesTableModel_documents_row();
+ case 4:
+ return Bundle.DataSourceSummaryPanel_FilesTableModel_executables_row();
+ default:
+ break;
+ }
+ } else if (columnIndex == 1) {
+ switch (rowIndex) {
+ case 0:
+ return getCountOfFiles(FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes());
+ case 1:
+ return getCountOfFiles(FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes());
+ case 2:
+ return getCountOfFiles(FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes());
+ case 3:
+ return getCountOfFiles(FileTypeUtils.FileTypeCategory.DOCUMENTS.getMediaTypes());
+ case 4:
+ return getCountOfFiles(FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes());
+ default:
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the number of files in the case database for the current data
+ * source which have the specified mimetypes.
+ *
+ * @param setOfMimeTypes the set of mime types which we are finding the
+ * number of occurences of
+ *
+ * @return a Long value which represents the number of occurrences of
+ * the specified mime types in the current case for the
+ * specified data source, null if no count was retrieved
+ */
+ private Long getCountOfFiles(Set setOfMimeTypes) {
+ if (currentDataSource != null) {
+ try {
+ String inClause = String.join("', '", setOfMimeTypes);
+ SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ return skCase.countFilesWhere("data_source_obj_id=" + currentDataSource.getId()
+ + " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ + " AND mime_type IN ('" + inClause + "')"
+ + " AND name<>''");
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ logger.log(Level.WARNING, "Unable to get count of files for specified mime types", ex);
+ //unable to get count of files for the specified mimetypes cell will be displayed as empty
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnHeaders.get(column);
+ }
+
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/ViewSummaryInformationAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/ViewSummaryInformationAction.java
new file mode 100644
index 0000000000..96572fbf51
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/ViewSummaryInformationAction.java
@@ -0,0 +1,75 @@
+/*
+ * 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.casemodule.datasourceSummary;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import javax.swing.AbstractAction;
+import javax.swing.JDialog;
+import javax.swing.SwingUtilities;
+import org.openide.util.NbBundle.Messages;
+import org.openide.windows.WindowManager;
+
+/**
+ * ViewSummaryInformationAction action for opening a Data Source Summary Panel
+ * with the specified data source selected if it is present.
+ */
+public final class ViewSummaryInformationAction extends AbstractAction {
+
+ private static JDialog dataSourceSummaryDialog;
+ private static Long selectDataSource;
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a ViewSummaryInformationAction for the selected datasource.
+ *
+ * @param selectedDataSource - the data source which is currently selected
+ * and will be selected initially when the
+ * DataSourceSummaryPanel opens.
+ */
+ @Messages({"ViewSummaryInformationAction.name.text=View Summary Information"})
+ public ViewSummaryInformationAction(Long selectedDataSource) {
+ super(Bundle.ViewSummaryInformationAction_name_text());
+ selectDataSource = selectedDataSource;
+ }
+
+ @Messages({"ViewSummaryInformationAction.window.title=Data Source Summary"})
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ SwingUtilities.invokeLater(() -> {
+ String title = Bundle.ViewSummaryInformationAction_window_title();
+ Frame mainWindow = WindowManager.getDefault().getMainWindow();
+ dataSourceSummaryDialog = new JDialog(mainWindow, title, true);
+ DataSourceSummaryPanel dataSourceSummaryPanel = new DataSourceSummaryPanel();
+ //allow the buttons in DataSourceSummaryPanel to close this dialog
+ dataSourceSummaryPanel.addCloseButtonAction((ActionEvent event) -> {
+ dataSourceSummaryDialog.dispose();
+ });
+ //select the specifed data source
+ dataSourceSummaryPanel.selectDataSource(selectDataSource);
+ dataSourceSummaryDialog.add(dataSourceSummaryPanel);
+ dataSourceSummaryDialog.setResizable(true);
+ dataSourceSummaryDialog.pack();
+ dataSourceSummaryDialog.setLocationRelativeTo(mainWindow);
+ dataSourceSummaryDialog.setVisible(true);
+ dataSourceSummaryDialog.toFront();
+ });
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
index 2be9a9b447..332a6d7ca7 100644
--- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
+++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.core;
+import java.nio.file.Paths;
import org.sleuthkit.autopsy.coreutils.TextConverter;
import java.util.prefs.BackingStoreException;
import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo;
@@ -38,7 +39,6 @@ import org.sleuthkit.datamodel.TskData.DbType;
*/
public final class UserPreferences {
- private static final boolean IS_WINDOWS_OS = PlatformUtil.isWindowsOS();
private static final Preferences preferences = NbPreferences.forModule(UserPreferences.class);
public static final String KEEP_PREFERRED_VIEWER = "KeepPreferredViewer"; // NON-NLS
public static final String HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE = "HideKnownFilesInDataSourcesTree"; //NON-NLS
@@ -75,6 +75,7 @@ public final class UserPreferences {
public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags";
public static final String HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES = "HideCentralRepoCommentsAndOccurrences";
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
+ public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
// Prevent instantiation.
private UserPreferences() {
@@ -471,4 +472,23 @@ public final class UserPreferences {
public static void setLogFileCount(int count) {
preferences.putInt(MAX_NUM_OF_LOG_FILE, count);
}
+
+ /**
+ * Set the HdX path.
+ *
+ * @param executablePath User-inputted path to HxD executable
+ */
+ public static void setExternalHexEditorPath(String executablePath) {
+ preferences.put(EXTERNAL_HEX_EDITOR_PATH, executablePath);
+ }
+
+ /**
+ * Retrieves the HdXEditor path set by the User. If not found, the default
+ * will be the default install location of HxD.
+ *
+ * @return Path to HdX
+ */
+ public static String getExternalHexEditorPath() {
+ return preferences.get(EXTERNAL_HEX_EDITOR_PATH, Paths.get("C:", "Program Files", "HxD", "HxD.exe").toString());
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml
index 7fef77ee04..783aaf5b42 100644
--- a/Core/src/org/sleuthkit/autopsy/core/layer.xml
+++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml
@@ -48,6 +48,7 @@
+
@@ -182,6 +183,10 @@
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 299b0e18d4..d91a751a1a 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -193,3 +193,34 @@ ViewPreferencesPanel.keepCurrentViewerRadioButton.text=Stay on the same file vie
ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=For example, change from Hex to Media when a JPEG is selected.
ViewPreferencesPanel.useBestViewerRadioButton.text=Change to the most specific file viewer
ViewPreferencesPanel.fileNameTranslationColumnCheckbox.text=Add column in result viewer for file name translation
+DataContentViewerHex.launchHxDButton.text=Launch in HxD
+ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2
+ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule
+ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
+ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text=Browse
+ExternalViewerGlobalSettingsPanel.jLabel2.text=Set launchable content viewer extensions
+ExternalViewerGlobalSettingsPanel.jLabel1.text=HxD Editor Path:
+ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
+ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
+ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Set aplication viewer to use for files with specific mime types/extensions:
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1=Application
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0=Mime type/Extension
+AddExternalViewerRulePanel.nameLabel.text=MIME type or extension
+AddExternalViewerRulePanel.browseButton.text=Browse
+AddExternalViewerRulePanel.exePathTextField.text=
+AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension
+AddExternalViewerRulePanel.extRadioButton.text=Extension
+AddExternalViewerRulePanel.mimeRadioButton.text=MIME type
+AddExternalViewerRulePanel.nameTextField.text=
+ExternalViewerGlobalSettingsPanel.jButton2.text_1=jButton2
+ExternalViewerGlobalSettingsPanel.newRuleButton1.text_1=New Rule
+ExternalViewerGlobalSettingsPanel.newRuleButton.text_1=New Rule
+ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text_1=Browse
+ExternalViewerGlobalSettingsPanel.jLabel2.text_1=Set launchable content viewer extensions
+ExternalViewerGlobalSettingsPanel.jLabel1.text_1=HxD Editor Path:
+ExternalViewerGlobalSettingsPanel.HxDPath.text=C:/Program Files/HxD/HxD.exe
+ExternalViewerGlobalSettingsPanel.editRuleButton.text_1=Edit Rule
+ExternalViewerGlobalSettingsPanel.deleteRuleButton.text_1=Delete Rule
+ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text_1=Set aplication viewer to use for files with specific mime types/extensions:
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1_1=Application
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extension
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.form
index 745c862756..0b59085cb8 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.form
@@ -45,7 +45,7 @@
-
+
@@ -54,7 +54,7 @@
-
+
@@ -120,7 +120,9 @@
-
+
+
+
@@ -140,7 +142,10 @@
-
+
+
+
+
@@ -289,6 +294,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java
index 6639856099..82c7df497a 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java
@@ -21,7 +21,11 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
@@ -31,7 +35,13 @@ import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
import org.openide.nodes.Node;
import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
+import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
+import org.sleuthkit.autopsy.coreutils.FileUtil;
+import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.datamodel.DataConversion;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskException;
@@ -105,6 +115,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
goToPageLabel = new javax.swing.JLabel();
goToOffsetLabel = new javax.swing.JLabel();
goToOffsetTextField = new javax.swing.JTextField();
+ launchHxDButton = new javax.swing.JButton();
copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.copyMenuItem.text")); // NOI18N
rightClickMenu.add(copyMenuItem);
@@ -187,6 +198,13 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
}
});
+ launchHxDButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.launchHxDButton.text")); // NOI18N
+ launchHxDButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ launchHxDButtonActionPerformed(evt);
+ }
+ });
+
javax.swing.GroupLayout hexViewerPanelLayout = new javax.swing.GroupLayout(hexViewerPanel);
hexViewerPanel.setLayout(hexViewerPanelLayout);
hexViewerPanelLayout.setHorizontalGroup(
@@ -214,7 +232,9 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
.addComponent(goToOffsetLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(32, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(launchHxDButton)
+ .addContainerGap(146, Short.MAX_VALUE))
);
hexViewerPanelLayout.setVerticalGroup(
hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -231,17 +251,21 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
.addComponent(goToPageLabel)
.addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(goToOffsetLabel)
- .addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(launchHxDButton)))
.addGap(0, 0, 0))
);
+ launchHxDButton.setEnabled(PlatformUtil.isWindowsOS());
+
jScrollPane2.setViewportView(hexViewerPanel);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE)
+ .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 827, Short.MAX_VALUE)
.addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
);
layout.setVerticalGroup(
@@ -249,7 +273,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE))
+ .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 239, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
@@ -334,6 +358,37 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
}
}//GEN-LAST:event_goToOffsetTextFieldActionPerformed
+ @NbBundle.Messages({"DataContentViewerHex.launchError=Unable to launch HxD Editor. "
+ + "Please set-up the HdX install location in Tools -> Options -> External Viewer"})
+ private void launchHxDButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_launchHxDButtonActionPerformed
+ try {
+ File HdXExecutable = new File(UserPreferences.getExternalHexEditorPath());
+ if(!HdXExecutable.exists() || !HdXExecutable.canExecute()) {
+ JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
+ return;
+ }
+
+ String tempDirectory = Case.getCurrentCaseThrows().getTempDirectory();
+ File dataSourceInTempDirectory = Paths.get(tempDirectory,
+ FileUtil.escapeFileName(dataSource.getId() + dataSource.getName())).toFile();
+ ContentUtils.writeToFile(dataSource, dataSourceInTempDirectory);
+
+ try {
+ ProcessBuilder launchHdXExecutable = new ProcessBuilder();
+ launchHdXExecutable.command(String.format("\"%s\" \"%s\"",
+ HdXExecutable.getAbsolutePath(),
+ dataSourceInTempDirectory.getAbsolutePath()));
+ launchHdXExecutable.start();
+ } catch (IOException ex) {
+ JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
+ dataSourceInTempDirectory.delete();
+ }
+ } catch (NoCurrentCaseException | IOException ex) {
+ logger.log(Level.SEVERE, "Unable to copy file into temp directory", ex);
+ JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
+ }
+ }//GEN-LAST:event_launchHxDButtonActionPerformed
+
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JLabel currentPageLabel;
@@ -344,6 +399,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
private javax.swing.JPanel hexViewerPanel;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JScrollPane jScrollPane3;
+ private javax.swing.JButton launchHxDButton;
private javax.swing.JButton nextPageButton;
private javax.swing.JLabel ofLabel;
private javax.swing.JTextArea outputTextArea;
@@ -504,6 +560,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
goToPageLabel.setVisible(isVisible);
goToOffsetTextField.setVisible(isVisible);
goToOffsetLabel.setVisible(isVisible);
+ launchHxDButton.setVisible(isVisible);
}
@Override
@@ -528,17 +585,4 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
public Component getComponent() {
return this;
}
-
- /*
- * Show the right click menu only if evt is the correct mouse event
- */
- private void maybeShowPopup(java.awt.event.MouseEvent evt) {
- if (evt.isPopupTrigger()) {
- rightClickMenu.setLocation(evt.getLocationOnScreen());
- rightClickMenu.setVisible(true);
- copyMenuItem.setEnabled(outputTextArea.getSelectedText() != null);
- } else {
- rightClickMenu.setVisible(false);
- }
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
index 0bb186d75e..9025b325e3 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
@@ -34,6 +34,7 @@ import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.casemodule.datasourceSummary.ViewSummaryInformationAction;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
@@ -108,6 +109,7 @@ public class ImageNode extends AbstractContentNode {
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
actionsList.add(new FileSearchAction(
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
+ actionsList.add(new ViewSummaryInformationAction(content.getId()));
actionsList.add(new RunIngestModulesAction(Collections.singletonList(content)));
actionsList.add(new NewWindowViewAction(
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java
index 7dc18a7394..9509ccb483 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import javax.swing.Action;
import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.datasourceSummary.ViewSummaryInformationAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
@@ -63,6 +64,7 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNodesingletonList(content)));
} else {
actions.add(new RunIngestModulesAction(content));
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileTypeUtils.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileTypeUtils.java
new file mode 100644
index 0000000000..74545fcc40
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileTypeUtils.java
@@ -0,0 +1,207 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018-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.datamodel.utils;
+
+import com.google.common.collect.ImmutableSet;
+import static java.util.Arrays.asList;
+import java.util.Collection;
+import java.util.Collections;
+import javax.imageio.ImageIO;
+import static org.apache.commons.collections4.ListUtils.removeAll;
+import org.openide.util.NbBundle;
+
+/**
+ * Utilities for dealing with file/mime-types
+ */
+public final class FileTypeUtils {
+
+ private static final ImmutableSet IMAGE_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .addAll(removeAll(asList(ImageIO.getReaderMIMETypes()),
+ asList("application/octet-stream"))) //this claims to be supported, but is not really an image.
+ .add("image/bmp", //NON-NLS
+ "image/gif", //NON-NLS
+ "image/jpeg", //NON-NLS
+ "image/png", //NON-NLS
+ "image/tiff", //NON-NLS
+ "image/vnd.adobe.photoshop", //NON-NLS
+ "image/x-raw-nikon", //NON-NLS
+ "image/x-ms-bmp", //NON-NLS
+ "image/x-icon", //NON-NLS
+ "image/webp", //NON-NLS
+ "image/vnd.microsoft.icon", //NON-NLS
+ "image/x-rgb", //NON-NLS
+ "image/x-ms-bmp", //NON-NLS
+ "image/x-xbitmap", //NON-NLS
+ "image/x-portable-graymap", //NON-NLS
+ "image/x-portable-bitmap" //NON-NLS
+ ).build();
+ private static final ImmutableSet AUDIO_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .add("audio/midi", //NON-NLS
+ "audio/mpeg", //NON-NLS
+ "audio/webm", //NON-NLS
+ "audio/ogg", //NON-NLS
+ "audio/wav", //NON-NLS
+ "audio/vnd.wave", //NON-NLS
+ "audio/x-ms-wma"//NON-NLS
+ ).build();
+ private static final ImmutableSet VIDEO_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .add("video/webm", //NON-NLS
+ "video/3gpp", //NON-NLS
+ "video/3gpp2", //NON-NLS
+ "video/ogg", //NON-NLS
+ "video/mpeg", //NON-NLS
+ "video/mp4", //NON-NLS
+ "video/quicktime", //NON-NLS
+ "video/x-msvideo", //NON-NLS
+ "video/x-flv", //NON-NLS
+ "video/x-m4v", //NON-NLS
+ "video/x-ms-wmv"//NON-NLS
+ ).build();
+ private static final ImmutableSet DOCUMENT_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .add("text/plain", //NON-NLS
+ "text/css", //NON-NLS
+ "text/html", //NON-NLS
+ "text/csv", //NON-NLS
+ "text/xml", //NON-NLS
+ "text/x-log", //NON-NLS
+ "application/rtf", //NON-NLS
+ "application/pdf", //NON-NLS
+ "application/json", //NON-NLS
+ "application/javascript", //NON-NLS
+ "application/xml", //NON-NLS
+ "application/x-msoffice", //NON-NLS
+ "application/x-ooxml", //NON-NLS
+ "application/msword", //NON-NLS
+ "application/msword2", //NON-NLS
+ "application/vnd.wordperfect", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
+ "application/vnd.ms-powerpoint", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
+ "application/vnd.ms-excel", //NON-NLS
+ "application/vnd.ms-excel.sheet.4", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
+ "application/vnd.oasis.opendocument.presentation", //NON-NLS
+ "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
+ "application/vnd.oasis.opendocument.text" //NON-NLS
+ ).build();
+ private static final ImmutableSet EXECUTABLE_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .add("application/x-bat",//NON-NLS
+ "application/x-dosexec",//NON-NLS
+ "application/vnd.microsoft.portable-executable",//NON-NLS
+ "application/x-msdownload",//NON-NLS
+ "application/exe",//NON-NLS
+ "application/x-exe",//NON-NLS
+ "application/dos-exe",//NON-NLS
+ "vms/exe",//NON-NLS
+ "application/x-winexe",//NON-NLS
+ "application/msdos-windows",//NON-NLS
+ "application/x-msdos-program"//NON-NLS
+ ).build();
+ private static final ImmutableSet MULTI_MEDIA_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .addAll(IMAGE_MIME_TYPES)
+ .addAll(AUDIO_MIME_TYPES)
+ .addAll(VIDEO_MIME_TYPES)
+ .build();
+ private static final ImmutableSet VISUAL_MEDIA_MIME_TYPES
+ = new ImmutableSet.Builder()
+ .addAll(IMAGE_MIME_TYPES)
+ .addAll(VIDEO_MIME_TYPES)
+ .add("application/vnd.ms-asf", //NON-NLS
+ "application/vnd.rn-realmedia", //NON-NLS
+ "application/x-shockwave-flash" //NON-NLS
+ ).build();
+
+ private FileTypeUtils() {
+
+ }
+
+ /**
+ * Enum of categories/groups of file types.
+ */
+ @NbBundle.Messages({
+ "FileTypeCategory.Audio.displayName=Audio",
+ "FileTypeCategory.Video.displayName=Video",
+ "FileTypeCategory.Image.displayName=Image",
+ "FileTypeCategory.Media.displayName=Media",
+ "FileTypeCategory.Visual.displayName=Visual",
+ "FileTypeCategory.Documents.displayName=Documents",
+ "FileTypeCategory.Executables.displayName=Executables"})
+ public enum FileTypeCategory {
+
+ IMAGE(Bundle.FileTypeCategory_Image_displayName(),
+ IMAGE_MIME_TYPES,
+ Collections.emptyList()),
+ VIDEO(Bundle.FileTypeCategory_Video_displayName(),
+ VIDEO_MIME_TYPES,
+ Collections.emptyList()),
+ AUDIO(Bundle.FileTypeCategory_Audio_displayName(),
+ AUDIO_MIME_TYPES,
+ Collections.emptyList()),
+ /**
+ * Images, Videos, flash Animations, etc
+ */
+ VISUAL(Bundle.FileTypeCategory_Media_displayName(),
+ VISUAL_MEDIA_MIME_TYPES,
+ Collections.emptyList()),
+ /**
+ * VISUAL plus AUDIO.
+ */
+ MEDIA(Bundle.FileTypeCategory_Media_displayName(),
+ MULTI_MEDIA_MIME_TYPES,
+ Collections.emptyList()),
+ EXECUTABLE(Bundle.FileTypeCategory_Executables_displayName(),
+ EXECUTABLE_MIME_TYPES,
+ Collections.emptyList()),
+ /**
+ * (Plain) Text and "Office" documents.
+ */
+ DOCUMENTS(Bundle.FileTypeCategory_Documents_displayName(),
+ DOCUMENT_MIME_TYPES,
+ Collections.emptyList());
+
+ private final String displayName;
+ private final ImmutableSet mediaTypes;
+ private final ImmutableSet extensions;
+
+ private FileTypeCategory(String displayName, Collection mediaTypes, Collection extensions) {
+ this.displayName = displayName;
+ this.mediaTypes = ImmutableSet.copyOf(mediaTypes);
+ this.extensions = ImmutableSet.copyOf(extensions);
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public ImmutableSet getMediaTypes() {
+ return mediaTypes;
+
+ }
+
+ public ImmutableSet getExtensions() {
+ throw new UnsupportedOperationException("This method is not implemented yet."); //just to be explicit.
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
index 26c970b2b3..c1939adf6c 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
@@ -90,12 +90,6 @@ ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space\: {0}
ExtractAction.done.notifyMsg.extractErr=Error extracting files\: {0}
OptionsCategory_Name_ExternalViewer=External Viewer
OptionsCategory_Keywords_ExternalViewer=ExternalViewer
-ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
-ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
-ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
-ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Add your custom rules for external viewers
-ExternalViewerGlobalSettingsPanel.ruleListLabel.text=MIME type and extensions
-ExternalViewerGlobalSettingsPanel.exePathLabel.text=Program associated with this MIME type or extension
ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text=Program associated with this MIME type
ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text=Program associated with this extension
ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text=No MIME type or extension selected
@@ -108,7 +102,6 @@ ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.message=The extension i
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.title=Invalid extension
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.message=The path to the program executable is invalid
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.title=Invalid Path
-ExternalViewerGlobalSettingsPanel.exePathNameLabel.text=No MIME type or extension currently selected
ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message=A rule already exists with this MIME type or extension. Please edit that one.
ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title=Rule not added
AddExternalViewerRulePanel.mimeRadioButton.text=MIME type
@@ -124,3 +117,16 @@ GroupDataSourcesDialog.yesButton.text=Yes
GroupDataSourcesDialog.noButton.text=No
GroupDataSourcesDialog.title=Group by Data Source?
DirectoryTreeTopComponent.openViewPreferencesButton.text=
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title3=Title 4
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title2=Title 3
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title3_1=Title 4
+ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title2_1=Title 3
+ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule
+ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
+ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
+ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Add your custom rules for external viewers:
+ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
+ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2
+ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text=Browse
+ExternalViewerGlobalSettingsPanel.HxDLabel.text=HxD Editor Path:
+ExternalViewerGlobalSettingsPanel.ContentViewerExtensionLabel.text=Add content viewer extensions:
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form
index e04dc19270..fde8a99209 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form
@@ -1,6 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java
index f5351393dc..0cfbd7d5e5 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java
@@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
- *
+ *
* Copyright 2011-2018 Basis Technology Corp.
* Contact: carrier sleuthkit org
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,16 +18,21 @@
*/
package org.sleuthkit.autopsy.directorytree;
+import java.awt.Color;
+import java.awt.event.ActionListener;
+import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
-import javax.swing.DefaultListModel;
-import javax.swing.JOptionPane;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import org.netbeans.spi.options.OptionsPanelController;
-import org.openide.util.NbBundle;
+import java.util.List;
+import javax.swing.JFileChooser;
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
-import org.sleuthkit.autopsy.coreutils.Logger;
+import javax.swing.JOptionPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import org.openide.util.NbBundle;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.sleuthkit.autopsy.casemodule.GeneralFilter;
+import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/**
* An options panel for the user to create, edit, and delete associations for
@@ -37,39 +42,67 @@ import org.sleuthkit.autopsy.coreutils.Logger;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel implements OptionsPanel {
- private static final Logger logger = Logger.getLogger(ExternalViewerGlobalSettingsPanel.class.getName());
- private DefaultListModel rulesListModel;
- private java.util.List rules;
+ private ExternalViewerGlobalSettingsTableModel tableModel;
+ public ExternalViewerGlobalSettingsPanel() {
+ this(new ExternalViewerGlobalSettingsTableModel(new String[] {
+ "Mime type/Extension", "Application"}));
+ }
+
/**
* Creates new form ExternalViewerGlobalSettingsPanel
*/
- public ExternalViewerGlobalSettingsPanel() {
+ public ExternalViewerGlobalSettingsPanel(ExternalViewerGlobalSettingsTableModel tableModel) {
initComponents();
- customizeComponents();
+ this.tableModel = tableModel;
+ customizeComponents(tableModel);
}
/**
* Initializes field variables. Adds a listener to the list of rules.
*/
- private void customizeComponents() {
- rulesListModel = new DefaultListModel<>();
- rules = new ArrayList<>();
- rulesList.setModel(rulesListModel);
- rulesList.addListSelectionListener(new ListSelectionListener() {
- @Override
- public void valueChanged(ListSelectionEvent e) {
- if (e.getValueIsAdjusting() == false) {
- if (rulesList.getSelectedIndex() == -1) {
- clearExePath();
- } else {
- populateExePath();
- }
- }
- }
+ private void customizeComponents(ExternalViewerGlobalSettingsTableModel tableModel) {
+ ExternalViewerRulesTable.setModel(tableModel);
+ ExternalViewerRulesTable.setAutoCreateRowSorter(true);
+ ExternalViewerRulesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ ListSelectionModel selectionModel = ExternalViewerRulesTable.getSelectionModel();
+
+ selectionModel.addListSelectionListener((ListSelectionEvent selection) -> {
+ enableButtons();
});
}
-
+
+ /**
+ * Simulate the delete rule button click action.
+ *
+ * @param selectedIndex Index to delete in JTable
+ */
+ public void deleteRuleButtonClick(int selectedIndex) {
+ setSelectionInterval(selectedIndex, selectedIndex);
+ deleteRuleButton.getListeners(ActionListener.class)[0].actionPerformed(null);
+ }
+
+ /**
+ * Simulate selecting an entry in the JTable.
+ *
+ * In single selection mode, only the endIndex is used.
+ *
+ * @param rowIndex Index to select
+ */
+ public void setSelectionInterval(int startingIndex, int endIndex) {
+ ExternalViewerRulesTable.getSelectionModel().setSelectionInterval(startingIndex, endIndex);
+ }
+
+ /**
+ * Tests whether the index is selected.
+ *
+ * @param index
+ * @return
+ */
+ public boolean isSelected(int index) {
+ return ExternalViewerRulesTable.getSelectionModel().isSelectedIndex(index);
+ }
+
/**
* 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
@@ -79,60 +112,33 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
// //GEN-BEGIN:initComponents
private void initComponents() {
+ newRuleButton1 = new javax.swing.JButton();
+ jButton2 = new javax.swing.JButton();
jPanel1 = new javax.swing.JPanel();
- externalViewerTitleLabel = new javax.swing.JLabel();
- jScrollPane1 = new javax.swing.JScrollPane();
- jSplitPane1 = new javax.swing.JSplitPane();
- exePanel = new javax.swing.JPanel();
- exePathLabel = new javax.swing.JLabel();
- exePathNameLabel = new javax.swing.JLabel();
- rulesPanel = new javax.swing.JPanel();
- ruleListLabel = new javax.swing.JLabel();
+ jPanel2 = new javax.swing.JPanel();
newRuleButton = new javax.swing.JButton();
- editRuleButton = new javax.swing.JButton();
+ jScrollPane4 = new javax.swing.JScrollPane();
+ ExternalViewerRulesTable = new javax.swing.JTable();
+ externalViewerTitleLabel = new javax.swing.JLabel();
deleteRuleButton = new javax.swing.JButton();
- jScrollPane2 = new javax.swing.JScrollPane();
- rulesList = new javax.swing.JList<>();
+ editRuleButton = new javax.swing.JButton();
+ jPanel3 = new javax.swing.JPanel();
+ HxDPath = new javax.swing.JTextField();
+ HxDLabel = new javax.swing.JLabel();
+ ContentViewerExtensionLabel = new javax.swing.JLabel();
+ browseHxDDirectory = new javax.swing.JButton();
+
+ newRuleButton1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(newRuleButton1, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton1.text")); // NOI18N
+ newRuleButton1.setMaximumSize(new java.awt.Dimension(111, 25));
+ newRuleButton1.setMinimumSize(new java.awt.Dimension(111, 25));
+
+ org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.jButton2.text")); // NOI18N
setPreferredSize(new java.awt.Dimension(701, 453));
jPanel1.setPreferredSize(new java.awt.Dimension(701, 453));
- org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N
-
- jSplitPane1.setDividerSize(1);
-
- exePanel.setPreferredSize(new java.awt.Dimension(311, 224));
-
- org.openide.awt.Mnemonics.setLocalizedText(exePathLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathLabel.text")); // NOI18N
-
- org.openide.awt.Mnemonics.setLocalizedText(exePathNameLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathNameLabel.text")); // NOI18N
-
- javax.swing.GroupLayout exePanelLayout = new javax.swing.GroupLayout(exePanel);
- exePanel.setLayout(exePanelLayout);
- exePanelLayout.setHorizontalGroup(
- exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(exePanelLayout.createSequentialGroup()
- .addContainerGap()
- .addGroup(exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(exePathLabel)
- .addComponent(exePathNameLabel))
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- );
- exePanelLayout.setVerticalGroup(
- exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(exePanelLayout.createSequentialGroup()
- .addContainerGap()
- .addComponent(exePathLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(exePathNameLabel)
- .addContainerGap(355, Short.MAX_VALUE))
- );
-
- jSplitPane1.setRightComponent(exePanel);
-
- org.openide.awt.Mnemonics.setLocalizedText(ruleListLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ruleListLabel.text")); // NOI18N
-
newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton.text")); // NOI18N
newRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
@@ -143,6 +149,26 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
}
});
+ jScrollPane4.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(204, 204, 204)));
+
+ jScrollPane4.setViewportView(ExternalViewerRulesTable);
+ if (ExternalViewerRulesTable.getColumnModel().getColumnCount() > 0) {
+ ExternalViewerRulesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title0")); // NOI18N
+ ExternalViewerRulesTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title1")); // NOI18N
+ }
+
+ org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N
+
+ deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N
+ deleteRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
+ deleteRuleButton.setMinimumSize(new java.awt.Dimension(111, 25));
+ deleteRuleButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ deleteRuleButtonActionPerformed(evt);
+ }
+ });
+
editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.editRuleButton.text")); // NOI18N
editRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
@@ -153,91 +179,112 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
}
});
- deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
- org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N
- deleteRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
- deleteRuleButton.setMinimumSize(new java.awt.Dimension(111, 25));
- deleteRuleButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- deleteRuleButtonActionPerformed(evt);
- }
- });
-
- jScrollPane2.setViewportView(rulesList);
-
- javax.swing.GroupLayout rulesPanelLayout = new javax.swing.GroupLayout(rulesPanel);
- rulesPanel.setLayout(rulesPanelLayout);
- rulesPanelLayout.setHorizontalGroup(
- rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(rulesPanelLayout.createSequentialGroup()
+ javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
+ jPanel2.setLayout(jPanel2Layout);
+ jPanel2Layout.setHorizontalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
.addContainerGap()
- .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(ruleListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, rulesPanelLayout.createSequentialGroup()
- .addGap(6, 6, 6)
- .addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(16, 16, 16)
- .addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(16, 16, 16)
- .addComponent(deleteRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- .addComponent(jScrollPane2))
- .addContainerGap())
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 634, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(externalViewerTitleLabel)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, 97, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, 109, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
);
- rulesPanelLayout.setVerticalGroup(
- rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(rulesPanelLayout.createSequentialGroup()
- .addContainerGap()
- .addComponent(ruleListLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jScrollPane2)
+ jPanel2Layout.setVerticalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(12, 12, 12)
- .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 219, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(newRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(editRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
- jSplitPane1.setLeftComponent(rulesPanel);
+ HxDPath.setEditable(false);
- jScrollPane1.setViewportView(jSplitPane1);
+ org.openide.awt.Mnemonics.setLocalizedText(HxDLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.HxDLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(ContentViewerExtensionLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ContentViewerExtensionLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(browseHxDDirectory, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text")); // NOI18N
+ browseHxDDirectory.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ browseHxDDirectoryActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addComponent(ContentViewerExtensionLabel)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addGap(21, 21, 21)
+ .addComponent(HxDLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(HxDPath, javax.swing.GroupLayout.DEFAULT_SIZE, 441, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseHxDDirectory, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addComponent(ContentViewerExtensionLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(HxDPath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(HxDLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(browseHxDDirectory))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ browseHxDDirectory.setEnabled(PlatformUtil.isWindowsOS());
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 681, Short.MAX_VALUE)
- .addContainerGap())
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(jScrollPane1)
- .addContainerGap()))
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGap(547, 547, 547))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
- .addComponent(externalViewerTitleLabel)
- .addContainerGap(428, Short.MAX_VALUE))
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(jPanel1Layout.createSequentialGroup()
- .addGap(32, 32, 32)
- .addComponent(jScrollPane1)
- .addContainerGap()))
+ .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(18, 18, 18)
+ .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(83, Short.MAX_VALUE))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 1191, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
}// //GEN-END:initComponents
@@ -247,16 +294,13 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
ExternalViewerRule newRule = dialog.getRule();
// Only allow one association for each MIME type or extension.
- if (rules.contains(newRule)) {
+ if (tableModel.containsRule(newRule)) {
JOptionPane.showMessageDialog(this,
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
JOptionPane.ERROR_MESSAGE);
} else {
- rules.add(newRule);
- updateRulesListModel();
- int index = rules.indexOf(newRule);
- rulesList.setSelectedIndex(index);
+ tableModel.addRule(newRule);
enableButtons();
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}
@@ -264,113 +308,107 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
}//GEN-LAST:event_newRuleButtonActionPerformed
private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed
- int selected = rulesList.getSelectedIndex();
- AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog(rulesListModel.get(rulesList.getSelectedIndex()));
+ int selectedIndex = ExternalViewerRulesTable.convertRowIndexToModel(ExternalViewerRulesTable.getSelectedRow());
+ ExternalViewerRule selectedRule = tableModel.getRuleAt(selectedIndex);
+ AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog(selectedRule);
AddExternalViewerRuleDialog.BUTTON_PRESSED result = dialog.getResult();
if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
- rules.remove(selected);
ExternalViewerRule newRule = dialog.getRule();
// Only allow one association for each MIME type or extension.
- if (rules.contains(newRule)) {
+ if (tableModel.containsRule(newRule)) {
JOptionPane.showMessageDialog(this,
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
JOptionPane.ERROR_MESSAGE);
} else {
- rules.add(selected, dialog.getRule());
- updateRulesListModel();
+ tableModel.setRule(selectedIndex, dialog.getRule());
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}
}
- rulesList.setSelectedIndex(selected);
- enableButtons();
}//GEN-LAST:event_editRuleButtonActionPerformed
private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed
- ExternalViewerRule rule = rulesList.getSelectedValue();
- rules.remove(rule);
- updateRulesListModel();
+ tableModel.removeRule(ExternalViewerRulesTable.convertRowIndexToModel(ExternalViewerRulesTable.getSelectedRow()));
+ ExternalViewerRulesTable.getSelectionModel().clearSelection();
+ enableButtons();
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_deleteRuleButtonActionPerformed
+ private void browseHxDDirectoryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseHxDDirectoryActionPerformed
+ JFileChooser fileWindow = new JFileChooser();
+ fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ GeneralFilter exeFilter = new GeneralFilter(GeneralFilter.EXECUTABLE_EXTS, GeneralFilter.EXECUTABLE_DESC);
+ File HxDPathFile = new File(HxDPath.getText());
+ if(HxDPathFile.exists() && HxDPathFile.canExecute()) {
+ fileWindow.setCurrentDirectory(new File(HxDPath.getText()));
+ }
+ fileWindow.setDragEnabled(false);
+ fileWindow.setFileFilter(exeFilter);
+ fileWindow.setMultiSelectionEnabled(false);
+ int returnVal = fileWindow.showSaveDialog(this);
+ if(returnVal == JFileChooser.APPROVE_OPTION) {
+ File HxDExecutable = fileWindow.getSelectedFile();
+ HxDPath.setForeground(Color.BLACK);
+ HxDPath.setText(HxDExecutable.getAbsolutePath());
+ firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }
+ }//GEN-LAST:event_browseHxDDirectoryActionPerformed
+
@Override
public void store() {
+ //Dump rules from table model into a list to be stored by the rules manager.
+ List rules = new ArrayList<>();
+ for(int i = 0; i < tableModel.getRowCount(); i++) {
+ rules.add(tableModel.getRuleAt(i));
+ }
ExternalViewerRulesManager.getInstance().setUserRules(rules);
+ UserPreferences.setExternalHexEditorPath(HxDPath.getText());
}
@Override
public void load() {
- rules = ExternalViewerRulesManager.getInstance().getUserRules();
- updateRulesListModel();
+ List rules = ExternalViewerRulesManager.getInstance().getUserRules();
+ for(ExternalViewerRule rule : rules) {
+ if(!tableModel.containsRule(rule)) {
+ tableModel.addRule(rule);
+ }
+ }
+ String editorPath = UserPreferences.getExternalHexEditorPath();
+ File HdXExecutable = new File(editorPath);
+ if(HdXExecutable.exists() || HdXExecutable.canExecute()) {
+ HxDPath.setText(editorPath);
+ } else {
+ HxDPath.setForeground(Color.RED);
+ HxDPath.setText(editorPath);
+ }
enableButtons();
}
/**
* Enable edit and delete buttons if there is a rule selected.
*/
- private void enableButtons() {
- boolean ruleIsSelected = rulesList.getSelectedIndex() != -1;
+ boolean enableButtons() {
+ boolean ruleIsSelected = ExternalViewerRulesTable.getSelectedRow() >= 0;
editRuleButton.setEnabled(ruleIsSelected);
deleteRuleButton.setEnabled(ruleIsSelected);
- }
-
- /**
- * Sets the list model for the rules list component, sorted by the MIME
- * type or extension alphabetically.
- */
- private void updateRulesListModel() {
- rulesListModel.clear();
- Collections.sort(rules);
- for (ExternalViewerRule rule : rules) {
- rulesListModel.addElement(rule);
- }
- }
-
- /**
- * Fills in the .exe file path label if a rule is selected.
- */
- private void populateExePath() {
- ExternalViewerRule rule = rulesList.getSelectedValue();
- if (rule != null) {
- if (rule.getRuleType() == ExternalViewerRule.RuleType.MIME) {
- exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
- "ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text"));
- } else {
- exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
- "ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text"));
- }
- exePathNameLabel.setText(rule.getExePath());
- }
- enableButtons();
- }
-
- /**
- * Clears the .exe file path label.
- */
- private void clearExePath() {
- rulesList.clearSelection();
- exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
- "ExternalViewerGlobalSettingsPanel.exePathLabel.text"));
- exePathNameLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
- "ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text"));
- enableButtons();
+ return ruleIsSelected;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel ContentViewerExtensionLabel;
+ private javax.swing.JTable ExternalViewerRulesTable;
+ private javax.swing.JLabel HxDLabel;
+ private javax.swing.JTextField HxDPath;
+ private javax.swing.JButton browseHxDDirectory;
private javax.swing.JButton deleteRuleButton;
private javax.swing.JButton editRuleButton;
- private javax.swing.JPanel exePanel;
- private javax.swing.JLabel exePathLabel;
- private javax.swing.JLabel exePathNameLabel;
private javax.swing.JLabel externalViewerTitleLabel;
+ private javax.swing.JButton jButton2;
private javax.swing.JPanel jPanel1;
- private javax.swing.JScrollPane jScrollPane1;
- private javax.swing.JScrollPane jScrollPane2;
- private javax.swing.JSplitPane jSplitPane1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JScrollPane jScrollPane4;
private javax.swing.JButton newRuleButton;
- private javax.swing.JLabel ruleListLabel;
- private javax.swing.JList rulesList;
- private javax.swing.JPanel rulesPanel;
- private javax.swing.JScrollPane rulesScrollPane;
+ private javax.swing.JButton newRuleButton1;
// End of variables declaration//GEN-END:variables
}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModel.java
new file mode 100755
index 0000000000..5662e0cd84
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModel.java
@@ -0,0 +1,166 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.directorytree;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Model that stores the External Viewer rules for Mime types and extensions.
+ * Located at Tools -> Options -> External Viewer
+ */
+class ExternalViewerGlobalSettingsTableModel extends AbstractTableModel {
+
+ private final List rules;
+ private final String[] columnNames;
+
+ public ExternalViewerGlobalSettingsTableModel(String... columnNames) {
+ this.columnNames = Arrays.copyOf(columnNames, columnNames.length);
+ this.rules = new ArrayList<>();
+ }
+
+ /**
+ * Stores a new external viewer rule.
+ *
+ * @param rule User-inputted rule
+ */
+ public void addRule(ExternalViewerRule rule) {
+ rules.add(rule);
+ fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1);
+ }
+
+ /**
+ * Returns the number of rules stored in this model.
+ *
+ * @return Integer denoting row count in table model
+ */
+ @Override
+ public int getRowCount() {
+ return rules.size();
+ }
+
+ /**
+ * Returns the column name at the given index.
+ *
+ * @param columnIndex
+ *
+ * @return Column name
+ */
+ @Override
+ public String getColumnName(int columnIndex) {
+ return columnNames[columnIndex];
+ }
+
+ /**
+ * Retrieves column class type. As for now, this is only type string.
+ *
+ * @param columnIndex
+ *
+ * @return String.class
+ */
+ @Override
+ public Class getColumnClass(int columnIndex) {
+ return String.class;
+ }
+
+ /**
+ * Retrieves the number of columns in this table model.
+ *
+ * @return Integer denoting column count
+ */
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ /**
+ * Retrieves value at a given row and column in the table.
+ *
+ * @param rowIndex Desired row index
+ * @param columnIndex Desired column index
+ *
+ * @return A generic pointer to the underlying data.
+ */
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ if (columnIndex == 0) {
+ return rules.get(rowIndex).getName();
+ } else {
+ return rules.get(rowIndex).getExePath();
+ }
+ }
+
+ /**
+ * Gets an entire rule instance from a given index.
+ *
+ * @param rowIndex Desired row
+ *
+ * @return User-inputted rule at the desired rowIndex
+ */
+ public ExternalViewerRule getRuleAt(int rowIndex) {
+ return rules.get(rowIndex);
+ }
+
+ /**
+ * Replaces an existing rule in the table.
+ *
+ * @param rowIndex Desired row index
+ * @param rule New rule to overwrite the old.
+ */
+ public void setRule(int rowIndex, ExternalViewerRule rule) {
+ rules.set(rowIndex, rule);
+ fireTableDataChanged();
+ }
+
+ /**
+ * Removes the rule from the table model.
+ *
+ * @param rowIndex Desired row index to delete
+ */
+ public void removeRule(int rowIndex) {
+ rules.remove(rowIndex);
+ fireTableDataChanged();
+ }
+
+ /**
+ * This table model is not editable.
+ *
+ * @param rowIndex
+ * @param colIndex
+ *
+ * @return False
+ */
+ @Override
+ public boolean isCellEditable(int rowIndex, int colIndex) {
+ return false;
+ }
+
+ /**
+ * Tests containment of a given rule in the table model.
+ *
+ * @param rule Rule in question
+ *
+ * @return
+ */
+ public boolean containsRule(ExternalViewerRule rule) {
+ return rules.contains(rule);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestModule.java
index 4389fe5bb7..fa8d70794a 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/dataSourceIntegrity/DataSourceIntegrityIngestModule.java
@@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestServices;
+import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.TskCoreException;
@@ -300,6 +301,8 @@ public class DataSourceIntegrityIngestModule implements DataSourceIngestModule {
BlackboardArtifact verificationFailedArtifact = Case.getCurrentCase().getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED, img.getId());
verificationFailedArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
DataSourceIntegrityModuleFactory.getModuleName(), artifactComment));
+ IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(DataSourceIntegrityModuleFactory.getModuleName(),
+ BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error creating verification failed artifact", ex);
}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
index 8b55988fa7..1fb60cf0cb 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.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");
@@ -258,7 +258,7 @@ class SevenZipExtractor {
org.sleuthkit.datamodel.Blackboard tskBlackboard = tskCase.getBlackboard();
// Create artifact if it doesn't already exist.
if (!tskBlackboard.artifactExists(archiveFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, attributes)) {
- BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
+ BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
artifact.addAttributes(attributes);
try {
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java
index af9266b36f..25d691ddbc 100755
--- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java
@@ -90,51 +90,51 @@ public class EmbeddedFileTest extends NbTestCase {
CaseUtils.closeCurrentCase();
}
-// public void testEncryptionAndZipBomb() {
-// try {
-// List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'");
-// final String zipBombSetName = "Possible Zip Bomb";
-// final String protectedName1 = "password_protected.zip";
-// final String protectedName2 = "level1_protected.zip";
-// final String protectedName3 = "42.zip";
-// final String depthZipBomb = "DepthTriggerZipBomb.zip";
-// final String ratioZipBomb = "RatioTriggerZipBomb.zip";
-// int zipBombs = 0;
-// assertEquals("The number of files in the test image has changed", 2221, results.size());
-// int passwdProtectedZips = 0;
-// for (AbstractFile file : results) {
-// //.zip file has artifact TSK_ENCRYPTION_DETECTED
-// if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)) {
-// ArrayList artifacts = file.getAllArtifacts();
-// assertEquals("Password protected zip file " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
-// for (BlackboardArtifact artifact : artifacts) {
-// assertEquals("Artifact for password protected zip file " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID());
-// passwdProtectedZips++;
-// }
-// } else if (file.getName().equalsIgnoreCase(depthZipBomb) || file.getName().equalsIgnoreCase(ratioZipBomb)) {
-// ArrayList artifacts = file.getAllArtifacts();
-// assertEquals("Zip bomb " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
-// for (BlackboardArtifact artifact : artifacts) {
-// assertEquals("Artifact for Zip bomb " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID());
-// BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
-// assertNotNull("No attribute found for artifact on zip bomb " + file.getName(), attribute);
-// assertEquals("Interesting artifact on file, " + file.getName() + ", does not reflect it being a zip bomb", zipBombSetName, attribute.getDisplayString());
-// zipBombs++;
-// }
-// } else {//No other files have artifact defined
-// assertEquals("Unexpected file, " + file.getName() + ", has artifacts", 0, file.getAllArtifacts().size());
-// }
-//
-// }
-// //Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing.
-// assertEquals("Unexpected number of artifacts reflecting password protected zip files found", 3, passwdProtectedZips);
-// //Make sure 2 zip bomb files have been tested: DepthTriggerZipBomb.zip and RatioTriggerZipBomb.zip.
-// assertEquals("Unexpected number of artifacts reflecting zip bombs found", 2, zipBombs);
-// } catch (TskCoreException ex) {
-// Exceptions.printStackTrace(ex);
-// Assert.fail(ex.getMessage());
-// }
-// }
+ public void testEncryptionAndZipBomb() {
+ try {
+ List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'");
+ final String zipBombSetName = "Possible Zip Bomb";
+ final String protectedName1 = "password_protected.zip";
+ final String protectedName2 = "level1_protected.zip";
+ final String protectedName3 = "42.zip";
+ final String depthZipBomb = "DepthTriggerZipBomb.zip";
+ final String ratioZipBomb = "RatioTriggerZipBomb.zip";
+ int zipBombs = 0;
+ assertEquals("The number of files in the test image has changed", 2221, results.size());
+ int passwdProtectedZips = 0;
+ for (AbstractFile file : results) {
+ //.zip file has artifact TSK_ENCRYPTION_DETECTED
+ if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)) {
+ ArrayList artifacts = file.getAllArtifacts();
+ assertEquals("Password protected zip file " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
+ for (BlackboardArtifact artifact : artifacts) {
+ assertEquals("Artifact for password protected zip file " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID());
+ passwdProtectedZips++;
+ }
+ } else if (file.getName().equalsIgnoreCase(depthZipBomb) || file.getName().equalsIgnoreCase(ratioZipBomb)) {
+ ArrayList artifacts = file.getAllArtifacts();
+ assertEquals("Zip bomb " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
+ for (BlackboardArtifact artifact : artifacts) {
+ assertEquals("Artifact for Zip bomb " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID());
+ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
+ assertNotNull("No attribute found for artifact on zip bomb " + file.getName(), attribute);
+ assertEquals("Interesting artifact on file, " + file.getName() + ", does not reflect it being a zip bomb", zipBombSetName, attribute.getDisplayString());
+ zipBombs++;
+ }
+ } else {//No other files have artifact defined
+ assertEquals("Unexpected file, " + file.getName() + ", has artifacts", 0, file.getAllArtifacts().size());
+ }
+
+ }
+ //Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing.
+ assertEquals("Unexpected number of artifacts reflecting password protected zip files found", 3, passwdProtectedZips);
+ //Make sure 2 zip bomb files have been tested: DepthTriggerZipBomb.zip and RatioTriggerZipBomb.zip.
+ assertEquals("Unexpected number of artifacts reflecting zip bombs found", 2, zipBombs);
+ } catch (TskCoreException ex) {
+ Exceptions.printStackTrace(ex);
+ Assert.fail(ex.getMessage());
+ }
+ }
public void testBigFolder() {
final int numOfFilesToTest = 1000;
diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanelTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanelTest.java
new file mode 100755
index 0000000000..cfbc5e0cf6
--- /dev/null
+++ b/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanelTest.java
@@ -0,0 +1,107 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.directorytree;
+
+import junit.framework.Assert;
+import org.junit.Test;
+import org.sleuthkit.autopsy.directorytree.ExternalViewerRule.RuleType;
+
+/**
+ * 72% code coverage of ExternalViewerGlobalSettingsPanel
+ */
+public class ExternalViewerGlobalSettingsPanelTest {
+
+ static final String[] testColumnNames = {"A", "B"};
+
+ /**
+ * Default constructor for JUnit
+ */
+ public ExternalViewerGlobalSettingsPanelTest(){
+ //Codacy complains if there is no comment here
+ }
+
+ @Test
+ public void testEnableButtons() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ ExternalViewerGlobalSettingsPanel panel = new ExternalViewerGlobalSettingsPanel(testModel);
+ Assert.assertFalse(panel.enableButtons());
+
+ testModel.addRule(new ExternalViewerRule("image/png", "fake.exe", RuleType.MIME));
+
+ Assert.assertFalse(panel.enableButtons());
+ panel.setSelectionInterval(0, 0);
+ Assert.assertTrue(panel.enableButtons());
+ }
+
+ @Test
+ public void testDisableButtons() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ ExternalViewerGlobalSettingsPanel panel = new ExternalViewerGlobalSettingsPanel(testModel);
+
+ testModel.addRule(new ExternalViewerRule("image/png", "fake.exe", RuleType.MIME));
+ Assert.assertFalse(panel.enableButtons());
+ panel.setSelectionInterval(0, 0);
+ Assert.assertTrue(panel.enableButtons());
+
+ testModel.removeRule(0);
+ Assert.assertFalse(panel.enableButtons());
+ }
+
+ @Test
+ public void testDeleteRuleButtonClick() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
+ Assert.assertFalse(testPanel.enableButtons());
+
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+ testPanel.setSelectionInterval(0, 0);
+ Assert.assertTrue(testPanel.enableButtons());
+ Assert.assertEquals(1, testModel.getRowCount());
+
+ testPanel.deleteRuleButtonClick(0);
+
+ Assert.assertFalse(testPanel.enableButtons());
+ Assert.assertEquals(0, testModel.getRowCount());
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testDeleteButtonClickFail() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
+
+ testPanel.deleteRuleButtonClick(-1);
+ }
+
+ @Test
+ public void testSingleSelection() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+ testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
+
+ testPanel.setSelectionInterval(0, 2);
+
+ Assert.assertFalse(testPanel.isSelected(0));
+ Assert.assertFalse(testPanel.isSelected(1));
+ Assert.assertTrue(testPanel.isSelected(2));
+ }
+}
diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModelTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModelTest.java
new file mode 100755
index 0000000000..72118c7e3e
--- /dev/null
+++ b/Core/test/unit/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsTableModelTest.java
@@ -0,0 +1,217 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.directorytree;
+
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.sleuthkit.autopsy.directorytree.ExternalViewerRule.RuleType;
+
+/**
+ * 100% code coverage of ExternalViewerGlobalSettingsTableModel
+ */
+public class ExternalViewerGlobalSettingsTableModelTest {
+
+ static final String[] testColumnNames = {"A", "B"};
+ private ExternalViewerRule pngMime;
+ private ExternalViewerRule txtExt;
+ private ExternalViewerRule wavExt;
+
+ /**
+ * Initialize JUnit test
+ */
+ public ExternalViewerGlobalSettingsTableModelTest() {
+ //Empty constructor
+ }
+
+ @Before
+ public void setUp() {
+ pngMime = new ExternalViewerRule("image/png", "test.exe", RuleType.MIME);
+ txtExt = new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT);
+ wavExt = new ExternalViewerRule(".wav", "video.exe", RuleType.EXT);
+ }
+
+ /**
+ * Test of addRule method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testAddRule() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+
+ Assert.assertEquals(1, testModel.getRowCount());
+
+ ExternalViewerRule rule = testModel.getRuleAt(0);
+ Assert.assertEquals("image/png", rule.getName());
+ Assert.assertEquals("test.exe", rule.getExePath());
+ Assert.assertEquals(RuleType.MIME, rule.getRuleType());
+ }
+
+ /**
+ * Test of getRowCount method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetRowCount() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ Assert.assertEquals(0, testModel.getRowCount());
+
+ testModel.addRule(pngMime);
+ testModel.addRule(txtExt);
+ testModel.addRule(wavExt);
+
+ Assert.assertEquals(3, testModel.getRowCount());
+ }
+
+ /**
+ * Test of getColumnName method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetColumnName() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ Assert.assertEquals("A", testModel.getColumnName(0));
+ Assert.assertEquals("B", testModel.getColumnName(1));
+ }
+
+ @Test(expected = ArrayIndexOutOfBoundsException.class)
+ public void testColumnNameOutOfBounds() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.getColumnName(2);
+ }
+
+ /**
+ * Test of getColumnClass method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetColumnClass() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ Assert.assertEquals(String.class, testModel.getColumnClass(0));
+ }
+
+ /**
+ * Test of getColumnCount method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetColumnCount() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ Assert.assertEquals(2, testModel.getColumnCount());
+ ExternalViewerGlobalSettingsTableModel testModelTwo = new ExternalViewerGlobalSettingsTableModel(new String[] {"A", "B", "C", "D", "E"});
+ Assert.assertEquals(5, testModelTwo.getColumnCount());
+ }
+
+ /**
+ * Test of getValueAt method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetValueAt() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ testModel.addRule(txtExt);
+ testModel.addRule(wavExt);
+
+ Assert.assertEquals(".txt", testModel.getValueAt(1,0));
+ Assert.assertEquals("notepad.exe", testModel.getValueAt(1,1));
+ Assert.assertEquals("image/png", testModel.getValueAt(0,0));
+ Assert.assertEquals("test.exe", testModel.getValueAt(0,1));
+ }
+
+ /**
+ * Test of getRuleAt method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testGetRuleAt() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ testModel.addRule(txtExt);
+ testModel.addRule(wavExt);
+
+ ExternalViewerRule rule = testModel.getRuleAt(1);
+ Assert.assertEquals(".txt", rule.getName());
+ Assert.assertEquals("notepad.exe", rule.getExePath());
+ Assert.assertEquals(RuleType.EXT, rule.getRuleType());
+
+ ExternalViewerRule ruleTwo = testModel.getRuleAt(0);
+ Assert.assertEquals("image/png", ruleTwo.getName());
+ Assert.assertEquals("test.exe", ruleTwo.getExePath());
+ Assert.assertEquals(RuleType.MIME, ruleTwo.getRuleType());
+ }
+
+ /**
+ * Test of setRule method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testSetRule() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ testModel.addRule(txtExt);
+ testModel.addRule(wavExt);
+
+ testModel.setRule(0, txtExt);
+ ExternalViewerRule rule = testModel.getRuleAt(1);
+ Assert.assertEquals(".txt", rule.getName());
+ Assert.assertEquals("notepad.exe", rule.getExePath());
+ Assert.assertEquals(RuleType.EXT, rule.getRuleType());
+
+ testModel.setRule(2, pngMime);
+ ExternalViewerRule ruleTwo = testModel.getRuleAt(2);
+ Assert.assertEquals("image/png", ruleTwo.getName());
+ Assert.assertEquals("test.exe", ruleTwo.getExePath());
+ Assert.assertEquals(RuleType.MIME, ruleTwo.getRuleType());
+ }
+
+ /**
+ * Test of removeRule method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testRemoveRule() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ Assert.assertEquals(1, testModel.getRowCount());
+
+ testModel.removeRule(0);
+ Assert.assertEquals(0, testModel.getRowCount());
+ Assert.assertFalse(testModel.containsRule(pngMime));
+ }
+
+ /**
+ * Test of isCellEditable method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testIsCellEditable() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ Assert.assertFalse(testModel.isCellEditable(0, 0));
+ }
+
+ /**
+ * Test of containsRule method, of class ExternalViewerGlobalSettingsTableModel.
+ */
+ @Test
+ public void testContainsRule() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ Assert.assertTrue(testModel.containsRule(pngMime));
+ }
+
+ @Test
+ public void testNotContains() {
+ ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
+ testModel.addRule(pngMime);
+ Assert.assertFalse(testModel.containsRule(new ExternalViewerRule("not", "a rule", RuleType.EXT)));
+ Assert.assertFalse(testModel.containsRule(null));
+ }
+}
\ No newline at end of file
diff --git a/ImageGallery/build.xml b/ImageGallery/build.xml
index 725c11d0e1..92d809a96d 100644
--- a/ImageGallery/build.xml
+++ b/ImageGallery/build.xml
@@ -15,11 +15,4 @@
out-of-date/unneeded stuff in the installer-->
-
-
-
-
-
-
-
diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml
index 16e4146740..183a947954 100644
--- a/ImageGallery/nbproject/project.xml
+++ b/ImageGallery/nbproject/project.xml
@@ -142,8 +142,8 @@
- ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar
- release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar
+ ext/sqlite-jdbc-3.25.2.jar
+ release/modules/ext/sqlite-jdbc-3.25.2.jar
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
index 3c72e9a27a..52295254c8 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
@@ -698,7 +698,8 @@ public final class ImageGalleryController {
//grab files with supported mime-types
+ MIMETYPE_CLAUSE //NON-NLS
//grab files with image or video mime-types even if we don't officially support them
- + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
+ + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS
+ + " ORDER BY parent_path ";
}
/**
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
index 8ab2668531..1c68d6c49d 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
@@ -393,6 +393,7 @@ public class ImageGalleryModule {
* database.
*/
if (controller.isListeningEnabled()) {
+ controller.getGroupManager().resetCurrentPathGroup();
DrawableDB drawableDb = controller.getDatabase();
if (drawableDb.getDataSourceDbBuildStatus(dataSourceObjId) == DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS) {
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
index e95a48c313..6b508c2a4c 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.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");
@@ -36,6 +36,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import static java.util.Objects.isNull;
@@ -77,6 +78,7 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.DbType;
import org.sleuthkit.datamodel.TskDataException;
+import org.sleuthkit.datamodel.VersionNumber;
import org.sqlite.SQLiteJDBCLoader;
/**
@@ -97,6 +99,16 @@ public final class DrawableDB {
private static final String GROUPS_TABLENAME = "image_gallery_groups"; //NON-NLS
private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS
+ private static final String IG_DB_INFO_TABLE = "image_gallery_db_info";
+
+ private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION";
+ private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION";
+ private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION";
+ private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION";
+
+ private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version
+ private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 1, 0); // IG Schema Current version
+
private PreparedStatement insertHashSetStmt;
private List preparedStatements = new ArrayList<>();
@@ -216,7 +228,7 @@ public final class DrawableDB {
dbWriteLock();
try {
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
- if (!initializeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !initializeImageList()) {
+ if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !initializeImageList()) {
close();
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
}
@@ -390,6 +402,34 @@ public final class DrawableDB {
}
}
+ /**
+ * Checks if the specified table exists in Drawable DB
+ *
+ * @param tableName table to check
+ * @return true if the table exists in the database
+ *
+ * @throws SQLException
+ */
+ private boolean doesTableExist(String tableName) throws SQLException {
+ ResultSet tableQueryResults = null;
+ boolean tableExists = false;
+ try (Statement stmt = con.createStatement()) {
+ tableQueryResults = stmt.executeQuery("SELECT name FROM sqlite_master WHERE type='table'"); //NON-NLS
+ while (tableQueryResults.next()) {
+ if (tableQueryResults.getString("name").equalsIgnoreCase(tableName)) {
+ tableExists = true;
+ break;
+ }
+ }
+ }
+ finally {
+ if (tableQueryResults != null) {
+ tableQueryResults.close();
+ }
+ }
+ return tableExists;
+ }
+
private static void deleteDatabaseIfOlderVersion(Path dbPath) throws SQLException, IOException {
if (Files.exists(dbPath)) {
boolean hasDrawableFilesTable = false;
@@ -474,6 +514,8 @@ public final class DrawableDB {
private boolean initializeDBSchema() {
dbWriteLock();
try {
+ boolean existingDB = true;
+
if (isClosed()) {
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
return false;
@@ -490,6 +532,31 @@ public final class DrawableDB {
* Create tables in the drawables database.
*/
try (Statement stmt = con.createStatement()) {
+
+ // Check if the database is a new or existing database
+ existingDB = doesTableExist("datasources");
+ if (false == doesTableExist(IG_DB_INFO_TABLE)) {
+ try {
+ VersionNumber ig_creation_schema_version = existingDB
+ ? IG_STARTING_SCHEMA_VERSION
+ : IG_SCHEMA_VERSION;
+
+ stmt.execute("CREATE TABLE IF NOT EXISTS " + IG_DB_INFO_TABLE + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
+
+ // backfill creation schema ver
+ stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() ));
+ stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() ));
+
+ // set current schema ver: at DB initialization - current version is same as starting version
+ stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() ));
+ stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() ));
+
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS
+ return false;
+ }
+ }
+
try {
String sql = "CREATE TABLE IF NOT EXISTS datasources " //NON-NLS
+ "( id INTEGER PRIMARY KEY, " //NON-NLS
@@ -582,12 +649,51 @@ public final class DrawableDB {
* Create tables in the case database.
*/
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
+
+ try {
+ VersionNumber ig_creation_schema_version = existingDB
+ ? IG_STARTING_SCHEMA_VERSION
+ : IG_SCHEMA_VERSION;
+
+ String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
+ + " name TEXT UNIQUE NOT NULL,"
+ + " value TEXT NOT NULL )";
+ tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema);
+
+ // backfill creation version
+ String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
+ String creationMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
+
+ // set current version - at the onset, current version is same as creation version
+ String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
+ String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
+
+ if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
+ creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
+ creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
+
+ currentMajorVerSQL += " ON CONFLICT DO NOTHING ";
+ currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
+ }
+
+ tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
+ tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
+
+ tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
+ tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
+
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
+ return false;
+ }
+
try {
String tableSchema
= "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS
+ " data_source_obj_id integer DEFAULT 0, "
+ " value VARCHAR(255) not null, " //NON-NLS
+ " attribute VARCHAR(255) not null, " //NON-NLS
+ + " is_analyzed integer DEFAULT 0, "
+ " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS
tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema);
@@ -620,6 +726,250 @@ public final class DrawableDB {
}
}
+ /**
+ * Gets the Schema version from DrawableDB
+ *
+ * @return image gallery schema version in DrawableDB
+ * @throws SQLException
+ * @throws TskCoreException
+ */
+ private VersionNumber getDrawableDbIgSchemaVersion() throws SQLException, TskCoreException {
+
+ Statement statement = con.createStatement();
+ ResultSet resultSet = null;
+
+ try {
+ int majorVersion = -1;
+ String majorVersionStr = null;
+ resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY));
+ if (resultSet.next()) {
+ majorVersionStr = resultSet.getString("value");
+ try {
+ majorVersion = Integer.parseInt(majorVersionStr);
+ } catch (NumberFormatException ex) {
+ throw new TskCoreException("Bad value for schema major version = " + majorVersionStr, ex);
+ }
+ } else {
+ throw new TskCoreException("Failed to read schema major version from ig_db_info table");
+ }
+
+ int minorVersion = -1;
+ String minorVersionStr = null;
+ resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY));
+ if (resultSet.next()) {
+ minorVersionStr = resultSet.getString("value");
+ try {
+ minorVersion = Integer.parseInt(minorVersionStr);
+ } catch (NumberFormatException ex) {
+ throw new TskCoreException("Bad value for schema minor version = " + minorVersionStr, ex);
+ }
+ } else {
+ throw new TskCoreException("Failed to read schema minor version from ig_db_info table");
+ }
+
+ return new VersionNumber(majorVersion, minorVersion, 0 );
+ }
+ finally {
+ if (resultSet != null) {
+ resultSet.close();
+ }
+ if (statement != null) {
+ statement.close();
+ }
+ }
+ }
+
+ /**
+ * Gets the ImageGallery schema version from CaseDB
+ *
+ * @return image gallery schema version in CaseDB
+ * @throws SQLException
+ * @throws TskCoreException
+ */
+ private VersionNumber getCaseDbIgSchemaVersion() throws TskCoreException {
+
+ // Callback to process result of get version query
+ class GetSchemaVersionQueryResultProcessor implements CaseDbAccessQueryCallback {
+
+ private int version = -1;
+
+ int getVersion() {
+ return version;
+ }
+
+ @Override
+ public void process(ResultSet resultSet) {
+ try {
+ if (resultSet.next()) {
+ String versionStr = resultSet.getString("value");
+ try {
+ version = Integer.parseInt(versionStr);
+ } catch (NumberFormatException ex) {
+ logger.log(Level.SEVERE, "Bad value for version = " + versionStr, ex);
+ }
+ } else {
+ logger.log(Level.SEVERE, "Failed to get version");
+ }
+ }
+ catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get version", ex); //NON-NLS
+ }
+ }
+ }
+
+ GetSchemaVersionQueryResultProcessor majorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
+ GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
+
+ String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
+ tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor);
+ tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
+
+ return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
+ }
+
+ /**
+ * Updates the IG schema version in the Drawable DB
+ *
+ * @param version new version number
+ * @param transaction transaction under which the update happens
+ *
+ * @throws SQLException
+ */
+ private void updateDrawableDbIgSchemaVersion(VersionNumber version, DrawableTransaction transaction) throws SQLException, TskCoreException {
+
+ if (transaction == null) {
+ throw new TskCoreException("Schema version update must be done in a transaction");
+ }
+
+ dbWriteLock();
+ try {
+ Statement statement = con.createStatement();
+
+ // update schema version
+ statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY ));
+ statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY ));
+
+ statement.close();
+ }
+ finally {
+ dbWriteUnlock();
+ }
+ }
+
+ /**
+ * Updates the IG schema version in CaseDB
+ *
+ * @param version new version number
+ * @param caseDbTransaction transaction to use to update the CaseDB
+ *
+ * @throws SQLException
+ */
+ private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
+
+ String updateSQLTemplate = " SET value = %s WHERE name = '%s' ";
+ tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction);
+ tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
+ }
+
+
+ /**
+ * Upgrades the DB schema.
+ *
+ * @return true if the upgrade is successful
+ *
+ * @throws SQLException
+ *
+ */
+ private boolean upgradeDBSchema() throws TskCoreException, SQLException {
+
+ // Read current version from the DBs
+ VersionNumber drawableDbIgSchemaVersion = getDrawableDbIgSchemaVersion();
+ VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion();
+
+ // Upgrade Schema in both DrawableDB and CaseDB
+ CaseDbTransaction caseDbTransaction = tskCase.beginTransaction();
+ DrawableTransaction transaction = beginTransaction();
+
+ try {
+ caseDbIgSchemaVersion = upgradeCaseDbIgSchema1dot0TO1dot1(caseDbIgSchemaVersion, caseDbTransaction);
+ drawableDbIgSchemaVersion = upgradeDrawableDbIgSchema1dot0TO1dot1(drawableDbIgSchemaVersion, transaction);
+
+ // update the versions in the tables
+ updateCaseDbIgSchemaVersion(caseDbIgSchemaVersion, caseDbTransaction );
+ updateDrawableDbIgSchemaVersion(drawableDbIgSchemaVersion, transaction);
+
+ caseDbTransaction.commit();
+ caseDbTransaction = null;
+ commitTransaction(transaction, false);
+ transaction = null;
+ }
+ catch (TskCoreException | SQLException ex) {
+ if (null != caseDbTransaction) {
+ try {
+ caseDbTransaction.rollback();
+ } catch (TskCoreException ex2) {
+ logger.log(Level.SEVERE, String.format("Failed to roll back case db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
+ }
+ }
+ if (null != transaction) {
+ try {
+ rollbackTransaction(transaction);
+ } catch (SQLException ex2) {
+ logger.log(Level.SEVERE, String.format("Failed to roll back drawables db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
+ }
+ }
+ throw ex;
+ }
+ return true;
+ }
+
+ /**
+ * Upgrades IG tables in CaseDB from 1.0 to 1.1
+ * Does nothing if the incoming version is not 1.0
+ *
+ * @param currVersion version to upgrade from
+ * @param caseDbTransaction transaction to use for all updates
+ *
+ * @return new version number
+ * @throws TskCoreException
+ */
+ private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException {
+
+ if (currVersion.getMajor() != 1 ||
+ currVersion.getMinor() != 0) {
+ return currVersion;
+ }
+
+ // 1.0 -> 1.1 upgrade
+ // Add a 'isAnalyzed' column to groups table in CaseDB
+ String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
+ if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) {
+ tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
+ }
+ return new VersionNumber(1,1,0);
+ }
+
+ /**
+ * Upgrades IG tables in DrawableDB from 1.0 to 1.1
+ * Does nothing if the incoming version is not 1.0
+ *
+ * @param currVersion version to upgrade from
+ * @param transaction transaction to use for all updates
+ *
+ * @return new version number
+ * @throws TskCoreException
+ */
+ private VersionNumber upgradeDrawableDbIgSchema1dot0TO1dot1(VersionNumber currVersion, DrawableTransaction transaction ) throws TskCoreException {
+
+ if (currVersion.getMajor() != 1 ||
+ currVersion.getMinor() != 0) {
+ return currVersion;
+ }
+
+ // There are no changes in DrawableDB schema in 1.0 -> 1.1
+ return new VersionNumber(1,1,0);
+ }
+
@Override
protected void finalize() throws Throwable {
/*
@@ -829,6 +1179,26 @@ public final class DrawableDB {
}
+ /**
+ * Sets the isAnalysed flag in the groups table for the given group to true.
+ *
+ * @param groupKey group key.
+ *
+ * @throws TskCoreException
+ */
+ public void markGroupAnalyzed(GroupKey> groupKey) throws TskCoreException {
+
+
+ String updateSQL = String.format(" SET is_analyzed = %d "
+ + " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
+ 1,
+ SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
+ SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
+ groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
+
+ tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL);
+ }
+
/**
* Removes a file from the drawables databse.
*
@@ -857,6 +1227,14 @@ public final class DrawableDB {
}
}
+ /**
+ * Updates the image file.
+ *
+ * @param f file to update.
+ *
+ * @throws TskCoreException
+ * @throws SQLException
+ */
public void updateFile(DrawableFile f) throws TskCoreException, SQLException {
DrawableTransaction trans = null;
CaseDbTransaction caseDbTransaction = null;
@@ -885,25 +1263,14 @@ public final class DrawableDB {
}
}
- /**
- * Insert basic file data (no groups) into the DB during pre-population
- * phase
- *
- * @param f
- * @param tr
- * @param caseDbTransaction
- */
- public void insertBasicFileData(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) {
- insertOrUpdateFile(f, tr, caseDbTransaction, false);
- }
-
+
/**
* Update an existing entry (or make a new one) into the DB that includes
* group information. Called when a file has been analyzed or during a bulk
* rebuild
*
- * @param f
- * @param tr
+ * @param f file to update
+ * @param tr
* @param caseDbTransaction
*/
public void updateFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) {
@@ -1285,29 +1652,53 @@ public final class DrawableDB {
}
}
- public Boolean isGroupAnalyzed(GroupKey> gk) throws SQLException, TskCoreException {
- dbWriteLock();
- try {
- if (isClosed()) {
- throw new SQLException("The drawables database is closed");
+ /**
+ * Returns whether or not the given group is analyzed and ready to be viewed.
+ *
+ * @param groupKey group key.
+ * @return true if the group is analyzed.
+ * @throws SQLException
+ * @throws TskCoreException
+ */
+ public Boolean isGroupAnalyzed(GroupKey> groupKey) throws SQLException, TskCoreException {
+
+ // Callback to process result of isAnalyzed query
+ class IsGroupAnalyzedQueryResultProcessor implements CaseDbAccessQueryCallback {
+
+ private boolean isAnalyzed = false;
+
+ boolean getIsAnalyzed() {
+ return isAnalyzed;
}
- try (Statement stmt = con.createStatement()) {
- // In testing, this method appears to be a lot faster than doing one large select statement
- Set fileIDsInGroup = getFileIDsInGroup(gk);
- for (Long fileID : fileIDsInGroup) {
- ResultSet analyzedQuery = stmt.executeQuery("SELECT analyzed FROM drawable_files WHERE obj_id = " + fileID); //NON-NLS
- while (analyzedQuery.next()) {
- if (analyzedQuery.getInt(ANALYZED) == 0) {
- return false;
- }
+
+ @Override
+ public void process(ResultSet resultSet) {
+ try {
+ if (resultSet.next()) {
+ isAnalyzed = resultSet.getInt("is_analyzed") == 1 ? true: false;
}
- return true; // THIS APPEARS TO BE A BUG (see JIRA-1130), THE FOR LOOP EXECUTES AT MOST ONCE
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Failed to get group is_analyzed", ex); //NON-NLS
}
}
- return false;
- } finally {
- dbWriteUnlock();
}
+
+ IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
+ try {
+ String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + GROUPS_TABLENAME
+ + " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
+ SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
+ SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
+ groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
+
+ tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
+ return queryResultProcessor.getIsAnalyzed();
+ } catch ( TskCoreException ex) {
+ String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS
+ logger.log(Level.SEVERE, msg, ex);
+ }
+
+ return false;
}
/**
@@ -1497,8 +1888,9 @@ public final class DrawableDB {
return;
}
- String insertSQL = String.format(" (data_source_obj_id, value, attribute) VALUES (%d, \'%s\', \'%s\')",
- ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()));
+ int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
+ String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)",
+ ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed);
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
insertSQL += " ON CONFLICT DO NOTHING";
}
@@ -1775,8 +2167,13 @@ public final class DrawableDB {
*/
public class DrawableTransaction {
- private final Set updatedFiles = new HashSet<>();
- private final Set removedFiles = new HashSet<>();
+ // The files are processed ORDERED BY parent path
+ // We want to preserve that order here, so that we can detect a
+ // change in path, and thus mark the path group as analyzed
+ // Hence we use a LinkedHashSet here.
+ private final Set updatedFiles = new LinkedHashSet<>();
+ private final Set removedFiles = new LinkedHashSet<>();
+
private boolean completed;
private DrawableTransaction() throws TskCoreException, SQLException {
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
index 82fc6f7469..c0c87c821f 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
@@ -107,6 +107,12 @@ public class GroupManager {
private final ImageGalleryController controller;
+ /**
+ * Keeps track of the current path group
+ * - a change in path indicates the current path group is analyzed
+ */
+ @GuardedBy("this") //NOPMD
+ private GroupKey> currentPathGroup = null;
/**
* list of all analyzed groups
*/
@@ -238,7 +244,7 @@ public class GroupManager {
setGroupBy(DrawableAttribute.PATH);
setSortOrder(SortOrder.ASCENDING);
setDataSource(null);
-
+
unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener);
unSeenGroups.clear();
analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener);
@@ -618,6 +624,8 @@ public class GroupManager {
for (GroupKey> gk : groupsForFile) {
// see if a group has been created yet for the key
DrawableGroup g = getGroupForKey(gk);
+
+ updateCurrentPathGroup(gk);
addFileToGroup(g, gk, fileId);
}
}
@@ -625,7 +633,58 @@ public class GroupManager {
//we fire this event for all files so that the category counts get updated during initial db population
controller.getCategoryManager().fireChange(updatedFileIDs, null);
}
+
+ /**
+ * Checks if the given path is different from the current path group.
+ * If so, updates the current path group as analyzed, and sets current path
+ * group to the given path.
+ *
+ * The idea is that when the path of the files being processed changes,
+ * we have moved from one folder to the next, and the group for the
+ * previous PATH can be considered as analyzed and can be displayed.
+ *
+ * NOTE: this a close approximation for when all files in a folder have been processed,
+ * but there's some room for error - files may go down the ingest pipleline
+ * out of order or the events may not always arrive in the same order
+ *
+ * @param groupKey
+ */
+ synchronized private void updateCurrentPathGroup(GroupKey> groupKey) {
+ try {
+ if (groupKey.getAttribute() == DrawableAttribute.PATH) {
+
+ if (this.currentPathGroup == null) {
+ currentPathGroup = groupKey;
+ }
+ else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
+ // mark the last path group as analyzed
+ getDrawableDB().markGroupAnalyzed(currentPathGroup);
+ popuplateIfAnalyzed(currentPathGroup, null);
+
+ currentPathGroup = groupKey;
+ }
+ }
+ }
+ catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Error setting is_analyzed status for group: %s", groupKey.getValue().toString()), ex); //NON-NLS
+ }
+ }
+ /**
+ * Resets current path group, after marking the current path group as analyzed.
+ */
+ synchronized public void resetCurrentPathGroup() {
+ try {
+ if (currentPathGroup != null) {
+ getDrawableDB().markGroupAnalyzed(currentPathGroup);
+ popuplateIfAnalyzed(currentPathGroup, null);
+ currentPathGroup = null;
+ }
+ }
+ catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, String.format("Error resetting last path group: %s", currentPathGroup.getValue().toString()), ex); //NON-NLS
+ }
+ }
/**
* If the group is analyzed (or other criteria based on grouping) and should
* be shown to the user, then add it to the appropriate data structures so
diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml
index 47be03672b..c66ad369eb 100644
--- a/KeywordSearch/nbproject/project.xml
+++ b/KeywordSearch/nbproject/project.xml
@@ -206,8 +206,8 @@
release/modules/ext/quartz-2.2.0.jar
- ext/sqlite-jdbc-3.19.3.jar
- release/modules/ext/sqlite-jdbc-3.19.3.jar
+ ext/sqlite-jdbc-3.25.2.jar
+ release/modules/ext/sqlite-jdbc-3.25.2.jar
ext/guava-17.0.jar
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
index 69381d8a13..70a94728fb 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
- * Copyright 2012-2018 Basis Technology Corp.
+ * Copyright 2012-2019 Basis Technology Corp.
*
* Copyright 2012 42six Solutions.
*
@@ -79,7 +79,6 @@ class Chrome extends Extract {
this.getHistory();
this.getBookmark();
this.getCookie();
- this.getLogin();
this.getDownload();
}
@@ -518,108 +517,6 @@ class Chrome extends Extract {
BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
}
- /**
- * Queries for login files and adds artifacts
- */
- private void getLogin() {
- FileManager fileManager = currentCase.getServices().getFileManager();
- List signonFiles;
- try {
- signonFiles = fileManager.findFiles(dataSource, "signons.sqlite", "Chrome"); //NON-NLS
- } catch (TskCoreException ex) {
- String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles");
- logger.log(Level.SEVERE, msg, ex);
- this.addErrorMessage(this.getName() + ": " + msg);
- return;
- }
-
- if (signonFiles.isEmpty()) {
- logger.log(Level.INFO, "Didn't find any Chrome signon files."); //NON-NLS
- return;
- }
-
- dataFound = true;
- Collection bbartifacts = new ArrayList<>();
- int j = 0;
- while (j < signonFiles.size()) {
- AbstractFile signonFile = signonFiles.get(j++);
- if (signonFile.getSize() == 0) {
- continue;
- }
- String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + signonFile.getName() + j + ".db"; //NON-NLS
- try {
- ContentUtils.writeToFile(signonFile, new File(temps), context::dataSourceIngestIsCancelled);
- } catch (ReadContentInputStreamException ex) {
- logger.log(Level.WARNING, String.format("Error reading Chrome login artifacts file '%s' (id=%d).",
- signonFile.getName(), signonFile.getId()), ex); //NON-NLS
- this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
- this.getName(), signonFile.getName()));
- continue;
- } catch (IOException ex) {
- logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome login artifacts file '%s' (id=%d).",
- temps, signonFile.getName(), signonFile.getId()), ex); //NON-NLS
- this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
- this.getName(), signonFile.getName()));
- continue;
- }
- File dbFile = new File(temps);
- if (context.dataSourceIngestIsCancelled()) {
- dbFile.delete();
- break;
- }
- List> tempList = this.dbConnect(temps, LOGIN_QUERY);
- logger.log(Level.INFO, "{0}- Now getting login information from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
- for (HashMap result : tempList) {
- Collection bbattributes = new ArrayList<>();
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- ((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : ""))); //NON-NLS
- //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "Recent Activity", ((result.get("origin_url").toString() != null) ? EscapeUtil.decodeURL(result.get("origin_url").toString()) : "")));
- //TODO Revisit usage of deprecated constructor as per TSK-583
- //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), "Recent Activity", "Last Visited", ((Long.valueOf(result.get("last_visit_time").toString())) / 1000000)));
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- (Long.valueOf(result.get("last_visit_time").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- ((result.get("from_visit").toString() != null) ? result.get("from_visit").toString() : ""))); //NON-NLS
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- ((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- (NetworkUtils.extractDomain((result.get("origin_url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- ((result.get("username_value").toString() != null) ? result.get("username_value").toString().replaceAll("'", "''") : ""))); //NON-NLS
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- result.get("signon_realm").toString())); //NON-NLS
-
- BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, signonFile, bbattributes);
- if (bbart != null) {
- bbartifacts.add(bbart);
- }
-
- // Don't add TSK_OS_ACCOUNT artifacts to the ModuleDataEvent
- Collection osAcctAttributes = new ArrayList<>();
- osAcctAttributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- ((result.get("username_value").toString() != null) ? result.get("username_value").toString().replaceAll("'", "''") : ""))); //NON-NLS
- this.addArtifact(ARTIFACT_TYPE.TSK_OS_ACCOUNT, signonFile, osAcctAttributes);
- }
-
- dbFile.delete();
- }
-
- IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
- NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
- BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
- }
-
private boolean isChromePreVersion30(String temps) {
String query = "PRAGMA table_info(downloads)"; //NON-NLS
List> columns = this.dbConnect(temps, query);
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
index 29844b0827..4ba48b8ceb 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
- * Copyright 2011-2018 Basis Technology Corp.
+ * Copyright 2011-2019 Basis Technology Corp.
*
* Copyright 2012 42six Solutions.
* Contact: aebadirad 42six com
@@ -23,7 +23,6 @@
package org.sleuthkit.autopsy.recentactivity;
import java.io.BufferedReader;
-
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ExecUtil;
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
@@ -36,8 +35,6 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
-import java.util.HashSet;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import java.util.Collection;
@@ -460,10 +457,6 @@ class ExtractIE extends Extract {
logger.log(Level.WARNING, "Unable to find the Pasco file at " + file.getPath(), ex); //NON-NLS
return bbartifacts;
}
-
- // Keep a list of reported user accounts to avoid repeats
- Set reportedUserAccounts = new HashSet<>();
-
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
if (!line.startsWith("URL")) { //NON-NLS
@@ -570,18 +563,6 @@ class ExtractIE extends Extract {
// index the artifact for keyword search
this.indexArtifact(bbart);
bbartifacts.add(bbart);
-
- if ((!user.isEmpty()) && (!reportedUserAccounts.contains(user))) {
- BlackboardArtifact osAttr = origFile.newArtifact(ARTIFACT_TYPE.TSK_OS_ACCOUNT);
- osAttr.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
- NbBundle.getMessage(this.getClass(), "ExtractIE.parentModuleName.noSpace"), user));
-
- // index the artifact for keyword search
- this.indexArtifact(osAttr);
- bbartifacts.add(osAttr);
-
- reportedUserAccounts.add(user);
- }
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error writing Internet Explorer web history artifact to the blackboard.", ex); //NON-NLS
}
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
index 6612feb60c..1fcce60cde 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
@@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
- * Copyright 2012-2018 Basis Technology Corp.
+ * Copyright 2012-2019 Basis Technology Corp.
*
* Copyright 2012 42six Solutions.
* Contact: aebadirad 42six com
@@ -688,18 +688,48 @@ class ExtractRegistry extends Extract {
case "ProfileList": //NON-NLS
try {
-
String homeDir = value;
String sid = artnode.getAttribute("sid"); //NON-NLS
String username = artnode.getAttribute("username"); //NON-NLS
- BlackboardArtifact bbart = regFile.newArtifact(ARTIFACT_TYPE.TSK_OS_ACCOUNT);
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
- parentModuleName, username));
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_ID,
- parentModuleName, sid));
- bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
- parentModuleName, homeDir));
-
+ BlackboardArtifact bbart = null;
+ try {
+ //check if any of the existing artifacts match this username
+ ArrayList existingArtifacts = currentCase.getSleuthkitCase().getBlackboardArtifacts(ARTIFACT_TYPE.TSK_OS_ACCOUNT);
+ for (BlackboardArtifact artifact : existingArtifacts) {
+ if (artifact.getDataSource().getId() == regFile.getDataSourceObjectId()) {
+ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_ID));
+ if (attribute != null && attribute.getValueString().equals(sid)) {
+ bbart = artifact;
+ break;
+ }
+ }
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.WARNING, "Error getting existing os account artifact", ex);
+ }
+ if (bbart == null) {
+ //create new artifact
+ bbart = regFile.newArtifact(ARTIFACT_TYPE.TSK_OS_ACCOUNT);
+ bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
+ parentModuleName, username));
+ bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_ID,
+ parentModuleName, sid));
+ bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
+ parentModuleName, homeDir));
+ } else {
+ //add attributes to existing artifact
+ BlackboardAttribute bbattr = bbart.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_USER_NAME));
+
+ if (bbattr == null) {
+ bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
+ parentModuleName, username));
+ }
+ bbattr = bbart.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH));
+ if (bbattr == null) {
+ bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
+ parentModuleName, homeDir));
+ }
+ }
bbart.addAttributes(bbattributes);
// index the artifact for keyword search
this.indexArtifact(bbart);
@@ -759,7 +789,7 @@ class ExtractRegistry extends Extract {
if (!usbBBartifacts.isEmpty()) {
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED, usbBBartifacts));
}
- if (!wifiBBartifacts.isEmpty()){
+ if (!wifiBBartifacts.isEmpty()) {
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_WIFI_NETWORK, wifiBBartifacts));
}
return true;
diff --git a/docs/doxygen-user/quick_start_guide.dox b/docs/doxygen-user/quick_start_guide.dox
index 1b5cf41234..0a279244cf 100644
--- a/docs/doxygen-user/quick_start_guide.dox
+++ b/docs/doxygen-user/quick_start_guide.dox
@@ -1,28 +1,36 @@
/*! \page quick_start_guide Quick Start Guide
-\section s1 Adding a Data Source (image, local disk, logical files)
+\section s1 Cases and Data Sources
-Data sources are added to a case. A case can have a single data source or it can have multiple data sources. Currently, a single report is generated for an entire case, so if you need to report on individual data sources, then you should use one data source per case. If there are many drives/phones/other data sources for one investigation, then your case should have multiple data sources.
+Autopsy organizes data by case. Each case can have one or more data sources, which can be a disk image, a set of logical files, a USB-connected device, etc.
-\subsection s2 Creating a Case
+Cases can either be single-user or multi-user. Multi-user cases allow several examiners to review the data at the same time and collaborate, but require some additional open source servers to be configured.
+
+When you have several data sources and are deciding about creating creating a case, consider:
+- You can have only one case open at a time
+- Reports are generated at a case-level
+- The application can slow down when there are many large data sources in the same case
+
+\subsection s1a Creating a Case
To create a case, use either the "Create New Case" option on the Welcome screen or from the "Case" menu. This will start the New Case Wizard. You will need to supply it with the name of the case and a directory to store the case results into. You can optionally provide case numbers and reviewer names.
-\subsection s3 Adding a Data Source
+\subsection s1b Adding a Data Source
The next step is to add an input data source to the case. The Add Data Source Wizard will start automatically after the case is created or you can manually start it from the "Case" menu or toolbar. You will need to choose the type of input data source to add (image, local disk, or logical files and folders). Next, supply it with the location of the source to add.
- For a disk image, browse to the first file in the set (Autopsy will find the rest of the files). Autopsy currently supports E01 and raw (dd) files.
-- For local disk, select one of the detected disks. Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). However, the individual file content (not meta-data) does get updated with the changes made to the disk. Note, you may need run Autopsy as an Administrator to detect all disks.
+- For local disk, select one of the detected disks. Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). However, the individual file content (not meta-data) does get updated with the changes made to the disk. You can optionally create a copy of all data read from the local disk to a VHD file, which can be useful for triage situations. Note, you may need run Autopsy as an Administrator to detect all disks.
- For logical files (a single file or folder of files), use the "Add" button to add one or more files or folders on your system to the case. Folders will be recursively added to the case.
-There are a couple of options in the wizard that will allow you to make the ingest process faster. These typically deal with deleted files. It will take longer if unallocated space is analyzed and the entire drive is searched for deleted files. In some scenarios, these recovery steps must be performed and in other scenarios these steps are not needed and instead fast results on the allocated files are needed. Use these options to control how long the analysis will take.
-Autopsy will start to analyze these data sources and add them to the case and the internal database. While it is doing that, it will prompt you to configure the Ingest Modules.
+After supplying the needed data, Autopsy will quickly review the data sources and add minimal metadata to the case databases so that it can schedule the files for analysis. While it is doing that, it will prompt you to configure the Ingest Modules.
-\subsection s4 Ingest Modules
+\subsection s1c Ingest Modules
-You will next be prompted to configure the Ingest Modules. Ingest modules will run in the background and perform specific tasks. The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. Ingest modules can be developed by third-parties. The standard ingest modules included with Autopsy are:
+Ingest modules are responsible for analyzing the data source contents and will run in the background. The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. Ingest modules can be developed by third-parties.
+
+The standard ingest modules included with Autopsy are:
- \subpage recent_activity_page extracts user activity as saved by web browsers and the OS. Also runs Regripper on the registry hive.
- \subpage hash_db_page uses hash sets to ignore known files from the NIST NSRL and flag known bad files. Use the "Advanced" button to add and configure the hash sets to use during this process. You will get updates on known bad file hits as the ingest occurs. You can later add hash sets via the Tools -> Options menu in the main UI. You can download an index of the NIST NSRL from http://sourceforge.net/projects/autopsy/files/NSRL/
@@ -36,7 +44,7 @@ You will next be prompted to configure the Ingest Modules. Ingest modules will r
- \subpage android_analyzer_page allows you to parse common items from Android devices. Places artifacts into the BlackBoard.
- \subpage interesting_files_identifier_page searches for files and directories based on user-specified rules in Tools, Options, Interesting Files. It works as a "File Alerting Module". It generates messages in the inbox when specified files are found.
- \subpage photorec_carver_page carves files from unallocated space and sends them through the file processing chain.
-- \subpage cr_ingest_module adds file hashes and other extracted properties to a central repository.
+- \subpage cr_ingest_module adds file hashes and other extracted properties to a central repository for future correlation and to flag previously notable files.
- \subpage encryption_page looks for encrypted files.
- \subpage vm_extractor_page extracts data from virtual machine files
@@ -44,7 +52,9 @@ When you select a module, you will have the option to change its settings. For
While ingest modules are running in the background, you will see a progress bar in the lower right. You can use the GUI to review incoming results and perform other tasks while ingesting at the same time.
-\section s1a Analysis Basics
+\section s2 Analysis Basics
+
+After the ingest modules start to analyze the data source, you'll see the main analysis interface. You can choose to search for specific items, browse to specific folders, or review ingest module results.
\image html screenshot.PNG
@@ -53,7 +63,7 @@ You will start all of your analysis techniques from the tree on the left.
- The Data Sources root node shows all data in the case.
- The individual image nodes show the file system structure of the disk images or local disks in the case.
- The LogicalFileSet nodes show the logical files in the case.
-- The Views node shows the same data from a file type or timeline perspective.
+- The Views node shows the same data from a different perspective, such as organized by file type.
- The Results node shows the output from the ingest modules.
When you select a node from the tree on the left, a list of files will be shown in the upper right. You can use the Thumbnail view in the upper right to view the pictures. When you select a file from the upper right, its contents will be shown in the lower right. You can use the tabs in the lower right to view the text of the file, an image, or the hex data.
@@ -66,23 +76,23 @@ The tree on the left as well as the table on the right have a \ref ui_quick_sear
You can tag (bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report.
-\subsection s2a Ingest Inbox
-As you are going through the results in the tree, the ingest modules are running in the background.
-The results are shown in the tree as soon as the ingest modules find them and report them.
+\section s3 Other Analysis Interfaces
-The Ingest Inbox receives messages from the ingest modules as they find results.
-You can open the inbox to see what has been recently found.
-It keeps track of what messages you have read.
+In addition to the 3-panel UI with the tree on the left, there are other interfaces that are more specialized.
-The intended use of this inbox is that you can focus on some data for a while and then check back on the inbox at a time that is convenient for them.
-You can then see what else was found while you were focused on the previous task.
-You may learn that a known bad file was found or that a file was found with a relevant keyword and then decide to focus on that for a while.
+\subsection s3a Timeline
-When you select a message, you can then jump to the Results tree where more details can be found or jump to the file's location in the filesystem.
+The timeline feature can be opened from the "Tools" menu or the toolbar. This will show you file system and other events organized by time using various display techniques. See the \subpage timeline_page section for more details.
-\subsection s2b Timeline
-There is a basic timeline view that you can access via the "Tools", "Make Timeline" feature. This will take a few minutes to create the timeline for analysis. Its features are still in development.
+
+\subsection s3b Image Gallery
+
+The Image Gallery focuses on showing the pictures and videos from the data source organized by folder. It will show you files as soon as they have been hashed and EXIF data extracted. You can open it from the "Tools" menu. See the \subpage image_gallery_page section for more details.
+
+\subsection s3c Communications
+
+The Communications interface focuses on showing which accounts were communicated with the most and what messages were sent. It allows you to focus on certain relationships or communications within a certain date rage. You can open it from the "Tools" menu. See the \subpage communications_page section for more details.
\section s5 Example Use Cases
@@ -110,16 +120,14 @@ If you want to see all images and video on the disk image, then go to the "
Select either "Images" or "Videos".
You can use the thumbnail option in the upper right to view thumbnails of all images.
-Note: We are working on making this more efficient when there are lots of images. We are also working on the feature to display video thumbnails.
You can select an image or video from the upper right and view the video or image in the lower right. Video will be played with sound.
\section s6 Reporting
-A final report can be generated that will include all analysis results.
-Use the "Generate Report" button to create this.
-It will create an HTML or XLS report in the Reports folder of the case folder.
-If you forgot the location of your case folder, you can determine it using the "Case Properties" option in the "Case" menu.
-There is also an option to export report files to a separate folder outside of the case folder.
+A final report can be generated that will include all analysis results using the "Generate Report" toolbar button. Reports can be generated in HTML, XLS, KML, and other formats.
+
+You can later find your generated reports by going to the tree and opening the Reports node at the bottom.
+
*/
\ No newline at end of file