diff --git a/Core/manifest.mf b/Core/manifest.mf index eabc8f026b..b1a14babea 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 26 +OpenIDE-Module-Implementation-Version: 27 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 19b4a67a42..1620df3d84 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -83,5 +83,5 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.14 +spec.version.base=10.15 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index ad482705b3..7c7284d935 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2016-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.DataSource; /** - * Panel for displaying ingest job history. + * Panel for displaying ingest job history for a data source. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestJobInfoPanel extends javax.swing.JPanel { @@ -79,11 +79,11 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { } /** - * Update the data source which ingest jobs are being displayed for + * Changes the data source for which ingest jobs are being displayed. * - * @param selectedDataSource the data source to display ingest jobs for + * @param selectedDataSource The data source. */ - public void updateIngestHistoryData(DataSource selectedDataSource) { + public void setDataSource(DataSource selectedDataSource) { this.selectedDataSource = selectedDataSource; ingestJobsForSelectedDataSource.clear(); if (selectedDataSource != null) { @@ -109,7 +109,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { try { SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); this.ingestJobs = skCase.getIngestJobs(); - updateIngestHistoryData(selectedDataSource); + setDataSource(selectedDataSource); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 87e6640e13..3fd86c8de8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -69,7 +69,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource(); countsPanel.updateCountsTableData(selectedDataSource); detailsPanel.updateDetailsPanelData(selectedDataSource); - ingestHistoryPanel.updateIngestHistoryData(selectedDataSource); + ingestHistoryPanel.setDataSource(selectedDataSource); this.repaint(); } }); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java index 1f19fe2652..154e692663 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java @@ -43,6 +43,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrows public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; + private static final int NAME_COLUMN_INDEX = 0; + private static final int NAME_COLUMN_WIDTH = 150; private final ExplorerManager explorerManager; private final MultiUserCaseBrowserCustomizer customizer; private final OutlineView outlineView; @@ -103,6 +105,11 @@ public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel impleme } } + /* + * Give the case name column a greater width. + */ + outline.getColumnModel().getColumn(NAME_COLUMN_INDEX).setPreferredWidth(NAME_COLUMN_WIDTH); + /* * Hide the root node and configure the node selection mode. */ diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED index fc30931457..21c7b81c76 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED @@ -26,13 +26,29 @@ DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting D DataContentViewerOtherCases.foundInLabel.text= DataContentViewerOtherCases.title=Other Occurrences DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences. +DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute +DataContentViewerOtherCasesModel.csvHeader.case=Case +DataContentViewerOtherCasesModel.csvHeader.comment=Comment +DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source +DataContentViewerOtherCasesModel.csvHeader.device=Device +DataContentViewerOtherCasesModel.csvHeader.known=Known +DataContentViewerOtherCasesModel.csvHeader.path=Path +DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value +OccurrencePanel.caseCreatedDateLabel.text=Created Date: +OccurrencePanel.caseDetails.text=Case Details +OccurrencePanel.caseNameLabel.text=Name: +OccurrencePanel.commonProperties.text=Common Properties +OccurrencePanel.commonPropertyCommentLabel.text=Comment: +OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status: +OccurrencePanel.commonPropertyTypeLabel.text=Type: +OccurrencePanel.commonPropertyValueLabel.text=Value: +OccurrencePanel.dataSourceDetails.text=Data Source Details +OccurrencePanel.dataSourceNameLabel.text=Name: +OccurrencePanel.fileDetails.text=File Details +OccurrencePanel.filePathLabel.text=File Path: OtherOccurrencesCasesTableModel.case=Case OtherOccurrencesCasesTableModel.noData=No Data. -OtherOccurrencesFilesTableModel.attribute=Matched Attribute -OtherOccurrencesFilesTableModel.comment=Comment -OtherOccurrencesFilesTableModel.dataSource=Data Source -OtherOccurrencesFilesTableModel.device=Device -OtherOccurrencesFilesTableModel.known=Known +OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name +OtherOccurrencesDataSourcesTableModel.noData=No Data. +OtherOccurrencesFilesTableModel.fileName=File Name OtherOccurrencesFilesTableModel.noData=No Data. -OtherOccurrencesFilesTableModel.path=Path -OtherOccurrencesFilesTableModel.value=Attribute Value diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 9b30385d0f..7f1449178e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -46,11 +46,11 @@ - + - + @@ -68,123 +68,114 @@ - + - + + + + - + - + + - - - - - + + + + + + + + + + + - - - - - + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + + @@ -195,6 +186,11 @@ + + + + + @@ -217,6 +213,11 @@ + + + + + @@ -231,11 +232,8 @@ - - - - -
+ +
@@ -243,10 +241,10 @@
- + - + @@ -261,7 +259,7 @@ - + @@ -273,7 +271,7 @@ - + @@ -284,6 +282,21 @@
+ + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index be268cc6b4..5427b2be42 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2019 Basis Technology Corp. + * Copyright 2017-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; @@ -48,9 +50,6 @@ import static javax.swing.JOptionPane.PLAIN_MESSAGE; import static javax.swing.JOptionPane.ERROR_MESSAGE; import javax.swing.JPanel; import javax.swing.filechooser.FileNameExtensionFilter; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import org.joda.time.DateTimeZone; @@ -94,49 +93,57 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts()); private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noResultsFound()); - private static final int DEFAULT_MIN_CELL_WIDTH = 15; - private final OtherOccurrencesFilesTableModel tableModel; + private final OtherOccurrencesFilesTableModel filesTableModel; private final OtherOccurrencesCasesTableModel casesTableModel; + private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel; + private OccurrencePanel occurrencePanel; private final Collection correlationAttributes; - private String dataSourceName = ""; - private String deviceId = ""; + private String dataSourceName = ""; //the data source of the file which the content viewer is being populated for + private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for /** * Could be null. */ - private AbstractFile file; + private AbstractFile file; //the file which the content viewer is being populated for /** * Creates new form DataContentViewerOtherCases */ public DataContentViewerOtherCases() { - this.tableModel = new OtherOccurrencesFilesTableModel(); + this.filesTableModel = new OtherOccurrencesFilesTableModel(); this.casesTableModel = new OtherOccurrencesCasesTableModel(); + this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel(); this.correlationAttributes = new ArrayList<>(); - + occurrencePanel = new OccurrencePanel(); initComponents(); customizeComponents(); + + detailsPanelScrollPane.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent componentEvent) { + //when its resized make sure the width of the panel resizes to match the scroll pane width to avoid a horizontal scroll bar + occurrencePanel.setPreferredSize(new java.awt.Dimension(detailsPanelScrollPane.getPreferredSize().width, occurrencePanel.getPreferredSize().height)); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } + }); reset(); } private void customizeComponents() { - ActionListener actList = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JMenuItem jmi = (JMenuItem) e.getSource(); - if (jmi.equals(selectAllMenuItem)) { - filesTable.selectAll(); - } else if (jmi.equals(showCaseDetailsMenuItem)) { - showCaseDetails(filesTable.getSelectedRow()); - } else if (jmi.equals(exportToCSVMenuItem)) { - try { - saveToCSV(); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS - } - } else if (jmi.equals(showCommonalityMenuItem)) { - showCommonalityDetails(); + ActionListener actList = (ActionEvent e) -> { + JMenuItem jmi = (JMenuItem) e.getSource(); + if (jmi.equals(selectAllMenuItem)) { + filesTable.selectAll(); + } else if (jmi.equals(showCaseDetailsMenuItem)) { + showCaseDetails(filesTable.getSelectedRow()); + } else if (jmi.equals(exportToCSVMenuItem)) { + try { + saveToCSV(); + } catch (NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } + } else if (jmi.equals(showCommonalityMenuItem)) { + showCommonalityDetails(); } }; @@ -145,10 +152,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi showCaseDetailsMenuItem.addActionListener(actList); showCommonalityMenuItem.addActionListener(actList); - // Set background of every nth row as light grey. - TableCellRenderer renderer = new OtherOccurrencesFilesTableCellRenderer(); - filesTable.setDefaultRenderer(Object.class, renderer); - // Configure column sorting. TableRowSorter sorter = new TableRowSorter<>(filesTable.getModel()); filesTable.setRowSorter(sorter); @@ -162,8 +165,18 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi updateOnDataSourceSelection(); } }); + + //alows resizing of the 4th section + filesTable.getSelectionModel().addListSelectionListener((e) -> { + if (Case.isCaseOpen()) { + occurrencePanel = new OccurrencePanel(); + updateOnFileSelection(); + } + }); + //sort tables alphabetically initially casesTable.getRowSorter().toggleSortOrder(0); dataSourcesTable.getRowSorter().toggleSortOrder(0); + filesTable.getRowSorter().toggleSortOrder(0); } @Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", @@ -218,54 +231,43 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi "DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error", "DataContentViewerOtherCases.noOpenCase.errMsg=No open case available."}) private void showCaseDetails(int selectedRowViewIdx) { - String caseDisplayName = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noCaseNameError(); + String details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(); try { if (-1 != selectedRowViewIdx) { EamDb dbManager = EamDb.getInstance(); int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx); - OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) tableModel.getRow(selectedRowModelIdx); - CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); - if (eamCasePartial == null) { - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(), - caseDisplayName, - DEFAULT_OPTION, PLAIN_MESSAGE); - return; + List rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx); + if (!rowList.isEmpty()) { + if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) { + CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase(); + caseDisplayName = eamCasePartial.getDisplayName(); + // query case details + CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID()); + if (eamCase != null) { + details = eamCase.getCaseDetailsOptionsPaneDialog(); + } else { + details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(); + } + } else { + details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected(); + } + } else { + details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(); } - caseDisplayName = eamCasePartial.getDisplayName(); - // query case details - CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID()); - if (eamCase == null) { - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(), - caseDisplayName, - DEFAULT_OPTION, PLAIN_MESSAGE); - return; - } - - // display case details - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - eamCase.getCaseDetailsOptionsPaneDialog(), - caseDisplayName, - DEFAULT_OPTION, PLAIN_MESSAGE); - } else { - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected(), - caseDisplayName, - DEFAULT_OPTION, PLAIN_MESSAGE); } } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error loading case details", ex); + } finally { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(), + details, caseDisplayName, DEFAULT_OPTION, PLAIN_MESSAGE); } } private void saveToCSV() throws NoCurrentCaseException { - if (0 != filesTable.getSelectedRowCount()) { + if (casesTableModel.getRowCount() > 0) { Calendar now = Calendar.getInstance(); String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now); CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory())); @@ -279,45 +281,46 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS } - - writeSelectedRowsToFileAsCSV(selectedFile); + writeOtherOccurrencesToFileAsCSV(selectedFile); } } } - private void writeSelectedRowsToFileAsCSV(File destFile) { - StringBuilder content; - int[] selectedRowViewIndices = filesTable.getSelectedRows(); - int colCount = tableModel.getColumnCount(); - + @Messages({ + "DataContentViewerOtherCasesModel.csvHeader.case=Case", + "DataContentViewerOtherCasesModel.csvHeader.device=Device", + "DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source", + "DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute", + "DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value", + "DataContentViewerOtherCasesModel.csvHeader.known=Known", + "DataContentViewerOtherCasesModel.csvHeader.path=Path", + "DataContentViewerOtherCasesModel.csvHeader.comment=Comment" + }) + /** + * Write data for all cases in the content viewer to a CSV file + */ + private void writeOtherOccurrencesToFileAsCSV(File destFile) { try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) { - - // write column names - content = new StringBuilder(""); - for (int colIdx = 0; colIdx < colCount; colIdx++) { - content.append('"').append(tableModel.getColumnName(colIdx)).append('"'); - if (colIdx < (colCount - 1)) { - content.append(","); + //write headers + StringBuilder headers = new StringBuilder("\""); + headers.append(Bundle.DataContentViewerOtherCasesModel_csvHeader_case()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_dataSource()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_attribute()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_value()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_known()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_path()) + .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_comment()) + .append('"').append(System.getProperty("line.separator")); + writer.write(headers.toString()); + //write content + for (CorrelationAttributeInstance corAttr : correlationAttributes) { + Map correlatedNodeDataMap = new HashMap<>(0); + // get correlation and reference set instances from DB + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { + writer.write(nodeData.toCsvString()); } } - - content.append(System.getProperty("line.separator")); - writer.write(content.toString()); - - // write rows - for (int rowViewIdx : selectedRowViewIndices) { - content = new StringBuilder(""); - for (int colIdx = 0; colIdx < colCount; colIdx++) { - int rowModelIdx = filesTable.convertRowIndexToModel(rowViewIdx); - content.append('"').append(tableModel.getValueAt(rowModelIdx, colIdx)).append('"'); - if (colIdx < (colCount - 1)) { - content.append(","); - } - } - content.append(System.getProperty("line.separator")); - writer.write(content.toString()); - } - } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); } @@ -329,11 +332,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private void reset() { // start with empty table casesTableModel.clearTable(); - ((DefaultTableModel) dataSourcesTable.getModel()).setRowCount(0); - tableModel.clearTable(); + dataSourcesTableModel.clearTable(); + filesTableModel.clearTable(); correlationAttributes.clear(); earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable()); foundInLabel.setText(""); + //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible + occurrencePanel = new OccurrencePanel(); + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); } @Override @@ -364,6 +371,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Override public int isPreferred(Node node) { return 1; + } /** @@ -558,7 +566,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { final Case openCase = Case.getCurrentCaseThrows(); String caseUUID = openCase.getName(); - HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { @@ -583,7 +590,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { List caseDbFiles = getCaseDbMatches(corAttr, openCase); @@ -591,7 +597,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); } } - return nodeDataMap; } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS @@ -748,14 +753,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); } catch (EamDbException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName()); + LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); } } else { try { dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName())); caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName())); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.WARNING, "No current case open for other occurrences"); + LOGGER.log(Level.WARNING, "No current case open for other occurrences", ex); } } totalCount++; @@ -770,7 +775,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else if (caseCount == 0) { casesTableModel.addCorrelationCase(NO_RESULTS_CASE); } - setColumnWidths(); setEarliestCaseDate(); foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), totalCount, caseCount, dataSources.size())); if (caseCount > 0) { @@ -791,33 +795,40 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi */ private void updateOnCaseSelection() { int[] selectedCaseIndexes = casesTable.getSelectedRows(); - DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel(); - dataSourceModel.setRowCount(0); - tableModel.clearTable(); - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); + dataSourcesTableModel.clearTable(); + filesTableModel.clearTable(); - // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - for (int selectedRow : selectedCaseIndexes) { - try { - if (nodeData.isCentralRepoNode()) { - if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null - && ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { - dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()}); + if (selectedCaseIndexes.length == 0) { + //special case when no cases are selected + occurrencePanel = new OccurrencePanel(); + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } else { + for (CorrelationAttributeInstance corAttr : correlationAttributes) { + Map correlatedNodeDataMap = new HashMap<>(0); + + // get correlation and reference set instances from DB + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { + for (int selectedRow : selectedCaseIndexes) { + try { + if (nodeData.isCentralRepoNode()) { + if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null + && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { + dataSourcesTableModel.addNodeData(nodeData); + } + } else { + dataSourcesTableModel.addNodeData(nodeData); } - } else { - dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()}); + } catch (EamDbException ex) { + LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); } - } catch (EamDbException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName()); } } } - } - if (dataSourcesTable.getRowCount() > 0) { - dataSourcesTable.setRowSelectionInterval(0, 0); + if (dataSourcesTable.getRowCount() > 0) { + dataSourcesTable.setRowSelectionInterval(0, 0); + } } } @@ -827,9 +838,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi */ private void updateOnDataSourceSelection() { int[] selectedCaseIndexes = casesTable.getSelectedRows(); - DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel(); int[] selectedDataSources = dataSourcesTable.getSelectedRows(); - tableModel.clearTable(); + filesTableModel.clearTable(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { Map correlatedNodeDataMap = new HashMap<>(0); @@ -841,43 +851,94 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { if (nodeData.isCentralRepoNode()) { if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)) != null - && ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) - && dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) { - tableModel.addNodeData(nodeData); + && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) + && dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); } } else { - if (dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) { - tableModel.addNodeData(nodeData); + if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); } } } catch (EamDbException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName()); + LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); } } } } } + if (filesTable.getRowCount() > 0) { + filesTable.setRowSelectionInterval(0, 0); + } } /** - * Adjust column widths to their preferred values. + * Update the data displayed in the details section to be correct for the + * currently selected File */ - private void setColumnWidths() { - for (int idx = 0; idx < tableModel.getColumnCount(); idx++) { - TableColumn column = filesTable.getColumnModel().getColumn(idx); - column.setMinWidth(DEFAULT_MIN_CELL_WIDTH); - int columnWidth = tableModel.getColumnPreferredWidth(idx); - if (columnWidth > 0) { - column.setPreferredWidth(columnWidth); + private void updateOnFileSelection() { + if (filesTable.getSelectedRowCount() == 1) { + //if there is one file selected update the deatils to show the data for that file + occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow()))); + } else if (dataSourcesTable.getSelectedRowCount() == 1) { + //if no files were selected and only one data source is selected update the information to reflect the data source + String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow())); + String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString(); + String caseCreatedDate = ""; + for (int row : casesTable.getSelectedRows()) { + if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) { + caseCreatedDate = getCaseCreatedDate(row); + break; + } } - } - for (int idx = 0; idx < dataSourcesTable.getColumnCount(); idx++) { - if (dataSourcesTable.getColumnModel().getColumn(idx).getHeaderValue().toString().equals(Bundle.DataContentViewerOtherCases_dataSources_header_text())) { - dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(100); + occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName); + } else if (casesTable.getSelectedRowCount() == 1) { + //if no files were selected and a number of data source other than 1 are selected + //update the information to reflect the case + String createdDate = ""; + String caseName = ""; + if (casesTable.getRowCount() > 0) { + caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString(); + } + if (caseName.isEmpty()) { + occurrencePanel = new OccurrencePanel(); } else { - dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(210); + createdDate = getCaseCreatedDate(casesTable.getSelectedRow()); + occurrencePanel = new OccurrencePanel(caseName, createdDate); } + } else { + //else display an empty details area + occurrencePanel = new OccurrencePanel(); } + //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } + + /** + * Get the date a case was created + * + * @param caseTableRowIdx the row from the casesTable representing the case + * + * @return A string representing the date the case was created or an empty + * string if the date could not be determined + */ + private String getCaseCreatedDate(int caseTableRowIdx) { + try { + if (EamDb.isEnabled()) { + CorrelationCase partialCase; + partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx)); + if (partialCase == null){ + return ""; + } + return EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); + } else { + return Case.getCurrentCase().getCreatedDate(); + } + } catch (EamDbException ex) { + LOGGER.log(Level.WARNING, "Error getting case created date for row: " + caseTableRowIdx, ex); + } + return ""; } /** @@ -895,19 +956,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi showCaseDetailsMenuItem = new javax.swing.JMenuItem(); showCommonalityMenuItem = new javax.swing.JMenuItem(); CSVFileChooser = new javax.swing.JFileChooser(); - otherCasesPanel = new javax.swing.JPanel(); tableContainerPanel = new javax.swing.JPanel(); earliestCaseLabel = new javax.swing.JLabel(); earliestCaseDate = new javax.swing.JLabel(); foundInLabel = new javax.swing.JLabel(); - jSplitPane2 = new javax.swing.JSplitPane(); - jSplitPane3 = new javax.swing.JSplitPane(); + tablesViewerSplitPane = new javax.swing.JSplitPane(); + caseDatasourceFileSplitPane = new javax.swing.JSplitPane(); + caseDatasourceSplitPane = new javax.swing.JSplitPane(); caseScrollPane = new javax.swing.JScrollPane(); casesTable = new javax.swing.JTable(); dataSourceScrollPane = new javax.swing.JScrollPane(); dataSourcesTable = new javax.swing.JTable(); - propertiesTableScrollPane = new javax.swing.JScrollPane(); + filesTableScrollPane = new javax.swing.JScrollPane(); filesTable = new javax.swing.JTable(); + detailsPanelScrollPane = new javax.swing.JScrollPane(); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { @@ -931,13 +993,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N rightClickPopupMenu.add(showCommonalityMenuItem); - setMinimumSize(new java.awt.Dimension(1500, 10)); + setMinimumSize(new java.awt.Dimension(600, 10)); setOpaque(false); - setPreferredSize(new java.awt.Dimension(1500, 44)); + setPreferredSize(new java.awt.Dimension(600, 63)); - otherCasesPanel.setPreferredSize(new java.awt.Dimension(921, 62)); - - tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63)); + tableContainerPanel.setPreferredSize(new java.awt.Dimension(600, 63)); + tableContainerPanel.setRequestFocusEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N @@ -946,49 +1007,50 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.foundInLabel.text")); // NOI18N - jSplitPane2.setDividerLocation(470); + tablesViewerSplitPane.setDividerLocation(450); + tablesViewerSplitPane.setResizeWeight(0.5); - jSplitPane3.setDividerLocation(150); + caseDatasourceFileSplitPane.setDividerLocation(300); + caseDatasourceFileSplitPane.setResizeWeight(0.6); + caseDatasourceFileSplitPane.setToolTipText(""); + + caseDatasourceSplitPane.setDividerLocation(150); + caseDatasourceSplitPane.setResizeWeight(0.5); + + caseScrollPane.setPreferredSize(new java.awt.Dimension(140, 30)); casesTable.setAutoCreateRowSorter(true); casesTable.setModel(casesTableModel); caseScrollPane.setViewportView(casesTable); - jSplitPane3.setLeftComponent(caseScrollPane); + caseDatasourceSplitPane.setLeftComponent(caseScrollPane); + + dataSourceScrollPane.setPreferredSize(new java.awt.Dimension(140, 30)); dataSourcesTable.setAutoCreateRowSorter(true); - dataSourcesTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - "Data Source Name", "Device ID" - } - ) { - boolean[] canEdit = new boolean [] { - false, false - }; - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return canEdit [columnIndex]; - } - }); + dataSourcesTable.setModel(dataSourcesTableModel); dataSourceScrollPane.setViewportView(dataSourcesTable); - jSplitPane3.setRightComponent(dataSourceScrollPane); + caseDatasourceSplitPane.setRightComponent(dataSourceScrollPane); - jSplitPane2.setLeftComponent(jSplitPane3); + caseDatasourceFileSplitPane.setLeftComponent(caseDatasourceSplitPane); - propertiesTableScrollPane.setPreferredSize(new java.awt.Dimension(1000, 30)); + filesTableScrollPane.setPreferredSize(new java.awt.Dimension(140, 30)); filesTable.setAutoCreateRowSorter(true); - filesTable.setModel(tableModel); + filesTable.setModel(filesTableModel); filesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N filesTable.setComponentPopupMenu(rightClickPopupMenu); - filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); - propertiesTableScrollPane.setViewportView(filesTable); + filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + filesTableScrollPane.setViewportView(filesTable); - jSplitPane2.setRightComponent(propertiesTableScrollPane); + caseDatasourceFileSplitPane.setRightComponent(filesTableScrollPane); + + tablesViewerSplitPane.setLeftComponent(caseDatasourceFileSplitPane); + + detailsPanelScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + detailsPanelScrollPane.setPreferredSize(new java.awt.Dimension(200, 100)); + tablesViewerSplitPane.setRightComponent(detailsPanelScrollPane); javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); @@ -997,65 +1059,49 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addGroup(tableContainerPanelLayout.createSequentialGroup() .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(earliestCaseLabel) + .addComponent(earliestCaseLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 161, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(earliestCaseDate) - .addGap(66, 66, 66) - .addComponent(foundInLabel)) - .addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 911, Short.MAX_VALUE)) + .addComponent(earliestCaseDate, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(foundInLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 590, Short.MAX_VALUE)) .addContainerGap()) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() - .addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 31, Short.MAX_VALUE) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 33, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(earliestCaseLabel) - .addComponent(earliestCaseDate) - .addComponent(foundInLabel)) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate)) + .addComponent(foundInLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); - javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); - otherCasesPanel.setLayout(otherCasesPanelLayout); - otherCasesPanelLayout.setHorizontalGroup( - otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 921, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(0, 0, 0))) - ); - otherCasesPanelLayout.setVerticalGroup( - otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 62, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE) - .addGap(0, 0, 0))) - ); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 64, Short.MAX_VALUE) + .addGap(0, 0, 0)) ); }// //GEN-END:initComponents private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible boolean enableCentralRepoActions = false; - if (EamDb.isEnabled() && filesTable.getSelectedRowCount() == 1) { int rowIndex = filesTable.getSelectedRow(); - OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); - if (selectedNode instanceof OtherOccurrenceNodeInstanceData) { - OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedNode; + List selectedFile = filesTableModel.getListOfNodesForFile(rowIndex); + if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) { + OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0); enableCentralRepoActions = instanceData.isCentralRepoNode(); } } @@ -1065,24 +1111,25 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JFileChooser CSVFileChooser; + private javax.swing.JSplitPane caseDatasourceFileSplitPane; + private javax.swing.JSplitPane caseDatasourceSplitPane; private javax.swing.JScrollPane caseScrollPane; private javax.swing.JTable casesTable; private javax.swing.JScrollPane dataSourceScrollPane; private javax.swing.JTable dataSourcesTable; + private javax.swing.JScrollPane detailsPanelScrollPane; private javax.swing.JLabel earliestCaseDate; private javax.swing.JLabel earliestCaseLabel; private javax.swing.JMenuItem exportToCSVMenuItem; private javax.swing.JTable filesTable; + private javax.swing.JScrollPane filesTableScrollPane; private javax.swing.JLabel foundInLabel; - private javax.swing.JSplitPane jSplitPane2; - private javax.swing.JSplitPane jSplitPane3; - private javax.swing.JPanel otherCasesPanel; - private javax.swing.JScrollPane propertiesTableScrollPane; private javax.swing.JPopupMenu rightClickPopupMenu; private javax.swing.JMenuItem selectAllMenuItem; private javax.swing.JMenuItem showCaseDetailsMenuItem; private javax.swing.JMenuItem showCommonalityMenuItem; private javax.swing.JPanel tableContainerPanel; + private javax.swing.JSplitPane tablesViewerSplitPane; // End of variables declaration//GEN-END:variables /** @@ -1119,9 +1166,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Override public int hashCode() { - //int hash = 7; - //hash = 67 * hash + this.dataSourceID.hashCode(); - //hash = 67 * hash + this.filePath.hashCode(); return Objects.hash(getDataSourceID(), getFilePath(), getType()); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.form new file mode 100644 index 0000000000..f889b3180a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.form @@ -0,0 +1,23 @@ + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java new file mode 100644 index 0000000000..d8b18d5f82 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java @@ -0,0 +1,370 @@ +/* + * Central Repository + * + * 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.centralrepository.contentviewer; + +import java.awt.Color; +import java.awt.Font; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TskData; + +/** + * Panel for displaying other occurrence details. + */ +final class OccurrencePanel extends javax.swing.JPanel { + + private static final Logger LOGGER = Logger.getLogger(OccurrencePanel.class.getName()); + private static final int LEFT_INSET = 10; + private static final int RIGHT_INSET = 10; + private static final int TOP_INSET = 10; + private static final int BOTTOM_INSET = 10; + private static final int VERTICAL_GAP = 6; + private static final int HORIZONTAL_GAP = 4; + private static final long serialVersionUID = 1L; + + private int gridY = 0; + private final List nodeDataList; + private final Map caseNamesAndDates = new HashMap<>(); + private final Set dataSourceNames = new HashSet<>(); + private final Set filePaths = new HashSet<>(); + + /** + * Construct an empty OccurrencePanel + */ + OccurrencePanel() { + nodeDataList = new ArrayList<>(); + customizeComponents(); + } + + /** + * Construct an OccurrencePanel which will display only Case information + * + * @param caseName the name of the case + * @param caseCreatedDate the date the case was created + */ + OccurrencePanel(String caseName, String caseCreatedDate) { + nodeDataList = new ArrayList<>(); + caseNamesAndDates.put(caseName, caseCreatedDate); + customizeComponents(); + } + + /** + * Construct an OccurrencePanel which will display only case and data source + * information + * + * @param caseName the name of the case + * @param caseCreatedDate the date the case was created + * @param dataSourceName the name of the data source + */ + OccurrencePanel(String caseName, String caseCreatedDate, String dataSourceName) { + nodeDataList = new ArrayList<>(); + caseNamesAndDates.put(caseName, caseCreatedDate); + dataSourceNames.add(dataSourceName); + customizeComponents(); + } + + /** + * Construct a OccurrencePanel which will display details for all other + * occurrences associated with a file + * + * @param nodeDataList the list of OtherOccurrenceNodeData representing + * common properties for the file + */ + OccurrencePanel(List nodeDataList) { + this.nodeDataList = nodeDataList; + customizeComponents(); + } + + /** + * Do all the construction of gui elements and adding of the appropriate + * elements to the gridbaglayout + */ + private void customizeComponents() { + initComponents(); + if (!this.nodeDataList.isEmpty()) { + //if addInstanceDetails is going to be called it should be called + // before addFileDetails, addDataSourceDetails, and addCaseDetails + //because it also collects the information they display + addInstanceDetails(); + if (!filePaths.isEmpty()) { + addFileDetails(); + } + } + if (!dataSourceNames.isEmpty()) { + addDataSourceDetails(); + } + if (!caseNamesAndDates.keySet().isEmpty()) { + addCaseDetails(); + } + //add filler to keep everything else at the top + addItemToBag(gridY, 0, 0, 0, new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767))); + } + + @Messages({ + "OccurrencePanel.commonProperties.text=Common Properties", + "OccurrencePanel.commonPropertyTypeLabel.text=Type:", + "OccurrencePanel.commonPropertyValueLabel.text=Value:", + "OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status:", + "OccurrencePanel.commonPropertyCommentLabel.text=Comment:" + }) + /** + * Add the Common Property instance details to the gridbaglayout supports + * adding multiple common properties + * + * Also collects the case, data source, and file path information to be + * displayed + */ + private void addInstanceDetails() { + javax.swing.JLabel commonPropertiesLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(commonPropertiesLabel, Bundle.OccurrencePanel_commonProperties_text()); + commonPropertiesLabel.setFont(commonPropertiesLabel.getFont().deriveFont(Font.BOLD, commonPropertiesLabel.getFont().getSize())); + addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel); + gridY++; + //for each other occurrence + for (OtherOccurrenceNodeData occurrence : nodeDataList) { + if (occurrence instanceof OtherOccurrenceNodeInstanceData) { + String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType(); + if (!type.isEmpty()) { + javax.swing.JLabel typeLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text()); + addItemToBag(gridY, 0, VERTICAL_GAP, 0, typeLabel); + javax.swing.JLabel typeFieldValue = new javax.swing.JLabel(); + typeFieldValue.setText(type); + addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue); + gridY++; + } + String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue(); + if (!value.isEmpty()) { + javax.swing.JLabel valueLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text()); + addItemToBag(gridY, 0, 0, 0, valueLabel); + javax.swing.JLabel valueFieldValue = new javax.swing.JLabel(); + valueFieldValue.setText(value); + addItemToBag(gridY, 1, 0, 0, valueFieldValue); + gridY++; + } + TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown(); + javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text()); + addItemToBag(gridY, 0, 0, 0, knownStatusLabel); + javax.swing.JLabel knownStatusValue = new javax.swing.JLabel(); + knownStatusValue.setText(knownStatus.toString()); + if (knownStatus == TskData.FileKnown.BAD) { + knownStatusValue.setForeground(Color.RED); + } + addItemToBag(gridY, 1, 0, 0, knownStatusValue); + gridY++; + String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment(); + if (!comment.isEmpty()) { + javax.swing.JLabel commentLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text()); + addItemToBag(gridY, 0, 0, VERTICAL_GAP, commentLabel); + javax.swing.JTextArea commentValue = new javax.swing.JTextArea(); + commentValue.setText(comment); + commentValue.setEditable(false); + commentValue.setColumns(20); + commentValue.setLineWrap(true); + commentValue.setRows(3); + commentValue.setTabSize(4); + commentValue.setWrapStyleWord(true); + commentValue.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + commentValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground")); + addItemToBag(gridY, 1, 0, VERTICAL_GAP, commentValue); + gridY++; + } + String caseDate = ""; + try { + OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence); + if (nodeData.isCentralRepoNode()) { + if (EamDb.isEnabled()) { + CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); + caseDate = EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); + } + } else { + caseDate = Case.getCurrentCase().getCreatedDate(); + } + } catch (EamDbException ex) { + LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex); + } + //Collect the data that is necessary for the other sections + caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate); + dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName()); + filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath()); + } + } + //end for each + } + + @Messages({ + "OccurrencePanel.fileDetails.text=File Details", + "OccurrencePanel.filePathLabel.text=File Path:" + }) + /** + * Add the File specific details such as file path to the gridbaglayout + */ + private void addFileDetails() { + String filePath = filePaths.size() > 1 ? "" : filePaths.iterator().next(); + if (!filePath.isEmpty()) { + javax.swing.JLabel fileDetailsLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(fileDetailsLabel, Bundle.OccurrencePanel_fileDetails_text()); + fileDetailsLabel.setFont(fileDetailsLabel.getFont().deriveFont(Font.BOLD, fileDetailsLabel.getFont().getSize())); + addItemToBag(gridY, 0, TOP_INSET, 0, fileDetailsLabel); + gridY++; + javax.swing.JLabel filePathLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(filePathLabel, Bundle.OccurrencePanel_filePathLabel_text()); + addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, filePathLabel); + javax.swing.JTextArea filePathValue = new javax.swing.JTextArea(); + filePathValue.setText(filePath); + filePathValue.setEditable(false); + filePathValue.setColumns(20); + filePathValue.setLineWrap(true); + filePathValue.setRows(3); + filePathValue.setTabSize(4); + filePathValue.setWrapStyleWord(true); + filePathValue.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + filePathValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground")); + addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, filePathValue); + gridY++; + } + } + + @Messages({ + "OccurrencePanel.dataSourceDetails.text=Data Source Details", + "OccurrencePanel.dataSourceNameLabel.text=Name:" + }) + /** + * Add the data source specific details such as data source name to the + * gridbaglayout + */ + private void addDataSourceDetails() { + String dataSourceName = dataSourceNames.size() > 1 ? "" : dataSourceNames.iterator().next(); + if (!dataSourceName.isEmpty()) { + javax.swing.JLabel dataSourceDetailsLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(dataSourceDetailsLabel, Bundle.OccurrencePanel_dataSourceDetails_text()); + dataSourceDetailsLabel.setFont(dataSourceDetailsLabel.getFont().deriveFont(Font.BOLD, dataSourceDetailsLabel.getFont().getSize())); + addItemToBag(gridY, 0, TOP_INSET, 0, dataSourceDetailsLabel); + gridY++; + javax.swing.JLabel dataSourceNameLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(dataSourceNameLabel, Bundle.OccurrencePanel_dataSourceNameLabel_text()); + addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameLabel); + javax.swing.JLabel dataSourceNameValue = new javax.swing.JLabel(); + dataSourceNameValue.setText(dataSourceName); + addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameValue); + gridY++; + } + } + + @Messages({ + "OccurrencePanel.caseDetails.text=Case Details", + "OccurrencePanel.caseNameLabel.text=Name:", + "OccurrencePanel.caseCreatedDateLabel.text=Created Date:" + }) + /** + * Add the case specific details such as case name to the gridbaglayout + */ + private void addCaseDetails() { + javax.swing.JLabel caseDetailsLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(caseDetailsLabel, Bundle.OccurrencePanel_caseDetails_text()); + caseDetailsLabel.setFont(caseDetailsLabel.getFont().deriveFont(Font.BOLD, caseDetailsLabel.getFont().getSize())); + addItemToBag(gridY, 0, TOP_INSET, 0, caseDetailsLabel); + gridY++; + String caseName = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.keySet().iterator().next(); + if (!caseName.isEmpty()) { + javax.swing.JLabel caseNameLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(caseNameLabel, Bundle.OccurrencePanel_caseNameLabel_text()); + addItemToBag(gridY, 0, VERTICAL_GAP, 0, caseNameLabel); + javax.swing.JLabel caseNameValue = new javax.swing.JLabel(); + caseNameValue.setText(caseName); + addItemToBag(gridY, 1, VERTICAL_GAP, 0, caseNameValue); + gridY++; + } + String caseCreatedDate = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.get(caseName); + if (caseCreatedDate != null && !caseCreatedDate.isEmpty()) { + javax.swing.JLabel caseCreatedLabel = new javax.swing.JLabel(); + org.openide.awt.Mnemonics.setLocalizedText(caseCreatedLabel, Bundle.OccurrencePanel_caseCreatedDateLabel_text()); + addItemToBag(gridY, 0, 0, BOTTOM_INSET, caseCreatedLabel); + javax.swing.JLabel caseCreatedValue = new javax.swing.JLabel(); + caseCreatedValue.setText(caseCreatedDate); + addItemToBag(gridY, 1, 0, BOTTOM_INSET, caseCreatedValue); + gridY++; + } + } + + /** + * Add a JComponent to the gridbaglayout + * + * @param gridYLocation the row number the item should be added at + * @param gridXLocation the column number the item should be added at + * @param topInset the gap from the top of the cell which should exist + * @param bottomInset the gap from the bottom of the cell which should + * exist + * @param item the JComponent to add to the gridbaglayout + */ + private void addItemToBag(int gridYLocation, int gridXLocation, int topInset, int bottomInset, javax.swing.JComponent item) { + java.awt.GridBagConstraints gridBagConstraints; + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = gridXLocation; + gridBagConstraints.gridy = gridYLocation; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START; + int leftInset = LEFT_INSET; + int rightInset = HORIZONTAL_GAP; + //change formating a bit if it is the value instead of the label + if (gridXLocation == 1) { + leftInset = 0; + rightInset = RIGHT_INSET; + gridBagConstraints.weightx = 0.1; + gridBagConstraints.gridwidth = 2; + } + gridBagConstraints.insets = new java.awt.Insets(topInset, leftInset, bottomInset, rightInset); + //if the item is a filler item ensure it will resize vertically + if (item instanceof javax.swing.Box.Filler) { + gridBagConstraints.weighty = 0.1; + } + add(item, gridBagConstraints); + } + + /** + * 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() { + + setMinimumSize(new java.awt.Dimension(50, 30)); + setPreferredSize(null); + setLayout(new java.awt.GridBagLayout()); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java index 79dcf21f64..7703e19e62 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,11 +31,12 @@ import org.sleuthkit.datamodel.TskDataException; * Class for populating the Other Occurrences tab */ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { - + // For now hard code the string for the central repo files type, since // getting it dynamically can fail. private static final String FILE_TYPE_STR = "Files"; - + private static final String CSV_ITEM_SEPARATOR = "\",\""; + private final String caseName; private String deviceID; private String dataSourceName; @@ -44,12 +45,13 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { private final String value; private TskData.FileKnown known; private String comment; - + private AbstractFile originalAbstractFile = null; private CorrelationAttributeInstance originalCorrelationInstance = null; - + /** * Create a node from a central repo instance. + * * @param instance The central repo instance * @param type The type of the instance * @param value The value of the instance @@ -63,15 +65,17 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { this.value = value; known = instance.getKnownStatus(); comment = instance.getComment(); - + originalCorrelationInstance = instance; } - + /** * Create a node from an abstract file. + * * @param newFile The abstract file * @param autopsyCase The current case - * @throws EamDbException + * + * @throws EamDbException */ OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws EamDbException { caseName = autopsyCase.getDisplayName(); @@ -82,115 +86,129 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { } catch (TskDataException | TskCoreException ex) { throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); } - + filePath = newFile.getParentPath() + newFile.getName(); typeStr = FILE_TYPE_STR; value = newFile.getMd5Hash(); known = newFile.getKnown(); comment = ""; - + originalAbstractFile = newFile; } - + /** * Check if this node is a "file" type + * * @return true if it is a file type */ boolean isFileType() { return FILE_TYPE_STR.equals(typeStr); } - + /** * Update the known status for this node + * * @param newKnownStatus The new known status */ void updateKnown(TskData.FileKnown newKnownStatus) { known = newKnownStatus; } - + /** * Update the comment for this node + * * @param newComment The new comment */ void updateComment(String newComment) { comment = newComment; } - + /** * Check if this is a central repo node. - * @return true if this node was created from a central repo instance, false otherwise + * + * @return true if this node was created from a central repo instance, false + * otherwise */ boolean isCentralRepoNode() { return (originalCorrelationInstance != null); - } - + } + /** * Get the case name + * * @return the case name */ String getCaseName() { return caseName; } - + /** * Get the device ID + * * @return the device ID */ String getDeviceID() { return deviceID; } - + /** * Get the data source name + * * @return the data source name */ String getDataSourceName() { return dataSourceName; } - + /** * Get the file path + * * @return the file path */ String getFilePath() { return filePath; } - + /** * Get the type (as a string) + * * @return the type */ String getType() { return typeStr; } - + /** * Get the value (MD5 hash for files) + * * @return the value */ String getValue() { return value; } - + /** * Get the known status + * * @return the known status */ TskData.FileKnown getKnown() { return known; } - + /** * Get the comment + * * @return the comment */ String getComment() { return comment; } - + /** - * Get the backing abstract file. - * Should only be called if isCentralRepoNode() is false + * Get the backing abstract file. Should only be called if + * isCentralRepoNode() is false + * * @return the original abstract file */ AbstractFile getAbstractFile() throws EamDbException { @@ -199,12 +217,14 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { } return originalAbstractFile; } - + /** - * Get the backing CorrelationAttributeInstance. - * Should only be called if isCentralRepoNode() is true + * Get the backing CorrelationAttributeInstance. Should only be called if + * isCentralRepoNode() is true + * * @return the original CorrelationAttributeInstance - * @throws EamDbException + * + * @throws EamDbException */ CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { if (originalCorrelationInstance == null) { @@ -212,4 +232,33 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { } return originalCorrelationInstance; } + + /** + * Get the string to append between elements when writing the node instance + * data to a CSV + * + * @return the CSV_ITEM_SEPARATOR string + */ + static String getCsvItemSeparator() { + return CSV_ITEM_SEPARATOR; + } + + /** + * Create a string representation of the node's data comma separated with a + * line separator ending + * + * @return a comma separated string representation of the node's data + */ + String toCsvString() { + StringBuilder line = new StringBuilder("\""); + line.append(getCaseName()).append(CSV_ITEM_SEPARATOR) + .append(getDataSourceName()).append(CSV_ITEM_SEPARATOR) + .append(getType()).append(CSV_ITEM_SEPARATOR) + .append(getValue()).append(CSV_ITEM_SEPARATOR) + .append(getKnown().toString()).append(CSV_ITEM_SEPARATOR) + .append(getFilePath()).append(CSV_ITEM_SEPARATOR) + .append(getComment()).append('"') + .append(System.getProperty("line.separator")); + return line.toString(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesCasesTableModel.java index 07ecc0878a..d72e8edb57 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesCasesTableModel.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; /** * Model for cells in the cases section of the other occurrences data content @@ -32,26 +33,16 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private final List correlationCaseList = new ArrayList<>(); + /** + * Create a table model for displaying case names + */ OtherOccurrencesCasesTableModel() { + // This constructor is intentionally empty. } @Override public int getColumnCount() { - return TableColumns.values().length; - } - - /** - * Get the preferred width that has been configured for this column. - * - * A value of 0 means that no preferred width has been defined for this - * column. - * - * @param colIdx Column index - * - * @return preferred column width >= 0 - */ - public int getColumnPreferredWidth(int colIdx) { - return TableColumns.values()[colIdx].columnWidth(); + return 1; } @Override @@ -59,45 +50,43 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel { return correlationCaseList.size(); } + @Messages({"OtherOccurrencesCasesTableModel.case=Case",}) @Override public String getColumnName(int colIdx) { - return TableColumns.values()[colIdx].columnName(); + return Bundle.OtherOccurrencesCasesTableModel_case(); } + @Messages({"OtherOccurrencesCasesTableModel.noData=No Data."}) @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == correlationCaseList.size()) { + //if anything would prevent this from working we will consider it no data for the sake of simplicity + if (correlationCaseList.isEmpty() || rowIdx < 0 + || rowIdx >= correlationCaseList.size() + || correlationCaseList.get(rowIdx) == null + || correlationCaseList.get(rowIdx).getMessage() == null + || correlationCaseList.get(rowIdx).getMessage().isEmpty()) { return Bundle.OtherOccurrencesCasesTableModel_noData(); } - - CorrelationCaseWrapper caseWrapper = correlationCaseList.get(rowIdx); - TableColumns columnId = TableColumns.values()[colIdx]; - return mapCorrelationCase(caseWrapper, columnId); + return correlationCaseList.get(rowIdx).getMessage(); } /** - * Map a column ID to the value in that cell for correlation case wrapper. + * Get a correlation case for the selected index. Does not query the Central + * Repository so CorrelationCase will be partial missing CR case ID and + * other information that is stored in the CR. * - * @param correlationCaseWrapper The correlation case wrapper - * @param columnId The ID of the cell column. + * @param rowIdx the row from the table model which corresponds to the case * - * @return The value in the cell. + * @return CorrelationCase for the table item specified or null if no + * correlation could be found for any reason */ - @Messages({"OtherOccurrencesCasesTableModel.noData=No Data."}) - private Object mapCorrelationCase(CorrelationCaseWrapper correlationCaseWrapper, TableColumns columnId) { - String value = Bundle.OtherOccurrencesCasesTableModel_noData(); - - switch (columnId) { - case CASE_NAME: - value = correlationCaseWrapper.getMessage(); - break; - default: //Use default "No data" value. - break; + CorrelationCase getCorrelationCase(int rowIdx) { + //if anything would prevent this from working we will return null + if (correlationCaseList.isEmpty() || rowIdx < 0 + || rowIdx >= correlationCaseList.size() + || correlationCaseList.get(rowIdx) == null) { + return null; } - return value; - } - - Object getCorrelationCase(int rowIdx) { return correlationCaseList.get(rowIdx).getCorrelationCase(); } @@ -107,7 +96,7 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel { } /** - * Add one correlated instance object to the table + * Add one correlation case wrapper object to the table * * @param newCorrelationCaseWrapper data to add to the table */ @@ -123,27 +112,4 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel { correlationCaseList.clear(); fireTableDataChanged(); } - - @Messages({"OtherOccurrencesCasesTableModel.case=Case",}) - enum TableColumns { - // Ordering here determines displayed column order in Content Viewer. - // If order is changed, update the CellRenderer to ensure correct row coloring. - CASE_NAME(Bundle.OtherOccurrencesCasesTableModel_case(), 100); - - private final String columnName; - private final int columnWidth; - - TableColumns(String columnName, int columnWidth) { - this.columnName = columnName; - this.columnWidth = columnWidth; - } - - public String columnName() { - return columnName; - } - - public int columnWidth() { - return columnWidth; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java new file mode 100644 index 0000000000..30b44ea5a6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java @@ -0,0 +1,206 @@ +/* + * Central Repository + * + * 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.centralrepository.contentviewer; + +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import javax.swing.table.AbstractTableModel; +import org.openide.util.NbBundle; + +/** + * Model for cells in the data sources section of the other occurrences data + * content viewer + */ +final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + private final Set dataSourceSet = new LinkedHashSet<>(); + + /** + * Create a table model for displaying data source names + */ + OtherOccurrencesDataSourcesTableModel() { + // This constructor is intentionally empty. + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public int getRowCount() { + return dataSourceSet.size(); + } + + @NbBundle.Messages({"OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name", + "OtherOccurrencesDataSourcesTableModel.noData=No Data."}) + @Override + public String getColumnName(int colIdx) { + return Bundle.OtherOccurrencesDataSourcesTableModel_dataSourceName(); + } + + @Override + public Object getValueAt(int rowIdx, int colIdx) { + //if anything would prevent this from working we will consider it no data for the sake of simplicity + if (dataSourceSet.isEmpty() || rowIdx < 0 + || rowIdx >= dataSourceSet.size() + || !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) { + return Bundle.OtherOccurrencesDataSourcesTableModel_noData(); + } + return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDataSourceName(); + } + + /** + * Get the device id of the data source shown at the specified row index + * + * @param rowIdx the row index of the data source you want the device id for + * + * @return the device id of the specified data source or an empty string if + * a device id could not be retrieved + */ + String getDeviceIdForRow(int rowIdx) { + //if anything would prevent this from working we will return an empty string + if (dataSourceSet.isEmpty() || rowIdx < 0 + || rowIdx >= dataSourceSet.size() + || !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) { + return ""; + } + return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDeviceId(); + } + + /** + * Get the case name of the data source shown at the specified row index + * + * @param rowIdx the row index of the data source you want the case name for + * + * @return the case name of the specified data source or an empty string if + * a case name could not be retrieved + */ + String getCaseNameForRow(int rowIdx) { + //if anything would prevent this from working we will return an empty string + if (dataSourceSet.isEmpty() || rowIdx < 0 + || rowIdx >= dataSourceSet.size() + || !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) { + return ""; + } + return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getCaseName(); + } + + @Override + public Class getColumnClass(int colIdx) { + return String.class; + } + + /** + * Add data source information to the table of unique data sources + * + * @param newNodeData data to add to the table + */ + void addNodeData(OtherOccurrenceNodeData newNodeData) { + dataSourceSet.add(new DataSourceColumnItem((OtherOccurrenceNodeInstanceData) newNodeData)); + fireTableDataChanged(); + } + + /** + * Clear the node data table. + */ + void clearTable() { + dataSourceSet.clear(); + fireTableDataChanged(); + } + + /** + * Private class for storing data source information in a way that + * facilitates de-duping. + */ + private final class DataSourceColumnItem { + + private final String caseName; + private final String deviceId; + private final String dataSourceName; + + /** + * Create a DataSourceColumnItem given an + * OtherOccurrenceNodeInstanceData object + * + * @param nodeData the OtherOccurrenceNodeInstanceData which contains + * the data source information + */ + private DataSourceColumnItem(OtherOccurrenceNodeInstanceData nodeData) { + this(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName()); + } + + /** + * Create a DataSourceColumnItem given a case name, device id, and data + * source name + * + * @param caseName the name of the case the data source exists in + * @param deviceId the name of the device id for the data source + * @param dataSourceName the name of the data source + */ + private DataSourceColumnItem(String caseName, String deviceId, String dataSourceName) { + this.caseName = caseName; + this.deviceId = deviceId; + this.dataSourceName = dataSourceName; + } + + /** + * Get the device id + * + * @return the data source's device id + */ + private String getDeviceId() { + return deviceId; + } + + /** + * Get the data source name + * + * @return the data source's name + */ + private String getDataSourceName() { + return dataSourceName; + } + + /** + * Get the name of the case the data source exists in + * + * @return the name of the case the data source is in + */ + private String getCaseName() { + return caseName; + } + + @Override + public boolean equals(Object other) { + return other instanceof DataSourceColumnItem + && caseName.equals(((DataSourceColumnItem) other).getCaseName()) + && dataSourceName.equals(((DataSourceColumnItem) other).getDataSourceName()) + && deviceId.equals(((DataSourceColumnItem) other).getDeviceId()); + } + + @Override + public int hashCode() { + return Objects.hash(caseName, deviceId, dataSourceName); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableCellRenderer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableCellRenderer.java deleted file mode 100644 index 2f258d3e7b..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableCellRenderer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Central Repository - * - * Copyright 2015-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.centralrepository.contentviewer; - -import java.awt.Color; -import java.awt.Component; -import javax.swing.JComponent; -import javax.swing.JTable; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableCellRenderer; -import org.sleuthkit.datamodel.TskData; - -/** - * Renderer for cells in the files section of the other occurrences data content viewer - */ -public class OtherOccurrencesFilesTableCellRenderer implements TableCellRenderer { - - public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer(); - - @Override - public Component getTableCellRendererComponent( - JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column) { - Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent( - table, value, isSelected, hasFocus, row, column); - ((JComponent) renderer).setOpaque(true); - Color foreground, background; - if (isSelected) { - foreground = Color.WHITE; - background = new Color(51,153,255); - } else { - String known_status = (String) table.getModel().getValueAt(table.convertRowIndexToModel(row), - table.getColumn(OtherOccurrencesFilesTableModel.TableColumns.KNOWN.columnName()).getModelIndex()); - if (known_status.equals(TskData.FileKnown.BAD.getName())) { - foreground = Color.WHITE; - background = Color.RED; - } else if (known_status.equals(TskData.FileKnown.UNKNOWN.getName())) { - foreground = Color.BLACK; - background = Color.WHITE; - } else { - foreground = Color.BLACK; - background = Color.WHITE; - } - } - renderer.setForeground(foreground); - renderer.setBackground(background); - return renderer; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java index e6c0a4215f..dd797a19d3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java @@ -19,150 +19,76 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; +import org.apache.commons.io.FilenameUtils; /** - * Model for cells in the files section of the other occurrences data content viewer + * Model for cells in the files section of the other occurrences data content + * viewer */ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; + private final List nodeKeys = new ArrayList<>(); + private final Map> nodeMap = new HashMap<>(); - @Messages({"OtherOccurrencesFilesTableModel.device=Device", - "OtherOccurrencesFilesTableModel.dataSource=Data Source", - "OtherOccurrencesFilesTableModel.path=Path", - "OtherOccurrencesFilesTableModel.attribute=Matched Attribute", - "OtherOccurrencesFilesTableModel.value=Attribute Value", - "OtherOccurrencesFilesTableModel.known=Known", - "OtherOccurrencesFilesTableModel.comment=Comment", - "OtherOccurrencesFilesTableModel.noData=No Data.",}) - enum TableColumns { - // Ordering here determines displayed column order in Content Viewer. - // If order is changed, update the CellRenderer to ensure correct row coloring. - ATTRIBUTE(Bundle.OtherOccurrencesFilesTableModel_attribute(), 75), - VALUE(Bundle.OtherOccurrencesFilesTableModel_value(), 190), - KNOWN(Bundle.OtherOccurrencesFilesTableModel_known(), 25), - FILE_PATH(Bundle.OtherOccurrencesFilesTableModel_path(), 470), - COMMENT(Bundle.OtherOccurrencesFilesTableModel_comment(), 190); - - private final String columnName; - private final int columnWidth; - - TableColumns(String columnName, int columnWidth) { - this.columnName = columnName; - this.columnWidth = columnWidth; - } - - public String columnName() { - return columnName; - } - - public int columnWidth() { - return columnWidth; - } - }; - - private final List nodeDataList = new ArrayList<>(); - + /** + * Create a table model for displaying file names + */ OtherOccurrencesFilesTableModel() { - + // This constructor is intentionally empty. } @Override public int getColumnCount() { - return TableColumns.values().length; - } - - /** - * Get the preferred width that has been configured for this column. - * - * A value of 0 means that no preferred width has been defined for this - * column. - * - * @param colIdx Column index - * - * @return preferred column width >= 0 - */ - public int getColumnPreferredWidth(int colIdx) { - return TableColumns.values()[colIdx].columnWidth(); + return 1; } @Override public int getRowCount() { - return nodeDataList.size(); + return nodeKeys.size(); } + @Messages({"OtherOccurrencesFilesTableModel.fileName=File Name", + "OtherOccurrencesFilesTableModel.noData=No Data."}) @Override public String getColumnName(int colIdx) { - return TableColumns.values()[colIdx].columnName(); + return Bundle.OtherOccurrencesFilesTableModel_fileName(); } @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == nodeDataList.size()) { + //if anything would prevent this from working we will consider it no data for the sake of simplicity + if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0 + || rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null + || nodeMap.get(nodeKeys.get(rowIdx)) == null + || nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) { return Bundle.OtherOccurrencesFilesTableModel_noData(); } - - OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); - TableColumns columnId = TableColumns.values()[colIdx]; - if (nodeData instanceof OtherOccurrenceNodeMessageData) { - return mapNodeMessageData((OtherOccurrenceNodeMessageData) nodeData, columnId); - } - return mapNodeInstanceData((OtherOccurrenceNodeInstanceData) nodeData, columnId); + return FilenameUtils.getName(((OtherOccurrenceNodeInstanceData) nodeMap.get(nodeKeys.get(rowIdx)).get(0)).getFilePath()); } /** - * Map a column ID to the value in that cell for node message data. + * Get a list of OtherOccurrenceNodeData that exist for the file which + * corresponds to the Index * - * @param nodeData The node message data. - * @param columnId The ID of the cell column. + * @param rowIdx the index of the file to get data for * - * @return The value in the cell. + * @return a list of OtherOccurrenceNodeData for the specified index or an + * empty list if no data was found */ - private Object mapNodeMessageData(OtherOccurrenceNodeMessageData nodeData, TableColumns columnId) { - if (columnId == TableColumns.ATTRIBUTE) { - return nodeData.getDisplayMessage(); + List getListOfNodesForFile(int rowIdx) { + //if anything would prevent this from working return an empty list + if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0 + || rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null + || nodeMap.get(nodeKeys.get(rowIdx)) == null) { + return new ArrayList<>(); } - return ""; - } - - /** - * Map a column ID to the value in that cell for node instance data. - * - * @param nodeData The node instance data. - * @param columnId The ID of the cell column. - * - * @return The value in the cell. - */ - private Object mapNodeInstanceData(OtherOccurrenceNodeInstanceData nodeData, TableColumns columnId) { - String value = Bundle.OtherOccurrencesFilesTableModel_noData(); - - switch (columnId) { - case FILE_PATH: - value = nodeData.getFilePath(); - break; - case ATTRIBUTE: - value = nodeData.getType(); - break; - case VALUE: - value = nodeData.getValue(); - break; - case KNOWN: - value = nodeData.getKnown().getName(); - break; - case COMMENT: - value = nodeData.getComment(); - break; - default: //Use default "No data" value. - break; - } - return value; - } - - Object getRow(int rowIdx) { - return nodeDataList.get(rowIdx); + return nodeMap.get(nodeKeys.get(rowIdx)); } @Override @@ -176,15 +102,27 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { * @param newNodeData data to add to the table */ void addNodeData(OtherOccurrenceNodeData newNodeData) { - nodeDataList.add(newNodeData); + String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath()); + List nodeList = nodeMap.get(newNodeKey); + if (nodeList == null) { + nodeKeys.add(newNodeKey); + nodeList = new ArrayList<>(); + } + nodeList.add(newNodeData); + nodeMap.put(newNodeKey, nodeList); fireTableDataChanged(); } + private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) { + return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath(); + } + /** * Clear the node data table. */ void clearTable() { - nodeDataList.clear(); + nodeKeys.clear(); + nodeMap.clear(); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java index d699c7cddf..a092d41483 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java @@ -44,7 +44,7 @@ public final class ContactDetailsPane extends javax.swing.JPanel implements Expl * @param nodes List of nodes to set */ public void setNode(Node[] nodes) { - if (nodes != null) { + if (nodes != null && nodes.length > 0) { nameLabel.setText(nodes[0].getDisplayName()); } else { nameLabel.setText(""); diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java index e58161b4f0..1f1a54038b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.communications; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import org.openide.nodes.Sheet; @@ -31,16 +29,9 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; import org.sleuthkit.datamodel.BlackboardAttribute; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL; import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java index 5e500b3482..cf1369661c 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java @@ -100,11 +100,7 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer, tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { final Node[] nodes = tableEM.getSelectedNodes(); - - if (nodes != null && nodes.length > 0) { - contactPane.setEnabled(true); - contactPane.setNode(nodes); - } + contactPane.setNode(nodes); } }); diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index d9bbcb8d39..210b0dfbb0 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -98,7 +98,6 @@ import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.Notifications; import org.jdesktop.layout.GroupLayout; import org.jdesktop.layout.LayoutStyle; -import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 0bfc78e987..7a251f4eea 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -59,7 +59,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 7b852e77cf..4d2ea876a0 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,6 @@ import java.beans.PropertyVetoException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; @@ -47,6 +46,7 @@ import javax.swing.tree.TreeSelectionModel; import org.apache.commons.lang3.StringUtils; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; +import org.openide.explorer.view.BeanTreeView; import org.openide.explorer.view.Visualizer; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; @@ -128,7 +128,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat getTree().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); //Hook into the JTree and pre-expand the Views Node and Results node when a user //expands an item in the tree that makes these nodes visible. - getTree().addTreeExpansionListener(new TreeExpansionListener() { + ((ExpansionBeanTreeView )getTree()).addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { //Bail immediately if we are not in the Group By view. @@ -192,7 +192,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * @param rootChildren Children node containing Results node and Views node. */ private void preExpandNodes(Children rootChildren) { - ExpansionBeanTreeView tree = getTree(); + BeanTreeView tree = getTree(); Node results = rootChildren.findChild(ResultsNode.NAME); if (!Objects.isNull(results)) { @@ -930,8 +930,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * * @return tree the BeanTreeView */ - public ExpansionBeanTreeView getTree() { - return (ExpansionBeanTreeView) this.treeView; + BeanTreeView getTree() { + return (BeanTreeView) this.treeView; } /** diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExpansionBeanTreeView.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExpansionBeanTreeView.java index 9f7bcb447d..30bd2bdf49 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExpansionBeanTreeView.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExpansionBeanTreeView.java @@ -24,7 +24,7 @@ import org.openide.explorer.view.BeanTreeView; /** * Adds the ability to listen to user-driven JTree expansion events. */ -class ExpansionBeanTreeView extends BeanTreeView { +final class ExpansionBeanTreeView extends BeanTreeView { public void addTreeExpansionListener(TreeExpansionListener listener) { this.tree.addTreeExpansionListener(listener); } diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index 8d6e81e3ef..20f16e4ac6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -37,7 +37,7 @@ FileReportDataTypes.knownStatus.text=Known Status FileReportDataTypes.perms.text=Permissions FileReportDataTypes.path.text=Full Path FileReportText.getName.text=Files - Text -FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case. +FileReportText.getDesc.text=A delimited text file containing information about individual files in the case. ReportBodyFile.progress.querying=Querying files... ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\! ReportBodyFile.progress.loading=Loading files... @@ -258,3 +258,5 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1 CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder CreatePortableCasePanel.jLabel1.text=Export files tagged as: CreatePortableCasePanel.jLabel2.text=Select output folder: +ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited +ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED index 3111ffbe6d..1187fe8e81 100755 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties-MERGED @@ -85,7 +85,7 @@ FileReportDataTypes.knownStatus.text=Known Status FileReportDataTypes.perms.text=Permissions FileReportDataTypes.path.text=Full Path FileReportText.getName.text=Files - Text -FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case. +FileReportText.getDesc.text=A delimited text file containing information about individual files in the case. ReportBodyFile.progress.querying=Querying files... ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\! ReportBodyFile.progress.loading=Loading files... @@ -306,4 +306,6 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1 CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder CreatePortableCasePanel.jLabel1.text=Export files tagged as: CreatePortableCasePanel.jLabel2.text=Select output folder: +ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited +ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited TableReportGenerator.StatusColumn.Header=Review Status diff --git a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java b/Core/src/org/sleuthkit/autopsy/report/FileReportText.java index f96bad446b..9d3de2c945 100644 --- a/Core/src/org/sleuthkit/autopsy/report/FileReportText.java +++ b/Core/src/org/sleuthkit/autopsy/report/FileReportText.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"); @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.report; import java.io.BufferedWriter; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; @@ -27,6 +28,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Level; +import javax.swing.JPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.util.NbBundle; @@ -36,18 +38,18 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; /** - * A Tab-delimited text report of the files in the case. + * A delimited text report of the files in the case. * * @author jwallace */ class FileReportText implements FileReportModule { private static final Logger logger = Logger.getLogger(FileReportText.class.getName()); + private static final String FILE_NAME = "file-report.txt"; //NON-NLS + private static FileReportText instance; private String reportPath; private Writer out; - private static final String FILE_NAME = "file-report.txt"; //NON-NLS - - private static FileReportText instance; + private ReportFileTextConfigurationPanel configPanel; // Get the default implementation of this report public static synchronized FileReportText getDefault() { @@ -62,7 +64,7 @@ class FileReportText implements FileReportModule { this.reportPath = baseReportDir + FILE_NAME; try { out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath))); - } catch (IOException ex) { + } catch (FileNotFoundException ex) { logger.log(Level.WARNING, "Failed to create report text file", ex); //NON-NLS } } @@ -85,11 +87,12 @@ class FileReportText implements FileReportModule { } } - private String getTabDelimitedList(List list) { - StringBuilder output = new StringBuilder(); + private String getDelimitedList(List list, String delimiter) { + StringBuilder output; + output = new StringBuilder(); Iterator it = list.iterator(); while (it.hasNext()) { - output.append(it.next()).append((it.hasNext() ? "\t" : System.lineSeparator())); + output.append('"').append(it.next()).append('"').append((it.hasNext() ? delimiter : System.lineSeparator())); } return output.toString(); } @@ -101,7 +104,7 @@ class FileReportText implements FileReportModule { titles.add(col.getName()); } try { - out.write(getTabDelimitedList(titles)); + out.write(getDelimitedList(titles, configPanel.getDelimiter())); } catch (IOException ex) { logger.log(Level.WARNING, "Error when writing headers to report file: {0}", ex); //NON-NLS } @@ -114,7 +117,7 @@ class FileReportText implements FileReportModule { cells.add(type.getValue(toAdd)); } try { - out.write(getTabDelimitedList(cells)); + out.write(getDelimitedList(cells, configPanel.getDelimiter())); } catch (IOException ex) { logger.log(Level.WARNING, "Error when writing row to report file: {0}", ex); //NON-NLS } @@ -143,4 +146,12 @@ class FileReportText implements FileReportModule { public String getRelativeFilePath() { return FILE_NAME; } + + @Override + public JPanel getConfigurationPanel() { + if (configPanel == null) { + configPanel = new ReportFileTextConfigurationPanel(); + } + return configPanel; + } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.form b/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.form new file mode 100644 index 0000000000..d0c59f7bdd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.form @@ -0,0 +1,68 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.java new file mode 100644 index 0000000000..e1b9de2df2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/ReportFileTextConfigurationPanel.java @@ -0,0 +1,99 @@ +/* + * 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.report; + +/** + * Panel for configuring settings for the file text report + */ +class ReportFileTextConfigurationPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + private static final String TAB_DELIMITER = "\t"; //NON-NLS + private static final String COMMA_DELIMITER = ","; //NON-NLS + + /** + * Creates new form ReportFileTextConfigurationPanel + */ + ReportFileTextConfigurationPanel() { + initComponents(); + } + + /** + * Get the delimiter that was selected on this panel + * + * @return the selected delimiter + */ + String getDelimiter() { + if (commaDelimitedButton.isSelected()) { + return COMMA_DELIMITER; + } else { + //if the comma button is not selected default to tab since it was previously the only option + return TAB_DELIMITER; + } + } + + /** + * 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() { + + delimiterGroup = new javax.swing.ButtonGroup(); + tabDelimitedButton = new javax.swing.JRadioButton(); + commaDelimitedButton = new javax.swing.JRadioButton(); + + delimiterGroup.add(tabDelimitedButton); + tabDelimitedButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(tabDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.tabDelimitedButton.text")); // NOI18N + + delimiterGroup.add(commaDelimitedButton); + org.openide.awt.Mnemonics.setLocalizedText(commaDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.commaDelimitedButton.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(tabDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(commaDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(166, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(tabDelimitedButton) + .addComponent(commaDelimitedButton)) + .addContainerGap(78, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton commaDelimitedButton; + private javax.swing.ButtonGroup delimiterGroup; + private javax.swing.JRadioButton tabDelimitedButton; + // End of variables declaration//GEN-END:variables +} diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index ddefe33a21..f7ebf84361 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -243,6 +243,7 @@ org.apache.commons.io.input org.apache.commons.io.monitor org.apache.commons.io.output + org.apache.commons.io.serialization org.apache.commons.lang org.apache.commons.lang.builder org.apache.commons.lang.enums diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index 55e6cfdadb..17173a0c09 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -135,7 +135,7 @@ 10 - 10.14 + 10.15 @@ -153,7 +153,7 @@ 6 - 6.5 + 6.6 diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index d1ed6ddc55..febcf4fb7a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -53,6 +53,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString(); + private final static String ADMIN_EXT_ACCESS_FILE_NAME = "adminext"; // NON-NLS + private final static String ADMIN_EXT_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_EXT_ACCESS_FILE_NAME).toString(); private final static String AID_REFRESH_THREAD_NAME = "AID-refresh-jobs-%d"; private final static int AID_REFRESH_INTERVAL_SECS = 30; private final static int AID_DELAY_BEFORE_FIRST_REFRESH = 0; @@ -277,8 +279,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { } /** - * Reloads the table models using a RefreshChildrenEvent and refreshes the JTables - * that use the models. + * Reloads the table models using a RefreshChildrenEvent and refreshes the + * JTables that use the models. * */ void refreshTables() { @@ -318,9 +320,24 @@ final class AutoIngestDashboard extends JPanel implements Observer { } + /** + * Determines whether or not system adminstrator features of the dashboard + * are enabled. + * + * @return True or false. + */ static boolean isAdminAutoIngestDashboard() { - File f = new File(ADMIN_ACCESS_FILE_PATH); - return f.exists(); + return new File(ADMIN_ACCESS_FILE_PATH).exists() || new File(ADMIN_EXT_ACCESS_FILE_PATH).exists(); + } + + /** + * Determines whether the extended system administrator features of the + * cases dashboard are enabled. + * + * @return True or false. + */ + static boolean extendedFeaturesAreEnabled() { + return new File(ADMIN_EXT_ACCESS_FILE_PATH).exists(); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index a8517b6faf..4bade61c1c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1271,12 +1271,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param nodeData The data stored in the manifest file coordination * service node for the job. * - * @throws AutoIngestJobException If there was an error working - * with the node data. - * @throws InterruptedException If the thread running the input - * directory scan task is - * interrupted while blocked, i.e., - * if auto ingest is shutting down. + * @throws AutoIngestJobException If there was an error working with the + * node data. + * @throws InterruptedException If the thread running the input + * directory scan task is interrupted + * while blocked, i.e., if auto ingest is + * shutting down. */ private void addPendingJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws AutoIngestJobException, InterruptedException { AutoIngestJob job; @@ -1415,6 +1415,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.SEVERE, String.format("Error writing case auto ingest log entry for crashed job for %s", manifestPath), ex); } } + updateAutoIngestJobData(job); + newPendingJobsList.add(job); } else { job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); job.setCompletedDate(Date.from(Instant.now())); @@ -1425,9 +1427,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.SEVERE, String.format("Error writing case auto ingest log entry for crashed job for %s", manifestPath), ex); } } + updateAutoIngestJobData(job); + newCompletedJobsList.add(job); } - updateAutoIngestJobData(job); - newPendingJobsList.add(job); } } } @@ -1440,12 +1442,12 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param nodeData The data stored in the manifest file lock * coordination service node for the job. * - * @throws AutoIngestJobException If there was an error working - * with the node data. - * @throws InterruptedException If the thread running the input - * directory scan task is - * interrupted while blocked, i.e., - * if auto ingest is shutting down. + * @throws AutoIngestJobException If there was an error working with the + * node data. + * @throws InterruptedException If the thread running the input + * directory scan task is interrupted + * while blocked, i.e., if auto ingest is + * shutting down. */ private void addCompletedJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws AutoIngestJobException, InterruptedException { Path caseDirectoryPath = nodeData.getCaseDirectoryPath(); @@ -1995,15 +1997,26 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); if (!nodeData.getProcessingStatus().equals(PENDING)) { iterator.remove(); + manifestLock.release(); + manifestLock = null; continue; } + /* + * Ditto for the presence of the manifest file. + */ File manifestFile = nodeData.getManifestFilePath().toFile(); if (!manifestFile.exists()) { iterator.remove(); + manifestLock.release(); + manifestLock = null; continue; } + /* + * Finally, check for devoting too many resources to a + * single case, if the check is enabled. + */ if (enforceMaxJobsPerCase) { int currentJobsForCase = 0; for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java index dbe8b507d1..3126becbfe 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CasesDashboardCustomizer.java @@ -18,14 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.io.File; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import javax.swing.Action; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrowserCustomizer; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * A customizer for the multi-user case browser panel used in the administrative @@ -34,8 +31,6 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; */ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { - private final static String ADMIN_EXT_ACCESS_FILE_NAME = "adminext"; // NON-NLS - private final static String ADMIN_EXT_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_EXT_ACCESS_FILE_NAME).toString(); private final DeleteCaseAction deleteCaseAction; private final DeleteCaseInputAction deleteCaseInputAction; private final DeleteCaseOutputAction deleteCaseOutputAction; @@ -67,7 +62,7 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { properties.add(Column.LAST_ACCESS_DATE); properties.add(Column.DIRECTORY); properties.add(Column.MANIFEST_FILE_ZNODES_DELETE_STATUS); - if (CasesDashboardCustomizer.extendedFeaturesAreEnabled()) { + if (AutoIngestDashboard.extendedFeaturesAreEnabled()) { properties.add(Column.DATA_SOURCES_DELETE_STATUS); } properties.add(Column.TEXT_INDEX_DELETE_STATUS); @@ -93,7 +88,7 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { List actions = new ArrayList<>(); actions.add(new OpenCaseAction(nodeData)); actions.add(new OpenAutoIngestLogAction(nodeData)); - if (CasesDashboardCustomizer.extendedFeaturesAreEnabled()) { + if (AutoIngestDashboard.extendedFeaturesAreEnabled()) { actions.add(deleteCaseInputAction); actions.add(deleteCaseOutputAction); actions.add(deleteCaseInputAndOutputAction); @@ -108,15 +103,4 @@ final class CasesDashboardCustomizer implements MultiUserCaseBrowserCustomizer { return new OpenCaseAction(nodeData); } - /** - * Determines whether the extended system administrator features of the - * cases dashboard are enabled. - * - * @return True or false. - */ - static boolean extendedFeaturesAreEnabled() { - File f = new File(ADMIN_EXT_ACCESS_FILE_PATH); - return f.exists(); - } - } diff --git a/ImageGallery/manifest.mf b/ImageGallery/manifest.mf index 21dea51481..4c9ca17c14 100644 --- a/ImageGallery/manifest.mf +++ b/ImageGallery/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.imagegallery/2 -OpenIDE-Module-Implementation-Version: 5 +OpenIDE-Module-Implementation-Version: 6 OpenIDE-Module-Layer: org/sleuthkit/autopsy/imagegallery/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/imagegallery/Bundle.properties diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index 183a947954..fc0058cb7b 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -127,7 +127,7 @@ 10 - 10.14 + 10.15 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 508da04d05..8e8587fe91 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -646,13 +646,22 @@ public class GroupManager { // reset the hash cache controller.getHashSetManager().invalidateHashSetsCacheForFile(fileId); + // first of all, update the current path group, regardless of what grouping is in view + try { + DrawableFile file = getDrawableDB().getFileFromID(fileId); + String pathVal = file.getDrawablePath(); + GroupKey pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource()); + + updateCurrentPathGroup(pathGroupKey); + } catch (TskCoreException | TskDataException ex) { + Exceptions.printStackTrace(ex); + } + // Update the current groups (if it is visible) Set> groupsForFile = getGroupKeysForCurrentGroupBy(fileId); 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); } } @@ -756,8 +765,10 @@ public class GroupManager { controller.getCategoryManager().registerListener(group); groupMap.put(groupKey, group); } - - if (analyzedGroups.contains(group) == false) { + + // Add to analyzedGroups only if it's the same group type as the one in view + if ((analyzedGroups.contains(group) == false) && + (getGroupBy() == group.getGroupKey().getAttribute())) { analyzedGroups.add(group); sortAnalyzedGroups(); } diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf index 5b53ffe61d..eb852c16bf 100644 --- a/KeywordSearch/manifest.mf +++ b/KeywordSearch/manifest.mf @@ -1,7 +1,7 @@ Manifest-Version: 1.0 AutoUpdate-Show-In-Client: true OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/6 -OpenIDE-Module-Implementation-Version: 20 +OpenIDE-Module-Implementation-Version: 21 OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties index b47080f282..7e931bf641 100644 --- a/KeywordSearch/nbproject/project.properties +++ b/KeywordSearch/nbproject/project.properties @@ -131,6 +131,7 @@ file.reference.uimaj-tools-2.6.0.jar=release/modules/ext/uimaj-tools-2.6.0.jar file.reference.vorbis-java-core-0.8.jar=release/modules/ext/vorbis-java-core-0.8.jar file.reference.vorbis-java-tika-0.8.jar=release/modules/ext/vorbis-java-tika-0.8.jar file.reference.woodstox-core-asl-4.4.1.jar=release/modules/ext/woodstox-core-asl-4.4.1.jar +file.reference.wstx-asl-3.2.7.jar=release\\modules\\ext\\wstx-asl-3.2.7.jar file.reference.xmlbeans-2.6.0.jar=release/modules/ext/xmlbeans-2.6.0.jar file.reference.xmpcore-5.1.3.jar=release/modules/ext/xmpcore-5.1.3.jar file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar @@ -141,4 +142,4 @@ license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/autopsy/ nbm.needs.restart=true source.reference.commons-validator-1.5.1.jar=release/modules/ext/commons-validator-1.5.1-sources.jar -spec.version.base=6.5 +spec.version.base=6.6 diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index 109ee0e66c..05aa7b3165 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -119,7 +119,7 @@ 10 - 10.14 + 10.15 @@ -133,9 +133,107 @@ + com.ctc.wstx.api + com.ctc.wstx.cfg + com.ctc.wstx.compat + com.ctc.wstx.dom + com.ctc.wstx.dtd + com.ctc.wstx.ent + com.ctc.wstx.evt + com.ctc.wstx.exc + com.ctc.wstx.io + com.ctc.wstx.msv + com.ctc.wstx.sax + com.ctc.wstx.sr + com.ctc.wstx.stax + com.ctc.wstx.sw + com.ctc.wstx.util org.apache.commons.logging.impl + org.apache.http + org.apache.http.annotation + org.apache.http.auth + org.apache.http.auth.params + org.apache.http.client + org.apache.http.client.config + org.apache.http.client.entity + org.apache.http.client.methods + org.apache.http.client.params + org.apache.http.client.protocol + org.apache.http.client.utils + org.apache.http.concurrent + org.apache.http.config + org.apache.http.conn + org.apache.http.conn.params + org.apache.http.conn.routing + org.apache.http.conn.scheme + org.apache.http.conn.socket + org.apache.http.conn.ssl + org.apache.http.conn.util + org.apache.http.cookie + org.apache.http.cookie.params + org.apache.http.entity + org.apache.http.entity.mime + org.apache.http.entity.mime.content + org.apache.http.impl + org.apache.http.impl.auth + org.apache.http.impl.bootstrap + org.apache.http.impl.client + org.apache.http.impl.conn + org.apache.http.impl.conn.tsccm + org.apache.http.impl.cookie + org.apache.http.impl.entity + org.apache.http.impl.execchain + org.apache.http.impl.io + org.apache.http.impl.pool + org.apache.http.io + org.apache.http.message + org.apache.http.params + org.apache.http.pool + org.apache.http.protocol + org.apache.http.ssl + org.apache.http.util + org.apache.jute + org.apache.jute.compiler + org.apache.jute.compiler.generated + org.apache.solr.client.solrj + org.apache.solr.client.solrj.beans + org.apache.solr.client.solrj.impl + org.apache.solr.client.solrj.request + org.apache.solr.client.solrj.response + org.apache.solr.client.solrj.util + org.apache.solr.common + org.apache.solr.common.cloud + org.apache.solr.common.luke + org.apache.solr.common.params + org.apache.solr.common.util org.apache.tika.parser.txt + org.apache.zookeeper + org.apache.zookeeper.client + org.apache.zookeeper.common + org.apache.zookeeper.data + org.apache.zookeeper.jmx + org.apache.zookeeper.proto + org.apache.zookeeper.server + org.apache.zookeeper.server.auth + org.apache.zookeeper.server.persistence + org.apache.zookeeper.server.quorum + org.apache.zookeeper.server.quorum.flexible + org.apache.zookeeper.server.upgrade + org.apache.zookeeper.server.util + org.apache.zookeeper.txn + org.apache.zookeeper.version + org.apache.zookeeper.version.util + org.codehaus.stax2 + org.codehaus.stax2.evt + org.codehaus.stax2.io + org.codehaus.stax2.ri + org.codehaus.stax2.validation + org.noggit org.sleuthkit.autopsy.keywordsearch + org.slf4j + org.slf4j.event + org.slf4j.helpers + org.slf4j.spi ext/commons-validator-1.5.1-sources.jar @@ -409,6 +507,10 @@ ext/jmatio-1.2.jar release/modules/ext/jmatio-1.2.jar + + ext/wstx-asl-3.2.7.jar + release\modules\ext\wstx-asl-3.2.7.jar + ext/commons-csv-1.0.jar release/modules/ext/commons-csv-1.0.jar diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index b56415094e..d7c7d7705f 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -31,7 +31,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; * with a keyword. This feature was added to support an initial implementation * of account number search and may be removed in the future. */ -class Keyword { +public class Keyword { private String searchTerm; private boolean isLiteral; @@ -123,7 +123,7 @@ class Keyword { * * @return The search term. */ - String getSearchTerm() { + public String getSearchTerm() { return searchTerm; } @@ -133,7 +133,7 @@ class Keyword { * * @return True or false. */ - boolean searchTermIsLiteral() { + public boolean searchTermIsLiteral() { return isLiteral; } @@ -144,7 +144,7 @@ class Keyword { * * @return True or false. */ - boolean searchTermIsWholeWord() { + public boolean searchTermIsWholeWord() { return isWholeWord; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java index 13f094a89d..f2db250baa 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java @@ -167,7 +167,7 @@ public class KeywordList { * * @return A colleciton of Keyword objects. */ - List getKeywords() { + public List getKeywords() { return keywords; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordListsManager.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordListsManager.java index bdae07a971..0a83603bbb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordListsManager.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordListsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,6 +52,8 @@ public class KeywordListsManager extends Observable { /** * Gets the singleton instance of the keyword lists manager. + * + * @return an instance of KeywordListsManager. */ public static synchronized KeywordListsManager getInstance() { if (instance == null) { @@ -73,6 +75,17 @@ public class KeywordListsManager extends Observable { return names; } + /** + * Get keyword list by name. + * + * @param name id of the list + * + * @return keyword list representation, null if no list by that name + */ + public KeywordList getList(String name) { + return XmlKeywordSearchList.getCurrent().getList(name); + } + /** * Force reload of the keyword lists XML file. */ diff --git a/NEWS.txt b/NEWS.txt index 9679e5a44b..e9c61baf7b 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,3 +1,50 @@ +---------------- VERSION 4.11.0 -------------- +New Features: + +Adding Data: +- Hashes can optionally be entered when adding a disk image data source to a case. +- Acquisition details can be stored when the data source is added. + + +Ingest Modules: +- Added support for Microsoft Edge browser (cookies, history, and bookmarks) +- Added support for Safari web browser (downloads, cookies, history, and bookmarks) +- Expanded Chrome browser support to include cache parsing and form/auto fill. +- Expanded Firefox browser support to extract form/auto fill fields. +- Parse Zone.Identifier files to identify the source of files. +- Added a TSK_SOURCE artifact to downloaded files to help users trace back to where it came from. +- Added support for parsing vCards (virtual cards). +- Extract more information about Windows user accounts (number of logins, creation date, and last login) +- Detect more operating system types, which get saved as a TSK_OS_INFO artifact. +- Detect Android media cards, which gets saved as a TSK_DATA_SOURCE_USAGE artifact. + + +UI: +- The Application content viewer now displays HTML files. +- Video playback now uses gstreamer on 64-bit systems, which supports more video formats. +- Pictures can be rotated and zoomed in the Application content viewer. +- The Other Occurrences content viewer layout was reorganized to make viewing the data easier. +- New "Data Source Summary" panel shows high-level statistics and details about the data sources in the case. +- Data sources are now listed in the data sources tree in alphabetical order. +- The presentation of finding common properties within a case was revised to group results in a more helpful way. + + +Report / Export: +- Portable Cases can be created based on tagged data. These cases contain a subset of the case data and can be opened anywhere. +- Users can now choose tabs or commas as the delimiter for a files report. +- Case notes are included in the HTML report. + + +Other: +- Added a new file type that allows module writers to specify a file based on its byte range. +- Data sources can be analyzed and have a CASE/UCO report generated using only the command line. + + +Bug Fixes: +- Decreased the time required to execute inter-case common properties searches of the Central Repository. +- Assorted small bug fixes are included. + + ---------------- VERSION 4.10.0 -------------- New Features: - Users can now view information on all cases/data sources in the Central diff --git a/RecentActivity/manifest.mf b/RecentActivity/manifest.mf index c115996776..02e6daf22d 100644 --- a/RecentActivity/manifest.mf +++ b/RecentActivity/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/6 -OpenIDE-Module-Implementation-Version: 16 +OpenIDE-Module-Implementation-Version: 17 OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties OpenIDE-Module-Requires: diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index 87619a8356..0f16ae22e4 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -60,7 +60,7 @@ 10 - 10.14 + 10.15 diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java index 05ec607834..ce92c2e084 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java @@ -32,15 +32,15 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie; /** - * The binary cookie reader encapsulates all the knowledge of how to read the mac - * .binarycookie files into one class. + * The binary cookie reader encapsulates all the knowledge of how to read the + * mac .binarycookie files into one class. * * The binarycookie file has a header which describes how many pages of cookies * and where they are located. Each cookie page has a header and a list of * cookies. * */ -public final class BinaryCookieReader implements Iterable { +final class BinaryCookieReader implements Iterable { private static final int MAGIC_SIZE = 4; private static final int SIZEOF_INT_BYTES = 4; @@ -56,8 +56,8 @@ public final class BinaryCookieReader implements Iterable { private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName()); /** - * The binary cookie reader encapsulates all the knowledge of how to read the mac - * .binarycookie files into one class. + * The binary cookie reader encapsulates all the knowledge of how to read + * the mac .binarycookie files into one class. * */ private BinaryCookieReader(File cookieFile, int[] pageSizeArray) { @@ -96,7 +96,7 @@ public final class BinaryCookieReader implements Iterable { for (int cnt = 0; cnt < pageCount; cnt++) { sizeArray[cnt] = dataStream.readInt(); } - + LOG.log(Level.INFO, "No cookies found in {0}", cookieFile.getName()); //NON-NLS } @@ -130,16 +130,16 @@ public final class BinaryCookieReader implements Iterable { * The cookiePageIterator iterates the binarycookie file by page. */ CookiePageIterator() { - if(pageSizeArray == null || pageSizeArray.length == 0) { + if (pageSizeArray == null || pageSizeArray.length == 0) { return; } - + try { dataStream = new DataInputStream(new FileInputStream(cookieFile)); // skip to the first page dataStream.skipBytes((2 * SIZEOF_INT_BYTES) + (pageSizeArray.length * SIZEOF_INT_BYTES)); } catch (IOException ex) { - + String errorMessage = String.format("An error occurred creating an input stream for %s", cookieFile.getName()); LOG.log(Level.WARNING, errorMessage, ex); //NON-NLS closeStream(); // Just incase the error was from skip @@ -147,7 +147,7 @@ public final class BinaryCookieReader implements Iterable { } /** - * Returns true if there are more cookies in the binarycookie file. + * Returns true if there are more cookies in the binarycookie file. * * @return True if there are more cookies */ @@ -157,7 +157,7 @@ public final class BinaryCookieReader implements Iterable { if (dataStream == null) { return false; } - + if (currentIterator == null || !currentIterator.hasNext()) { try { @@ -360,7 +360,7 @@ public final class BinaryCookieReader implements Iterable { * @return Cookie expiration date in milliseconds with java epoch */ public final Long getExpirationDate() { - return ((long)expirationDate) + MAC_EPOC_FIX; + return ((long) expirationDate) + MAC_EPOC_FIX; } /** @@ -370,7 +370,7 @@ public final class BinaryCookieReader implements Iterable { * @return Cookie creation date in milliseconds with java epoch */ public final Long getCreationDate() { - return ((long)creationDate) + MAC_EPOC_FIX; + return ((long) creationDate) + MAC_EPOC_FIX; } /** diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java index 2938bb8dd5..19b76806c4 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java @@ -170,16 +170,21 @@ abstract class Extract { ResultSet temprs; List> list; String connectionString = "jdbc:sqlite:" + path; //NON-NLS + SQLiteDBConnect tempdbconnect = null; try { - SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", connectionString); //NON-NLS + tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", connectionString); //NON-NLS temprs = tempdbconnect.executeQry(query); list = this.resultSetToArrayList(temprs); - tempdbconnect.closeConnection(); } catch (SQLException ex) { logger.log(Level.SEVERE, "Error while trying to read into a sqlite db." + connectionString, ex); //NON-NLS errorMessages.add(NbBundle.getMessage(this.getClass(), "Extract.dbConn.errMsg.failedToQueryDb", getName())); return Collections.>emptyList(); } + finally { + if (tempdbconnect != null) { + tempdbconnect.closeConnection(); + } + } return list; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 230aac04ef..aa23e0d7bd 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -253,6 +253,7 @@ class ExtractRegistry extends Extract { logger.log(Level.WARNING, "Keyword search service not found. Report will not be indexed"); } else { searchService.index(report); + report.close(); } } catch (TskCoreException e) { this.addErrorMessage("Error adding regripper output as Autopsy report: " + e.getLocalizedMessage()); //NON-NLS diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java index fd9630cebd..ae4e3d0c89 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java @@ -148,8 +148,9 @@ class Util { String query = "PRAGMA table_info(" + tablename + ")"; //NON-NLS boolean found = false; ResultSet temprs; + SQLiteDBConnect tempdbconnect = null; try { - SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + connection); //NON-NLS + tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + connection); //NON-NLS temprs = tempdbconnect.executeQry(query); while (temprs.next()) { if (temprs.getString("name") == null ? column == null : temprs.getString("name").equals(column)) { //NON-NLS @@ -159,6 +160,11 @@ class Util { } catch (Exception ex) { logger.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS } + finally{ + if (tempdbconnect != null) { + tempdbconnect.closeConnection(); + } + } return found; } diff --git a/Testing/nbproject/project.xml b/Testing/nbproject/project.xml index 5c1e969964..4ed9232a8f 100644 --- a/Testing/nbproject/project.xml +++ b/Testing/nbproject/project.xml @@ -47,7 +47,7 @@ 10 - 10.14 + 10.15 @@ -56,7 +56,7 @@ 6 - 6.3 + 6.6 diff --git a/docs/doxygen-user/portable_case.dox b/docs/doxygen-user/portable_case.dox index 692b70bcb8..4f4e809ae8 100644 --- a/docs/doxygen-user/portable_case.dox +++ b/docs/doxygen-user/portable_case.dox @@ -2,10 +2,13 @@ \section portable_case_overview Overview -A portable case is a partial copy of a normal Autopsy case that can be opened from anywhere. The general use case is as follows: +A portable case is a partial copy of a normal Autopsy case that can be opened from anywhere. It contains a subset of the data from its original case and has been designed to make it easy to share relevant data with other examiners. + + +The general use case is as follows:
  1. Alice is analyzing one or more data sources using Autopsy. She tags files and results that are of particular interest. -
  2. Alice wants to share her findings with Bob but is unable to send him the original data sources. +
  3. Alice wants to share her findings with Bob but is unable to send him the original data sources because he shouldn't see them or the originals are too big.
  4. Alice creates a portable case which will contain only her tagged files and results, plus any files associated with those results, and sends it to Bob.
  5. Bob can open the portable case in Autopsy and view all files and results Alice tagged, and run any of the normal Autopsy features.
@@ -43,6 +46,14 @@ Portable cases generally behave like any other Autopsy case. You can run ingest, This may cause warning or error messages when using ingest modules that run on the full image, such as the \ref data_source_integrity_page. You will also not be able to view the data sources in the content viewer. -You can also add additonal data sources to the portable case if you wish. The case will no longer be portable, but if desired you could generate a new portable case that will include tagged files and results from the new data sources as well as the original case. +You can also add additional data sources to the portable case if you wish. The case will no longer be portable, but if desired you could generate a new portable case that will include tagged files and results from the new data sources as well as the original case. -*/ \ No newline at end of file +\section portable_case_inside Inside a Portable Case + +A portable case is a folder, just like any other Autopsy case. It contains a SQLite database (just like a normal Autopsy case) with rows for only the items that the user selected to be in the portable case. For example, if a user tagged a file and included that in the portable case, the database will have a row for the tag, a row for the file, a row for the file system the file was in, a row for the volume system, a row for the image etc. Everything assocated with the tag is in there and you should see those items in Autopsy. + +A copy of any tagged file is made into the case folder and the SQLite database will refer to it. This allows you to examine the file contents without the original data source. + +Because a portable case is really just a subset of the original case, nearly all other Autopsy operations work as normal. + +*/ diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index c8ecd28927..d6e024f08c 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -36,7 +36,7 @@ 10 - 10.14 + 10.15 @@ -45,7 +45,7 @@ 6 - 6.5 + 6.6