Merge remote-tracking branch 'upstream/develop' into timeline-event-mgr

# Conflicts:
#	Core/src/org/sleuthkit/autopsy/commonpropertiessearch/AbstractCommonAttributeSearcher.java
#	Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java
#	KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED
#	thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java
This commit is contained in:
millmanorama 2019-03-22 09:11:46 +01:00
commit 886356dcd5
107 changed files with 5814 additions and 2329 deletions

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.casemodule;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@ -189,6 +190,27 @@ public final class CaseMetadata {
this.metadataFilePath = metadataFilePath;
readFromFile();
}
/**
* Locate the case meta data file in the supplied directory. If the file does
* not exist, null is returned.
*
* @param directoryPath Directory path to search
* @return case meta data file path or null
*/
public static Path getCaseMetadataFile(Path directoryPath) {
final File[] caseFiles = directoryPath.toFile().listFiles();
if(caseFiles != null) {
for (File file : caseFiles) {
final String fileName = file.getName().toLowerCase();
if (fileName.endsWith(CaseMetadata.getFileExtension())) {
return file.toPath();
}
}
}
return null;
}
/**
* Gets the full path to the case metadata file.

View File

@ -9,13 +9,13 @@ DataContentViewerOtherCases.correlatedArtifacts.byType={0}% of data sources have
DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details.
DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency
DataContentViewerOtherCases.dataSources.header.text=Data Source Name
DataContentViewerOtherCases.earliestCaseNotAvailable=\ Not Enabled.
DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources.
DataContentViewerOtherCases.noOpenCase.errMsg=No open case available.
DataContentViewerOtherCases.selectAllMenuItem.text=Select All
DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details
DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.
DataContentViewerOtherCases.table.nodbconnection=Cannot connect to central repository database.
DataContentViewerOtherCases.table.noResultsFound=No results found.
DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options.
DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV
@ -26,12 +26,13 @@ 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.
DataContentViewerOtherCasesTableModel.attribute=Matched Attribute
DataContentViewerOtherCasesTableModel.case=Case
DataContentViewerOtherCasesTableModel.comment=Comment
DataContentViewerOtherCasesTableModel.dataSource=Data Source
DataContentViewerOtherCasesTableModel.device=Device
DataContentViewerOtherCasesTableModel.known=Known
DataContentViewerOtherCasesTableModel.noData=No Data.
DataContentViewerOtherCasesTableModel.path=Path
DataContentViewerOtherCasesTableModel.value=Attribute Value
OtherOccurrencesCasesTableModel.case=Case
OtherOccurrencesCasesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.attribute=Matched Attribute
OtherOccurrencesFilesTableModel.comment=Comment
OtherOccurrencesFilesTableModel.dataSource=Data Source
OtherOccurrencesFilesTableModel.device=Device
OtherOccurrencesFilesTableModel.known=Known
OtherOccurrencesFilesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.path=Path
OtherOccurrencesFilesTableModel.value=Attribute Value

View File

@ -0,0 +1,60 @@
/*
* Central Repository
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
/**
* Class to wrap CorrelationCases or a text message
*/
class CorrelationCaseWrapper {
private final CorrelationCase corCase;
private final String message;
CorrelationCaseWrapper(CorrelationCase corrCase) {
corCase = corrCase;
message = corrCase.getDisplayName();
}
CorrelationCaseWrapper(String msg) {
corCase = null;
message = msg;
}
/**
* Get the correlation case this is wrapping or null if it only has a
* message.
*
* @return CorrelationCase or Null
*/
CorrelationCase getCorrelationCase() {
return corCase;
}
/**
* Get the message this is wrapping, if a correlation case is being wrapped
* this will be it's display name.
*
* @return the message or Correlation Case display name
*/
String getMessage() {
return message;
}
}

View File

@ -68,12 +68,12 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" alignment="0" max="32767" attributes="0"/>
<Component id="otherCasesPanel" alignment="0" pref="1500" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" pref="53" max="32767" attributes="0"/>
<Component id="otherCasesPanel" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
@ -81,25 +81,28 @@
<Container class="javax.swing.JPanel" name="otherCasesPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 144]"/>
<Dimension value="[921, 62]"/>
</Property>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="1500" max="32767" attributes="0"/>
<EmptySpace min="0" pref="921" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Component id="tableContainerPanel" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="61" max="32767" attributes="0"/>
<EmptySpace min="0" pref="62" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="53" max="32767" attributes="0"/>
<Component id="tableContainerPanel" pref="62" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
@ -117,67 +120,37 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tableScrollPane" alignment="0" pref="1508" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="earliestCaseLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="earliestCaseDate" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="66" max="-2" attributes="0"/>
<Component id="foundInLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="1157" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="earliestCaseLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="earliestCaseDate" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="66" max="-2" attributes="0"/>
<Component id="foundInLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="jSplitPane2" alignment="0" pref="911" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="tableScrollPane" pref="71" max="32767" attributes="0"/>
<Component id="jSplitPane2" pref="31" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="foundInLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="tableScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 30]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="otherCasesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="tableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.table.toolTip.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
<TableColumnModel selectionModel="0"/>
</Property>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="rightClickPopupMenu"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="1"/>
</Property>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -202,6 +175,115 @@
</Property>
</Properties>
</Component>
<Container class="javax.swing.JSplitPane" name="jSplitPane2">
<Properties>
<Property name="dividerLocation" type="int" value="470"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="jSplitPane3">
<Properties>
<Property name="dividerLocation" type="int" value="150"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="caseScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="casesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="casesTableModel" type="code"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="dataSourceScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="dataSourcesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="2" rowCount="0">
<Column editable="false" title="Data Source Name" type="java.lang.Object"/>
<Column editable="false" title="Device ID" type="java.lang.Object"/>
</Table>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="propertiesTableScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 30]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="filesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="tableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.table.toolTip.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
<TableColumnModel selectionModel="0"/>
</Property>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="rightClickPopupMenu"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="1"/>
</Property>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>

View File

@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
@ -48,9 +47,8 @@ import static javax.swing.JOptionPane.DEFAULT_OPTION;
import static javax.swing.JOptionPane.PLAIN_MESSAGE;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import javax.swing.JPanel;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
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;
@ -86,18 +84,23 @@ import org.sleuthkit.datamodel.TskData;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
@ServiceProvider(service = DataContentViewer.class, position = 9)
@Messages({"DataContentViewerOtherCases.title=Other Occurrences",
"DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",})
"DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",
"DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.",
"DataContentViewerOtherCases.table.noResultsFound=No results found."})
public class DataContentViewerOtherCases extends JPanel implements DataContentViewer {
private static final long serialVersionUID = -1L;
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_noArtifacts());
private static final int DEFAULT_MIN_CELL_WIDTH = 15;
private static final int CELL_TEXT_WIDTH_PADDING = 5;
private final DataContentViewerOtherCasesTableModel tableModel;
private final OtherOccurrencesFilesTableModel tableModel;
private final OtherOccurrencesCasesTableModel casesTableModel;
private final Collection<CorrelationAttributeInstance> correlationAttributes;
private String dataSourceName = "";
private String deviceId = "";
/**
* Could be null.
*/
@ -107,7 +110,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* Creates new form DataContentViewerOtherCases
*/
public DataContentViewerOtherCases() {
this.tableModel = new DataContentViewerOtherCasesTableModel();
this.tableModel = new OtherOccurrencesFilesTableModel();
this.casesTableModel = new OtherOccurrencesCasesTableModel();
this.correlationAttributes = new ArrayList<>();
initComponents();
@ -121,9 +125,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(selectAllMenuItem)) {
otherCasesTable.selectAll();
filesTable.selectAll();
} else if (jmi.equals(showCaseDetailsMenuItem)) {
showCaseDetails(otherCasesTable.getSelectedRow());
showCaseDetails(filesTable.getSelectedRow());
} else if (jmi.equals(exportToCSVMenuItem)) {
try {
saveToCSV();
@ -142,22 +146,24 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
showCommonalityMenuItem.addActionListener(actList);
// Set background of every nth row as light grey.
TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer();
otherCasesTable.setDefaultRenderer(Object.class, renderer);
TableCellRenderer renderer = new OtherOccurrencesFilesTableCellRenderer();
filesTable.setDefaultRenderer(Object.class, renderer);
// Configure column sorting.
TableRowSorter<TableModel> sorter = new TableRowSorter<>(otherCasesTable.getModel());
otherCasesTable.setRowSorter(sorter);
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
int caseNameColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.CASE_NAME.ordinal();
sortKeys.add(new RowSorter.SortKey(caseNameColumnIndex, SortOrder.ASCENDING));
int dataSourceColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.DATA_SOURCE.ordinal();
sortKeys.add(new RowSorter.SortKey(dataSourceColumnIndex, SortOrder.ASCENDING));
sorter.setSortKeys(sortKeys);
sorter.sort();
TableRowSorter<TableModel> sorter = new TableRowSorter<>(filesTable.getModel());
filesTable.setRowSorter(sorter);
casesTable.getSelectionModel().addListSelectionListener((e) -> {
if (Case.isCaseOpen()) {
updateOnCaseSelection();
}
});
dataSourcesTable.getSelectionModel().addListSelectionListener((e) -> {
if (Case.isCaseOpen()) {
updateOnDataSourceSelection();
}
});
casesTable.getRowSorter().toggleSortOrder(0);
dataSourcesTable.getRowSorter().toggleSortOrder(0);
}
@Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.",
@ -217,7 +223,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
if (-1 != selectedRowViewIdx) {
EamDb dbManager = EamDb.getInstance();
int selectedRowModelIdx = otherCasesTable.convertRowIndexToModel(selectedRowViewIdx);
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) tableModel.getRow(selectedRowModelIdx);
CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
if (eamCasePartial == null) {
@ -259,14 +265,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
private void saveToCSV() throws NoCurrentCaseException {
if (0 != otherCasesTable.getSelectedRowCount()) {
if (0 != filesTable.getSelectedRowCount()) {
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()));
CSVFileChooser.setSelectedFile(new File(fileName));
CSVFileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv"));
int returnVal = CSVFileChooser.showSaveDialog(otherCasesTable);
int returnVal = CSVFileChooser.showSaveDialog(filesTable);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selectedFile = CSVFileChooser.getSelectedFile();
@ -281,7 +287,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private void writeSelectedRowsToFileAsCSV(File destFile) {
StringBuilder content;
int[] selectedRowViewIndices = otherCasesTable.getSelectedRows();
int[] selectedRowViewIndices = filesTable.getSelectedRows();
int colCount = tableModel.getColumnCount();
try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) {
@ -302,7 +308,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
for (int rowViewIdx : selectedRowViewIndices) {
content = new StringBuilder("");
for (int colIdx = 0; colIdx < colCount; colIdx++) {
int rowModelIdx = otherCasesTable.convertRowIndexToModel(rowViewIdx);
int rowModelIdx = filesTable.convertRowIndexToModel(rowViewIdx);
content.append('"').append(tableModel.getValueAt(rowModelIdx, colIdx)).append('"');
if (colIdx < (colCount - 1)) {
content.append(",");
@ -322,6 +328,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
*/
private void reset() {
// start with empty table
casesTableModel.clearTable();
((DefaultTableModel) dataSourcesTable.getModel()).setRowCount(0);
tableModel.clearTable();
correlationAttributes.clear();
earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable());
@ -357,40 +365,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
public int isPreferred(Node node) {
return 1;
}
/**
* Set the number of unique cases and data sources.
*/
@Messages({
"DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources."
})
private void setOccurrenceCounts() {
DataContentViewerOtherCasesTableModel model = (DataContentViewerOtherCasesTableModel) otherCasesTable.getModel();
int caseColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.CASE_NAME.ordinal();
int deviceColumnIndex = DataContentViewerOtherCasesTableModel.TableColumns.DEVICE.ordinal();
/*
* We need a unique set of data sources. We rely on device ID for this.
* To mitigate edge cases where a device ID could be duplicated in the
* same case (e.g. "report.xml"), we put the device ID and case name in
* a key-value pair.
*
* Note: Relying on the case name isn't a fool-proof way of determining
* a case to be unique. We should improve this in the future.
*/
Set<String> cases = new HashSet<>();
Map<String, String> devices = new HashMap<>();
for (int i=0; i < model.getRowCount(); i++) {
String caseName = (String) model.getValueAt(i, caseColumnIndex);
String deviceId = (String) model.getValueAt(i, deviceColumnIndex);
cases.add(caseName);
devices.put(deviceId, caseName);
}
foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), model.getRowCount(), cases.size(), devices.size()));
}
/**
* Get the associated BlackboardArtifact from a node, if it exists.
@ -399,9 +373,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
*
* @return The associated BlackboardArtifact, or null
*/
private BlackboardArtifact getBlackboardArtifactFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class);
private BlackboardArtifact
getBlackboardArtifactFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class
);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class
);
if (nodeBbArtifactTag != null) {
return nodeBbArtifactTag.getArtifact();
@ -410,6 +387,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
return null;
}
/**
@ -420,10 +398,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* @return The associated AbstractFile, or null
*/
private AbstractFile getAbstractFileFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class);
ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class);
AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class);
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class
);
ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class
);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class
);
AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class
);
if (nodeBbArtifactTag != null) {
Content content = nodeBbArtifactTag.getContent();
@ -489,7 +471,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()),
file.getParentPath() + file.getName(),
"",
file.getKnown(),
file.getKnown(),
file.getId()));
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex);
@ -574,7 +556,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private Map<UniquePathKey, OtherOccurrenceNodeInstanceData> getCorrelatedInstances(CorrelationAttributeInstance corAttr, String dataSourceName, String deviceId) {
// @@@ Check exception
try {
final Case openCase = Case.getCurrentCase();
final Case openCase = Case.getCurrentCaseThrows();
String caseUUID = openCase.getName();
HashMap<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap = new HashMap<>();
@ -709,14 +691,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
if (EamDb.isEnabled()) {
return !getCorrelationAttributesFromNode(node).isEmpty();
} else {
return this.file != null
return this.file != null
&& this.file.getSize() > 0
&& ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty()));
}
}
@Override
@Messages({"DataContentViewerOtherCases.table.nodbconnection=Cannot connect to central repository database."})
public void setNode(Node node) {
reset(); // reset the table to empty.
@ -726,6 +707,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
//could be null
this.file = this.getAbstractFileFromNode(node);
populateTable(node);
}
/**
@ -735,12 +717,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* @param node The node being viewed.
*/
@Messages({
"DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.",
"DataContentViewerOtherCases.table.noResultsFound=No results found."
"DataContentViewerOtherCases.dataSources.header.text=Data Source Name",
"DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources."
})
private void populateTable(Node node) {
String dataSourceName = "";
String deviceId = "";
try {
if (this.file != null) {
Content dataSource = this.file.getDataSource();
@ -754,43 +734,129 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
// get the attributes we can correlate on
correlationAttributes.addAll(getCorrelationAttributesFromNode(node));
Map<String, CorrelationCase> caseNames = new HashMap<>();
int totalCount = 0;
Set<String> dataSources = new HashSet<>();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
correlatedNodeDataMap.values().forEach((nodeData) -> {
tableModel.addNodeData(nodeData);
});
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
if (nodeData.isCentralRepoNode()) {
try {
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());
}
} 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");
}
}
totalCount++;
}
}
for (CorrelationCase corCase : caseNames.values()) {
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
}
int caseCount = casesTableModel.getRowCount();
if (correlationAttributes.isEmpty()) {
tableModel.addNodeData(new OtherOccurrenceNodeMessageData(Bundle.DataContentViewerOtherCases_table_noArtifacts()));
setColumnWidthToText(0, Bundle.DataContentViewerOtherCases_table_noArtifacts());
} else if (0 == tableModel.getRowCount()) {
tableModel.addNodeData(new OtherOccurrenceNodeMessageData(Bundle.DataContentViewerOtherCases_table_noResultsFound()));
setColumnWidthToText(0, Bundle.DataContentViewerOtherCases_table_noResultsFound());
} else {
setColumnWidths();
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
} else if (caseCount == 0) {
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
}
setColumnWidths();
setEarliestCaseDate();
setOccurrenceCounts();
foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), totalCount, caseCount, dataSources.size()));
if (caseCount > 0) {
casesTable.setRowSelectionInterval(0, 0);
}
}
/**
* Adjust a given column for the text provided.
*
* @param columnIndex The index of the column to adjust.
* @param text The text whose length will be used to adjust the
* column width.
* Create a unique string to be used as a key for deduping data sources as
* best as possible
*/
private void setColumnWidthToText(int columnIndex, String text) {
TableColumn column = otherCasesTable.getColumnModel().getColumn(columnIndex);
FontMetrics fontMetrics = otherCasesTable.getFontMetrics(otherCasesTable.getFont());
int stringWidth = fontMetrics.stringWidth(text);
column.setMinWidth(stringWidth + CELL_TEXT_WIDTH_PADDING);
private String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) {
return caseUUID + deviceId + dataSourceName;
}
/**
* Updates diplayed information to be correct for the current case selection
*/
private void updateOnCaseSelection() {
int[] selectedCaseIndexes = casesTable.getSelectedRows();
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
dataSourceModel.setRowCount(0);
tableModel.clearTable();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> 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
&& ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()});
}
} 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());
}
}
}
}
if (dataSourcesTable.getRowCount() > 0) {
dataSourcesTable.setRowSelectionInterval(0, 0);
}
}
/**
* Updates diplayed information to be correct for the current data source
* selection
*/
private void updateOnDataSourceSelection() {
int[] selectedCaseIndexes = casesTable.getSelectedRows();
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
int[] selectedDataSources = dataSourcesTable.getSelectedRows();
tableModel.clearTable();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> 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 selectedCaseRow : selectedCaseIndexes) {
for (int selectedDataSourceRow : selectedDataSources) {
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);
}
} else {
if (dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) {
tableModel.addNodeData(nodeData);
}
}
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName());
}
}
}
}
}
}
/**
@ -798,13 +864,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
*/
private void setColumnWidths() {
for (int idx = 0; idx < tableModel.getColumnCount(); idx++) {
TableColumn column = otherCasesTable.getColumnModel().getColumn(idx);
TableColumn column = filesTable.getColumnModel().getColumn(idx);
column.setMinWidth(DEFAULT_MIN_CELL_WIDTH);
int columnWidth = tableModel.getColumnPreferredWidth(idx);
if (columnWidth > 0) {
column.setPreferredWidth(columnWidth);
}
}
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);
} else {
dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(210);
}
}
}
/**
@ -824,11 +897,17 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
CSVFileChooser = new javax.swing.JFileChooser();
otherCasesPanel = new javax.swing.JPanel();
tableContainerPanel = new javax.swing.JPanel();
tableScrollPane = new javax.swing.JScrollPane();
otherCasesTable = new javax.swing.JTable();
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();
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();
filesTable = new javax.swing.JTable();
rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
@ -856,19 +935,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
setOpaque(false);
setPreferredSize(new java.awt.Dimension(1500, 44));
otherCasesPanel.setPreferredSize(new java.awt.Dimension(1500, 144));
otherCasesPanel.setPreferredSize(new java.awt.Dimension(921, 62));
tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63));
tableScrollPane.setPreferredSize(new java.awt.Dimension(1500, 30));
otherCasesTable.setAutoCreateRowSorter(true);
otherCasesTable.setModel(tableModel);
otherCasesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N
otherCasesTable.setComponentPopupMenu(rightClickPopupMenu);
otherCasesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);
tableScrollPane.setViewportView(otherCasesTable);
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
@ -876,45 +946,93 @@ 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);
jSplitPane3.setDividerLocation(150);
casesTable.setAutoCreateRowSorter(true);
casesTable.setModel(casesTableModel);
caseScrollPane.setViewportView(casesTable);
jSplitPane3.setLeftComponent(caseScrollPane);
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];
}
});
dataSourceScrollPane.setViewportView(dataSourcesTable);
jSplitPane3.setRightComponent(dataSourceScrollPane);
jSplitPane2.setLeftComponent(jSplitPane3);
propertiesTableScrollPane.setPreferredSize(new java.awt.Dimension(1000, 30));
filesTable.setAutoCreateRowSorter(true);
filesTable.setModel(tableModel);
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);
jSplitPane2.setRightComponent(propertiesTableScrollPane);
javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel);
tableContainerPanel.setLayout(tableContainerPanelLayout);
tableContainerPanelLayout.setHorizontalGroup(
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1508, Short.MAX_VALUE)
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addComponent(earliestCaseLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(earliestCaseDate)
.addGap(66, 66, 66)
.addComponent(foundInLabel)
.addGap(0, 1157, Short.MAX_VALUE))
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addComponent(earliestCaseLabel)
.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))
.addContainerGap())
);
tableContainerPanelLayout.setVerticalGroup(
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup()
.addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE)
.addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 31, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(earliestCaseLabel)
.addComponent(earliestCaseDate)
.addComponent(foundInLabel))
.addGap(6, 6, 6))
.addContainerGap())
);
javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel);
otherCasesPanel.setLayout(otherCasesPanelLayout);
otherCasesPanelLayout.setHorizontalGroup(
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 1500, Short.MAX_VALUE)
.addGap(0, 921, Short.MAX_VALUE)
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(tableContainerPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.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, 61, Short.MAX_VALUE)
.addGap(0, 62, Short.MAX_VALUE)
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(otherCasesPanelLayout.createSequentialGroup()
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 53, Short.MAX_VALUE)
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE)
.addGap(0, 0, 0)))
);
@ -922,19 +1040,19 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 53, Short.MAX_VALUE)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible
boolean enableCentralRepoActions = false;
if (EamDb.isEnabled() && otherCasesTable.getSelectedRowCount() == 1) {
int rowIndex = otherCasesTable.getSelectedRow();
if (EamDb.isEnabled() && filesTable.getSelectedRowCount() == 1) {
int rowIndex = filesTable.getSelectedRow();
OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex);
if (selectedNode instanceof OtherOccurrenceNodeInstanceData) {
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedNode;
@ -947,18 +1065,24 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JFileChooser CSVFileChooser;
private javax.swing.JScrollPane caseScrollPane;
private javax.swing.JTable casesTable;
private javax.swing.JScrollPane dataSourceScrollPane;
private javax.swing.JTable dataSourcesTable;
private javax.swing.JLabel earliestCaseDate;
private javax.swing.JLabel earliestCaseLabel;
private javax.swing.JMenuItem exportToCSVMenuItem;
private javax.swing.JTable filesTable;
private javax.swing.JLabel foundInLabel;
private javax.swing.JSplitPane jSplitPane2;
private javax.swing.JSplitPane jSplitPane3;
private javax.swing.JPanel otherCasesPanel;
private javax.swing.JTable otherCasesTable;
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.JScrollPane tableScrollPane;
// End of variables declaration//GEN-END:variables
/**

View File

@ -0,0 +1,149 @@
/*
* Central Repository
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages;
/**
* Model for cells in the cases section of the other occurrences data content
* viewer
*/
public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final List<CorrelationCaseWrapper> correlationCaseList = new ArrayList<>();
OtherOccurrencesCasesTableModel() {
}
@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();
}
@Override
public int getRowCount() {
return correlationCaseList.size();
}
@Override
public String getColumnName(int colIdx) {
return TableColumns.values()[colIdx].columnName();
}
@Override
public Object getValueAt(int rowIdx, int colIdx) {
if (0 == correlationCaseList.size()) {
return Bundle.OtherOccurrencesCasesTableModel_noData();
}
CorrelationCaseWrapper caseWrapper = correlationCaseList.get(rowIdx);
TableColumns columnId = TableColumns.values()[colIdx];
return mapCorrelationCase(caseWrapper, columnId);
}
/**
* Map a column ID to the value in that cell for correlation case wrapper.
*
* @param correlationCaseWrapper The correlation case wrapper
* @param columnId The ID of the cell column.
*
* @return The value in the cell.
*/
@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;
}
return value;
}
Object getCorrelationCase(int rowIdx) {
return correlationCaseList.get(rowIdx).getCorrelationCase();
}
@Override
public Class<String> getColumnClass(int colIdx) {
return String.class;
}
/**
* Add one correlated instance object to the table
*
* @param newCorrelationCaseWrapper data to add to the table
*/
void addCorrelationCase(CorrelationCaseWrapper newCorrelationCaseWrapper) {
correlationCaseList.add(newCorrelationCaseWrapper);
fireTableDataChanged();
}
/**
* Clear the correlation case table.
*/
void clearTable() {
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;
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2017 Basis Technology Corp.
* Copyright 2015-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -20,16 +20,16 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
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 data content viewer table
* Renderer for cells in the files section of the other occurrences data content viewer
*/
public class DataContentViewerOtherCasesTableCellRenderer implements TableCellRenderer {
public class OtherOccurrencesFilesTableCellRenderer implements TableCellRenderer {
public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer();
@ -43,20 +43,19 @@ public class DataContentViewerOtherCasesTableCellRenderer implements TableCellRe
int column) {
Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
((JLabel) renderer).setOpaque(true);
((JComponent) renderer).setOpaque(true);
Color foreground, background;
if (isSelected) {
foreground = Color.WHITE;
background = Color.BLUE;
background = new Color(51,153,255);
} else {
String known_status = (String) table.getModel().getValueAt(table.convertRowIndexToModel(row),
table.getColumn(DataContentViewerOtherCasesTableModel.TableColumns.KNOWN.columnName()).getModelIndex());
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.YELLOW;
background = Color.WHITE;
} else {
foreground = Color.BLACK;

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2015-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -24,33 +24,29 @@ import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages;
/**
* Model for cells in data content viewer table
* Model for cells in the files section of the other occurrences data content viewer
*/
public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
@Messages({"DataContentViewerOtherCasesTableModel.case=Case",
"DataContentViewerOtherCasesTableModel.device=Device",
"DataContentViewerOtherCasesTableModel.dataSource=Data Source",
"DataContentViewerOtherCasesTableModel.path=Path",
"DataContentViewerOtherCasesTableModel.attribute=Matched Attribute",
"DataContentViewerOtherCasesTableModel.value=Attribute Value",
"DataContentViewerOtherCasesTableModel.known=Known",
"DataContentViewerOtherCasesTableModel.comment=Comment",
"DataContentViewerOtherCasesTableModel.noData=No Data.",})
@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.
CASE_NAME(Bundle.DataContentViewerOtherCasesTableModel_case(), 100),
DATA_SOURCE(Bundle.DataContentViewerOtherCasesTableModel_dataSource(), 100),
ATTRIBUTE(Bundle.DataContentViewerOtherCasesTableModel_attribute(), 125),
VALUE(Bundle.DataContentViewerOtherCasesTableModel_value(), 200),
KNOWN(Bundle.DataContentViewerOtherCasesTableModel_known(), 50),
FILE_PATH(Bundle.DataContentViewerOtherCasesTableModel_path(), 450),
COMMENT(Bundle.DataContentViewerOtherCasesTableModel_comment(), 200),
DEVICE(Bundle.DataContentViewerOtherCasesTableModel_device(), 250);
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;
@ -68,10 +64,10 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
}
};
private final List<OtherOccurrenceNodeData> nodeDataList;
private final List<OtherOccurrenceNodeData> nodeDataList = new ArrayList<>();
DataContentViewerOtherCasesTableModel() {
nodeDataList = new ArrayList<>();
OtherOccurrencesFilesTableModel() {
}
@Override
@ -106,7 +102,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
@Override
public Object getValueAt(int rowIdx, int colIdx) {
if (0 == nodeDataList.size()) {
return Bundle.DataContentViewerOtherCasesTableModel_noData();
return Bundle.OtherOccurrencesFilesTableModel_noData();
}
OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx);
@ -126,7 +122,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
* @return The value in the cell.
*/
private Object mapNodeMessageData(OtherOccurrenceNodeMessageData nodeData, TableColumns columnId) {
if (columnId == TableColumns.CASE_NAME) {
if (columnId == TableColumns.ATTRIBUTE) {
return nodeData.getDisplayMessage();
}
return "";
@ -141,24 +137,9 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
* @return The value in the cell.
*/
private Object mapNodeInstanceData(OtherOccurrenceNodeInstanceData nodeData, TableColumns columnId) {
String value = Bundle.DataContentViewerOtherCasesTableModel_noData();
String value = Bundle.OtherOccurrencesFilesTableModel_noData();
switch (columnId) {
case CASE_NAME:
if (null != nodeData.getCaseName()) {
value = nodeData.getCaseName();
}
break;
case DEVICE:
if (null != nodeData.getDeviceID()) {
value = nodeData.getDeviceID();
}
break;
case DATA_SOURCE:
if (null != nodeData.getDataSourceName()) {
value = nodeData.getDataSourceName();
}
break;
case FILE_PATH:
value = nodeData.getFilePath();
break;
@ -174,7 +155,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel {
case COMMENT:
value = nodeData.getComment();
break;
default: // This shouldn't occur! Use default "No data" value.
default: //Use default "No data" value.
break;
}
return value;

View File

@ -168,7 +168,7 @@ public class CorrelationDataSource implements Serializable {
*
* @return the ID or -1 if unknown
*/
int getID() {
public int getID() {
return dataSourceID;
}

View File

@ -1,96 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.commandlineingest;
import java.util.List;
import java.util.UUID;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.datamodel.Content;
/**
* A "callback" that collects the results of running a data source processor on
* a data source and unblocks the job processing thread when the data source
* processor finishes running in its own thread.
*/
class AddDataSourceCallback extends DataSourceProcessorCallback {
private final Case caseForJob;
private final DataSource dataSourceInfo;
private final UUID taskId;
private final Object lock;
/**
* Constructs a "callback" that collects the results of running a data
* source processor on a data source and unblocks the job processing thread
* when the data source processor finishes running in its own thread.
*
* @param caseForJob The case for the current job.
* @param dataSourceInfo The data source
* @param taskId The task id to associate with ingest job events.
*/
AddDataSourceCallback(Case caseForJob, DataSource dataSourceInfo, UUID taskId, Object lock) {
this.caseForJob = caseForJob;
this.dataSourceInfo = dataSourceInfo;
this.taskId = taskId;
this.lock = lock;
}
/**
* Called by the data source processor when it finishes running in its own
* thread.
*
* @param result The result code for the processing of the data source.
* @param errorMessages Any error messages generated during the processing
* of the data source.
* @param dataSourceContent The content produced by processing the data
* source.
*/
@Override
public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errorMessages, List<Content> dataSourceContent) {
if (!dataSourceContent.isEmpty()) {
caseForJob.notifyDataSourceAdded(dataSourceContent.get(0), taskId);
} else {
caseForJob.notifyFailedAddingDataSource(taskId);
}
dataSourceInfo.setDataSourceProcessorOutput(result, errorMessages, dataSourceContent);
dataSourceContent.addAll(dataSourceContent);
synchronized (lock) {
lock.notifyAll();
}
}
/**
* Called by the data source processor when it finishes running in its own
* thread, if that thread is the AWT (Abstract Window Toolkit) event
* dispatch thread (EDT).
*
* @param result The result code for the processing of the data source.
* @param errorMessages Any error messages generated during the processing
* of the data source.
* @param dataSourceContent The content produced by processing the data
* source.
*/
@Override
public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errorMessages, List<Content> dataSources) {
done(result, errorMessages, dataSources);
}
}

View File

@ -43,6 +43,9 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSource;
import org.sleuthkit.autopsy.datasourceprocessors.AddDataSourceCallback;
import org.sleuthkit.autopsy.datasourceprocessors.DataSourceProcessorUtility;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
@ -177,7 +180,7 @@ public class CommandLineIngestManager {
return;
}
DataSource dataSource = new DataSource("", Paths.get(dataSourcePath));
AutoIngestDataSource dataSource = new AutoIngestDataSource("", Paths.get(dataSourcePath));
try {
// run data source processor
runDataSourceProcessor(caseForJob, dataSource);
@ -228,7 +231,7 @@ public class CommandLineIngestManager {
* @param dataSource DataSource object
* @return object ID
*/
private Long getDataSourceId(DataSource dataSource) {
private Long getDataSourceId(AutoIngestDataSource dataSource) {
Content content = dataSource.getContent().get(0);
return content.getId();
}
@ -271,7 +274,7 @@ public class CommandLineIngestManager {
* task is interrupted while blocked, i.e., if auto ingest is shutting
* down.
*/
private void runDataSourceProcessor(Case caseForJob, DataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException {
private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException {
LOGGER.log(Level.INFO, "Adding data source {0} ", dataSource.getPath().toString());
@ -329,7 +332,7 @@ public class CommandLineIngestManager {
*
* @param dataSource The data source.
*/
private void logDataSourceProcessorResult(DataSource dataSource) {
private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) {
DataSourceProcessorCallback.DataSourceProcessorResult resultCode = dataSource.getResultDataSourceProcessorResultCode();
if (null != resultCode) {
@ -376,7 +379,7 @@ public class CommandLineIngestManager {
* task is interrupted while blocked, i.e., if auto ingest is shutting
* down.
*/
private void analyze(DataSource dataSource) throws AnalysisStartupException, InterruptedException {
private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, InterruptedException {
LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", dataSource.getPath());
IngestJobEventListener ingestJobEventListener = new IngestJobEventListener();

View File

@ -193,7 +193,12 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel {
if (outputPath.isEmpty()) {
jLabelInvalidResultsFolder.setVisible(true);
jLabelInvalidResultsFolder.setText(NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified"));
return false;
/*
NOTE: JIRA-4850: Returning false disables OK and Apply buttons for the entire
Tools->Options bar until the path is set. It was decided to only validate
the path if the path is set.
*/
return true;
}
if (!isFolderPathValid(outputPath)) {

View File

@ -1,66 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.commandlineingest;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
import org.sleuthkit.datamodel.Content;
class DataSource {
private final String deviceId;
private final Path path;
private DataSourceProcessorResult resultCode;
private List<String> errorMessages;
private List<Content> content;
DataSource(String deviceId, Path path) {
this.deviceId = deviceId;
this.path = path;
}
String getDeviceId() {
return deviceId;
}
Path getPath() {
return this.path;
}
synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List<String> errorMessages, List<Content> content) {
this.resultCode = result;
this.errorMessages = new ArrayList<>(errorMessages);
this.content = new ArrayList<>(content);
}
synchronized DataSourceProcessorResult getResultDataSourceProcessorResultCode() {
return resultCode;
}
synchronized List<String> getDataSourceProcessorErrorMessages() {
return new ArrayList<>(errorMessages);
}
synchronized List<Content> getContent() {
return new ArrayList<>(content);
}
}

View File

@ -26,6 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
@ -130,12 +132,12 @@ public abstract class AbstractCommonAttributeSearcher {
}
}
static Map<Integer, CommonAttributeValueList> collateMatchesByNumberOfInstances(Map<String, CommonAttributeValue> commonFiles) {
static TreeMap<Integer, CommonAttributeValueList> collateMatchesByNumberOfInstances(Map<String, CommonAttributeValue> commonFiles) {
//collate matches by number of matching instances - doing this in sql doesnt seem efficient
Map<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new TreeMap<>();
TreeMap<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new TreeMap<>();
for (CommonAttributeValue md5Metadata : commonFiles.values()) {
Integer size = md5Metadata.getInstanceCount();
Integer size = md5Metadata.getNumberOfDataSourcesInCurrentCase();
if (instanceCollatedCommonFiles.containsKey(size)) {
instanceCollatedCommonFiles.get(size).addMetadataToList(md5Metadata);
@ -147,7 +149,71 @@ public abstract class AbstractCommonAttributeSearcher {
}
return instanceCollatedCommonFiles;
}
/*
* The set of the MIME types that will be checked for extension mismatches
* when checkType is ONLY_MEDIA. ".jpg", ".jpeg", ".png", ".psd", ".nef",
* ".tiff", ".bmp", ".tec" ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v",
* //NON-NLS ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm",
* ".wmv", ".mpv", ".flv", ".swf"
*/
static final Set<String> MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of(
"image/bmp", //NON-NLS
"image/gif", //NON-NLS
"image/jpeg", //NON-NLS
"image/png", //NON-NLS
"image/tiff", //NON-NLS
"image/vnd.adobe.photoshop", //NON-NLS
"image/x-raw-nikon", //NON-NLS
"image/x-ms-bmp", //NON-NLS
"image/x-icon", //NON-NLS
"video/webm", //NON-NLS
"video/3gpp", //NON-NLS
"video/3gpp2", //NON-NLS
"video/ogg", //NON-NLS
"video/mpeg", //NON-NLS
"video/mp4", //NON-NLS
"video/quicktime", //NON-NLS
"video/x-msvideo", //NON-NLS
"video/x-flv", //NON-NLS
"video/x-m4v", //NON-NLS
"video/x-ms-wmv", //NON-NLS
"application/vnd.ms-asf", //NON-NLS
"application/vnd.rn-realmedia", //NON-NLS
"application/x-shockwave-flash" //NON-NLS
).collect(Collectors.toSet());
/*
* The set of the MIME types that will be checked for extension mismatches
* when checkType is ONLY_TEXT_FILES. ".doc", ".docx", ".odt", ".xls",
* ".xlsx", ".ppt", ".pptx" ".txt", ".rtf", ".log", ".text", ".xml" ".html",
* ".htm", ".css", ".js", ".php", ".aspx" ".pdf"
* //ignore text/plain due to large number of results with that type
*/
static final Set<String> TEXT_FILES_MIME_TYPES = Stream.of(
"application/rtf", //NON-NLS
"application/pdf", //NON-NLS
"text/css", //NON-NLS
"text/html", //NON-NLS
"text/csv", //NON-NLS
"application/json", //NON-NLS
"application/javascript", //NON-NLS
"application/xml", //NON-NLS
"text/calendar", //NON-NLS
"application/x-msoffice", //NON-NLS
"application/x-ooxml", //NON-NLS
"application/msword", //NON-NLS
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
"application/vnd.ms-powerpoint", //NON-NLS
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
"application/vnd.ms-excel", //NON-NLS
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
"application/vnd.oasis.opendocument.presentation", //NON-NLS
"application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
"application/vnd.oasis.opendocument.text" //NON-NLS
).collect(Collectors.toSet());
/**
* @return the filterByMedia
*/

View File

@ -44,5 +44,5 @@ CommonAttributePanel.resultsDisplayLabel.text_2=Display results organized by:
CommonAttributePanel.organizeByCaseRadio.text=Case
CommonAttributePanel.organizeByCountRadio.text=Number of occurrences
CommonAttributePanel.caseResultsRadioButton.text=Case
CommonAttributePanel.countResultsRadioButton.text=Number of occurrences
CommonAttributePanel.countResultsRadioButton.text=Number of data sources
CommonAttributePanel.displayResultsLabel.text_2=Display results organized by:

View File

@ -60,7 +60,7 @@ CommonFilesSearchResultsViewerTable.noDescText=\
CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path
CommonFilesSearchResultsViewerTable.valueColLbl=Value
InstanceCountNode.createSheet.noDescription=\
InstanceCountNode.displayName=Files with %s instances (%s)
InstanceCountNode.displayName=Exists in %s data sources (%s)
IntraCasePanel.selectDataSourceComboBox.actionCommand=
CommonAttributePanel.jCheckBox1.text=Hide files found in over
CommonAttributePanel.jLabel1.text=% of data sources in central repository.
@ -101,7 +101,7 @@ CommonAttributePanel.resultsDisplayLabel.text_2=Display results organized by:
CommonAttributePanel.organizeByCaseRadio.text=Case
CommonAttributePanel.organizeByCountRadio.text=Number of occurrences
CommonAttributePanel.caseResultsRadioButton.text=Case
CommonAttributePanel.countResultsRadioButton.text=Number of occurrences
CommonAttributePanel.countResultsRadioButton.text=Number of data sources
CommonAttributePanel.displayResultsLabel.text_2=Display results organized by:
# {0} - case name
# {1} - attr type

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
@ -58,7 +59,7 @@ final public class CommonAttributeCountSearchResults {
*/
CommonAttributeCountSearchResults(Map<Integer, CommonAttributeValueList> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType) {
//wrap in a new object in case any client code has used an unmodifiable collection
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.instanceCountToAttributeValues = new TreeMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = resultType.getId();
}
@ -73,7 +74,7 @@ final public class CommonAttributeCountSearchResults {
*/
CommonAttributeCountSearchResults(Map<Integer, CommonAttributeValueList> metadata, int percentageThreshold) {
//wrap in a new object in case any client code has used an unmodifiable collection
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.instanceCountToAttributeValues = new TreeMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = CorrelationAttributeInstance.FILES_TYPE_ID;
}

View File

@ -294,7 +294,11 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
DataResultTopComponent.createInstance(tabTitle, Bundle.CommonAttributePanel_search_results_pathText(), commonFilesNode, 1);
} else {
// -3969
Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata);
CorrelationAttributeInstance.Type correlationType = null;
if (interCaseRadio.isSelected()){
correlationType = interCasePanel.getSelectedCorrelationType();
}
Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata, correlationType);
DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this));
TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3);
DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable();

View File

@ -25,6 +25,7 @@ import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
@ -35,8 +36,8 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
*/
final public class CommonAttributeSearchResultRootNode extends DisplayableItemNode {
CommonAttributeSearchResultRootNode(CommonAttributeCountSearchResults metadataList) {
super(Children.create(new InstanceCountNodeFactory(metadataList), true));
CommonAttributeSearchResultRootNode(CommonAttributeCountSearchResults metadataList, CorrelationAttributeInstance.Type type) {
super(Children.create(new InstanceCountNodeFactory(metadataList, type), true));
}
CommonAttributeSearchResultRootNode(CommonAttributeCaseSearchResults metadataList) {
@ -73,6 +74,7 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo
private static final Logger LOGGER = Logger.getLogger(InstanceCountNodeFactory.class.getName());
private final CommonAttributeCountSearchResults searchResults;
private final CorrelationAttributeInstance.Type type;
/**
* Build a factory which converts a
@ -81,8 +83,9 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo
*
* @param searchResults
*/
InstanceCountNodeFactory(CommonAttributeCountSearchResults searchResults) {
InstanceCountNodeFactory(CommonAttributeCountSearchResults searchResults, CorrelationAttributeInstance.Type type) {
this.searchResults = searchResults;
this.type = type;
}
@Override
@ -94,7 +97,7 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo
@Override
protected Node createNodeForKey(Integer instanceCount) {
CommonAttributeValueList attributeValues = this.searchResults.getAttributeValuesForInstanceCount(instanceCount);
return new InstanceCountNode(instanceCount, attributeValues);
return new InstanceCountNode(instanceCount, attributeValues, type);
}
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,10 +22,13 @@ package org.sleuthkit.autopsy.commonpropertiessearch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Defines a value that was in the common file search results as well as
@ -35,6 +38,7 @@ final public class CommonAttributeValue {
private final String value;
private final List<AbstractCommonAttributeInstance> fileInstances;
private final Map<String, Integer> fileNames = new HashMap<>();
CommonAttributeValue(String value) {
this.value = value;
@ -45,6 +49,23 @@ final public class CommonAttributeValue {
return this.value;
}
/**
* Get the file name of the first available instance of this value.
*
* @return the file name of an instance of this file
*/
String getTokenFileName() {
String tokenFileName = null;
int maxValue = 0;
for (String key : fileNames.keySet()){
if (fileNames.get(key) > maxValue){
maxValue = fileNames.get(key);
tokenFileName = key;
}
}
return tokenFileName;
}
/**
* concatenate cases this value was seen into a single string
*
@ -54,16 +75,43 @@ final public class CommonAttributeValue {
return this.fileInstances.stream().map(AbstractCommonAttributeInstance::getCaseName).collect(Collectors.joining(", "));
}
public String getDataSources() {
/**
* Get the set of data sources names this value exists in
*
* @return a set of data source names
*/
public Set<String> getDataSources() {
Set<String> sources = new HashSet<>();
for (AbstractCommonAttributeInstance data : this.fileInstances) {
sources.add(data.getDataSource());
}
return sources;
}
return String.join(", ", sources);
/**
* Get the number of unique data sources in the current case which the value
* appeared in.
*
* @return the number of unique data sources in the current case which
* contained the value
*/
int getNumberOfDataSourcesInCurrentCase() {
Set<Long> dataSourceIds = new HashSet<>();
for (AbstractCommonAttributeInstance data : this.fileInstances) {
AbstractFile file = data.getAbstractFile();
if (file != null) {
dataSourceIds.add(file.getDataSourceObjectId());
}
}
return dataSourceIds.size();
}
void addInstance(AbstractCommonAttributeInstance metadata) {
if (metadata.getAbstractFile() != null) {
Integer currentValue = fileNames.get(metadata.getAbstractFile().getName());
currentValue = currentValue == null ? 1 : currentValue+1;
fileNames.put(metadata.getAbstractFile().getName(), currentValue);
}
this.fileInstances.add(metadata);
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -25,6 +25,8 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import static org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.FILES_TYPE_ID;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
@ -49,15 +51,19 @@ public class CommonAttributeValueNode extends DisplayableItemNode {
*
* @param data the common feature, and the children
*/
public CommonAttributeValueNode(CommonAttributeValue data) {
public CommonAttributeValueNode(CommonAttributeValue data, CorrelationAttributeInstance.Type type) {
super(Children.create(
new FileInstanceNodeFactory(data), true));
this.commonFileCount = data.getInstanceCount();
this.cases = data.getCases();
// @@ We seem to be doing this string concat twice. We also do it in getDataSources()
this.dataSources = String.join(", ", data.getDataSources());
this.value = data.getValue();
this.setDisplayName(String.format(Bundle.CommonAttributeValueNode_CommonAttributeValueNode_format(), this.value));
//if the type is null (indicating intra-case) or files then make the node name the representitive file name
if (type == null || type.getId() == FILES_TYPE_ID) {
this.setDisplayName(data.getTokenFileName());
} else {
this.setDisplayName(String.format(Bundle.CommonAttributeValueNode_CommonAttributeValueNode_format(), this.value));
}
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -28,6 +28,7 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
@ -45,6 +46,7 @@ public final class InstanceCountNode extends DisplayableItemNode {
final private int instanceCount;
final private CommonAttributeValueList attributeValues;
final private CorrelationAttributeInstance.Type type;
/**
* Create a node with the given number of instances, and the given selection
@ -54,11 +56,11 @@ public final class InstanceCountNode extends DisplayableItemNode {
* @param attributeValues
*/
@NbBundle.Messages({
"InstanceCountNode.displayName=Files with %s instances (%s)"
"InstanceCountNode.displayName=Exists in %s data sources (%s)"
})
public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues) {
super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), false));
public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues, CorrelationAttributeInstance.Type type) {
super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), false));
this.type = type;
this.instanceCount = instanceCount;
this.attributeValues = attributeValues;
@ -81,7 +83,7 @@ public final class InstanceCountNode extends DisplayableItemNode {
*/
void createChildren() {
attributeValues.displayDelayedMetadata();
setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), false));
setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), false));
}
/**
@ -146,10 +148,11 @@ public final class InstanceCountNode extends DisplayableItemNode {
*/
// maps sting version of value to value Object (??)
private final Map<String, CommonAttributeValue> metadata;
private final CorrelationAttributeInstance.Type type;
CommonAttributeValueNodeFactory(List<CommonAttributeValue> attributeValues) {
CommonAttributeValueNodeFactory(List<CommonAttributeValue> attributeValues, CorrelationAttributeInstance.Type type) {
this.metadata = new HashMap<>();
this.type = type;
Iterator<CommonAttributeValue> iterator = attributeValues.iterator();
while (iterator.hasNext()) {
CommonAttributeValue attributeValue = iterator.next();
@ -167,7 +170,7 @@ public final class InstanceCountNode extends DisplayableItemNode {
@Override
protected Node createNodeForKey(String attributeValue) {
CommonAttributeValue md5Metadata = this.metadata.get(attributeValue);
return new CommonAttributeValueNode(md5Metadata);
return new CommonAttributeValueNode(md5Metadata, type);
}
}
}

View File

@ -134,7 +134,6 @@
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="InterCasePanel.allFileCategoriesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -152,6 +151,7 @@
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="InterCasePanel.selectedFileCategoriesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -164,7 +164,6 @@ public final class InterCasePanel extends javax.swing.JPanel {
categoriesLabel.setName(""); // NOI18N
buttonGroup.add(allFileCategoriesRadioButton);
allFileCategoriesRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.text")); // NOI18N
allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N
allFileCategoriesRadioButton.setEnabled(false);
@ -175,6 +174,7 @@ public final class InterCasePanel extends javax.swing.JPanel {
});
buttonGroup.add(selectedFileCategoriesButton);
selectedFileCategoriesButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.text")); // NOI18N
selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N
selectedFileCategoriesButton.setEnabled(false);

View File

@ -29,7 +29,9 @@ import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
@ -173,7 +175,7 @@ final class InterCaseSearchResultsProcessor {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
return new TreeMap<>();
}
/**
@ -205,7 +207,7 @@ final class InterCaseSearchResultsProcessor {
} catch (EamDbException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex);
}
return new HashMap<>();
return new TreeMap<>();
}
/**
@ -248,7 +250,7 @@ final class InterCaseSearchResultsProcessor {
*/
private class InterCaseByCountCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback, InstanceTableCallback {
private final Map<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new HashMap<>();
private final TreeMap<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new TreeMap<>();
private final int caseID;
private final int targetCase;
@ -284,7 +286,7 @@ final class InterCaseSearchResultsProcessor {
} else {
instances = EamDb.getInstance().getArtifactInstancesByTypeValuesAndCases(correlationType, Arrays.asList(corValue), targetCases);
}
int size = instances.size();
int size = instances.stream().map(instance -> instance.getCorrelationDataSource().getID()).collect(Collectors.toSet()).size();
if (size > 1) {
CommonAttributeValue commonAttributeValue = new CommonAttributeValue(corValue);
boolean anotherCase = false;
@ -311,7 +313,7 @@ final class InterCaseSearchResultsProcessor {
}
Map<Integer, CommonAttributeValueList> getInstanceCollatedCommonFiles() {
return Collections.unmodifiableMap(instanceCollatedCommonFiles);
return Collections.unmodifiableSortedMap(instanceCollatedCommonFiles);
}
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -99,6 +99,7 @@
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="IntraCasePanel.selectedFileCategoriesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -116,7 +117,6 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="IntraCasePanel.pictureVideoCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pictureVideoCheckboxActionPerformed"/>
@ -128,7 +128,6 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="IntraCasePanel.documentsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="documentsCheckboxActionPerformed"/>
@ -139,7 +138,6 @@
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties" key="IntraCasePanel.allFileCategoriesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -154,6 +154,7 @@ public final class IntraCasePanel extends javax.swing.JPanel {
categoriesLabel.setName(""); // NOI18N
buttonGroup.add(selectedFileCategoriesButton);
selectedFileCategoriesButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.text")); // NOI18N
selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N
selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() {
@ -164,7 +165,6 @@ public final class IntraCasePanel extends javax.swing.JPanel {
pictureVideoCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.pictureVideoCheckbox.text")); // NOI18N
pictureVideoCheckbox.setEnabled(false);
pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pictureVideoCheckboxActionPerformed(evt);
@ -173,7 +173,6 @@ public final class IntraCasePanel extends javax.swing.JPanel {
documentsCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.documentsCheckbox.text")); // NOI18N
documentsCheckbox.setEnabled(false);
documentsCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
documentsCheckboxActionPerformed(evt);
@ -181,7 +180,6 @@ public final class IntraCasePanel extends javax.swing.JPanel {
});
buttonGroup.add(allFileCategoriesRadioButton);
allFileCategoriesRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.text")); // NOI18N
allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N
allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() {

View File

@ -38,3 +38,4 @@ VisualizationPanel.organicLayoutButton.text=Organic
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
VisualizationPanel.clearVizButton.text_1=Clear Viz.
VisualizationPanel.snapshotButton.text_1=Snapshot Report

View File

@ -32,6 +32,23 @@ ResetAndPinAccountsAction.singularText=Visualize Only Selected Account
UnpinAccountsAction.pluralText=Remove Selected Accounts
UnpinAccountsAction.singularText=Remove Selected Account
VisalizationPanel.paintingError=Problem painting visualization.
# {0} - default name
VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}
VisualizationPane_blank_report_title=Blank Report Name
VisualizationPane_DisplayName=Open Report
VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:
VisualizationPane_MessageBoxTitle=Open Report Failure
VisualizationPane_MissingReportFileMessage=The report file no longer exists.
VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.
VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.
VisualizationPane_Open_Report=Open Report
# {0} - report name
VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}
VisualizationPane_Report_OK_Button=OK
# {0} - report path
VisualizationPane_Report_Success=Report Successfully create at:\n{0}
VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.
VisualizationPane_reportName=Communications Snapshot
VisualizationPanel.cancelButton.text=Cancel
VisualizationPanel.computingLayout=Computing Layout
VisualizationPanel.jButton1.text=Fast Organic
@ -65,3 +82,8 @@ VisualizationPanel.organicLayoutButton.text=Organic
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
VisualizationPanel.clearVizButton.text_1=Clear Viz.
VisualizationPanel.snapshotButton.text_1=Snapshot Report
VisualizationPanel_action_dialogs_title=Communications
VisualizationPanel_action_name_text=Snapshot Report
VisualizationPanel_module_name=Communications
VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation.

View File

@ -11,7 +11,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-93,0,0,3,71"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-93,0,0,4,-19"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
@ -49,9 +49,9 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace pref="71" max="32767" attributes="0"/>
<EmptySpace pref="268" max="32767" attributes="0"/>
<Component id="jTextArea1" min="-2" pref="424" max="-2" attributes="0"/>
<EmptySpace pref="248" max="32767" attributes="0"/>
<EmptySpace pref="445" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -120,6 +120,10 @@
<Component id="zoomActualButton" min="-2" pref="33" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fitZoomButton" min="-2" pref="32" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jSeparator3" min="-2" pref="10" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="snapshotButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
@ -143,6 +147,8 @@
<Component id="zoomLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="clearVizButton" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator2" alignment="2" max="32767" attributes="0"/>
<Component id="snapshotButton" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator3" alignment="2" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
</Group>
@ -310,6 +316,24 @@
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="snapshotButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/report/images/image.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.snapshotButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="snapshotButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JToolBar$Separator" name="jSeparator3">
<Properties>
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javafx.embed.swing.JFXPanel" name="notificationsJFXPanel">

View File

@ -28,6 +28,7 @@ import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
@ -41,19 +42,28 @@ import com.mxgraph.view.mxGraphView;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@ -75,14 +85,17 @@ import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.Notifications;
import org.jdesktop.layout.GroupLayout;
import org.jdesktop.layout.LayoutStyle;
@ -92,8 +105,11 @@ import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.communications.snapshot.CommSnapShotReportWriter;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
@ -101,7 +117,6 @@ import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A panel that goes in the Visualize tab of the Communications Visualization
* Tool. Hosts an JGraphX mxGraphComponent that implements the communications
@ -172,7 +187,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
public void paint(Graphics graphics) {
try {
super.paint(graphics);
} catch (NullPointerException ex) { //NOPMD
} catch (NullPointerException ex) { //NOPMD
/* We can't find the underlying cause of the NPE in
* jgraphx, but it doesn't seem to cause any
* noticeable problems, so we are just logging it
@ -387,6 +402,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
zoomLabel = new JLabel();
clearVizButton = new JButton();
jSeparator2 = new JToolBar.Separator();
snapshotButton = new JButton();
jSeparator3 = new JToolBar.Separator();
notificationsJFXPanel = new JFXPanel();
setLayout(new BorderLayout());
@ -406,9 +423,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
placeHolderPanel.setLayout(placeHolderPanelLayout);
placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
.add(placeHolderPanelLayout.createSequentialGroup()
.addContainerGap(71, Short.MAX_VALUE)
.addContainerGap(268, Short.MAX_VALUE)
.add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE)
.addContainerGap(248, Short.MAX_VALUE))
.addContainerGap(445, Short.MAX_VALUE))
);
placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
.add(placeHolderPanelLayout.createSequentialGroup()
@ -505,6 +522,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
jSeparator2.setOrientation(SwingConstants.VERTICAL);
snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N
snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N
snapshotButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
snapshotButtonActionPerformed(evt);
}
});
jSeparator3.setOrientation(SwingConstants.VERTICAL);
GroupLayout toolbarLayout = new GroupLayout(toolbar);
toolbar.setLayout(toolbarLayout);
toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
@ -537,6 +564,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
.add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.RELATED)
.add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.RELATED)
.add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.RELATED)
.add(snapshotButton)
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
@ -556,7 +587,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
.add(jLabel2)
.add(zoomLabel)
.add(clearVizButton)
.add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(snapshotButton)
.add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(3, 3, 3))
);
@ -648,6 +681,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
setCursor(Cursor.getDefaultCursor());
}//GEN-LAST:event_clearVizButtonActionPerformed
@NbBundle.Messages({
"VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation."
})
private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed
try {
handleSnapshotEvent();
} catch (NoCurrentCaseException | IOException ex) {
logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS
Platform.runLater(()
-> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow())
.text(Bundle.VisualizationPanel_snapshot_report_failure())
.showWarning());
} catch( TskCoreException ex) {
logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS
}
}//GEN-LAST:event_snapshotButtonActionPerformed
private void fitGraph() {
graphComponent.zoomTo(1, true);
mxPoint translate = graph.getView().getTranslate();
@ -674,7 +725,129 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graphComponent.zoom((heightFactor + widthFactor) / 2.0);
}
/**
* Handle the ActionPerformed event from the Snapshot button.
*
* @throws NoCurrentCaseException
* @throws IOException
*/
@NbBundle.Messages({
"VisualizationPanel_action_dialogs_title=Communications",
"VisualizationPanel_module_name=Communications",
"VisualizationPanel_action_name_text=Snapshot Report",
"VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:",
"VisualizationPane_reportName=Communications Snapshot",
"# {0} - default name",
"VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}",
"VisualizationPane_blank_report_title=Blank Report Name",
"# {0} - report name",
"VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}"
})
private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException {
Case currentCase = Case.getCurrentCaseThrows();
Date generationDate = new Date();
final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS
final JTextField text = new JTextField(50);
final JPanel panel = new JPanel(new GridLayout(2, 1));
panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt()));
panel.add(text);
text.setText(defaultReportName);
int result = JOptionPane.showConfirmDialog(graphComponent, panel,
Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
String enteredReportName = text.getText();
if(enteredReportName.trim().isEmpty()){
result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION);
if(result != JOptionPane.OK_OPTION) {
return;
}
}
String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName);
if (Files.exists(reportPath)) {
result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName),
Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
FileUtil.deleteFileDir(reportPath.toFile());
createReport(currentCase, reportName);
}
} else {
createReport(currentCase, reportName);
currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName);
}
}
}
/**
* Create the Snapshot Report.
*
* @param currentCase The current case
* @param reportName User selected name for the report
*
* @throws IOException
*/
@NbBundle.Messages({
"VisualizationPane_DisplayName=Open Report",
"VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
"VisualizationPane_MessageBoxTitle=Open Report Failure",
"VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
"VisualizationPane_MissingReportFileMessage=The report file no longer exists.",
"VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.",
"# {0} - report path",
"VisualizationPane_Report_Success=Report Successfully create at:\n{0}",
"VisualizationPane_Report_OK_Button=OK",
"VisualizationPane_Open_Report=Open Report",})
private void createReport(Case currentCase, String reportName) throws IOException {
// Create the report.
Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS
BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null);
Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport();
// Report success to the user and offer to open the report.
String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath());
String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()};
int result = JOptionPane.showOptionDialog(graphComponent, message,
Bundle.VisualizationPanel_action_dialogs_title(),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE,
null, buttons, buttons[1]);
if (result == JOptionPane.YES_NO_OPTION) {
try {
Desktop.getDesktop().open(reportPath.toFile());
} catch (IOException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.VisualizationPane_NoAssociatedEditorMessage(),
Bundle.VisualizationPane_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (UnsupportedOperationException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.VisualizationPane_NoOpenInEditorSupportMessage(),
Bundle.VisualizationPane_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (IllegalArgumentException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.VisualizationPane_MissingReportFileMessage(),
Bundle.VisualizationPane_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (SecurityException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(),
Bundle.VisualizationPane_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
}
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private JPanel borderLayoutPanel;
@ -687,10 +860,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private JLabel jLabel2;
private JToolBar.Separator jSeparator1;
private JToolBar.Separator jSeparator2;
private JToolBar.Separator jSeparator3;
private JTextArea jTextArea1;
private JFXPanel notificationsJFXPanel;
private JButton organicLayoutButton;
private JPanel placeHolderPanel;
private JButton snapshotButton;
private JSplitPane splitPane;
private JPanel toolbar;
private JButton zoomActualButton;
@ -1001,4 +1176,4 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
lockedVertexModel.lock(selectedVertices);
}
}
}
}

View File

@ -0,0 +1,175 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.communications.snapshot;
import java.util.List;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;
import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.SubFilter;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Generate and write the Communication snapshot report to disk.
*/
public class CommSnapShotReportWriter extends UiSnapShotReportWriter {
private final BufferedImage image;
private final CommunicationsFilter filter;
/**
* Constructor
*
* @param currentCase The Case to write a report for.
* @param reportFolderPath The Path to the folder that will contain the
* report.
* @param reportName The name of the report.
* @param generationDate The generation Date of the report.
* @param snapshot A snapshot of the view to include in the report.
*/
public CommSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate, BufferedImage snapshot, CommunicationsFilter filter) {
super(currentCase, reportFolderPath, reportName, generationDate);
this.image = snapshot;
this.filter = filter;
}
/**
* Generate and write the html page that shows the snapshot and the state of
* the CommunicationFilters
*
* @throws IOException If there is a problem writing the html file to disk.
*/
@Override
protected void writeSnapShotHTMLFile() throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat("MMMMM dd, yyyy"); //NON-NLS
ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> snapShotContext = new HashMap<>();
snapShotContext.put("reportTitle", getReportName()); //NON-NLS
List<SubFilter> filters = filter.getAndFilters();
for (SubFilter filter : filters) {
if (filter instanceof DateRangeFilter) {
long startDate = ((DateRangeFilter) filter).getStartDate();
long endDate = ((DateRangeFilter) filter).getEndDate();
if (startDate > 0) {
snapShotContext.put("startTime", formatter.format(new Date((Instant.ofEpochSecond(startDate)).toEpochMilli()))); //NON-NLS
}
if (endDate > 0) {
snapShotContext.put("endTime", formatter.format(new Date((Instant.ofEpochSecond(endDate)).toEpochMilli()))); //NON-NLS
}
} else if (filter instanceof AccountTypeFilter) {
Set<Account.Type> selectedAccounts = ((AccountTypeFilter) filter).getAccountTypes();
ArrayList<ReportWriterHelper> fullAccountList = new ArrayList<>();
for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) {
if (type == Account.Type.CREDIT_CARD) {
continue;
}
fullAccountList.add(new ReportWriterHelper(type.getDisplayName(), selectedAccounts.contains(type)));
}
snapShotContext.put("accounts", fullAccountList);
} else if (filter instanceof DeviceFilter) {
Collection<String> ids = ((DeviceFilter) filter).getDevices();
ArrayList<ReportWriterHelper> list = new ArrayList<>();
try {
final SleuthkitCase sleuthkitCase = getCurrentCase().getSleuthkitCase();
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
boolean selected = ids.contains(dataSource.getDeviceId());
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
list.add(new ReportWriterHelper(dsName, selected));
}
} catch (TskCoreException ex) {
}
snapShotContext.put("devices", list);
}
}
fillTemplateAndWrite("/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS
}
/**
* Helper class for use with the html template
*/
private final class ReportWriterHelper {
private final String label;
private final boolean selected;
/**
* Helper class for use with the html template.
*
* @param label Display label
* @param selected Boolean selected state
*/
ReportWriterHelper(String label, boolean selected) {
this.label = label;
this.selected = selected;
}
/**
* Returns the display label
*
* @return The display label
*/
public String getLabel(){
return label;
}
/**
* Returns the selection state
*
* @return The selection state
*/
public boolean isSelected(){
return selected;
}
}
}

View File

@ -0,0 +1,21 @@
<html>
<head>
<title>Communications Snapshot: {{reportTitle}}</title>
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body><div id="header">Communications Snapshot</div>
<div id="content">
<img id="snapshot" src="snapshot.png" alt="Snapshot" >
<table>
<tr><td><b>Date Range</b></td><td></td></tr>
<tr><td>Start:</td><td>{{startTime}}</td></tr>
<tr><td>End:</td><td>{{endTime}}</td></tr>
<tr><td><b>Devices:</b></td><td></td></tr>
{{#devices}}<tr><td>{{label}}</td><td><input type="checkbox" {{#selected}}checked{{/selected}}{{^selected}} {{/selected}} disabled></td></tr>{{/devices}}
<tr><td><b>Account Types</b></td><td></td></tr>
{{#accounts}}<tr><td>{{label}}</td><td><input type="checkbox" {{#selected}}checked{{/selected}}{{^selected}} {{/selected}} disabled></td></tr>{{/accounts}}
</table>
</div>
</body>
</html>

View File

@ -342,9 +342,9 @@
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.modules.stix.STIXReportModule.getDefault"/>
<attr name="position" intvalue="910"/>
</file>
<file name="org-sleuthkit-autopsy-modules-case_uco-ReportCaseUco.instance">
<file name="org-sleuthkit-autopsy-report-caseuco-ReportCaseUco.instance">
<attr name="instanceOf" stringvalue="org.sleuthkit.autopsy.report.GeneralReportModule"/>
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.modules.case_uco.ReportCaseUco.getDefault"/>
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.report.caseuco.ReportCaseUco.getDefault"/>
<attr name="position" intvalue="911"/>
</file>
<!--<folder name="JavaHelp">

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +18,8 @@
*/
package org.sleuthkit.autopsy.corecomponents;
import java.awt.*;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
@ -26,7 +27,6 @@ import java.io.IOException;
import java.nio.file.Paths;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JMenuItem;
@ -46,7 +46,7 @@ import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.datamodel.DataConversion;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Hex view of file contents.
@ -55,8 +55,8 @@ import org.sleuthkit.datamodel.TskException;
@ServiceProvider(service = DataContentViewer.class, position = 1)
public class DataContentViewerHex extends javax.swing.JPanel implements DataContentViewer {
private static final long pageLength = 16384;
private final byte[] data = new byte[(int) pageLength];
private static final long PAGE_LENGTH = 16384;
private final byte[] data = new byte[(int) PAGE_LENGTH];
private static int currentPage = 1;
private int totalPages;
private Content dataSource;
@ -291,8 +291,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
private void goToPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToPageTextFieldActionPerformed
String pageNumberStr = goToPageTextField.getText();
int pageNumber = 0;
int pageNumber;
try {
pageNumber = Integer.parseInt(pageNumberStr);
} catch (NumberFormatException ex) {
@ -330,7 +329,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
Utilities.getRowEnd(outputTextArea, outputTextArea.getCaretPosition()))
.toString();
// NOTE: This needs to change if the outputFormat of outputTextArea changes.
String hexForUserSelectedLine = userSelectedLine.substring(0, userSelectedLine.indexOf(":"));
String hexForUserSelectedLine = userSelectedLine.substring(0, userSelectedLine.indexOf(':'));
return Long.decode(hexForUserSelectedLine) + userInput;
} catch (BadLocationException | StringIndexOutOfBoundsException | NumberFormatException ex) {
@ -361,19 +360,20 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
}//GEN-LAST:event_goToOffsetTextFieldActionPerformed
@NbBundle.Messages({"DataContentViewerHex.launchError=Unable to launch HxD Editor. "
+ "Please specify the HxD install location in Tools -> Options -> External Viewer",
"DataContentViewerHex.copyingFile=Copying file to open in HxD..."})
+ "Please specify the HxD install location in Tools -> Options -> External Viewer",
"DataContentViewerHex.copyingFile=Copying file to open in HxD..."})
private void launchHxDButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_launchHxDButtonActionPerformed
new BackgroundFileCopyTask().execute();
}//GEN-LAST:event_launchHxDButtonActionPerformed
/**
* Performs the file copying and process launching in a SwingWorker so that the
* UI is not blocked when opening large files.
* Performs the file copying and process launching in a SwingWorker so that
* the UI is not blocked when opening large files.
*/
private class BackgroundFileCopyTask extends SwingWorker<Void, Void> {
private boolean wasCancelled = false;
@Override
public Void doInBackground() throws InterruptedException {
ProgressHandle progress = ProgressHandle.createHandle(DataContentViewerHex_copyingFile(), () -> {
@ -382,31 +382,31 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
wasCancelled = true;
return true;
});
try {
File HxDExecutable = new File(UserPreferences.getExternalHexEditorPath());
if(!HxDExecutable.exists() || !HxDExecutable.canExecute()) {
if (!HxDExecutable.exists() || !HxDExecutable.canExecute()) {
JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
return null;
}
String tempDirectory = Case.getCurrentCaseThrows().getTempDirectory();
File tempFile = Paths.get(tempDirectory,
FileUtil.escapeFileName(dataSource.getId() + dataSource.getName())).toFile();
progress.start(100);
ContentUtils.writeToFile(dataSource, tempFile, progress, this, true);
if(wasCancelled) {
if (wasCancelled) {
tempFile.delete();
progress.finish();
return null;
}
try {
ProcessBuilder launchHxDExecutable = new ProcessBuilder();
launchHxDExecutable.command(String.format("\"%s\" \"%s\"",
HxDExecutable.getAbsolutePath(),
launchHxDExecutable.command(String.format("\"%s\" \"%s\"",
HxDExecutable.getAbsolutePath(),
tempFile.getAbsolutePath()));
launchHxDExecutable.start();
} catch (IOException ex) {
@ -418,14 +418,13 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
logger.log(Level.SEVERE, "Unable to copy file into temp directory", ex);
JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
}
progress.finish();
return null;
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JLabel currentPageLabel;
@ -461,7 +460,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
return;
}
currentPage = page;
long offset = (currentPage - 1) * pageLength;
long offset = (currentPage - 1) * PAGE_LENGTH;
setDataView(offset);
goToOffsetTextField.setText(Long.toString(offset));
}
@ -475,7 +474,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
if (this.dataSource == null) {
return;
}
currentPage = (int) (offset / pageLength) + 1;
currentPage = (int) (offset / PAGE_LENGTH) + 1;
setDataView(offset);
goToPageTextField.setText(Integer.toString(currentPage));
}
@ -489,10 +488,10 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
int bytesRead = 0;
if (dataSource.getSize() > 0) {
try {
bytesRead = dataSource.read(data, offset, pageLength); // read the data
} catch (TskException ex) {
bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskCoreException ex) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + pageLength);
offset + PAGE_LENGTH);
logger.log(Level.WARNING, "Error while trying to show the hex content.", ex); //NON-NLS
}
}
@ -500,7 +499,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
// set the data on the bottom and show it
if (bytesRead <= 0) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + pageLength);
offset + PAGE_LENGTH);
}
// disable or enable the next button
@ -521,7 +520,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
// set the output view
if (errorText == null) {
int showLength = bytesRead < pageLength ? bytesRead : (int) pageLength;
int showLength = bytesRead < PAGE_LENGTH ? bytesRead : (int) PAGE_LENGTH;
outputTextArea.setText(DataConversion.byteArrayToHex(data, showLength, offset));
} else {
outputTextArea.setText(errorText);
@ -547,7 +546,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
dataSource = content;
totalPages = 0;
if (dataSource.getSize() > 0) {
totalPages = Math.round((dataSource.getSize() - 1) / pageLength) + 1;
totalPages = Math.round((dataSource.getSize() - 1) / PAGE_LENGTH) + 1;
}
totalPageLabel.setText(Integer.toString(totalPages));
@ -605,12 +604,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
if (node == null) {
return false;
}
Content content = node.getLookup().lookup(Content.class);
if (content != null && content.getSize() > 0) {
return true;
}
return false;
Content content = DataContentViewerUtility.getDefaultContent(node);
return content != null && content.getSize() > 0;
}
@Override

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,12 +18,12 @@
*/
package org.sleuthkit.autopsy.corecomponents;
import java.awt.*;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JMenuItem;
@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractResult;
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT;
import org.sleuthkit.autopsy.datamodel.StringContent;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Viewer displays strings extracted from contents.
@ -264,7 +264,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
//@@@ this is part of the code dealing with the data viewer. could be copied/removed to implement the scrollbar
currentOffset -= PAGE_LENGTH;
currentPage = currentPage - 1;
currentPage -= 1;
currentPageLabel.setText(Integer.toString(currentPage));
setDataView(dataSource, currentOffset);
}//GEN-LAST:event_prevPageButtonActionPerformed
@ -272,7 +272,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
//@@@ this is part of the code dealing with the data viewer. could be copied/removed to implement the scrollbar
currentOffset += PAGE_LENGTH;
currentPage = currentPage + 1;
currentPage += 1;
currentPageLabel.setText(Integer.toString(currentPage));
setDataView(dataSource, currentOffset);
}//GEN-LAST:event_nextPageButtonActionPerformed
@ -348,18 +348,15 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
int bytesRead = 0;
// set the data on the bottom and show it
String text = "";
if (dataSource.getSize() > 0) {
try {
bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskException ex) {
text = NbBundle.getMessage(this.getClass(),
"DataContentViewerString.setDataView.errorText", currentOffset,
currentOffset + PAGE_LENGTH);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error while trying to show the String content.", ex); //NON-NLS
}
}
String text;
if (bytesRead > 0) {
//text = DataConversion.getString(data, bytesRead, 4);
final SCRIPT selScript = (SCRIPT) languageCombo.getSelectedItem();
@ -498,7 +495,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
if (node == null) {
return false;
}
Content content = node.getLookup().lookup(Content.class);
Content content = DataContentViewerUtility.getDefaultContent(node);
return (content != null && content.getSize() > 0);
}
@ -511,18 +508,4 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
public Component getComponent() {
return this;
}
/*
* Show the right click menu only if evt is the correct mouse event
*/
private void maybeShowPopup(java.awt.event.MouseEvent evt) {
if (evt.isPopupTrigger()) {
rightClickMenu.setLocation(evt.getLocationOnScreen());
rightClickMenu.setVisible(true);
copyMenuItem.setEnabled(outputViewPane.getSelectedText() != null);
} else {
rightClickMenu.setVisible(false);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -78,7 +78,7 @@ public class SlackFileNode extends AbstractFsContentNode<AbstractFile> {
actionsList.add(a);
}
if (!this.getDirectoryBrowseMode()) {
actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "SlackFileNode.viewFileInDir.text"), this.content));
actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "SlackFileNode.getActions.viewFileInDir.text"), this.content));
actionsList.add(null); // creates a menu separator
}
actionsList.add(new NewWindowViewAction(

View File

@ -1,16 +1,16 @@
/*
*
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
*
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.
@ -30,10 +30,10 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Encapsulates logic required to create a mapping of data sources in the
* Encapsulates logic required to create a mapping of data sources in the
* current case to their data source IDs.
*
* Intended to be used within the context of a SwingWorker or other background
*
* Intended to be used within the context of a SwingWorker or other background
* thread.
*/
public class DataSourceLoader {
@ -46,8 +46,7 @@ public class DataSourceLoader {
//try block releases resources - exceptions are handled in done()
try (
SleuthkitCase.CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_LOGICAL);
ResultSet resultSet = query.getResultSet()
) {
ResultSet resultSet = query.getResultSet()) {
while (resultSet.next()) {
Long objectId = resultSet.getLong(1);
String dataSourceName = resultSet.getString(2);
@ -56,7 +55,7 @@ public class DataSourceLoader {
}
}
private void loadImageSources(SleuthkitCase tskDb, Map<Long, String> dataSouceMap) throws SQLException, TskCoreException {
private void loadImageSources(SleuthkitCase tskDb, Map<Long, String> dataSourceMap) throws SQLException, TskCoreException {
//try block releases resources - exceptions are handled in done()
try (
SleuthkitCase.CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_IMAGE);
@ -64,21 +63,24 @@ public class DataSourceLoader {
while (resultSet.next()) {
Long objectId = resultSet.getLong(1);
String dataSourceName = resultSet.getString(2);
File image = new File(dataSourceName);
String dataSourceNameTrimmed = image.getName();
dataSouceMap.put(objectId, dataSourceNameTrimmed);
if (!dataSourceMap.containsKey(objectId)) {
String dataSourceName = resultSet.getString(2);
File image = new File(dataSourceName);
String dataSourceNameTrimmed = image.getName();
dataSourceMap.put(objectId, dataSourceNameTrimmed);
}
}
}
}
/**
* Get a map of data source Ids to their string names for the current case.
*
*
* @return Map of Long (id) to String (name)
*
* @throws NoCurrentCaseException
* @throws TskCoreException
* @throws SQLException
* @throws SQLException
*/
public Map<Long, String> getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException {
Map<Long, String> dataSouceMap = new HashMap<>();

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
package org.sleuthkit.autopsy.datasourceprocessors;
import java.util.List;
import java.util.UUID;
@ -32,7 +32,7 @@ import org.sleuthkit.datamodel.Content;
* processor finishes running in its own thread.
*/
@Immutable
class AddDataSourceCallback extends DataSourceProcessorCallback {
public class AddDataSourceCallback extends DataSourceProcessorCallback {
private final Case caseForJob;
private final AutoIngestDataSource dataSourceInfo;
@ -48,7 +48,7 @@ class AddDataSourceCallback extends DataSourceProcessorCallback {
* @param dataSourceInfo The data source
* @param taskId The task id to associate with ingest job events.
*/
AddDataSourceCallback(Case caseForJob, AutoIngestDataSource dataSourceInfo, UUID taskId, Object lock) {
public AddDataSourceCallback(Case caseForJob, AutoIngestDataSource dataSourceInfo, UUID taskId, Object lock) {
this.caseForJob = caseForJob;
this.dataSourceInfo = dataSourceInfo;
this.taskId = taskId;
@ -87,7 +87,7 @@ class AddDataSourceCallback extends DataSourceProcessorCallback {
* @param result The result code for the processing of the data source.
* @param errorMessages Any error messages generated during the processing
* of the data source.
* @param dataSourceContent The content produced by processing the data
* @param dataSources The content produced by processing the data
* source.
*/
@Override

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
package org.sleuthkit.autopsy.datasourceprocessors;
import java.nio.file.Path;
import java.util.ArrayList;
@ -26,7 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
import org.sleuthkit.datamodel.Content;
@ThreadSafe
class AutoIngestDataSource {
public class AutoIngestDataSource {
private final String deviceId;
private final Path path;
@ -34,34 +34,34 @@ class AutoIngestDataSource {
private List<String> errorMessages;
private List<Content> content;
AutoIngestDataSource(String deviceId, Path path) {
public AutoIngestDataSource(String deviceId, Path path) {
this.deviceId = deviceId;
this.path = path;
}
String getDeviceId() {
public String getDeviceId() {
return deviceId;
}
Path getPath() {
public Path getPath() {
return this.path;
}
synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List<String> errorMessages, List<Content> content) {
public synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List<String> errorMessages, List<Content> content) {
this.resultCode = result;
this.errorMessages = new ArrayList<>(errorMessages);
this.content = new ArrayList<>(content);
}
synchronized DataSourceProcessorResult getResultDataSourceProcessorResultCode() {
public synchronized DataSourceProcessorResult getResultDataSourceProcessorResultCode() {
return resultCode;
}
synchronized List<String> getDataSourceProcessorErrorMessages() {
public synchronized List<String> getDataSourceProcessorErrorMessages() {
return new ArrayList<>(errorMessages);
}
synchronized List<Content> getContent() {
public synchronized List<Content> getContent() {
return new ArrayList<>(content);
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2019 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.commandlineingest;
package org.sleuthkit.autopsy.datasourceprocessors;
import java.nio.file.Path;
import java.util.Collection;
@ -25,13 +25,12 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
/**
* A utility class to find Data Source Processors
*/
final class DataSourceProcessorUtility {
public class DataSourceProcessorUtility {
private DataSourceProcessorUtility() {
}
@ -47,7 +46,7 @@ final class DataSourceProcessorUtility {
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
public static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = new HashMap<>();
for (AutoIngestDataSourceProcessor processor : processorCandidates) {
int confidence = processor.canProcess(dataSourcePath);
@ -74,7 +73,7 @@ final class DataSourceProcessorUtility {
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
// lookup all AutomatedIngestDataSourceProcessors
Collection<? extends AutoIngestDataSourceProcessor> processorCandidates = Lookup.getDefault().lookupAll(AutoIngestDataSourceProcessor.class);
return getOrderedListOfDataSourceProcessors(dataSourcePath, processorCandidates);
@ -96,7 +95,7 @@ final class DataSourceProcessorUtility {
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = getDataSourceProcessorForFile(dataSourcePath, processorCandidates);
return orderDataSourceProcessorsByConfidence(validDataSourceProcessorsMap);
}
@ -110,7 +109,7 @@ final class DataSourceProcessorUtility {
* the data source along with their confidence score
* @return Ordered list of data source processors
*/
static List<AutoIngestDataSourceProcessor> orderDataSourceProcessorsByConfidence(Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap) {
public static List<AutoIngestDataSourceProcessor> orderDataSourceProcessorsByConfidence(Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap) {
List<AutoIngestDataSourceProcessor> validDataSourceProcessors = validDataSourceProcessorsMap.entrySet().stream()
.sorted(Map.Entry.<AutoIngestDataSourceProcessor, Integer>comparingByValue().reversed())
.map(Map.Entry::getKey)

View File

@ -5,7 +5,6 @@ DataSourceIntegrityIngestModule.process.errProcImg=Error processing {0}
DataSourceIntegrityIngestModule.process.skipNonEwf=Skipping non-disk image data source {0}
DataSourceIntegrityIngestModule.process.noStoredHash=Image {0} does not have stored hash.
DataSourceIntegrityIngestModule.process.startingImg=Starting {0}
DataSourceIntegrityIngestModule.process.errGetSizeOfImg=Error getting size of {0}. Image will not be processed.
DataSourceIntegrityIngestModule.process.errReadImgAtChunk=Error reading {0} at chunk {1}
DataSourceIntegrityIngestModule.shutDown.verified=\ verified
DataSourceIntegrityIngestModule.shutDown.notVerified=\ not verified

View File

@ -37,7 +37,6 @@ DataSourceIntegrityIngestModule.process.errProcImg=Error processing {0}
DataSourceIntegrityIngestModule.process.skipNonEwf=Skipping non-disk image data source {0}
DataSourceIntegrityIngestModule.process.noStoredHash=Image {0} does not have stored hash.
DataSourceIntegrityIngestModule.process.startingImg=Starting {0}
DataSourceIntegrityIngestModule.process.errGetSizeOfImg=Error getting size of {0}. Image will not be processed.
DataSourceIntegrityIngestModule.process.errReadImgAtChunk=Error reading {0} at chunk {1}
DataSourceIntegrityIngestModule.shutDown.verified=\ verified
DataSourceIntegrityIngestModule.shutDown.notVerified=\ not verified

View File

@ -5,7 +5,6 @@ DataSourceIntegrityModuleFactory.moduleDesc.text=E01\u30d5\u30a1\u30a4\u30eb\u30
DataSourceIntegrityIngestModule.process.skipNonEwf=E01\u30a4\u30e1\u30fc\u30b8\u3067\u306f\u306a\u3044{0}\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u3066\u3044\u307e\u3059
DataSourceIntegrityIngestModule.process.noStoredHash=\u30a4\u30e1\u30fc\u30b8{0}\u306f\u4fdd\u5b58\u3055\u308c\u3066\u3044\u308b\u30cf\u30c3\u30b7\u30e5\u304c\u3042\u308a\u307e\u305b\u3093\u3002
DataSourceIntegrityIngestModule.process.startingImg={0}\u3092\u958b\u59cb\u4e2d
DataSourceIntegrityIngestModule.process.errGetSizeOfImg={0}\u306e\u30b5\u30a4\u30ba\u306e\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30a4\u30e1\u30fc\u30b8\u306f\u51e6\u7406\u3055\u308c\u307e\u305b\u3093\u3002
DataSourceIntegrityIngestModule.process.errReadImgAtChunk={0}\u306e\u30c1\u30e3\u30f3\u30af{1}\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
DataSourceIntegrityIngestModule.shutDown.calcHashLi=<li>\u8a08\u7b97\u3055\u308c\u305f\u30cf\u30c3\u30b7\u30e5\u5024\uff1a{0}</li>
DataSourceIntegrityIngestModule.shutDown.notVerified=\u8a8d\u8a3c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f

View File

@ -137,14 +137,10 @@ public class DataSourceIntegrityIngestModule implements DataSourceIngestModule {
}
Image img = (Image) dataSource;
// Make sure the image size we have is not zero
// Get the image size. Log a warning if it is zero.
long size = img.getSize();
if (size == 0) {
logger.log(Level.WARNING, "Size of image {0} was 0 when queried.", imgName); //NON-NLS
services.postMessage(IngestMessage.createMessage(MessageType.ERROR, DataSourceIntegrityModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(),
"DataSourceIntegrityIngestModule.process.errGetSizeOfImg",
imgName)));
}
// Determine which mode we're in.

View File

@ -1,5 +1,6 @@
EncryptionDetectionDataSourceIngestModule.artifactComment.bitlocker=Bitlocker encryption detected.
EncryptionDetectionDataSourceIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f).
EncryptionDetectionDataSourceIngestModule.processing.message=Checking image for encryption.
EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.
EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f).
EncryptionDetectionFileIngestModule.getDesc.text=Looks for files with the specified minimum entropy.

View File

@ -70,11 +70,14 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
@Messages({
"EncryptionDetectionDataSourceIngestModule.artifactComment.bitlocker=Bitlocker encryption detected.",
"EncryptionDetectionDataSourceIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
"EncryptionDetectionDataSourceIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f).",
"EncryptionDetectionDataSourceIngestModule.processing.message=Checking image for encryption."
})
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
try {
if (dataSource instanceof Image) {
@ -82,8 +85,11 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
logger.log(Level.SEVERE, String.format("Unable to process data source '%s' - image has no paths", dataSource.getName()));
return IngestModule.ProcessResult.ERROR;
}
List<VolumeSystem> volumeSystems = ((Image) dataSource).getVolumeSystems();
progressBar.switchToDeterminate(volumeSystems.size());
int numVolSystemsChecked = 0;
progressBar.progress(Bundle.EncryptionDetectionDataSourceIngestModule_processing_message(), 0);
for (VolumeSystem volumeSystem : volumeSystems) {
for (Volume volume : volumeSystem.getVolumes()) {
if (BitlockerDetection.isBitlockerVolume(volume)) {
@ -93,6 +99,9 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
return flagVolume(volume, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, String.format(Bundle.EncryptionDetectionDataSourceIngestModule_artifactComment_suspected(), calculatedEntropy));
}
}
// Update progress bar
numVolSystemsChecked++;
progressBar.progress(Bundle.EncryptionDetectionDataSourceIngestModule_processing_message(), numVolSystemsChecked);
}
}
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {

View File

@ -5,6 +5,7 @@ CreatePortableCaseModule.createCase.caseDirExists=Case folder {0} already exists
CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case
# {0} - folder
CreatePortableCaseModule.createCase.errorCreatingFolder=Error creating folder {0}
CreatePortableCaseModule.createCase.errorStoringMaxIds=Error storing maximum database IDs
CreatePortableCaseModule.generateReport.caseClosed=Current case has been closed
# {0} - tag name
CreatePortableCaseModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...

View File

@ -24,6 +24,8 @@ import java.util.logging.Level;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -41,6 +43,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.FileSystem;
@ -63,6 +66,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(CreatePortableCaseModule.class.getName());
private static final String FILE_FOLDER_NAME = "PortableCaseFiles";
private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other";
private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids";
private CreatePortableCasePanel configPanel;
// These are the types for the exported file subfolders
@ -290,6 +294,7 @@ public class CreatePortableCaseModule implements GeneralReportModule {
"CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case",
"# {0} - folder",
"CreatePortableCaseModule.createCase.errorCreatingFolder=Error creating folder {0}",
"CreatePortableCaseModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
})
private void createCase(File outputDir, ReportProgressPanel progressPanel) {
@ -312,6 +317,15 @@ public class CreatePortableCaseModule implements GeneralReportModule {
return;
}
// Store the highest IDs
try {
saveHighestIds();
} catch (TskCoreException ex) {
handleError("Error storing maximum database IDs",
Bundle.CreatePortableCaseModule_createCase_errorStoringMaxIds(), ex, progressPanel);
return;
}
// Create the base folder for the copied files
copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
if (! copiedFilesFolder.mkdir()) {
@ -338,6 +352,26 @@ public class CreatePortableCaseModule implements GeneralReportModule {
}
/**
* Save the current highest IDs to the portable case.
*
* @throws TskCoreException
*/
private void saveHighestIds() throws TskCoreException {
CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
String tableSchema = "( table_name TEXT PRIMARY KEY, "
+ " max_id TEXT)";
portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects"));
currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags"));
currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags"));
currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners"));
}
/**
* Add all files with a given tag to the portable case.
*
@ -674,19 +708,53 @@ public class CreatePortableCaseModule implements GeneralReportModule {
oldIdToNewContent.clear();
newIdToContent.clear();
oldTagNameToNewTagName.clear();
oldArtTypeIdToNewArtTypeId.clear();
oldAttrTypeIdToNewAttrType.clear();
oldArtifactIdToNewArtifact.clear();
currentCase = null;
if (portableSkCase != null) {
// We want to close the database connections here but it is currently not possible. JIRA-4736
portableSkCase.close();
portableSkCase = null;
}
caseFolder = null;
copiedFilesFolder = null;
}
@Override
public JPanel getConfigurationPanel() {
configPanel = new CreatePortableCasePanel();
return configPanel;
}
private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
private final String tableName;
StoreMaxIdCallback(String tableName) {
this.tableName = tableName;
}
@Override
public void process(ResultSet rs) {
try {
while (rs.next()) {
try {
Long maxId = rs.getLong("max_id");
String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')";
portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
} catch (SQLException ex) {
logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex);
}
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

View File

@ -0,0 +1,241 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.uisnapshot;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.report.ReportBranding;
/**
* Generate and write the snapshot report to disk.
*/
public abstract class UiSnapShotReportWriter {
/**
* mustache.java template factory.
*/
private final static MustacheFactory mf = new DefaultMustacheFactory();
private final Case currentCase;
private final Path reportFolderPath;
private final String reportName;
private final ReportBranding reportBranding;
private Date generationDate;
/**
* Constructor
*
* @param currentCase The Case to write a report for.
* @param reportFolderPath The Path to the folder that will contain the
* report.
* @param reportName The name of the report.
* @param generationDate The generation Date of the report.
* @param snapshot A snapshot of the view to include in the
* report.
*/
protected UiSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate) {
this.currentCase = currentCase;
this.reportFolderPath = reportFolderPath;
this.reportName = reportName;
this.generationDate = generationDate;
this.reportBranding = new ReportBranding();
}
/**
* Generate and write the report to disk.
*
* @return The Path to the "main file" of the report. This is the file that
* Autopsy shows in the results view when the Reports Node is
* selected in the DirectoryTree.
*
* @throws IOException If there is a problem writing the report.
*/
public Path writeReport() throws IOException {
//ensure directory exists
Files.createDirectories(reportFolderPath);
copyResources();
writeSummaryHTML();
writeSnapShotHTMLFile();
return writeIndexHTML();
}
/**
* Get the name for the report.
*
* @return Returns the reportName
*/
protected String getReportName() {
return reportName;
}
/**
* Get the folder path for the report.
*
* @return Report folder path
*/
protected Path getReportFolderPath() {
return reportFolderPath;
}
/**
* Get the case for this report.
*
* @return Current case object
*/
protected Case getCurrentCase() {
return currentCase;
}
/**
* Generate and write the html page that shows the snapshot and the state of
* any filters.
*
* @throws IOException If there is a problem writing the html file to disk.
*/
protected abstract void writeSnapShotHTMLFile() throws IOException ;
/**
* Generate and write the main html page with frames for navigation on the
* left and content on the right.
*
* @return The Path of the written html file.
*
* @throws IOException If there is a problem writing the html file to disk.
*/
private Path writeIndexHTML() throws IOException {
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> indexContext = new HashMap<>();
indexContext.put("reportBranding", reportBranding); //NON-NLS
indexContext.put("reportName", reportName); //NON-NLS
Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS
fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS
return reportIndexFile;
}
/**
* * Generate and write the summary of the current case for this report.
*
* @throws IOException If there is a problem writing the html file to disk.
*/
private void writeSummaryHTML() throws IOException {
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> summaryContext = new HashMap<>();
summaryContext.put("reportName", reportName); //NON-NLS
summaryContext.put("reportBranding", reportBranding); //NON-NLS
summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS
summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS
summaryContext.put("currentCase", currentCase); //NON-NLS
String agencyLogo = "agency_logo.png"; //default name for agency logo.
if (StringUtils.isNotBlank(reportBranding.getAgencyLogoPath())){
agencyLogo = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
}
summaryContext.put("agencyLogoFileName", agencyLogo);
fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS
}
/**
* Fill in the mustache template at the given location using the values from
* the given context object and save it to the given outPutFile.
*
* @param templateLocation The location of the template. suitible for use
* with Class.getResourceAsStream
* @param templateName The name of the tempalte. (Used by mustache to
* cache templates?)
* @param context The contect to use to fill in the template
* values.
* @param outPutFile The filled in tempalte will be saced at this
* Path.
*
* @throws IOException If there is a problem saving the filled in template
* to disk.
*/
protected void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException {
Mustache summaryMustache = mf.compile(new InputStreamReader(UiSnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName);
try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS
summaryMustache.execute(writer, context);
}
}
/**
* Copy static resources (static html, css, images, etc) to the reports
* folder.
*
* @throws IOException If there is a problem copying the resources.
*/
private void copyResources() throws IOException {
//pull generator and agency logos from branding
String generatorLogoPath = reportBranding.getGeneratorLogoPath();
if (StringUtils.isNotBlank(generatorLogoPath)) {
Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS
}
String agencyLogoPath = reportBranding.getAgencyLogoPath();
if (StringUtils.isNotBlank(agencyLogoPath)) {
Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve(Paths.get(reportBranding.getAgencyLogoPath()).getFileName())); //NON-NLS
}
//copy favicon
if (StringUtils.isBlank(agencyLogoPath)) {
copyInternalResource("/org/sleuthkit/autopsy/report/images/favicon.ico", "favicon.ico");
} else {
Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS
}
copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/navigation.html", "nav.html");
copyInternalResource("/org/sleuthkit/autopsy/report/images/summary.png", "summary.png");
copyInternalResource("/org/sleuthkit/autopsy/report/images/image.png", "snapshot_icon.png");
copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/index.css", "index.css");
copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/summary.css", "summary.css");
}
/**
* Copies internal resource to the report folder.
*
* @param internalPath Location in jar of the image
* @param fileName Name to give resource in new location
*
* @throws IOException
*/
private void copyInternalResource(String internalPath, String fileName) throws IOException{
try (InputStream resource = UiSnapShotReportWriter.class.getResourceAsStream(internalPath)) { //NON-NLS
Files.copy(resource, reportFolderPath.resolve(fileName)); //NON-NLS
}
}
}

View File

@ -9,7 +9,7 @@
<h1>Report Navigation</h1>
<ul class="nav">
<li style="background: url(summary.png) left center no-repeat;"><a href="summary.html" target="content">Case Summary</a></li>
<li style="background: url(snapshot_icon.png) left center no-repeat;"><a href="snapshot.html" target="content">Timeline Snapshot</a></li>
<li style="background: url(snapshot_icon.png) left center no-repeat;"><a href="snapshot.html" target="content">Snapshot</a></li>
</ul>
</div>
</body>

View File

@ -9,7 +9,7 @@
<div id="wrapper">
<h1>{{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}<span>Warning, this report was run before ingest services completed!</span>{{/ingestRunning}}</h1>
<p class="subheadding">Timeline Report generated on {{generationDateTime}}</p>
<p class="subheadding">Report generated on {{generationDateTime}}</p>
<div class="title">
{{#reportBranding.getAgencyLogoPath}}
<div class="left">

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Copyright 2016 - 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,46 +18,23 @@
*/
package org.sleuthkit.autopsy.timeline.snapshot;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.format.DateTimeFormat;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.report.ReportBranding;
import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter;
import org.sleuthkit.autopsy.timeline.zooming.ZoomState;
/**
* Generate and write the Timeline snapshot report to disk.
*/
public class SnapShotReportWriter {
/**
* mustache.java template factory.
*/
private final static MustacheFactory mf = new DefaultMustacheFactory();
private final Case currentCase;
private final Path reportFolderPath;
private final String reportName;
private final ReportBranding reportBranding;
public class SnapShotReportWriter extends UiSnapShotReportWriter{
private final ZoomState zoomState;
private final Date generationDate;
private final BufferedImage image;
/**
@ -73,37 +50,9 @@ public class SnapShotReportWriter {
* @param snapshot A snapshot of the view to include in the report.
*/
public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomState zoomState, Date generationDate, BufferedImage snapshot) {
this.currentCase = currentCase;
this.reportFolderPath = reportFolderPath;
this.reportName = reportName;
super(currentCase, reportFolderPath, reportName, generationDate);
this.zoomState = zoomState;
this.generationDate = generationDate;
this.image = snapshot;
this.reportBranding = new ReportBranding();
}
/**
* Generate and write the report to disk.
*
* @return The Path to the "main file" of the report. This is the file that
* Autopsy shows in the results view when the Reports Node is
* selected in the DirectoryTree.
*
* @throws IOException If there is a problem writing the report.
*/
public Path writeReport() throws IOException {
//ensure directory exists
Files.createDirectories(reportFolderPath);
//save the snapshot in the report directory
ImageIO.write(image, "png", reportFolderPath.resolve("snapshot.png").toFile()); //NON-NLS
copyResources();
writeSummaryHTML();
writeSnapShotHTMLFile();
return writeIndexHTML();
}
/**
@ -111,128 +60,18 @@ public class SnapShotReportWriter {
*
* @throws IOException If there is a problem writing the html file to disk.
*/
private void writeSnapShotHTMLFile() throws IOException {
@Override
protected void writeSnapShotHTMLFile() throws IOException {
//save the snapshot in the report directory
ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> snapShotContext = new HashMap<>();
snapShotContext.put("reportTitle", reportName); //NON-NLS
snapShotContext.put("reportTitle", getReportName()); //NON-NLS
snapShotContext.put("startTime", zoomState.getTimeRange().getStart().toString(DateTimeFormat.fullDateTime())); //NON-NLS
snapShotContext.put("endTime", zoomState.getTimeRange().getEnd().toString(DateTimeFormat.fullDateTime())); //NON-NLS
snapShotContext.put("zoomState", zoomState); //NON-NLS
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html")); //NON-NLS
}
/**
* Generate and write the main html page with frames for navigation on the
* left and content on the right.
*
* @return The Path of the written html file.
*
* @throws IOException If there is a problem writing the html file to disk.
*/
private Path writeIndexHTML() throws IOException {
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> indexContext = new HashMap<>();
indexContext.put("reportBranding", reportBranding); //NON-NLS
indexContext.put("reportName", reportName); //NON-NLS
Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS
return reportIndexFile;
}
/**
* * Generate and write the summary of the current case for this report.
*
* @throws IOException If there is a problem writing the html file to disk.
*/
private void writeSummaryHTML() throws IOException {
//make a map of context objects to resolve template paramaters against
HashMap<String, Object> summaryContext = new HashMap<>();
summaryContext.put("reportName", reportName); //NON-NLS
summaryContext.put("reportBranding", reportBranding); //NON-NLS
summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS
summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS
summaryContext.put("currentCase", currentCase); //NON-NLS
String agencyLogo = "agency_logo.png"; //default name for agency logo.
if (StringUtils.isNotBlank(reportBranding.getAgencyLogoPath())) {
agencyLogo = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
}
summaryContext.put("agencyLogoFileName", agencyLogo);
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS
}
/**
* Fill in the mustache template at the given location using the values from
* the given context object and save it to the given outPutFile.
*
* @param templateLocation The location of the template. suitible for use
* with Class.getResourceAsStream
* @param templateName The name of the tempalte. (Used by mustache to
* cache templates?)
* @param context The contect to use to fill in the template
* values.
* @param outPutFile The filled in tempalte will be saced at this
* Path.
*
* @throws IOException If there is a problem saving the filled in template
* to disk.
*/
private void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException {
Mustache summaryMustache = mf.compile(new InputStreamReader(SnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName);
try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS
summaryMustache.execute(writer, context);
}
}
/**
* Copy static resources (static html, css, images, etc) to the reports
* folder.
*
* @throws IOException If there is a problem copying the resources.
*/
private void copyResources() throws IOException {
//pull generator and agency logos from branding
String generatorLogoPath = reportBranding.getGeneratorLogoPath();
if (StringUtils.isNotBlank(generatorLogoPath)) {
Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS
}
String agencyLogoPath = reportBranding.getAgencyLogoPath();
if (StringUtils.isNotBlank(agencyLogoPath)) {
Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve(Paths.get(reportBranding.getAgencyLogoPath()).getFileName())); //NON-NLS
}
//copy navigation html
try (InputStream navStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/navigation.html")) { //NON-NLS
Files.copy(navStream, reportFolderPath.resolve("nav.html")); //NON-NLS
}
//copy favicon
if (StringUtils.isBlank(agencyLogoPath)) {
// use default Autopsy icon if custom icon is not set
try (InputStream faviconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { //NON-NLS
Files.copy(faviconStream, reportFolderPath.resolve("favicon.ico")); //NON-NLS
}
} else {
Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS
}
//copy report summary icon
try (InputStream summaryStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { //NON-NLS
Files.copy(summaryStream, reportFolderPath.resolve("summary.png")); //NON-NLS
}
//copy snapshot icon
try (InputStream snapshotIconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/images/image.png")) { //NON-NLS
Files.copy(snapshotIconStream, reportFolderPath.resolve("snapshot_icon.png")); //NON-NLS
}
//copy main report css
try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/index.css")) { //NON-NLS
Files.copy(resource, reportFolderPath.resolve("index.css")); //NON-NLS
}
//copy summary css
try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/summary.css")) { //NON-NLS
Files.copy(resource, reportFolderPath.resolve("summary.css")); //NON-NLS
}
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS
}
}

View File

@ -1,22 +1,3 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.
*/
Timeline.node.root=Root
Timeline.ui.ZoomRanges.onemin.text=One Minute
Timeline.ui.ZoomRanges.fifteenmin.text=Fifteen Minutes

View File

@ -1,22 +1,3 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.
*/
AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.
HistoryToolBar.historyLabel.text=History
IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval

View File

@ -10,6 +10,7 @@
<dependency conf="autopsy_core->*" org="org.reflections" name="reflections" rev="0.9.8"/>
<dependency org="com.google.code.gson" name="gson" rev="2.8.1"/>
<dependency org="com.apple" name="AppleJavaExtensions" rev="1.4"/>
<!-- for viewers -->
<dependency conf="autopsy_core->*" org="org.freedesktop.gstreamer" name="gst1-java-core" rev="0.9.3"/>

View File

@ -1,374 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
import org.sleuthkit.autopsy.datasourceprocessors.RawDSProcessor;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
/*
* A runnable that adds an archive data source as well as data sources contained
* in the archive to the case database.
*/
class AddArchiveTask implements Runnable {
private final Logger logger = Logger.getLogger(AddArchiveTask.class.getName());
private final String deviceId;
private final String archivePath;
private final DataSourceProcessorProgressMonitor progressMonitor;
private final DataSourceProcessorCallback callback;
private boolean criticalErrorOccurred;
private final Object archiveDspLock;
private static final String ARCHIVE_EXTRACTOR_MODULE_OUTPUT_DIR = "Archive Extractor";
/**
* Constructs a runnable task that adds an archive as well as data sources
* contained in the archive to the case database.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param archivePath Path to the archive file.
* @param progressMonitor Progress monitor to report progress during
* processing.
* @param callback Callback to call when processing is done.
*/
AddArchiveTask(String deviceId, String archivePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
this.deviceId = deviceId;
this.archivePath = archivePath;
this.callback = callback;
this.progressMonitor = progressMonitor;
this.archiveDspLock = new Object();
}
/**
* Adds the archive to the case database.
*/
@Override
public void run() {
progressMonitor.setIndeterminate(true);
List<String> errorMessages = new ArrayList<>();
List<Content> newDataSources = new ArrayList<>();
DataSourceProcessorCallback.DataSourceProcessorResult result;
if (!ArchiveUtil.isArchive(Paths.get(archivePath))) {
criticalErrorOccurred = true;
logger.log(Level.SEVERE, String.format("Input data source is not a valid datasource: %s", archivePath)); //NON-NLS
errorMessages.add("Input data source is not a valid datasource: " + archivePath);
result = DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;
callback.done(result, errorMessages, newDataSources);
}
logger.log(Level.INFO, "Using Archive Extractor DSP to process archive {0} ", archivePath);
// extract the archive and pass the extracted folder as input
try {
Case currentCase = Case.getCurrentCaseThrows();
// create folder to extract archive to
Path destinationFolder = createDirectoryForFile(archivePath, currentCase.getModuleDirectory());
if (destinationFolder.toString().isEmpty()) {
// unable to create directory
criticalErrorOccurred = true;
errorMessages.add(String.format("Unable to create directory {0} to extract archive {1} ", new Object[]{destinationFolder.toString(), archivePath}));
logger.log(Level.SEVERE, "Unable to create directory {0} to extract archive {1} ", new Object[]{destinationFolder.toString(), archivePath});
return;
}
// extract contents of ZIP archive into destination folder
List<String> extractedFiles = new ArrayList<>();
int numExtractedFilesRemaining = 0;
try {
progressMonitor.setProgressText(String.format("Extracting archive contents to: %s", destinationFolder.toString()));
extractedFiles = ArchiveUtil.unpackArchiveFile(archivePath, destinationFolder.toString());
numExtractedFilesRemaining = extractedFiles.size();
} catch (ArchiveUtil.ArchiveExtractionException ex) {
// delete extracted contents
logger.log(Level.SEVERE,"Exception while extracting archive contents into {0}. Deleteing the directory", destinationFolder.toString());
FileUtils.deleteDirectory(destinationFolder.toFile());
throw ex;
}
// lookup all AutomatedIngestDataSourceProcessors so that we only do it once.
// LocalDisk, LocalFiles, and ArchiveDSP are removed from the list.
List<AutoIngestDataSourceProcessor> processorCandidates = getListOfValidDataSourceProcessors();
// do processing
for (String file : extractedFiles) {
// we only care about files, skip directories
File fileObject = new File(file);
if (fileObject.isDirectory()) {
numExtractedFilesRemaining--;
continue;
}
// identify all "valid" DSPs that can process this file
List<AutoIngestDataSourceProcessor> validDataSourceProcessors = getDataSourceProcessorsForFile(Paths.get(file), errorMessages, processorCandidates);
if (validDataSourceProcessors.isEmpty()) {
continue;
}
// identified a "valid" data source within the archive
progressMonitor.setProgressText(String.format("Adding: %s", file));
/*
* NOTE: we have to move the valid data sources to a separate
* folder and then add the data source from that folder. This is
* necessary because after all valid data sources have been
* identified, we are going to add the remaining extracted
* contents of the archive as a single logical file set. Hence,
* if we do not move the data sources out of the extracted
* contents folder, those data source files will get added twice
* and can potentially result in duplicate keyword hits.
*/
Path newFolder = createDirectoryForFile(file, currentCase.getModuleDirectory());
if (newFolder.toString().isEmpty()) {
// unable to create directory
criticalErrorOccurred = true;
errorMessages.add(String.format("Unable to create directory {0} to extract content of archive {1} ", new Object[]{newFolder.toString(), archivePath}));
logger.log(Level.SEVERE, "Unable to create directory {0} to extract content of archive {1} ", new Object[]{newFolder.toString(), archivePath});
return;
}
// Copy it to a different folder
FileUtils.copyFileToDirectory(fileObject, newFolder.toFile());
Path newFilePath = Paths.get(newFolder.toString(), FilenameUtils.getName(file));
// Try each DSP in decreasing order of confidence
boolean success = false;
for (AutoIngestDataSourceProcessor selectedProcessor : validDataSourceProcessors) {
logger.log(Level.INFO, "Using {0} to process extracted file {1} ", new Object[]{selectedProcessor.getDataSourceType(), file});
synchronized (archiveDspLock) {
UUID taskId = UUID.randomUUID();
currentCase.notifyAddingDataSource(taskId);
AutoIngestDataSource internalDataSource = new AutoIngestDataSource(deviceId, newFilePath);
DataSourceProcessorCallback internalArchiveDspCallBack = new AddDataSourceCallback(currentCase, internalDataSource, taskId, archiveDspLock);
selectedProcessor.process(deviceId, newFilePath, progressMonitor, internalArchiveDspCallBack);
archiveDspLock.wait();
// at this point we got the content object(s) from the current DSP.
// check whether the data source was processed successfully
if ((internalDataSource.getResultDataSourceProcessorResultCode() == CRITICAL_ERRORS)
|| internalDataSource.getContent().isEmpty()) {
// move onto the the next DSP that can process this data source
for (String errorMessage : internalDataSource.getDataSourceProcessorErrorMessages()) {
logger.log(Level.SEVERE, "Data source processor {0} was unable to process {1}: {2}", new Object[]{selectedProcessor.getDataSourceType(), internalDataSource.getPath(), errorMessage});
}
continue;
}
// if we are here it means the data source was added successfully
success = true;
newDataSources.addAll(internalDataSource.getContent());
// update data source info
for (Content c:internalDataSource.getContent()) {
if (c instanceof DataSource) {
DataSource ds = (DataSource) c;
// Read existing aquisition details and update them
String details = "Extracted from archive: " + archivePath.toString();
String existingDetails = ds.getAcquisitionDetails();
if (existingDetails != null && !existingDetails.isEmpty()) {
ds.setAcquisitionDetails(existingDetails + System.getProperty("line.separator") + details);
} else {
ds.setAcquisitionDetails(details);
}
// Update the names for all new data sources to be the root archive plus the name of the data source
String newName = Paths.get(archivePath).getFileName() + "/" + ds.getName();
ds.setDisplayName(newName);
currentCase.notifyDataSourceNameChanged(c, newName);
}
}
// skip all other DSPs for this data source
break;
}
}
if (success) {
// one of the DSPs successfully processed the data source. delete the
// copy of the data source in the original extracted archive folder.
// otherwise the data source is going to be added again as a logical file.
numExtractedFilesRemaining--;
FileUtils.deleteQuietly(fileObject);
} else {
// none of the DSPs were able to process the data source. delete the
// copy of the data source in the temporary folder. the data source is
// going to be added as a logical file with the rest of the extracted contents.
FileUtils.deleteQuietly(newFolder.toFile());
}
}
// after all archive contents have been examined (and moved to separate folders if necessary),
// add remaining extracted contents as one logical file set
if (numExtractedFilesRemaining > 0) {
progressMonitor.setProgressText(String.format("Adding: %s", destinationFolder.toString()));
logger.log(Level.INFO, "Adding directory {0} as logical file set", destinationFolder.toString());
synchronized (archiveDspLock) {
UUID taskId = UUID.randomUUID();
currentCase.notifyAddingDataSource(taskId);
AutoIngestDataSource internalDataSource = new AutoIngestDataSource(deviceId, destinationFolder);
DataSourceProcessorCallback internalArchiveDspCallBack = new AddDataSourceCallback(currentCase, internalDataSource, taskId, archiveDspLock);
// folder where archive was extracted to
List<String> pathsList = new ArrayList<>();
pathsList.add(destinationFolder.toString());
// use archive file name as the name of the logical file set
String archiveFileName = FilenameUtils.getName(archivePath);
LocalFilesDSProcessor localFilesDSP = new LocalFilesDSProcessor();
localFilesDSP.run(deviceId, archiveFileName, pathsList, progressMonitor, internalArchiveDspCallBack);
archiveDspLock.wait();
// at this point we got the content object(s) from the current DSP.
newDataSources.addAll(internalDataSource.getContent());
for (Content c : internalDataSource.getContent()) {
if (c instanceof DataSource) {
DataSource ds = (DataSource) c;
// This is a new data source so just write the aquisition details
String details = "Extracted from archive: " + archivePath.toString();
ds.setAcquisitionDetails(details);
}
}
}
}
} catch (Exception ex) {
criticalErrorOccurred = true;
errorMessages.add(ex.getMessage());
logger.log(Level.SEVERE, String.format("Critical error occurred while extracting archive %s", archivePath), ex); //NON-NLS
} finally {
logger.log(Level.INFO, "Finished processing of archive {0}", archivePath);
progressMonitor.setProgress(100);
if (criticalErrorOccurred) {
result = DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;
} else if (!errorMessages.isEmpty()) {
result = DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS;
} else {
result = DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS;
}
callback.done(result, errorMessages, newDataSources);
}
}
/**
* Get a list of data source processors. LocalFiles, RawDSProcessor, and
* ArchiveDSP are removed from the list.
*
* @return List of data source processors
*/
private List<AutoIngestDataSourceProcessor> getListOfValidDataSourceProcessors() {
Collection<? extends AutoIngestDataSourceProcessor> processorCandidates = Lookup.getDefault().lookupAll(AutoIngestDataSourceProcessor.class);
List<AutoIngestDataSourceProcessor> validDataSourceProcessors = processorCandidates.stream().collect(Collectors.toList());
for (Iterator<AutoIngestDataSourceProcessor> iterator = validDataSourceProcessors.iterator(); iterator.hasNext();) {
AutoIngestDataSourceProcessor selectedProcessor = iterator.next();
// skip local files, only looking for "valid" data sources.
// also skip RawDSP as we don't want to add random "bin" and "raw" files that may be inside archive
// as individual data sources.
// also skip nested archive files, those will be ingested as logical files and extracted during ingest
if ((selectedProcessor instanceof LocalFilesDSProcessor)
|| (selectedProcessor instanceof RawDSProcessor)
|| (selectedProcessor instanceof ArchiveExtractorDSProcessor)) {
iterator.remove();
}
}
return validDataSourceProcessors;
}
/**
* Get a list of data source processors that can process the data source of
* interest. The list is sorted by confidence in decreasing order.
*
* @param dataSourcePath Full path to the data source
* @param errorMessages List<String> for error messages
* @param errorMessages List of AutoIngestDataSourceProcessor to try
*
* @return Ordered list of applicable DSPs
*/
private List<AutoIngestDataSourceProcessor> getDataSourceProcessorsForFile(Path dataSourcePath, List<String> errorMessages,
List<AutoIngestDataSourceProcessor> processorCandidates) {
// Get an ordered list of data source processors to try
List<AutoIngestDataSourceProcessor> validDataSourceProcessorsForFile = Collections.emptyList();
try {
validDataSourceProcessorsForFile = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSourcePath, processorCandidates);
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) {
criticalErrorOccurred = true;
errorMessages.add(ex.getMessage());
logger.log(Level.SEVERE, String.format("Critical error occurred while extracting archive %s", archivePath), ex); //NON-NLS
return Collections.emptyList();
}
return validDataSourceProcessorsForFile;
}
/**
* Create a directory in ModuleOutput folder based on input file name. A
* time stamp is appended to the directory name.
*
* @param fileName File name
* @param baseDirectory Base directory. Typically the case output directory.
*
* @return Full path to the new directory
*/
private Path createDirectoryForFile(String fileName, String baseDirectory) {
// get file name without full path or extension
String fileNameNoExt = FilenameUtils.getBaseName(fileName);
// create folder to extract archive to
Path newFolder = Paths.get(baseDirectory, ARCHIVE_EXTRACTOR_MODULE_OUTPUT_DIR, fileNameNoExt + "_" + TimeStampUtils.createTimeStamp());
if (newFolder.toFile().mkdirs() == false) {
// unable to create directory
return Paths.get("");
}
return newFolder;
}
}

View File

@ -1,176 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JPanel;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
/**
* A data source processor that handles archive files. Implements the
* DataSourceProcessor service provider interface to allow integration with the
* add data source wizard. It also provides a run method overload to allow it to
* be used independently of the wizard.
*/
@ServiceProviders(value={
@ServiceProvider(service=AutoIngestDataSourceProcessor.class)}
)
@NbBundle.Messages({
"ArchiveDSP.dsType.text=Archive file"})
public class ArchiveExtractorDSProcessor implements AutoIngestDataSourceProcessor {
private final static String DATA_SOURCE_TYPE = Bundle.ArchiveDSP_dsType_text();
private final ArchiveFilePanel configPanel;
private String deviceId;
private String archivePath;
private boolean setDataSourceOptionsCalled;
private final ExecutorService jobProcessingExecutor;
private static final String ARCHIVE_DSP_THREAD_NAME = "Archive-DSP-%d";
private AddArchiveTask addArchiveTask;
/**
* Constructs an archive data source processor that
* implements the DataSourceProcessor service provider interface to allow
* integration with the add data source wizard. It also provides a run
* method overload to allow it to be used independently of the wizard.
*/
public ArchiveExtractorDSProcessor() {
configPanel = ArchiveFilePanel.createInstance(ArchiveExtractorDSProcessor.class.getName(), ArchiveUtil.getArchiveFilters());
jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(ARCHIVE_DSP_THREAD_NAME).build());
}
@Override
public int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
// check whether this is an archive
if (ArchiveUtil.isArchive(dataSourcePath)){
// return "high confidence" value
return 100;
}
return 0;
}
@Override
public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
run(deviceId, dataSourcePath.toString(), progressMonitor, callBack);
}
@Override
public String getDataSourceType() {
return DATA_SOURCE_TYPE;
}
/**
* Gets the panel that allows a user to select a data source and do any
* configuration required by the data source. The panel is less than 544
* pixels wide and less than 173 pixels high.
*
* @return A selection and configuration panel for this data source
* processor.
*/
@Override
public JPanel getPanel() {
configPanel.readSettings();
configPanel.select();
return configPanel;
}
/**
* Indicates whether the settings in the selection and configuration panel
* are valid and complete.
*
* @return True if the settings are valid and complete and the processor is
* ready to have its run method called, false otherwise.
*/
@Override
public boolean isPanelValid() {
return configPanel.validatePanel();
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
* configuration panel. Returns as soon as the background task is started.
* The background task uses a callback object to signal task completion and
* return results.
*
* This method should not be called unless isPanelValid returns true.
*
* @param progressMonitor Progress monitor that will be used by the
* background task to report progress.
* @param callback Callback that will be used by the background task
* to return results.
*/
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
if (!setDataSourceOptionsCalled) {
configPanel.storeSettings();
deviceId = UUID.randomUUID().toString();
archivePath = configPanel.getContentPaths();
}
run(deviceId, archivePath, progressMonitor, callback);
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the given settings instead of those provided by the
* selection and configuration panel. Returns as soon as the background task
* is started and uses the callback object to signal task completion and
* return results.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
* intended to be unique across multiple cases
* (e.g., a UUID).
* @param archivePath Path to the archive file.
* @param progressMonitor Progress monitor for reporting progress
* during processing.
* @param callback Callback to call when processing is done.
*/
public void run(String deviceId, String archivePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
addArchiveTask = new AddArchiveTask(deviceId, archivePath, progressMonitor, callback);
jobProcessingExecutor.submit(addArchiveTask);
}
/**
* This DSP is a service to AutoIngestDataSourceProcessor only. Hence it is
* only used by AIM. AIM currently doesn't support DSP cancellation.
*/
@Override
public void cancel() {
}
@Override
public void reset() {
deviceId = null;
archivePath = null;
configPanel.reset();
setDataSourceOptionsCalled = false;
}
}

View File

@ -1,94 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 65]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[403, 65]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="pathTextField" max="32767" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="browseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pathLabel" min="-2" max="-2" attributes="0"/>
<Component id="errorLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="277" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="pathLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="browseButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="pathTextField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="pathLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="ArchiveFilePanel.pathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="browseButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="ArchiveFilePanel.browseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="browseButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="pathTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="ArchiveFilePanel.pathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="errorLabel">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="ff" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="ArchiveFilePanel.errorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -1,289 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.io.File;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import static org.sleuthkit.autopsy.experimental.autoingest.Bundle.*;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.coreutils.DriveUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PathValidator;
/**
* Panel for adding an archive file which is supported by 7zip library (e.g.
* "zip", "rar", "arj", "7z", "7zip", "gzip, etc). Allows the user to select a
* file.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
class ArchiveFilePanel extends JPanel implements DocumentListener {
private static final Logger logger = Logger.getLogger(ArchiveFilePanel.class.getName());
private static final String PROP_LAST_ARCHIVE_PATH = "LBL_LastImage_PATH"; //NON-NLS
private final JFileChooser fileChooser = new JFileChooser();
/**
* Externally supplied name is used to store settings
*/
private final String contextName;
/**
* Creates new form ArchiveFilePanel
*
* @param context A string context name used to read/store last
* used settings.
* @param fileChooserFilters A list of filters to be used with the
* FileChooser.
*/
private ArchiveFilePanel(String context, List<FileFilter> fileChooserFilters) {
this.contextName = context;
initComponents();
errorLabel.setVisible(false);
fileChooser.setDragEnabled(false);
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooserFilters.forEach(fileChooser::addChoosableFileFilter);
if (fileChooserFilters.isEmpty() == false) {
fileChooser.setFileFilter(fileChooserFilters.get(0));
}
}
/**
* Creates and returns an instance of a ArchiveFilePanel.
*
* @param context A string context name used to read/store last
* used settings.
* @param fileChooserFilters A list of filters to be used with the
* FileChooser.
*
* @return instance of the ArchiveFilePanel
*/
public static synchronized ArchiveFilePanel createInstance(String context, List<FileFilter> fileChooserFilters) {
ArchiveFilePanel instance = new ArchiveFilePanel(context, fileChooserFilters);
// post-constructor initialization of listener support without leaking references of uninitialized objects
instance.pathTextField.getDocument().addDocumentListener(instance);
return instance;
}
/**
* 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.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
pathLabel = new javax.swing.JLabel();
browseButton = new javax.swing.JButton();
pathTextField = new javax.swing.JTextField();
errorLabel = new javax.swing.JLabel();
setMinimumSize(new java.awt.Dimension(0, 65));
setPreferredSize(new java.awt.Dimension(403, 65));
org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(ArchiveFilePanel.class, "ArchiveFilePanel.pathLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(ArchiveFilePanel.class, "ArchiveFilePanel.browseButton.text")); // NOI18N
browseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
browseButtonActionPerformed(evt);
}
});
pathTextField.setText(org.openide.util.NbBundle.getMessage(ArchiveFilePanel.class, "ArchiveFilePanel.pathTextField.text")); // NOI18N
errorLabel.setForeground(new java.awt.Color(255, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(ArchiveFilePanel.class, "ArchiveFilePanel.errorLabel.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()
.addComponent(pathTextField)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(browseButton)
.addGap(2, 2, 2))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pathLabel)
.addComponent(errorLabel))
.addGap(0, 277, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(pathLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(browseButton)
.addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(3, 3, 3)
.addComponent(errorLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
String oldText = getContentPaths();
// set the current directory of the FileChooser if the ArchivePath Field is valid
File currentDir = new File(oldText);
if (currentDir.exists()) {
fileChooser.setCurrentDirectory(currentDir);
}
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
String path = fileChooser.getSelectedFile().getPath();
setContentPath(path);
}
updateHelper();
}//GEN-LAST:event_browseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
private javax.swing.JLabel errorLabel;
private javax.swing.JLabel pathLabel;
private javax.swing.JTextField pathTextField;
// End of variables declaration//GEN-END:variables
/**
* Get the path of the user selected archive.
*
* @return the archive path
*/
public String getContentPaths() {
return pathTextField.getText();
}
/**
* Set the path of the archive file.
*
* @param s path of the archive file
*/
public void setContentPath(String s) {
pathTextField.setText(s);
}
public void reset() {
//reset the UI elements to default
pathTextField.setText(null);
}
/**
* Should we enable the next button of the wizard?
*
* @return true if a proper archive has been selected, false otherwise
*/
@NbBundle.Messages({"DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on \"C:\" drive",
"DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case."
})
public boolean validatePanel() {
errorLabel.setVisible(false);
String path = getContentPaths();
if (StringUtils.isBlank(path)) {
return false;
}
// display warning if there is one (but don't disable "next" button)
try {
if (false == PathValidator.isValidForMultiUserCase(path, Case.getCurrentCaseThrows().getCaseType())) {
errorLabel.setVisible(true);
errorLabel.setText(Bundle.DataSourceOnCDriveError_text());
}
} catch (NoCurrentCaseException ex) {
errorLabel.setVisible(true);
errorLabel.setText(Bundle.DataSourceOnCDriveError_noOpenCase_errMsg());
}
return new File(path).isFile()
|| DriveUtils.isPhysicalDrive(path)
|| DriveUtils.isPartition(path);
}
public void storeSettings() {
String archivePathName = getContentPaths();
if (null != archivePathName) {
String archivePath = archivePathName.substring(0, archivePathName.lastIndexOf(File.separator) + 1);
ModuleSettings.setConfigSetting(contextName, PROP_LAST_ARCHIVE_PATH, archivePath);
}
}
public void readSettings() {
String lastArchivePath = ModuleSettings.getConfigSetting(contextName, PROP_LAST_ARCHIVE_PATH);
if (StringUtils.isNotBlank(lastArchivePath)) {
setContentPath(lastArchivePath);
}
}
@Override
public void insertUpdate(DocumentEvent e) {
updateHelper();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateHelper();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateHelper();
}
/**
* Update functions are called by the pathTextField which has this set as
* it's DocumentEventListener. Each update function fires a property change
* to be caught by the parent panel.
*
*/
@NbBundle.Messages({"ArchiveFilePanel.moduleErr=Module Error",
"ArchiveFilePanel.moduleErr.msg=A module caused an error listening to ArchiveFilePanel updates."
+ " See log to determine which module. Some data could be incomplete.\n"})
private void updateHelper() {
try {
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
} catch (Exception e) {
logger.log(Level.SEVERE, "ArchiveFilePanel listener threw exception", e); //NON-NLS
MessageNotifyUtil.Notify.error(ArchiveFilePanel_moduleErr(), ArchiveFilePanel_moduleErr_msg());
}
}
/**
* Set the focus to the pathTextField.
*/
public void select() {
pathTextField.requestFocusInWindow();
}
}

View File

@ -1,313 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.filechooser.FileFilter;
import net.sf.sevenzipjbinding.ISequentialOutStream;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.GeneralFilter;
import org.sleuthkit.autopsy.coreutils.FileUtil;
/**
* Set of utilities that handles archive file extraction. Uses 7zip library.
*/
final class ArchiveUtil {
private static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS
private static final List<String> ARCHIVE_EXTS = Arrays.asList(".zip", ".rar", ".arj", ".7z", ".7zip", ".gzip", ".gz", ".bzip2", ".tar", ".tgz"); //NON-NLS
@NbBundle.Messages("GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz)")
private static final String ARCHIVE_DESC = Bundle.GeneralFilter_archiveDesc_text();
private static final GeneralFilter SEVEN_ZIP_FILTER = new GeneralFilter(ARCHIVE_EXTS, ARCHIVE_DESC);
private static final List<FileFilter> ARCHIVE_FILTERS = new ArrayList<>();
static {
ARCHIVE_FILTERS.add(SEVEN_ZIP_FILTER);
}
private ArchiveUtil() {
}
static List<FileFilter> getArchiveFilters() {
return ARCHIVE_FILTERS;
}
static boolean isArchive(Path dataSourcePath) {
String fileName = dataSourcePath.getFileName().toString();
// check whether it's a zip archive file that can be extracted
return isAcceptedByFiler(new File(fileName), ARCHIVE_FILTERS);
}
private static boolean isAcceptedByFiler(File file, List<FileFilter> filters) {
for (FileFilter filter : filters) {
if (filter.accept(file)) {
return true;
}
}
return false;
}
/**
* Enum of mime types which support archive extraction
*/
private enum SupportedArchiveExtractionFormats {
ZIP("application/zip"), //NON-NLS
SEVENZ("application/x-7z-compressed"), //NON-NLS
GZIP("application/gzip"), //NON-NLS
XGZIP("application/x-gzip"), //NON-NLS
XBZIP2("application/x-bzip2"), //NON-NLS
XTAR("application/x-tar"), //NON-NLS
XGTAR("application/x-gtar"),
XRAR("application/x-rar-compressed"); //NON-NLS
private final String mimeType;
SupportedArchiveExtractionFormats(final String mimeType) {
this.mimeType = mimeType;
}
@Override
public String toString() {
return this.mimeType;
}
}
/**
* Exception thrown when archive handling resulted in an error
*/
static class ArchiveExtractionException extends Exception {
private static final long serialVersionUID = 1L;
ArchiveExtractionException(String message) {
super(message);
}
ArchiveExtractionException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* This method returns array of supported file extensions.
*
* @return String array of supported file extensions.
*/
static String[] getSupportedArchiveTypes(){
return SUPPORTED_EXTENSIONS;
}
/**
* This method returns true if the MIME type is currently supported. Else it
* returns false.
*
* @param mimeType File mime type
*
* @return This method returns true if the file format is currently
* supported. Else it returns false.
*/
static boolean isExtractionSupportedByMimeType(String mimeType) {
for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
if (s.toString().equals(mimeType)) {
return true;
}
}
return false;
}
/**
* This method returns true if the file extension is currently supported.
* Else it returns false. Attempt extension based detection in case Apache
* Tika based detection fails.
*
* @param extension File extension
*
* @return This method returns true if the file format is currently
* supported. Else it returns false.
*/
static boolean isExtractionSupportedByFileExtension(String extension) {
// attempt extension matching
for (String supportedExtension : SUPPORTED_EXTENSIONS) {
if (extension.equals(supportedExtension)) {
return true;
}
}
return false;
}
/**
* Returns a list of file names contained within an archive.
*
* @param archiveFilePath Full path to the archive file
*
* @return List of file names contained within archive
*
* @throws
* ArchiveExtractionException
*/
static List<String> getListOfFilesWithinArchive(String archiveFilePath) throws ArchiveExtractionException {
if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
try {
SevenZip.initSevenZipFromPlatformJAR();
} catch (SevenZipNativeInitializationException ex) {
throw new ArchiveExtractionException("AutoIngestDashboard_bnPause_paused", ex);
}
}
List<String> files = new ArrayList<>();
ISevenZipInArchive inArchive = null;
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(new File(archiveFilePath), "r");
inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));
final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
files.add(item.getPath());
}
} catch (Exception ex) {
throw new ArchiveExtractionException("Exception while reading archive contents", ex);
} finally {
if (inArchive != null) {
try {
inArchive.close();
} catch (SevenZipException ex) {
throw new ArchiveExtractionException("Exception while closing the archive", ex);
}
}
}
return files;
}
/**
* Extracts contents of an archive file into a directory.
*
* @param archiveFilePath Full path to archive.
* @param destinationFolder Path to directory where results will be
* extracted to.
*
* @return List of file names contained within archive
* @throws
* ArchiveExtractionException
*/
static List<String> unpackArchiveFile(String archiveFilePath, String destinationFolder) throws ArchiveExtractionException {
if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
try {
SevenZip.initSevenZipFromPlatformJAR();
} catch (SevenZipNativeInitializationException ex) {
throw new ArchiveExtractionException("Unable to initialize 7Zip libraries", ex);
}
}
List<String> files = new ArrayList<>();
ISevenZipInArchive inArchive = null;
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(new File(archiveFilePath), "r");
inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));
final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
for (ISimpleInArchiveItem entry : simpleInArchive.getArchiveItems()) {
String entryPathInArchive = entry.getPath();
Path fullPath = Paths.get(destinationFolder, FileUtil.escapeFileName(entryPathInArchive)); // remove illegal characters from file name
File destFile = new File(fullPath.toString());
File destinationParent = destFile.getParentFile();
destinationParent.mkdirs();
if (!entry.isFolder()) {
UnpackStream unpackStream = null;
try {
Long size = entry.getSize();
unpackStream = new UnpackStream(destFile.toString(), size);
entry.extractSlow(unpackStream);
} catch (Exception ex) {
throw new ArchiveExtractionException("Exception while unpacking archive contents", ex);
} finally {
if (unpackStream != null) {
unpackStream.close();
}
}
}
// keep track of extracted files
files.add(fullPath.toString());
}
} catch (Exception ex) {
throw new ArchiveExtractionException("Exception while unpacking archive contents", ex);
} finally {
try {
if (inArchive != null) {
inArchive.close();
}
} catch (SevenZipException ex) {
throw new ArchiveExtractionException("Exception while closing the archive", ex);
}
}
return files;
}
/**
* Stream used to unpack an archive to local file
*/
private static class UnpackStream implements ISequentialOutStream {
private OutputStream output;
private String destFilePath;
UnpackStream(String destFilePath, long size) throws ArchiveExtractionException {
this.destFilePath = destFilePath;
try {
output = new FileOutputStream(destFilePath);
} catch (IOException ex) {
throw new ArchiveExtractionException("Exception while unpacking archive contents", ex);
}
}
@Override
public int write(byte[] bytes) throws SevenZipException {
try {
output.write(bytes);
} catch (IOException ex) {
throw new SevenZipException("Error writing unpacked file to " + destFilePath, ex);
}
return bytes.length;
}
public void close() throws ArchiveExtractionException {
if (output != null) {
try {
output.flush();
output.close();
} catch (IOException ex) {
throw new ArchiveExtractionException("Exception while closing the archive", ex);
}
}
}
}
}

View File

@ -32,7 +32,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@ -70,7 +69,6 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.ServicesMonitor;
import org.sleuthkit.autopsy.core.ServicesMonitor.ServicesMonitorException;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;
@ -92,6 +90,9 @@ import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration;
import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSource;
import org.sleuthkit.autopsy.datasourceprocessors.AddDataSourceCallback;
import org.sleuthkit.autopsy.datasourceprocessors.DataSourceProcessorUtility;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.AutoIngestJobException;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
import org.sleuthkit.autopsy.ingest.IngestJob;

View File

@ -219,10 +219,6 @@ AutoIngestMetricsDialog.reportTextArea.text=
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
AutoIngestMetricsDialog.closeButton.text=Close
AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date
ArchiveFilePanel.pathLabel.text=Browse for an archive file:
ArchiveFilePanel.browseButton.text=Browse
ArchiveFilePanel.pathTextField.text=
ArchiveFilePanel.errorLabel.text=Error Label
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job

View File

@ -8,9 +8,6 @@ AinStatusNode.status.shuttingdown=Shutting Down
AinStatusNode.status.startingup=Starting Up
AinStatusNode.status.title=Status
AinStatusNode.status.unknown=Unknown
ArchiveDSP.dsType.text=Archive file
ArchiveFilePanel.moduleErr=Module Error
ArchiveFilePanel.moduleErr.msg=A module caused an error listening to ArchiveFilePanel updates. See log to determine which module. Some data could be incomplete.\n
AutoIngestAdminActions.cancelJobAction.title=Cancel Job
AutoIngestAdminActions.cancelModuleAction.title=Cancel Module
AutoIngestAdminActions.deleteCaseAction.error=Failed to delete case.
@ -170,12 +167,9 @@ CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard
CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs
CTL_CasesDashboardAction=Multi-User Cases Dashboard
CTL_CasesDashboardTopComponent=Cases
DataSourceOnCDriveError.noOpenCase.errMsg=Warning: Exception while getting open case.
DataSourceOnCDriveError.text=Warning: Path to multi-user data source is on "C:" drive
DeleteCaseInputDirectoriesAction.menuItemText=Delete Input Directories
DeleteCasesAction.menuItemText=Delete Case and Jobs
DeleteCasesForReprocessingAction.menuItemText=Delete for Reprocessing
GeneralFilter.archiveDesc.text=Archive Files (.zip, .rar, .arj, .7z, .7zip, .gzip, .gz, .bzip2, .tar, .tgz)
HINT_CasesDashboardTopComponent=This is an adminstrative dashboard for multi-user cases
OpenAutoIngestLogAction.deletedLogErrorMsg=The case auto ingest log has been deleted.
OpenAutoIngestLogAction.logOpenFailedErrorMsg=Failed to open case auto ingest log. See application log for details.
@ -183,7 +177,7 @@ OpenAutoIngestLogAction.menuItemText=Open Auto Ingest Log File
# {0} - caseErrorMessage
OpenCaseAction.errorMsg=Failed to open case: {0}
OpenCaseAction.menuItemText=Open
OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change. \n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it.
OpenIDE-Module-Long-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution. You can enable this module to use the new features. The features should be stable, but their exact behavior and API are subject to change.\n\nWe make no guarantee that the API of this module will not change, so developers should be careful when relying on it.
OpenIDE-Module-Name=Experimental
OpenIDE-Module-Short-Description=This module contains features that are being developed by Basis Technology and are not part of the default Autopsy distribution.
DisplayLogDialog.cannotOpenLog=Unable to open the selected case log file
@ -377,10 +371,6 @@ AutoIngestMetricsDialog.reportTextArea.text=
AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report
AutoIngestMetricsDialog.closeButton.text=Close
AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date
ArchiveFilePanel.pathLabel.text=Browse for an archive file:
ArchiveFilePanel.browseButton.text=Browse
ArchiveFilePanel.pathTextField.text=
ArchiveFilePanel.errorLabel.text=Error Label
AutoIngestMetricsDialog.startingDataLabel.text=Starting Date:
AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case
AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job

View File

@ -1,121 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
/**
* A utility class to find Data Source Processors
*/
class DataSourceProcessorUtility {
private DataSourceProcessorUtility() {
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used.
*
* @param dataSourcePath Full path to the data source
* @return Hash map of all DSPs that can process the data source along with
* their confidence score
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = new HashMap<>();
for (AutoIngestDataSourceProcessor processor : processorCandidates) {
int confidence = processor.canProcess(dataSourcePath);
if (confidence > 0) {
validDataSourceProcessorsMap.put(processor, confidence);
}
}
return validDataSourceProcessorsMap;
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used. Returns ordered list of
* data source processors. DSPs are ordered in descending order from highest
* confidence to lowest.
*
* @param dataSourcePath Full path to the data source
*
* @return Ordered list of data source processors. DSPs are ordered in
* descending order from highest confidence to lowest.
*
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
// lookup all AutomatedIngestDataSourceProcessors
Collection<? extends AutoIngestDataSourceProcessor> processorCandidates = Lookup.getDefault().lookupAll(AutoIngestDataSourceProcessor.class);
return getOrderedListOfDataSourceProcessors(dataSourcePath, processorCandidates);
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used. Returns ordered list of
* data source processors. DSPs are ordered in descending order from highest
* confidence to lowest.
*
* @param dataSourcePath Full path to the data source
* @param processorCandidates Collection of AutoIngestDataSourceProcessor objects to use
*
* @return Ordered list of data source processors. DSPs are ordered in
* descending order from highest confidence to lowest.
*
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = getDataSourceProcessorForFile(dataSourcePath, processorCandidates);
return orderDataSourceProcessorsByConfidence(validDataSourceProcessorsMap);
}
/**
* A utility method to get an ordered list of data source processors. DSPs
* are ordered in descending order from highest confidence to lowest.
*
* @param validDataSourceProcessorsMap Hash map of all DSPs that can process
* the data source along with their confidence score
* @return Ordered list of data source processors
*/
static List<AutoIngestDataSourceProcessor> orderDataSourceProcessorsByConfidence(Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap) {
List<AutoIngestDataSourceProcessor> validDataSourceProcessors = validDataSourceProcessorsMap.entrySet().stream()
.sorted(Map.Entry.<AutoIngestDataSourceProcessor, Integer>comparingByValue().reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
return validDataSourceProcessors;
}
}

View File

@ -890,9 +890,6 @@ public class GroupPane extends BorderPane {
t.consume();
break;
case SECONDARY:
if (t.getClickCount() == 1) {
selectAllFiles();
}
if (isNotEmpty(selectionModel.getSelected())) {
if (contextMenu == null) {
contextMenu = buildContextMenu();

View File

@ -49,6 +49,14 @@
<specification-version>1.31.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.swing.outline</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<specification-version>1.34.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.awt</code-name-base>
<build-prerequisite/>

View File

@ -34,7 +34,8 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc
KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found.
KeywordSearchResultFactory.query.exception.msg=Could not perform the query
OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time. \nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword seach bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Name=KeywordSearch
OptionsCategory_Name_KeywordSearchOptions=Keyword Search
OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search
@ -128,7 +129,7 @@ KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=View in New Window
KeywordSearchIngestModule.init.noKwInLstMsg=No keywords in keyword list.
KeywordSearchIngestModule.init.onlyIdxKwSkipMsg=Only indexing will be done and keyword search will be skipped (you can still add keyword lists using the Keyword Lists - Add to Ingest).
KeywordSearchIngestModule.doInBackGround.displayName=Periodic Keyword Search
KeywordSearchIngestModule.doInBackGround.finalizeMsg=- Finalizing
KeywordSearchIngestModule.doInBackGround.finalizeMsg=Finalizing
KeywordSearchIngestModule.doInBackGround.pendingMsg=(Pending)
RawText.FileText=File Text
RawText.ResultText=Result Text
@ -224,7 +225,7 @@ Server.start.exception.cantStartSolr.msg=Could not start Solr server process
Server.start.exception.cantStartSolr.msg2=Could not start Solr server process
Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running
Server.isRunning.exception.errCheckSolrRunning.msg2=Error checking if Solr server is running
Server.openCore.exception.alreadyOpen.msg=Already an open Core! Explicitely close Core first.
Server.openCore.exception.alreadyOpen.msg=There is an already open Solr core. Explicitly close the core first.
Server.queryNumIdxFiles.exception.msg=Error querying number of indexed files,
Server.queryNumIdxChunks.exception.msg=Error querying number of indexed chunks,
Server.queryNumIdxDocs.exception.msg=Error querying number of indexed documents,

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -25,7 +25,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@ -71,9 +70,11 @@ import org.sleuthkit.datamodel.TskData.FileKnown;
"CannotRunFileTypeDetection=Unable to run file type detection."
})
public final class KeywordSearchIngestModule implements FileIngestModule {
/** generally text extractors should ignore archives and let unpacking
* modules take care of them */
/**
* generally text extractors should ignore archives and let unpacking
* modules take care of them
*/
private static final List<String> ARCHIVE_MIME_TYPES
= ImmutableList.of(
//ignore unstructured binary and compressed data, for which string extraction or unzipper works better
@ -108,7 +109,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
"application/x-lzop", //NON-NLS
"application/x-z", //NON-NLS
"application/x-compress"); //NON-NLS
/**
* Options for this extractor
*/
@ -117,7 +118,6 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
EXTRACT_UTF8, ///< extract UTF8 text, true/false
};
enum UpdateFrequency {
FAST(20),
@ -290,15 +290,15 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
}
}
}
StringsConfig stringsConfig = new StringsConfig();
Map<String, String> stringsOptions = KeywordSearchSettings.getStringExtractOptions();
stringsConfig.setExtractUTF8(Boolean.parseBoolean(stringsOptions.get(StringsExtractOptions.EXTRACT_UTF8.toString())));
stringsConfig.setExtractUTF16(Boolean.parseBoolean(stringsOptions.get(StringsExtractOptions.EXTRACT_UTF16.toString())));
stringsConfig.setLanguageScripts(KeywordSearchSettings.getStringExtractScripts());
stringsExtractionContext = Lookups.fixed(stringsConfig);
indexer = new Indexer();
initialized = true;
}
@ -482,12 +482,12 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
imageConfig.setOCREnabled(KeywordSearchSettings.getOcrOption());
ProcessTerminator terminator = () -> context.fileIngestIsCancelled();
Lookup extractionContext = Lookups.fixed(imageConfig, terminator);
try {
TextExtractor extractor = TextExtractorFactory.getExtractor(aFile,extractionContext);
TextExtractor extractor = TextExtractorFactory.getExtractor(aFile, extractionContext);
Reader extractedTextReader = extractor.getReader();
//divide into chunks and index
return Ingester.getDefault().indexText(extractedTextReader,aFile.getId(),aFile.getName(), aFile, context);
return Ingester.getDefault().indexText(extractedTextReader, aFile.getId(), aFile.getName(), aFile, context);
} catch (TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
//No text extractor found... run the default instead
return false;
@ -509,7 +509,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
}
TextExtractor stringsExtractor = TextExtractorFactory.getStringsExtractor(aFile, stringsExtractionContext);
Reader extractedTextReader = stringsExtractor.getReader();
if (Ingester.getDefault().indexText(extractedTextReader,aFile.getId(),aFile.getName(), aFile, KeywordSearchIngestModule.this.context)) {
if (Ingester.getDefault().indexText(extractedTextReader, aFile.getId(), aFile.getName(), aFile, KeywordSearchIngestModule.this.context)) {
putIngestStatus(jobId, aFile.getId(), IngestStatus.STRINGS_INGESTED);
return true;
} else {
@ -619,12 +619,16 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
try {
TextFileExtractor textFileExtractor = new TextFileExtractor();
Reader textReader = textFileExtractor.getReader(aFile);
if (Ingester.getDefault().indexText(textReader, aFile.getId(), aFile.getName(), aFile, context)) {
if (textReader == null) {
logger.log(Level.INFO, "Unable to extract with TextFileExtractor, Reader was null for file: {0}", aFile.getName());
} else if (Ingester.getDefault().indexText(textReader, aFile.getId(), aFile.getName(), aFile, context)) {
putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED);
wasTextAdded = true;
}
} catch (IngesterException | TextFileExtractorException ex) {
} catch (IngesterException ex) {
logger.log(Level.WARNING, "Unable to index as unicode", ex);
} catch (TextFileExtractorException ex) {
logger.log(Level.INFO, "Could not extract text with TextFileExtractor", ex);
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -48,7 +48,9 @@ final class TextFileExtractor {
throw new TextFileExtractorException("Unable to get string from detected text in TextFileExtractor", ex);
}
CharsetMatch match = detector.detect();
if (match.getConfidence() < MIN_MATCH_CONFIDENCE) {
if (match == null) {
throw new TextFileExtractorException("Unable to detect any matches using TextFileExtractor");
} else if (match.getConfidence() < MIN_MATCH_CONFIDENCE) {
throw new TextFileExtractorException("Text does not match any character set with a high enough confidence for TextFileExtractor");
}

View File

@ -0,0 +1,21 @@
SelectMultiUserCasesPanel.selectAllButton.text=Select All
SelectMultiUserCasesPanel.deselectAllButton.text=Deselect All
SelectMultiUserCasesPanel.jLabel1.text=Select case(s) for keyword search or start typing to search by case name
SelectMultiUserCasesPanel.confirmSelections.text=OK
SelectMultiUserCasesPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchErrorDialog.closeButton.text=Close
MultiCaseKeywordSearchPanel.exactRadioButton.text_1=Exact Match
MultiCaseKeywordSearchPanel.substringRadioButton.text_1=Substring Match
MultiCaseKeywordSearchPanel.regexRadioButton.text_1=Regular Expression
MultiCaseKeywordSearchPanel.keywordTextField.text_1=
MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text=Perform a keyword search on the selected cases. The case can be opened to examine the results more closely.
MultiCaseKeywordSearchPanel.casesLabel.text_1=Cases
MultiCaseKeywordSearchPanel.resultsLabel.text=Results
MultiCaseKeywordSearchPanel.searchButton.text=Search
MultiCaseKeywordSearchPanel.viewErrorsButton.text=View Errors
MultiCaseKeywordSearchPanel.warningLabel.text=
MultiCaseKeywordSearchPanel.exportButton.text=Export Results
MultiCaseKeywordSearchPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchPanel.resultsCountLabel.text=
MultiCaseKeywordSearchPanel.pickCasesButton.text_1=Add Cases
SelectMultiUserCasesPanel.refreshButton.text=Refresh

View File

@ -0,0 +1,102 @@
CTL_MultiCaseKeywordSearchOpenAction=Multi-case Keyword Search
CTL_MultiCaseKeywordSearchTopComponent=Multi-case Keyword Search
CTL_MultiCaseKeywordSearchTopComponentAction=Multi-case Keyword Search
MultiCaseKeywordSearchErrorDialog.title.text=Error(s) While Searching
MultiCaseKeywordSearchNode.copyResultAction.text=Copy to clipboard
MultiCaseKeywordSearchNode.OpenCaseAction.text=Open Case
MultiCaseKeywordSearchNode.properties.case=Case
MultiCaseKeywordSearchNode.properties.caseDirectory=Case Directory
MultiCaseKeywordSearchNode.properties.dataSource=Data Source
MultiCaseKeywordSearchNode.properties.path=Keyword Hit Source Path
MultiCaseKeywordSearchNode.properties.source=Keyword Hit Source
MultiCaseKeywordSearchNode.properties.sourceType=Keyword Hit Source Type
MultiCaseKeywordSearchPanel.continueSearch.text=A search is currently being performed. Would you like the search to continue in the background while the search window is closed?
MultiCaseKeywordSearchPanel.continueSearch.title=Closing multi-case search
MultiCaseKeywordSearchPanel.countOfResults.label=Count:
MultiCaseKeywordSearchPanel.emptyNode.waitText=Please Wait...
# {0} - numberOfErrors
MultiCaseKeywordSearchPanel.errorsEncounter.text={0} Error(s) encountered while performing search
MultiCaseKeywordSearchPanel.searchResultsExport.csvExtensionFilterlbl=Comma Separated Values File (csv)
# {0} - file name
MultiCaseKeywordSearchPanel.searchResultsExport.exportMsg=Search results exported to {0}
MultiCaseKeywordSearchPanel.searchResultsExport.failedExportMsg=Export of search results failed
MultiCaseKeywordSearchPanel.searchResultsExport.featureName=Search Results Export
# {0} - file name
MultiCaseKeywordSearchPanel.searchResultsExport.fileExistPrompt=File {0} exists, overwrite?
MultiCaseKeywordSearchPanel.searchThread.cancellingText=Cancelling search
MultiCaseKeywordSearchPanel.warningText.emptySearch=You must enter something to search for in the text field.
MultiCaseKeywordSearchPanel.warningText.noCases=At least one case must be selected to perform a search.
MultiCaseKeywordSearchTopComponent.exceptionMessage.failedToCreatePanel=Failed to create Multi-case Keyword Search panel.
MultiCaseKeywordSearchTopComponent.name.text=Multi-case Keyword Search
MultiCaseSearcher.exceptionMessage.cancelledMessage=Search cancelled
# {0} - connection info
# {1} - case name
# {2} - case directory
MultiCaseSearcher.exceptionMessage.errorLoadingCore=Error connecting to Solr server and loading core (URL: {0}) for case {1} in {2}
# {0} - PostgreSQL server host
# {1} - PostgreSQL server port
# {2} - case database name
# {3} - case directory
MultiCaseSearcher.exceptionMessage.errorOpeningCaseDatabase=Error connecting to PostgreSQL server (Host/Port: [{0}:{1}] and opening case database {2} for case at {3}
# {0} - case directory
MultiCaseSearcher.exceptionMessage.failedToFindCaseMetadata=Failed to find case metadata file in {0}
# {0} - case_name
MultiCaseSearcher.exceptionMessage.failedToGetCaseDatabaseConnectionInfo=Failed to get case database connection info for case {0}
# {0} - case directory path
MultiCaseSearcher.exceptionMessage.failedToGetCaseDirReadlock=Failed to obtain read lock for case directory at {0}
# {0} - case directory
MultiCaseSearcher.exceptionMessage.failedToParseCaseMetadata=Failed to parse case file metadata in {0}
# {0} - Solr document id
# {1} - case database name
# {2} - case directory
MultiCaseSearcher.exceptionMessage.hitProcessingError=Failed to query case database for processing of Solr object id {0} of case {1} in {2}
# {0} - file name
# {1} - case directory
MultiCaseSearcher.exceptionMessage.missingSolrPropertiesFile=Missing {0} file in {1}
# {0} - file name
# {1} - case directory
MultiCaseSearcher.exceptionMessage.solrPropertiesFileParseError=Error parsing {0} file in {1}
# {0} - query
# {1} - case_name
MultiCaseSearcher.exceptionMessage.solrQueryError=Failed to execute query "{0}" on case {1}
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.acquiringSharedLockForCase=Acquiring shared lock for "{0}" ({1} of {2} case(s))
MultiCaseSearcher.progressMessage.creatingSolrQuery=Creating search query for Solr server
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.executingSolrQueryForCase=Getting keyword hits for "{0}" ({1} of {2} case(s))
MultiCaseSearcher.progressMessage.findingCases=Finding selected cases
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.loadingSolrCoreForCase=Loading Solr core for "{0}" ({1} of {2} case(s))
# {0} - case name
# {1} - case counter
# {2} - total cases
MultiCaseSearcher.progressMessage.openingCaseDbForCase=Opening case database for "{0}" ({1} of {2} case(s))
# {0} - total cases
MultiCaseSearcher.progressMessage.startingCaseSearches=Searching {0} case(s)
SelectMultiUserCasesPanel.selectAllButton.text=Select All
SelectMultiUserCasesPanel.deselectAllButton.text=Deselect All
SelectMultiUserCasesPanel.jLabel1.text=Select case(s) for keyword search or start typing to search by case name
SelectMultiUserCasesPanel.confirmSelections.text=OK
SelectMultiUserCasesPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchErrorDialog.closeButton.text=Close
MultiCaseKeywordSearchPanel.exactRadioButton.text_1=Exact Match
MultiCaseKeywordSearchPanel.substringRadioButton.text_1=Substring Match
MultiCaseKeywordSearchPanel.regexRadioButton.text_1=Regular Expression
MultiCaseKeywordSearchPanel.keywordTextField.text_1=
MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text=Perform a keyword search on the selected cases. The case can be opened to examine the results more closely.
MultiCaseKeywordSearchPanel.casesLabel.text_1=Cases
MultiCaseKeywordSearchPanel.resultsLabel.text=Results
MultiCaseKeywordSearchPanel.searchButton.text=Search
MultiCaseKeywordSearchPanel.viewErrorsButton.text=View Errors
MultiCaseKeywordSearchPanel.warningLabel.text=
MultiCaseKeywordSearchPanel.exportButton.text=Export Results
MultiCaseKeywordSearchPanel.cancelButton.text=Cancel
MultiCaseKeywordSearchPanel.resultsCountLabel.text=
MultiCaseKeywordSearchPanel.pickCasesButton.text_1=Add Cases
SelectMultiUserCasesPanel.refreshButton.text=Refresh

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="2"/>
</SyntheticProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="errorsScrollPane" pref="480" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="closeButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="errorsScrollPane" pref="196" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="closeButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="14" pref="14" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="errorsScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[470, 175]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="errorsTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="40"/>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[460, 160]"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JButton" name="closeButton">
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="closeButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,113 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import javax.swing.JDialog;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
/**
* Dialog to display the errors encounter while perfomring a multi-case keyword
* search.
*/
final class MultiCaseKeywordSearchErrorDialog extends JDialog {
private static final long serialVersionUID = 1L;
/**
* Creates new MultiCaseKeywordSearchErrorDialog
*/
@Messages({"MultiCaseKeywordSearchErrorDialog.title.text=Error(s) While Searching"})
MultiCaseKeywordSearchErrorDialog(String contents) {
setTitle(Bundle.MultiCaseKeywordSearchErrorDialog_title_text());
initComponents();
errorsTextArea.setText(contents);
this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
pack();
setModal(true);
setResizable(false);
setVisible(true);
}
/**
* 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
errorsScrollPane = new javax.swing.JScrollPane();
errorsTextArea = new javax.swing.JTextArea();
closeButton = new javax.swing.JButton();
errorsScrollPane.setPreferredSize(new java.awt.Dimension(470, 175));
errorsTextArea.setEditable(false);
errorsTextArea.setColumns(40);
errorsTextArea.setLineWrap(true);
errorsTextArea.setRows(5);
errorsTextArea.setWrapStyleWord(true);
errorsTextArea.setPreferredSize(new java.awt.Dimension(460, 160));
errorsScrollPane.setViewportView(errorsTextArea);
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchErrorDialog.class, "MultiCaseKeywordSearchErrorDialog.closeButton.text")); // NOI18N
closeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(errorsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 480, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(closeButton)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(errorsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(closeButton)
.addGap(14, 14, 14))
);
}// </editor-fold>//GEN-END:initComponents
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
dispose();
}//GEN-LAST:event_closeButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton closeButton;
private javax.swing.JScrollPane errorsScrollPane;
private javax.swing.JTextArea errorsTextArea;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,292 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import static org.sleuthkit.autopsy.casemodule.CaseMetadata.getFileExtension;
import org.sleuthkit.autopsy.casemodule.StartupWindowProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
@NbBundle.Messages({
"MultiCaseKeywordSearchNode.properties.case=Case",
"MultiCaseKeywordSearchNode.properties.caseDirectory=Case Directory",
"MultiCaseKeywordSearchNode.properties.dataSource=Data Source",
"MultiCaseKeywordSearchNode.properties.sourceType=Keyword Hit Source Type",
"MultiCaseKeywordSearchNode.properties.source=Keyword Hit Source",
"MultiCaseKeywordSearchNode.properties.path=Keyword Hit Source Path"
})
/**
* A root node containing child nodes of the results of a multi-case keyword
* Search.
*/
class MultiCaseKeywordSearchNode extends AbstractNode {
private static final Logger LOGGER = Logger.getLogger(MultiCaseKeywordSearchNode.class.getName());
/**
* Construct a new MultiCaseKeywordSearchNode
*
* @param resultList the list of KeywordSearchHits which will be the
* children of this node.
*/
MultiCaseKeywordSearchNode(Collection<SearchHit> resultList) {
super(new MultiCaseKeywordSearchChildren(resultList));
}
/**
* A factory for creating children of the MultiCaseKeywordSearchNode.
*/
static class MultiCaseKeywordSearchChildren extends Children.Keys<SearchHit> {
private final Collection<SearchHit> resultList;
/**
* Construct a new MultiCaseKeywordSearchChildren
*
* @param resultList the list of KeywordSearchHits which will be used to
* construct the children.
*/
MultiCaseKeywordSearchChildren(Collection<SearchHit> resultList) {
this.resultList = resultList;
}
@Override
protected void addNotify() {
super.addNotify();
setKeys(resultList);
}
@Override
protected void removeNotify() {
super.removeNotify();
setKeys(Collections.emptyList());
}
@Override
protected Node[] createNodes(SearchHit t) {
return new Node[]{new SearchHitNode(t)};
}
@Override
public Object clone() {
return super.clone();
}
}
/**
* A leaf node which represents a hit for the multi-case keyword search.
*/
static final class SearchHitNode extends AbstractNode {
private final SearchHit searchHit;
/**
* Construct a new SearchHitNode
*
* @param kwsHit the KeywordSearchHit which will be represented by this
* node.
*/
SearchHitNode(SearchHit kwsHit) {
super(Children.LEAF);
searchHit = kwsHit;
super.setName(searchHit.getCaseDisplayName());
setDisplayName(searchHit.getCaseDisplayName());
}
@Override
public Action getPreferredAction() {
return new OpenCaseAction(getCasePath());
}
/**
* Get the path to the case directory
*
* @return the path to the case directory for the KeywordSearchHit
* represented by this node
*/
private String getCasePath() {
return searchHit.getCaseDirectoryPath();
}
@Override
protected Sheet createSheet() {
Sheet s = super.createSheet();
Sheet.Set ss = s.get(Sheet.PROPERTIES);
if (ss == null) {
ss = Sheet.createPropertiesSet();
s.put(ss);
}
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_case(), Bundle.MultiCaseKeywordSearchNode_properties_case(), Bundle.MultiCaseKeywordSearchNode_properties_case(),
searchHit.getCaseDisplayName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(),
searchHit.getCaseDirectoryPath()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(),
searchHit.getDataSourceName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(),
searchHit.getSourcePath()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(),
searchHit.getSourceType().getDisplayName()));
ss.put(new NodeProperty<>(Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source(),
searchHit.getSourceName()));
return s;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actions = new ArrayList<>();
actions.add(new OpenCaseAction(getCasePath()));
actions.add(new CopyResultAction(searchHit));
return actions.toArray(new Action[actions.size()]);
}
}
@NbBundle.Messages({"MultiCaseKeywordSearchNode.copyResultAction.text=Copy to clipboard"})
/**
* Put the contents of the selected row in the clipboard in the same tab
* seperated format as pressing ctrl+c.
*/
private static class CopyResultAction extends AbstractAction {
private static final long serialVersionUID = 1L;
SearchHit result;
/**
* Construct a new CopyResultAction
*/
CopyResultAction(SearchHit selectedResult) {
super(Bundle.MultiCaseKeywordSearchNode_copyResultAction_text());
result = selectedResult;
}
@Override
public void actionPerformed(ActionEvent e) {
StringSelection resultSelection = new StringSelection(result.getCaseDisplayName() + "\t"
+ result.getCaseDirectoryPath() + "\t"
+ result.getDataSourceName() + "\t"
+ result.getSourceType().getDisplayName() + "\t"
+ result.getSourceName() + "\t"
+ result.getSourcePath() + "\t");
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(resultSelection, resultSelection);
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"MultiCaseKeywordSearchNode.OpenCaseAction.text=Open Case"})
/**
* Action to open the case associated with the selected node.
*/
private static class OpenCaseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final String caseDirPath;
/**
* Finds the path to the .aut file for the specified case directory.
*
* @param caseDirectory the directory to check for a .aut file
*
* @return the path to the first .aut file found in the directory
*
* @throws CaseActionException if there was an issue finding a .aut file
*/
private static String findAutFile(String caseDirectory) throws CaseActionException {
File caseFolder = Paths.get(caseDirectory).toFile();
if (caseFolder.exists()) {
/*
* Search for '*.aut' files.
*/
File[] fileArray = caseFolder.listFiles();
if (fileArray == null) {
throw new CaseActionException("No files found in case directory");
}
String autFilePath = null;
for (File file : fileArray) {
String name = file.getName().toLowerCase();
if (autFilePath == null && name.endsWith(getFileExtension())) {
return file.getAbsolutePath();
}
}
throw new CaseActionException("No .aut files found in case directory");
}
throw new CaseActionException("Case directory was not found");
}
/**
* Construct a new open case action
*
* @param path the path to the case directory for the case to open
*/
OpenCaseAction(String path) {
super(Bundle.MultiCaseKeywordSearchNode_OpenCaseAction_text());
caseDirPath = path;
}
@Override
public void actionPerformed(ActionEvent e) {
StartupWindowProvider.getInstance().close();
new Thread(
() -> {
try {
Case.openAsCurrentCase(findAutFile(caseDirPath));
} catch (CaseActionException ex) {
if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) {
LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseDirPath), ex); //NON-NLS
MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage());
}
}
}
).start();
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.core.UserPreferences;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.MultiCaseKeywordSearchOpenAction")
@ActionReference(path = "Menu/Tools", position = 202)
@ActionRegistration(displayName = "#CTL_MultiCaseKeywordSearchOpenAction", lazy = false)
@Messages({"CTL_MultiCaseKeywordSearchOpenAction=Multi-case Keyword Search"})
/**
* Action to open the top level component for the multi-case keyword search.
*/
public final class MultiCaseKeywordSearchOpenAction extends CallableSystemAction {
private static final String DISPLAY_NAME = Bundle.CTL_MultiCaseKeywordSearchOpenAction();
private static final long serialVersionUID = 1L;
@Override
public boolean isEnabled() {
return UserPreferences.getIsMultiUserModeEnabled();
}
@Override
public void performAction() {
MultiCaseKeywordSearchTopComponent.openTopComponent();
}
@Override
public String getName() {
return DISPLAY_NAME;
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
@Override
public boolean asynchronous() {
return false; // run on edt
}
}

View File

@ -0,0 +1,374 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="searchTypeGroup">
</Component>
</NonVisualComponents>
<Properties>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
<Property name="opaque" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 442]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="exactRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="substringRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="regexRadioButton" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="keywordTextField" pref="591" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="toolDescriptionScrollPane" min="-2" pref="295" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="casesLabel" min="-2" max="-2" attributes="0"/>
<Component id="jScrollPane1" min="-2" pref="174" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="pickCasesButton" min="-2" pref="84" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="searchButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="resultsLabel" min="-2" pref="154" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="resultsCountLabel" min="-2" pref="98" max="-2" attributes="0"/>
</Group>
<Component id="resultsScrollPane" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="viewErrorsButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="warningLabel" alignment="0" pref="607" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="exportButton" max="32767" attributes="0"/>
<Component id="cancelButton" pref="87" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="196" max="-2" attributes="0"/>
<Component id="searchProgressBar" pref="608" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="108" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="102" attributes="0">
<Component id="keywordTextField" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="regexRadioButton" alignment="0" min="-2" pref="23" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="exactRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="substringRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<Component id="toolDescriptionScrollPane" max="32767" attributes="0"/>
</Group>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="casesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
<Component id="resultsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="resultsCountLabel" alignment="3" min="-2" pref="14" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="resultsScrollPane" pref="281" max="32767" attributes="0"/>
<Component id="jScrollPane1" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="warningLabel" min="-2" pref="15" max="-2" attributes="0"/>
<Component id="exportButton" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="pickCasesButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="searchButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="viewErrorsButton" min="-2" max="-2" attributes="0"/>
<Component id="cancelButton" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace pref="433" max="32767" attributes="0"/>
<Component id="searchProgressBar" min="-2" pref="22" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="searchButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.searchButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="substringRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.substringRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="substringRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="keywordTextField">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Monospaced" size="14" style="0"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.keywordTextField.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
<LineBorder roundedCorners="true">
<Color PropertyName="color" blue="c0" green="c0" red="c0" type="rgb"/>
</LineBorder>
</Border>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2, 25]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2, 25]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="exactRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.exactRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="regexRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="searchTypeGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.regexRadioButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="casesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.casesLabel.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="resultsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.resultsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JScrollPane" name="toolDescriptionScrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="toolDescriptionTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="3"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="resultsScrollPane">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 40]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 100]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JProgressBar" name="searchProgressBar">
</Component>
<Component class="javax.swing.JLabel" name="warningLabel">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="c8" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.warningLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="exportButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.exportButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 2, 2, 2]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[84, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="resultsCountLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.resultsCountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="viewErrorsButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.viewErrorsButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewErrorsButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="pickCasesButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="MultiCaseKeywordSearchPanel.pickCasesButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pickCasesButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="caseSelectionList">
<Properties>
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
<StringArray count="0"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,860 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import com.google.common.eventbus.Subscribe;
import com.google.common.eventbus.DeadEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.AbstractButton;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.table.TableColumn;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.TableCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.openide.explorer.ExplorerManager;
import org.netbeans.swing.outline.Outline;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.EmptyNode;
import org.sleuthkit.autopsy.keywordsearch.multicase.MultiCaseSearcher.MultiCaseSearcherException;
import org.sleuthkit.autopsy.keywordsearch.multicase.SearchQuery.QueryType;
/**
* Panel to display the controls and results for the multi-case search.
*/
final class MultiCaseKeywordSearchPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
@Messages({
"MultiCaseKeywordSearchPanel.emptyNode.waitText=Please Wait..."
})
private static final long serialVersionUID = 1L;
private volatile SearchThread searchThread = null;
private final Outline outline;
private final ExplorerManager em;
private final org.openide.explorer.view.OutlineView outlineView;
private static final Logger LOGGER = Logger.getLogger(MultiCaseKeywordSearchPanel.class.getName());
private static final EmptyNode PLEASE_WAIT_NODE = new EmptyNode(Bundle.MultiCaseKeywordSearchPanel_emptyNode_waitText());
private static final MultiCaseKeywordSearchNode NO_RESULTS_NODE = new MultiCaseKeywordSearchNode(new ArrayList<>());
private Collection<SearchHit> allSearchHits = new ArrayList<>();
private Collection<MultiCaseSearcherException> searchExceptions = new ArrayList<>();
private final SelectMultiUserCasesDialog caseSelectionDialog = SelectMultiUserCasesDialog.getInstance();
private final Map<String, CaseNodeData> caseNameToCaseDataMap;
private Node[] currentConfirmedSelections;
/**
* Creates new form MultiCaseKeywordSearchPanel
*/
MultiCaseKeywordSearchPanel() {
em = new ExplorerManager();
outlineView = new org.openide.explorer.view.OutlineView();
outline = outlineView.getOutline();
outlineView.setPropertyColumns(
Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(),
Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(),
Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(),
Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(),
Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source());
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.MultiCaseKeywordSearchNode_properties_case());
initComponents();
outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
outline.setRootVisible(false);
outlineView.setPreferredSize(resultsScrollPane.getPreferredSize());
resultsScrollPane.setViewportView(outlineView);
caseSelectionDialog.subscribeToNewCaseSelections(new ChangeListener() {
@Override
public void nodeSelectionChanged(Node[] selections, List<CaseNodeData> selectionCaseData) {
populateCasesList(selectionCaseData);
currentConfirmedSelections = selections;
revalidate();
repaint();
}
});
searchEnabled(true);
outline.setRowSelectionAllowed(false);
searchProgressBar.setVisible(false);
exportButton.setEnabled(false);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
caseNameToCaseDataMap = new HashMap<>();
setColumnWidths();
//Disable selection in JList
caseSelectionList.setSelectionModel(new DefaultListSelectionModel() {
@Override
public void setSelectionInterval(int index0, int index1) {
super.setSelectionInterval(-1, -1);
}
});
}
/**
* Listener for new selections
*/
public interface ChangeListener {
public void nodeSelectionChanged(Node[] selections, List<CaseNodeData> selectionCaseData);
}
/**
* If a collection of SearchHits is received update the results shown on the
* panel to include them.
*
* @param hits the collection of SearchHits which was received.
*/
@Messages({"MultiCaseKeywordSearchPanel.countOfResults.label=Count: "})
@Subscribe
void subscribeToResults(Collection<SearchHit> hits) {
allSearchHits.addAll(hits);
if (allSearchHits.size() > 0) {
MultiCaseKeywordSearchNode resultsNode = new MultiCaseKeywordSearchNode(allSearchHits);
SwingUtilities.invokeLater(() -> {
em.setRootContext(resultsNode);
outline.setRowSelectionAllowed(true);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + Integer.toString(outline.getRowCount()));
});
} else {
em.setRootContext(NO_RESULTS_NODE);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
}
}
/**
* If a string is received and it matches the
* MultiCaseSearcher.SEARCH_COMPLETE_STRING reset elements of this panel to
* reflect that the search is done.
*
* @param stringRecived the String which was received
*/
@Subscribe
void subscribeToStrings(String stringReceived) {
if (stringReceived.equals(MultiCaseSearcher.getSearchCompleteMessage())) {
searchThread.unregisterWithSearcher(MultiCaseKeywordSearchPanel.this);
searchThread = null;
searchEnabled(true);
if (!searchExceptions.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_errorsEncounter_text(searchExceptions.size()));
}
if (!em.getRootContext().equals(PLEASE_WAIT_NODE) && !em.getRootContext().equals(NO_RESULTS_NODE)) {
exportButton.setEnabled(true);
SwingUtilities.invokeLater(() -> {
exportButton.setEnabled(true);
setColumnWidths();
});
}
} else {
//If it is not the SEARCH_COMPLETE_STRING log it.
LOGGER.log(Level.INFO, "String posted to MultiCaseKeywordSearchPanel EventBus with value of " + stringReceived);
}
}
/**
* If a InterruptedException is received over the EventBus update the
* warning label.
*
* @param exception the InterruptedException which was received.
*/
@Subscribe
void subscribeToInterruptionExceptions(InterruptedException exception) {
warningLabel.setText(exception.getMessage());
//if we are still displaying please wait force it to update to no results
if (em.getRootContext().equals(PLEASE_WAIT_NODE)) {
em.setRootContext(NO_RESULTS_NODE);
resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
}
}
/**
* If a MultiCaseSearcherException is received over the EventBus cancel the
* current search and update the warning label.
*
* @param exception the MultiCaseSearcherException which was received.
*/
@Messages({"# {0} - numberOfErrors",
"MultiCaseKeywordSearchPanel.errorsEncounter.text={0} Error(s) encountered while performing search"
})
@Subscribe
void subscribeToMultiCaseSearcherExceptions(MultiCaseSearcherException exception) {
searchExceptions.add(exception);
}
/**
* Log all other events received over the event bus which are not
* specifically covered by another @Subscribe method
*
* @param deadEvent Any object received over the event bus which was not of
* a type otherwise subscribed to
*/
@Subscribe
void subscribeToDeadEvents(DeadEvent deadEvent) {
LOGGER.log(Level.INFO, "Dead Event posted to MultiCaseKeywordSearchPanel EventBus " + deadEvent.toString());
}
private void displaySearchErrors() {
if (!searchExceptions.isEmpty()) {
StringBuilder strBuilder = new StringBuilder("");
searchExceptions.forEach((exception) -> {
strBuilder.append("- ").append(exception.getMessage()).append(System.lineSeparator());
});
SwingUtilities.invokeLater(() -> {
new MultiCaseKeywordSearchErrorDialog(strBuilder.toString());
});
}
}
/**
* Get the list of cases from the Multi user case browser
*/
private void populateCasesList(List<CaseNodeData> selectedNodes) {
caseSelectionList.removeAll();
caseSelectionList.revalidate();
caseSelectionList.repaint();
caseNameToCaseDataMap.clear();
DefaultListModel<String> listModel = new DefaultListModel<>();
Collections.sort(selectedNodes, (CaseNodeData o1, CaseNodeData o2) -> {
return o1.getName().toLowerCase()
.compareTo(o2.getName().toLowerCase());
});
for (int i = 0; i < selectedNodes.size(); i++) {
CaseNodeData data = selectedNodes.get(i);
String multiUserCaseName = data.getName();
listModel.addElement(multiUserCaseName);
/**
* Map out the name to CaseNodeData so we can retrieve it later for
* search.
*/
caseNameToCaseDataMap.put(multiUserCaseName, data);
}
caseSelectionList.setModel(listModel);
}
@Override
public ExplorerManager getExplorerManager() {
return em;
}
/**
* 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
searchTypeGroup = new javax.swing.ButtonGroup();
searchButton = new javax.swing.JButton();
substringRadioButton = new javax.swing.JRadioButton();
keywordTextField = new javax.swing.JTextField();
exactRadioButton = new javax.swing.JRadioButton();
regexRadioButton = new javax.swing.JRadioButton();
casesLabel = new javax.swing.JLabel();
resultsLabel = new javax.swing.JLabel();
toolDescriptionScrollPane = new javax.swing.JScrollPane();
toolDescriptionTextArea = new javax.swing.JTextArea();
resultsScrollPane = new javax.swing.JScrollPane();
cancelButton = new javax.swing.JButton();
searchProgressBar = new javax.swing.JProgressBar();
warningLabel = new javax.swing.JLabel();
exportButton = new javax.swing.JButton();
resultsCountLabel = new javax.swing.JLabel();
viewErrorsButton = new javax.swing.JButton();
pickCasesButton = new javax.swing.JButton();
jScrollPane1 = new javax.swing.JScrollPane();
caseSelectionList = new javax.swing.JList<>();
setName(""); // NOI18N
setOpaque(false);
setPreferredSize(new java.awt.Dimension(1000, 442));
org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.searchButton.text")); // NOI18N
searchButton.setMaximumSize(new java.awt.Dimension(84, 23));
searchButton.setMinimumSize(new java.awt.Dimension(84, 23));
searchButton.setPreferredSize(new java.awt.Dimension(84, 23));
searchButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
searchButtonActionPerformed(evt);
}
});
searchTypeGroup.add(substringRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(substringRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.substringRadioButton.text_1")); // NOI18N
substringRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
substringRadioButtonActionPerformed(evt);
}
});
keywordTextField.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N
keywordTextField.setText(org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.keywordTextField.text_1")); // NOI18N
keywordTextField.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(192, 192, 192), 1, true));
keywordTextField.setMinimumSize(new java.awt.Dimension(2, 25));
keywordTextField.setPreferredSize(new java.awt.Dimension(2, 25));
searchTypeGroup.add(exactRadioButton);
exactRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.exactRadioButton.text_1")); // NOI18N
searchTypeGroup.add(regexRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.regexRadioButton.text_1")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(casesLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.casesLabel.text_1")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(resultsLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.resultsLabel.text")); // NOI18N
toolDescriptionTextArea.setEditable(false);
toolDescriptionTextArea.setBackground(new java.awt.Color(240, 240, 240));
toolDescriptionTextArea.setColumns(20);
toolDescriptionTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
toolDescriptionTextArea.setLineWrap(true);
toolDescriptionTextArea.setRows(3);
toolDescriptionTextArea.setText(org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text")); // NOI18N
toolDescriptionTextArea.setWrapStyleWord(true);
toolDescriptionTextArea.setFocusable(false);
toolDescriptionScrollPane.setViewportView(toolDescriptionTextArea);
resultsScrollPane.setMinimumSize(new java.awt.Dimension(100, 40));
resultsScrollPane.setPreferredSize(new java.awt.Dimension(200, 100));
resultsScrollPane.setRequestFocusEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.cancelButton.text")); // NOI18N
cancelButton.setEnabled(false);
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
warningLabel.setForeground(new java.awt.Color(200, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.warningLabel.text")); // NOI18N
warningLabel.setFocusable(false);
org.openide.awt.Mnemonics.setLocalizedText(exportButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.exportButton.text")); // NOI18N
exportButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
exportButton.setMaximumSize(new java.awt.Dimension(84, 23));
exportButton.setMinimumSize(new java.awt.Dimension(84, 23));
exportButton.setPreferredSize(new java.awt.Dimension(84, 23));
exportButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
exportButtonActionPerformed(evt);
}
});
resultsCountLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
org.openide.awt.Mnemonics.setLocalizedText(resultsCountLabel, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.resultsCountLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(viewErrorsButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.viewErrorsButton.text")); // NOI18N
viewErrorsButton.setEnabled(false);
viewErrorsButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
viewErrorsButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(pickCasesButton, org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, "MultiCaseKeywordSearchPanel.pickCasesButton.text_1")); // NOI18N
pickCasesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pickCasesButtonActionPerformed(evt);
}
});
jScrollPane1.setViewportView(caseSelectionList);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(exactRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(substringRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(regexRadioButton))
.addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 591, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(toolDescriptionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 295, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(casesLabel)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 174, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(pickCasesButton, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(resultsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(viewErrorsButton)
.addComponent(warningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 607, Short.MAX_VALUE))
.addGap(14, 14, 14)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(exportButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(cancelButton, javax.swing.GroupLayout.DEFAULT_SIZE, 87, Short.MAX_VALUE))))))
.addContainerGap())
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(196, 196, 196)
.addComponent(searchProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 608, Short.MAX_VALUE)
.addGap(108, 108, 108)))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(regexRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(exactRadioButton)
.addComponent(substringRadioButton))))
.addComponent(toolDescriptionScrollPane))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(casesLabel)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(resultsLabel)
.addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE)
.addComponent(jScrollPane1))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(exportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(pickCasesButton)
.addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(viewErrorsButton)
.addComponent(cancelButton))
.addContainerGap())
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(433, Short.MAX_VALUE)
.addComponent(searchProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap()))
);
}// </editor-fold>//GEN-END:initComponents
@Messages({
"MultiCaseKeywordSearchPanel.warningText.noCases=At least one case must be selected to perform a search.",
"MultiCaseKeywordSearchPanel.warningText.emptySearch=You must enter something to search for in the text field."
})
/**
* perform a search if the previous search is done or no previous search has
* occured
*/
private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
if (null == searchThread) {
Collection<String> cases = getCases();
String searchString = keywordTextField.getText();
if (cases.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_noCases());
} else if (searchString.isEmpty()) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_emptySearch());
} else {
//Map case names to CaseNodeData objects
Collection<CaseNodeData> caseNodeData = cases.stream()
.map(c -> caseNameToCaseDataMap.get(c))
.collect(Collectors.toList());
//perform the search
warningLabel.setText("");
allSearchHits = new ArrayList<>();
searchExceptions = new ArrayList<>();
searchEnabled(false);
exportButton.setEnabled(false);
outline.setRowSelectionAllowed(false);
SearchQuery kwsQuery = new SearchQuery(getQueryType(), searchString);
em.setRootContext(PLEASE_WAIT_NODE);
resultsCountLabel.setText("");
searchThread = new SearchThread(caseNodeData, kwsQuery);
searchThread.registerWithSearcher(MultiCaseKeywordSearchPanel.this);
searchThread.start();
}
}
}//GEN-LAST:event_searchButtonActionPerformed
/**
* Get the case names from the Case List
*
* @return cases the cases that match the selected status of isSelected
*/
private Collection<String> getCases() {
Collection<String> cases = new HashSet<>();
ListModel<String> listModel = caseSelectionList.getModel();
for(int i = 0; i < listModel.getSize(); i++) {
String caseName = listModel.getElementAt(i);
cases.add(caseName);
}
return cases;
}
/**
* Get the type of Query which was selected by the user.
*
* @return one of the values of the QueryType enum
*/
private QueryType getQueryType() {
String queryTypeText = "";
Enumeration<AbstractButton> buttonGroup = searchTypeGroup.getElements();
while (buttonGroup.hasMoreElements()) {
AbstractButton dspButton = buttonGroup.nextElement();
if (dspButton.isSelected()) {
queryTypeText = dspButton.getText();
break;
}
}
if (queryTypeText.equals(substringRadioButton.getText())) {
return QueryType.SUBSTRING;
} else if (queryTypeText.equals(regexRadioButton.getText())) {
return QueryType.REGEX;
} else {
//default to Exact match
return QueryType.EXACT_MATCH;
}
}
/**
* Set the column widths to have their width influenced by the width of the
* content in them for up to the first hundred rows.
*/
private void setColumnWidths() {
int widthLimit = 1000;
int margin = 4;
int padding = 8;
for (int col = 0; col < outline.getColumnCount(); col++) {
int width = 115; //min initial width for columns
int rowsToResize = Math.min(outline.getRowCount(), 100);
for (int row = 0; row < rowsToResize; row++) {
if (outline.getValueAt(row, col) != null) {
TableCellRenderer renderer = outline.getCellRenderer(row, col);
Component comp = outline.prepareRenderer(renderer, row, col);
width = Math.max(comp.getPreferredSize().width, width);
}
}
width += 2 * margin + padding;
width = Math.min(width, widthLimit);
TableColumn column = outline.getColumnModel().getColumn(outline.convertColumnIndexToModel(col));
column.setPreferredWidth(width);
}
resultsScrollPane.setPreferredSize(new Dimension(outline.getPreferredSize().width, resultsScrollPane.getPreferredSize().height));
}
/**
* Cancel the current multi-case search which is being performed.
*
* @param evt ignored
*/
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
cancelSearch();
}//GEN-LAST:event_cancelButtonActionPerformed
/**
* Cancel the current multi-case search which is being performed.
*/
@Messages({
"MultiCaseKeywordSearchPanel.searchThread.cancellingText=Cancelling search"})
private void cancelSearch() {
if (null != searchThread) {
warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_searchThread_cancellingText());
searchThread.interrupt();
}
}
@Messages({"MultiCaseKeywordSearchPanel.searchResultsExport.csvExtensionFilterlbl=Comma Separated Values File (csv)",
"MultiCaseKeywordSearchPanel.searchResultsExport.featureName=Search Results Export",
"MultiCaseKeywordSearchPanel.searchResultsExport.failedExportMsg=Export of search results failed"
})
/**
* Export the currently displayed search results to a file specified by the
* user with data saved in comma seperated format.
*/
private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
JFileChooser chooser = new JFileChooser();
final String EXTENSION = "csv"; //NON-NLS
FileNameExtensionFilter csvFilter = new FileNameExtensionFilter(
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_csvExtensionFilterlbl(), EXTENSION);
chooser.setFileFilter(csvFilter);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setName("Choose file to export results to");
chooser.setMultiSelectionEnabled(false);
int returnVal = chooser.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selFile = chooser.getSelectedFile();
if (selFile == null) {
JOptionPane.showMessageDialog(this,
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.WARNING_MESSAGE);
LOGGER.warning("Selected file was null, when trying to export search results");
return;
}
String fileAbs = selFile.getAbsolutePath();
if (!fileAbs.endsWith("." + EXTENSION)) {
fileAbs = fileAbs + "." + EXTENSION;
selFile = new File(fileAbs);
}
saveResultsAsTextFile(selFile);
}
}//GEN-LAST:event_exportButtonActionPerformed
private void viewErrorsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewErrorsButtonActionPerformed
displaySearchErrors();
}//GEN-LAST:event_viewErrorsButtonActionPerformed
private void pickCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pickCasesButtonActionPerformed
caseSelectionDialog.setVisible(true);
if (currentConfirmedSelections != null) {
caseSelectionDialog.setNodeSelections(currentConfirmedSelections);
}
}//GEN-LAST:event_pickCasesButtonActionPerformed
private void substringRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_substringRadioButtonActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_substringRadioButtonActionPerformed
/**
* Set the user interface elements to reflect whether the search feature is
* currently enabled or disabled.
*
* @param canSearch True if the search feature should be enabled, false if
* it should be disabled.
*/
private void searchEnabled(boolean canSearch) {
searchButton.setEnabled(canSearch);
cancelButton.setEnabled(!canSearch);
viewErrorsButton.setEnabled(canSearch);
viewErrorsButton.setVisible(!searchExceptions.isEmpty());
}
@Messages({"# {0} - file name",
"MultiCaseKeywordSearchPanel.searchResultsExport.fileExistPrompt=File {0} exists, overwrite?",
"# {0} - file name",
"MultiCaseKeywordSearchPanel.searchResultsExport.exportMsg=Search results exported to {0}"
})
/**
* Saves the results to the file specified
*/
private void saveResultsAsTextFile(File resultsFile) {
if (resultsFile.exists()) {
//if the file already exists ask the user how to proceed
boolean shouldWrite = JOptionPane.showConfirmDialog(null,
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_fileExistPrompt(resultsFile.getName()),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
== JOptionPane.YES_OPTION;
if (!shouldWrite) {
return;
}
}
try {
BufferedWriter resultsWriter;
resultsWriter = new BufferedWriter(new FileWriter(resultsFile));
int col = 0;
//write headers
while (col < outline.getColumnCount()) {
resultsWriter.write(outline.getColumnName(col));
col++;
if (col < outline.getColumnCount()) {
resultsWriter.write(",");
}
}
resultsWriter.write(System.lineSeparator());
//write data
Children resultsChildren = em.getRootContext().getChildren();
for (int row = 0; row < resultsChildren.getNodesCount(); row++) {
col = 0;
while (col < outline.getColumnCount()) {
if (outline.getValueAt(row, col) instanceof Node.Property) {
resultsWriter.write(((Node.Property) outline.getValueAt(row, col)).getValue().toString());
} else {
resultsWriter.write(outline.getValueAt(row, col).toString());
}
col++;
if (col < outline.getColumnCount()) {
resultsWriter.write(",");
}
}
resultsWriter.write(System.lineSeparator());
}
resultsWriter.flush();
resultsWriter.close();
setColumnWidths();
JOptionPane.showMessageDialog(
WindowManager.getDefault().getMainWindow(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_exportMsg(resultsFile.getName()),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.INFORMATION_MESSAGE);
} catch (IllegalAccessException | IOException | InvocationTargetException ex) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
JOptionPane.WARNING_MESSAGE);
LOGGER.log(Level.WARNING, "Export of search results failed unable to write results csv file", ex);
}
}
/**
* Ask the user if they want to continue their search while this window is
* closed. Cancels the current search if they select no.
*/
@Messages({
"MultiCaseKeywordSearchPanel.continueSearch.text=A search is currently being performed. "
+ "Would you like the search to continue in the background while the search window is closed?",
"MultiCaseKeywordSearchPanel.continueSearch.title=Closing multi-case search"
})
void closeSearchPanel() {
if (cancelButton.isEnabled()) {
boolean shouldContinueSearch = JOptionPane.showConfirmDialog(null,
Bundle.MultiCaseKeywordSearchPanel_continueSearch_text(),
Bundle.MultiCaseKeywordSearchPanel_continueSearch_title(),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
== JOptionPane.YES_OPTION;
if (!shouldContinueSearch) {
cancelSearch();
}
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton cancelButton;
private javax.swing.JList<String> caseSelectionList;
private javax.swing.JLabel casesLabel;
private javax.swing.JRadioButton exactRadioButton;
private javax.swing.JButton exportButton;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextField keywordTextField;
private javax.swing.JButton pickCasesButton;
private javax.swing.JRadioButton regexRadioButton;
private javax.swing.JLabel resultsCountLabel;
private javax.swing.JLabel resultsLabel;
private javax.swing.JScrollPane resultsScrollPane;
private javax.swing.JButton searchButton;
private javax.swing.JProgressBar searchProgressBar;
private javax.swing.ButtonGroup searchTypeGroup;
private javax.swing.JRadioButton substringRadioButton;
private javax.swing.JScrollPane toolDescriptionScrollPane;
private javax.swing.JTextArea toolDescriptionTextArea;
private javax.swing.JButton viewErrorsButton;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
/*
* A thread that performs a keyword search of cases
*/
private final class SearchThread extends Thread {
private final Collection<CaseNodeData> caseNodes;
private final SearchQuery searchQuery;
private final MultiCaseSearcher multiCaseSearcher = new MultiCaseSearcher();
/**
* Constructs a thread that performs a keyword search of cases
*
* @param caseNames The names of the cases to search.
* @param query The keyword search query to perform.
*/
private SearchThread(Collection<CaseNodeData> caseNodes, SearchQuery searchQuery) {
this.caseNodes = caseNodes;
this.searchQuery = searchQuery;
}
/**
* Register an object with the MultiCaseSearcher eventBus so that the
* object's subscribe methods can receive results.
*
* @param object the object to register with the MultiCaseSearcher
*/
private void registerWithSearcher(Object object) {
multiCaseSearcher.registerWithEventBus(object);
}
/**
* Unregister an object with the MultiCaseSearcher so that the object's
* subscribe methods no longer receive results.
*
* @param object the object to unregister with the MultiCaseSearcher
*/
private void unregisterWithSearcher(Object object) {
multiCaseSearcher.unregisterWithEventBus(object);
}
@Override
public void interrupt() {
super.interrupt();
//in case it is running a method which causes InterruptedExceptions to be ignored
multiCaseSearcher.stopMultiCaseSearch();
}
@Override
public void run() {
multiCaseSearcher.performKeywordSearch(caseNodes, searchQuery, new MultiCaseKeywordSearchProgressIndicator(searchProgressBar));
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
/**
* A progress indicator that updates a JProgressBar.
*/
final class MultiCaseKeywordSearchProgressIndicator implements ProgressIndicator {
private final JProgressBar progress;
/**
* Construct a new JProgressIndicator
*
* @param progressBar the JProgressBar you want this indicator to update
*/
MultiCaseKeywordSearchProgressIndicator(JProgressBar progressBar) {
progress = progressBar;
progress.setStringPainted(true);
}
/**
* Start showing progress in the progress bar.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param max The total number of work units to be completed.
*/
@Override
public void start(String message, int max) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(false);
progress.setMinimum(0);
progress.setString(message); //the message
progress.setValue(0);
progress.setMaximum(max);
progress.setVisible(true);
});
}
/**
* Start showing progress in the progress bar.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void start(String message) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(true);
progress.setMinimum(0);
progress.setString(message);
progress.setValue(0);
progress.setVisible(true);
});
}
/**
* Switches the progress indicator to indeterminate mode (the total number
* of work units to be completed is unknown).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void switchToIndeterminate(String message) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(true);
progress.setString(message);
});
}
/**
* Switches the progress indicator to determinate mode (the total number of
* work units to be completed is known).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param current The number of work units completed so far.
* @param max The total number of work units to be completed.
*/
@Override
public void switchToDeterminate(String message, int current, int max) {
SwingUtilities.invokeLater(() -> {
progress.setIndeterminate(false);
progress.setMinimum(0);
progress.setString(message);
progress.setValue(current);
progress.setMaximum(max);
});
}
/**
* Updates the progress indicator with a progress message.
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
*/
@Override
public void progress(String message) {
SwingUtilities.invokeLater(() -> {
progress.setString(message);
});
}
/**
* Updates the progress indicator with the number of work units completed so
* far when in determinate mode (the total number of work units to be
* completed is known).
*
* @param current Number of work units completed so far.
*/
@Override
public void progress(int current) {
SwingUtilities.invokeLater(() -> {
progress.setValue(current);
});
}
/**
* Updates the progress indicator with a progress message and the number of
* work units completed so far when in determinate mode (the total number of
* work units to be completed is known).
*
* @param message the message to be displayed on the progress bar, null to
* display percent complete
* @param current Number of work units completed so far.
*/
@Override
public void progress(String message, int current) {
SwingUtilities.invokeLater(() -> {
progress.setString(message);
progress.setValue(current);
});
}
/**
* Finishes the progress indicator when the task is completed.
*/
@Override
public void finish() {
SwingUtilities.invokeLater(() -> {
progress.setVisible(false);
});
}
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1002, 444]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="902" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="444" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Form>

View File

@ -0,0 +1,140 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import java.awt.BorderLayout;
import java.awt.Component;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.openide.windows.Mode;
@TopComponent.Description(
preferredID = "MultiCaseKeywordSearchTopComponent",
persistenceType = TopComponent.PERSISTENCE_NEVER
)
@TopComponent.Registration(mode = "multiCaseKeywordSearch", openAtStartup = false)
@Messages({
"CTL_MultiCaseKeywordSearchTopComponentAction=Multi-case Keyword Search",
"CTL_MultiCaseKeywordSearchTopComponent=Multi-case Keyword Search"})
/**
* A top level component for the multi case keyword search feature.
*/
final class MultiCaseKeywordSearchTopComponent extends TopComponent {
public final static String PREFERRED_ID = "MultiCaseKeywordSearchTopComponent"; // NON-NLS
private static final long serialVersionUID = 1L;
private static boolean topComponentInitialized = false;
@Messages({
"MultiCaseKeywordSearchTopComponent.exceptionMessage.failedToCreatePanel=Failed to create Multi-case Keyword Search panel.",})
/**
* Open the top level component if it is not already open, if it is open
* bring it to the front and select it.
*/
static void openTopComponent() {
final MultiCaseKeywordSearchTopComponent tc = (MultiCaseKeywordSearchTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
if (tc.isOpened() == false) {
topComponentInitialized = true;
Mode mode = WindowManager.getDefault().findMode("multiCaseKeywordSearch"); // NON-NLS
if (mode != null) {
mode.dockInto(tc);
}
tc.open();
}
tc.toFront();
tc.requestActive();
}
}
/**
* Close the top level componet.
*/
static void closeTopComponent() {
if (topComponentInitialized) {
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
try {
tc.close();
} catch (Exception e) {
}
}
}
}
@Messages({"MultiCaseKeywordSearchTopComponent.name.text=Multi-case Keyword Search"})
/**
* Construct a new "MultiCaseKeywordSearchTopComponent.
*/
MultiCaseKeywordSearchTopComponent() {
initComponents();
setName(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setDisplayName(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setToolTipText(Bundle.MultiCaseKeywordSearchTopComponent_name_text());
setSize(this.getPreferredSize());
setLayout(new BorderLayout());
MultiCaseKeywordSearchPanel searchPanel = new MultiCaseKeywordSearchPanel();
searchPanel.setSize(searchPanel.getPreferredSize());
searchPanel.setVisible(true);
add(searchPanel);
}
@Override
public void componentOpened() {
super.componentOpened();
WindowManager.getDefault().setTopComponentFloating(this, true);
}
@Override
public boolean canClose() {
for (Component component : getComponents()) {
if (component instanceof MultiCaseKeywordSearchPanel) {
((MultiCaseKeywordSearchPanel) component).closeSearchPanel();
}
}
return super.canClose();
}
/**
* 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.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setPreferredSize(new java.awt.Dimension(1002, 444));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 902, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 444, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,775 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import com.google.common.eventbus.EventBus;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.CursorMarkParams;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
import org.sleuthkit.autopsy.keywordsearch.Server;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Performs keyword searches across multiple cases
*/
final class MultiCaseSearcher {
private static final String CASE_AUTO_INGEST_LOG_NAME = "AUTO_INGEST_LOG.TXT"; //NON-NLS
private static final String SEARCH_COMPLETE_MESSAGE = "SEARCH_COMPLETE";
private static final String RESOURCES_LOCK_SUFFIX = "_RESOURCES"; //NON-NLS
private static final int CASE_DIR_READ_LOCK_TIMEOUT_HOURS = 12; //NON-NLS
private static final String SOLR_SERVER_URL_FORMAT_STRING = "http://%s:%s/solr"; //NON-NLS
private static final String SOLR_CORE_URL_FORMAT_STRING = "http://%s:%s/solr/%s"; //NON-NLS
private final static String SOLR_METADATA_FILE_NAME = "SolrCore.properties"; //NON-NLS
private static final String SOLR_CORE_NAME_XPATH = "/SolrCores/Core/CoreName/text()"; //NON-NLS
private static final String TEXT_INDEX_NAME_XPATH = "/SolrCores/Core/TextIndexPath/text()"; //NON-NLS
private static final String SOLR_CORE_INSTANCE_PATH_PROPERTY = "instanceDir"; //NON-NLS
private static final String SOLR_CONFIG_SET_NAME = "AutopsyConfig"; //NON-NLS
private static final int MAX_RESULTS_PER_CURSOR_MARK = 512;
private static final String SOLR_DOC_ID_FIELD = Server.Schema.ID.toString(); //NON-NLS
private static final String SOLR_DOC_CONTENT_STR_FIELD = Server.Schema.CONTENT_STR.toString(); //NON-NLS
private static final String SOLR_DOC_CHUNK_SIZE_FIELD = Server.Schema.CHUNK_SIZE.toString(); //NON-NLS
private static final String SOLR_DOC_ID_PARTS_SEPARATOR = "_";
private static final Logger logger = Logger.getLogger(MultiCaseSearcher.class.getName());
private final EventBus eventBus = new EventBus("MultiCaseSearcherEventBus");
private static final UNCPathUtilities pathUtils = new UNCPathUtilities();
private volatile boolean searchStopped = true;
MultiCaseSearcher() {
}
static String getSearchCompleteMessage() {
return SEARCH_COMPLETE_MESSAGE;
}
/**
*
* Performs keyword searches across multiple cases
*
* @param caseNames The names of the cases to search.
* @param query The keyword search query to perform.
* @param progressIndicator A progrss indicator for the search.
*
* @return The search results.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"MultiCaseSearcher.progressMessage.findingCases=Finding selected cases",
"MultiCaseSearcher.progressMessage.creatingSolrQuery=Creating search query for Solr server",
"# {0} - total cases",
"MultiCaseSearcher.progressMessage.startingCaseSearches=Searching {0} case(s)",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.acquiringSharedLockForCase=Acquiring shared lock for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.loadingSolrCoreForCase=Loading Solr core for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.openingCaseDbForCase=Opening case database for \"{0}\" ({1} of {2} case(s))",
"# {0} - case name",
"# {1} - case counter",
"# {2} - total cases",
"MultiCaseSearcher.progressMessage.executingSolrQueryForCase=Getting keyword hits for \"{0}\" ({1} of {2} case(s))",
"# {0} - case directory path",
"MultiCaseSearcher.exceptionMessage.failedToGetCaseDirReadlock=Failed to obtain read lock for case directory at {0}",
"MultiCaseSearcher.exceptionMessage.cancelledMessage=Search cancelled"
})
void performKeywordSearch(final Collection<CaseNodeData> caseNodes, final SearchQuery query, final ProgressIndicator progressIndicator) {
progressIndicator.start(Bundle.MultiCaseSearcher_progressMessage_findingCases());
try {
searchStopped = false; //mark the search as started
final List<MultiCaseMetadata> caseMetadata = getMultiCaseMetadata(caseNodes);
checkForCancellation();
//eventBus.post("number of cases to search determined");
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_creatingSolrQuery());
final SolrQuery solrQuery = createSolrQuery(query);
checkForCancellation();
final int totalCases = caseMetadata.size();
int caseCounter = 1;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases));
int totalSteps = 5;
progressIndicator.switchToDeterminate(Bundle.MultiCaseSearcher_progressMessage_startingCaseSearches(totalCases), 0, totalCases * totalSteps);
int caseNumber = 0;
for (MultiCaseMetadata aCase : caseMetadata) {
CaseMetadata metadata = aCase.getCaseMetadata();
String caseName = metadata.getCaseDisplayName();
SleuthkitCase caseDatabase = null;
int stepsCompleted = 0;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_acquiringSharedLockForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
try (CoordinationService.Lock caseDirReadLock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.CASES, aCase.getCaseMetadata().getCaseDirectory(), CASE_DIR_READ_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS)) {
if (null == caseDirReadLock) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()));
}
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_loadingSolrCoreForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
final HttpSolrServer solrServer = loadSolrCoreForCase(aCase);
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_openingCaseDbForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
caseDatabase = openCase(aCase);
checkForCancellation();
++stepsCompleted;
progressIndicator.progress(Bundle.MultiCaseSearcher_progressMessage_executingSolrQueryForCase(caseName, caseCounter, totalCases), stepsCompleted + caseNumber * totalSteps);
eventBus.post(executeQuery(solrServer, solrQuery, caseDatabase, aCase));
++stepsCompleted;
progressIndicator.progress(stepsCompleted + caseNumber * totalSteps);
++caseCounter;
} catch (CoordinationService.CoordinationServiceException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDirReadlock(aCase.getCaseMetadata().getCaseDirectory()), ex);
} catch (MultiCaseSearcherException exception) {
logger.log(Level.INFO, "Exception encountered while performing multi-case keyword search", exception);
eventBus.post(exception);
} finally {
if (null != caseDatabase) {
closeCase(caseDatabase);
}
}
caseNumber++;
}
} catch (InterruptedException exception) {
logger.log(Level.INFO, Bundle.MultiCaseSearcher_exceptionMessage_cancelledMessage(), exception);
eventBus.post(exception);
} catch (MultiCaseSearcherException exception) {
logger.log(Level.WARNING, "Exception encountered while performing multi-case keyword search", exception);
eventBus.post(new InterruptedException("Exception encountered while performing multi-case keyword search"));
eventBus.post(exception);
} finally {
progressIndicator.finish();
eventBus.post(SEARCH_COMPLETE_MESSAGE);
}
}
/**
* Gets metadata for the cases associated with one or more with the search
*
* @param caseNames The names of the cases to search.
*
* @return The metadata for the cases.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
private List<MultiCaseMetadata> getMultiCaseMetadata(final Collection<CaseNodeData> caseNodes) throws MultiCaseSearcherException, InterruptedException {
final Map<Path, String> casesToCasePaths = caseNodes.stream()
.collect(Collectors.toMap(CaseNodeData::getDirectory, CaseNodeData::getName));
checkForCancellation();
final List<MultiCaseMetadata> cases = new ArrayList<>();
for (Map.Entry<Path, String> entry : casesToCasePaths.entrySet()) {
final Path caseDirectoryPath = entry.getKey();
final CaseMetadata caseMetadata = getCaseMetadata(caseDirectoryPath);
checkForCancellation();
final TextIndexMetadata textIndexMetadata = getTextIndexMetadata(caseDirectoryPath);
checkForCancellation();
cases.add(new MultiCaseMetadata(caseMetadata, textIndexMetadata));
}
return cases;
}
/**
* Gets the metadata for a case from the case metadata file in a given case
* directory.
*
* @param caseDirectoryPath A case directory path.
*
* @return The case metadata.
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - case directory", "MultiCaseSearcher.exceptionMessage.failedToFindCaseMetadata=Failed to find case metadata file in {0}",
"# {0} - case directory", "MultiCaseSearcher.exceptionMessage.failedToParseCaseMetadata=Failed to parse case file metadata in {0}"
})
private static CaseMetadata getCaseMetadata(Path caseDirectoryPath) throws MultiCaseSearcherException {
Path metadataPath = CaseMetadata.getCaseMetadataFile(caseDirectoryPath);
if (metadataPath != null) {
try {
return new CaseMetadata(metadataPath);
} catch (CaseMetadata.CaseMetadataException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToParseCaseMetadata(caseDirectoryPath), ex);
}
}
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToFindCaseMetadata(caseDirectoryPath));
}
/**
* Gets the text index metadata from the Solr.properties file in a given
* case directory.
*
* @param caseDirectoryPath A case directory path.
*
* @return The text index metadata.
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - file name", "# {1} - case directory", "MultiCaseSearcher.exceptionMessage.missingSolrPropertiesFile=Missing {0} file in {1}",
"# {0} - file name", "# {1} - case directory", "MultiCaseSearcher.exceptionMessage.solrPropertiesFileParseError=Error parsing {0} file in {1}",})
private static TextIndexMetadata getTextIndexMetadata(Path caseDirectoryPath) throws MultiCaseSearcherException {
final Path solrMetaDataFilePath = Paths.get(caseDirectoryPath.toString(), SOLR_METADATA_FILE_NAME);
final File solrMetaDataFile = solrMetaDataFilePath.toFile();
if (!solrMetaDataFile.exists() || !solrMetaDataFile.canRead()) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_missingSolrPropertiesFile(SOLR_METADATA_FILE_NAME, caseDirectoryPath));
}
try {
final DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document doc = docBuilder.parse(solrMetaDataFile);
final XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression xPathExpr = xPath.compile(SOLR_CORE_NAME_XPATH);
final String solrCoreName = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
xPathExpr = xPath.compile(TEXT_INDEX_NAME_XPATH);
final String relativeTextIndexPath = (String) xPathExpr.evaluate(doc, XPathConstants.STRING);
Path textIndexPath = caseDirectoryPath.resolve(relativeTextIndexPath);
textIndexPath = textIndexPath.getParent(); // Remove "index" path component
final String textIndexUNCPath = pathUtils.convertPathToUNC(textIndexPath.toString());
return new TextIndexMetadata(caseDirectoryPath, solrCoreName, textIndexUNCPath);
} catch (ParserConfigurationException | SAXException | XPathExpressionException | IOException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrPropertiesFileParseError(SOLR_METADATA_FILE_NAME, caseDirectoryPath), ex);
}
}
/**
* Converts a keyword search query into a Solr query.
*
* @param searchQuery A keyword search query.
*
* @return A Solr query.
*/
private static SolrQuery createSolrQuery(SearchQuery searchQuery) {
final SolrQuery solrQuery = new SolrQuery();
solrQuery.setQuery(searchQuery.getSearchTerm());
solrQuery.setRows(MAX_RESULTS_PER_CURSOR_MARK);
/*
* Note that setting the sort order is necessary for cursor based paging
* to work.
*/
solrQuery.setSort(SolrQuery.SortClause.asc(SOLR_DOC_ID_FIELD));
solrQuery.setFields(SOLR_DOC_ID_FIELD, SOLR_DOC_CHUNK_SIZE_FIELD, SOLR_DOC_CONTENT_STR_FIELD);
return solrQuery;
}
/**
* Connects to the Solr server and loads the Solr core for a given case.
*
* @param aCase
*
* @return A Solr server client object that can be used for executing
* queries of the specified text index.
*
* MultiCaseSearcherException
*
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - connection info",
"# {1} - case name",
"# {2} - case directory",
"MultiCaseSearcher.exceptionMessage.errorLoadingCore=Error connecting to Solr server and loading core (URL: {0}) for case {1} in {2}"
})
private HttpSolrServer loadSolrCoreForCase(MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
TextIndexMetadata textIndexMetadata = aCase.getTextIndexMetadata();
Server.IndexingServerProperties indexServer = Server.getMultiUserServerProperties(aCase.getCaseMetadata().getCaseDirectory());
final String serverURL = String.format(SOLR_SERVER_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort());
try {
/*
* Connect to the Solr server.
*/
final HttpSolrServer solrServer = new HttpSolrServer(serverURL);
CoreAdminRequest statusRequest = new CoreAdminRequest();
statusRequest.setCoreName(null);
statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
statusRequest.setIndexInfoNeeded(false);
checkForCancellation();
statusRequest.process(solrServer);
checkForCancellation();
/*
* Load the core for the text index if it is not already loaded.
*/
CoreAdminResponse response = CoreAdminRequest.getStatus(textIndexMetadata.getSolrCoreName(), solrServer);
if (null == response.getCoreStatus(textIndexMetadata.getSolrCoreName()).get(SOLR_CORE_INSTANCE_PATH_PROPERTY)) {
CoreAdminRequest.Create loadCoreRequest = new CoreAdminRequest.Create();
loadCoreRequest.setDataDir(textIndexMetadata.getTextIndexPath());
loadCoreRequest.setCoreName(textIndexMetadata.getSolrCoreName());
loadCoreRequest.setConfigSet(SOLR_CONFIG_SET_NAME);
loadCoreRequest.setIsLoadOnStartup(false);
loadCoreRequest.setIsTransient(true);
solrServer.request(loadCoreRequest);
}
/*
* Create a server client object that can be used for executing
* queries of the specified text index.
*/
final String coreURL = String.format(SOLR_CORE_URL_FORMAT_STRING, indexServer.getHost(), indexServer.getPort(), textIndexMetadata.getSolrCoreName());
final HttpSolrServer coreServer = new HttpSolrServer(coreURL);
return coreServer;
} catch (SolrServerException | IOException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorLoadingCore(serverURL, aCase.getCaseMetadata().getCaseName(), textIndexMetadata.getCaseDirectoryPath()), ex);
}
}
/**
* Opens a case database.
*
* @param caseMetadata
*
* @return A case database.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - case_name",
"MultiCaseSearcher.exceptionMessage.failedToGetCaseDatabaseConnectionInfo=Failed to get case database connection info for case {0}",
"# {0} - PostgreSQL server host",
"# {1} - PostgreSQL server port",
"# {2} - case database name",
"# {3} - case directory",
"MultiCaseSearcher.exceptionMessage.errorOpeningCaseDatabase=Error connecting to PostgreSQL server (Host/Port: [{0}:{1}] and opening case database {2} for case at {3}"
})
private SleuthkitCase openCase(MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
CaseDbConnectionInfo dbConnectionInfo;
try {
dbConnectionInfo = UserPreferences.getDatabaseConnectionInfo();
} catch (UserPreferencesException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_failedToGetCaseDatabaseConnectionInfo(aCase.getCaseMetadata().getCaseName()), ex);
}
checkForCancellation();
final CaseMetadata caseMetadata = aCase.getCaseMetadata();
try {
return SleuthkitCase.openCase(caseMetadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseMetadata.getCaseDirectory());
} catch (UserPreferencesException | TskCoreException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_errorOpeningCaseDatabase(dbConnectionInfo.getHost(), dbConnectionInfo.getPort(), caseMetadata.getCaseDatabaseName(), caseMetadata.getCaseDirectory()), ex);
}
}
/**
* Closes a case database.
*
* @param aCase a case database.
*/
private static void closeCase(SleuthkitCase aCase) {
aCase.close();
}
/**
* Executes a keyword search searchTerm in the text index of a case.
*
* @param solrServer The Solr server.
* @param solrQuery The Solr searchTerm.
* @param caseDatabase The case database.
* @param aCase The case metadata.
*
* @return A list of search results, possibly empty.
*
* @throws MultiCaseSearcherException
* @throws InterruptedException
*/
@NbBundle.Messages({
"# {0} - query",
"# {1} - case_name",
"MultiCaseSearcher.exceptionMessage.solrQueryError=Failed to execute query \"{0}\" on case {1}"
})
private Collection<SearchHit> executeQuery(HttpSolrServer solrServer, SolrQuery solrQuery, SleuthkitCase caseDatabase, MultiCaseMetadata aCase) throws MultiCaseSearcherException, InterruptedException {
final List<SearchHit> hits = new ArrayList<>();
final Set<Long> uniqueObjectIds = new HashSet<>();
String cursorMark = CursorMarkParams.CURSOR_MARK_START;
boolean allResultsProcessed = false;
while (!allResultsProcessed) {
checkForCancellation();
solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse response;
try {
checkForCancellation();
response = solrServer.query(solrQuery, SolrRequest.METHOD.POST);
} catch (SolrServerException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_solrQueryError(solrQuery.getQuery(), aCase.getCaseMetadata().getCaseName()), ex);
}
SolrDocumentList resultDocuments = response.getResults();
for (SolrDocument resultDoc : resultDocuments) {
checkForCancellation();
String solrDocumentId = resultDoc.getFieldValue(SOLR_DOC_ID_FIELD).toString();
Long solrObjectId = parseSolrObjectId(solrDocumentId);
if (!uniqueObjectIds.contains(solrObjectId)) {
uniqueObjectIds.add(solrObjectId);
checkForCancellation();
hits.add(processHit(solrObjectId, caseDatabase, aCase));
}
}
checkForCancellation();
String nextCursorMark = response.getNextCursorMark();
if (cursorMark.equals(nextCursorMark)) {
allResultsProcessed = true;
}
cursorMark = nextCursorMark;
}
return hits;
}
/**
* Parses a Solr document id to get the Solr object id.
*
* @param solrDocumentId A Solr document id.
*
* @return A Solr object id.
*/
private static Long parseSolrObjectId(String solrDocumentId) {
/**
* A Solr document id is of the form [solr_object_id] for Content object
* metadata documents and
* [solr_object_id][SOLR_DOC_ID_PARTS_SEPARATOR][chunk_id] for Content
* object text chunk documents.
*/
final String[] solrDocumentIdParts = solrDocumentId.split(SOLR_DOC_ID_PARTS_SEPARATOR);
if (1 == solrDocumentIdParts.length) {
return Long.parseLong(solrDocumentId);
} else {
return Long.parseLong(solrDocumentIdParts[0]);
}
}
/**
* Creates a keyword search hit object for a Content object identified by
* its Solr object id.
*
* @param solrObjectId The Solr object id of a Content object.
* @param caseDatabase The case database of the case that has the Content.
* @param caseInfo Metadata about the case that has the content.
*
* @return
*
* @throws MultiCaseSearcherException
*/
@NbBundle.Messages({
"# {0} - Solr document id",
"# {1} - case database name",
"# {2} - case directory",
"MultiCaseSearcher.exceptionMessage.hitProcessingError=Failed to query case database for processing of Solr object id {0} of case {1} in {2}"
})
private static SearchHit processHit(Long solrObjectId, SleuthkitCase caseDatabase, MultiCaseMetadata caseInfo) throws MultiCaseSearcherException {
try {
final long objectId = getObjectIdForSolrObjectId(solrObjectId, caseDatabase);
final CaseMetadata caseMetadata = caseInfo.getCaseMetadata();
final String caseDisplayName = caseMetadata.getCaseDisplayName();
final String caseDirectoryPath = caseMetadata.getCaseDirectory();
final Content content = caseDatabase.getContentById(objectId);
final Content dataSource = content.getDataSource();
final String dataSourceName = (dataSource == null) ? "" : dataSource.getName();
SearchHit.SourceType sourceType = SearchHit.SourceType.FILE;
String sourceName = "";
String sourcePath = "";
if (content instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) content;
sourceName = sourceFile.getName();
sourcePath = sourceFile.getLocalAbsPath();
if (null == sourcePath) {
sourceType = SearchHit.SourceType.FILE;
sourcePath = sourceFile.getUniquePath();
} else {
sourceType = SearchHit.SourceType.LOCAL_FILE;
sourceName = sourceFile.getName();
}
} else if (content instanceof BlackboardArtifact) {
BlackboardArtifact sourceArtifact = (BlackboardArtifact) content;
sourceType = SearchHit.SourceType.ARTIFACT;
BlackboardArtifact.Type artifactType = caseDatabase.getArtifactType(sourceArtifact.getArtifactTypeName());
sourceName = artifactType.getDisplayName();
Content source = sourceArtifact.getParent();
if (source instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) source;
sourcePath = sourceFile.getLocalAbsPath();
if (null == sourcePath) {
sourcePath = sourceFile.getUniquePath();
}
} else {
sourcePath = source.getUniquePath();
}
} else if (content instanceof Report) {
Report report = (Report) content;
sourceType = SearchHit.SourceType.REPORT;
sourceName = report.getReportName();
sourcePath = report.getUniquePath();
}
return new SearchHit(caseDisplayName, caseDirectoryPath, dataSourceName, sourceType, sourceName, sourcePath);
} catch (SQLException | TskCoreException ex) {
throw new MultiCaseSearcherException(Bundle.MultiCaseSearcher_exceptionMessage_hitProcessingError(solrObjectId, caseInfo.getCaseMetadata().getCaseName(), caseInfo.getCaseMetadata().getCaseDirectory()), ex);
}
}
/**
* Gets the Sleuthkit object id that corresponds to the Solr object id of
* some content.
*
* @param solrObjectId A solr object id for some content.
* @param caseDatabase The case database for the case that includes the
* content.
*
* @return The Sleuthkit object id of the content.
*
* @throws MultiCaseSearcherException
* @throws TskCoreException
* @throws SQLException
*/
private static long getObjectIdForSolrObjectId(long solrObjectId, SleuthkitCase caseDatabase) throws MultiCaseSearcherException, TskCoreException, SQLException {
if (0 < solrObjectId) {
return solrObjectId;
} else {
try (SleuthkitCase.CaseDbQuery databaseQuery = caseDatabase.executeQuery("SELECT artifact_obj_id FROM blackboard_artifacts WHERE artifact_id = " + solrObjectId)) {
final ResultSet resultSet = databaseQuery.getResultSet();
if (resultSet.next()) {
return resultSet.getLong("artifact_obj_id");
} else {
throw new TskCoreException("Empty result set getting obj_id for artifact with artifact_id =" + solrObjectId);
}
}
}
}
/**
* Checks to see if the current thread has been interrupted (i.e, the search
* has been cancelled) and throws an InterruptedException if it has been.
*
* @throws InterruptedException
*/
private void checkForCancellation() throws InterruptedException {
if (Thread.currentThread().isInterrupted() || searchStopped) {
throw new InterruptedException("Search Cancelled");
}
}
/**
* A bundle of metadata for a case.
*/
private final static class MultiCaseMetadata {
private final CaseMetadata caseMetadata;
private final TextIndexMetadata textIndexMetadata;
/**
* Contructs a bundle of metadata for a case
*
* @param caseMetadata The case metadata.
* @param textIndexMetaData The text index metadata for the case.
*/
private MultiCaseMetadata(CaseMetadata caseMetadata, TextIndexMetadata textIndexMetaData) {
this.caseMetadata = caseMetadata;
this.textIndexMetadata = textIndexMetaData;
}
/**
* Gets the case metadata.
*
* @return The case metadata.
*/
private CaseMetadata getCaseMetadata() {
return this.caseMetadata;
}
/**
* Gets the text index metadata for the case.
*
* @return The text index metadata.
*/
private TextIndexMetadata getTextIndexMetadata() {
return this.textIndexMetadata;
}
}
/**
* Bundles a case directory path, a Solr core fileName, and a text index UNC
* path.
*/
private final static class TextIndexMetadata {
private final Path caseDirectoryPath;
private final String solrCoreName;
private final String textIndexUNCPath;
/**
* Constructs an object that bundles a Solr core fileName and a text
* index UNC path.
*
* @param caseDirectoryPath The case directory path.
* @param solrCoreName The core fileName.
* @param textIndexUNCPath The text index path.
*/
private TextIndexMetadata(Path caseDirectoryPath, String solrCoreName, String textIndexUNCPath) {
this.caseDirectoryPath = caseDirectoryPath;
this.solrCoreName = solrCoreName;
this.textIndexUNCPath = textIndexUNCPath;
}
/**
* Gets the case directory path.
*
* @return The path.
*/
private Path getCaseDirectoryPath() {
return this.caseDirectoryPath;
}
/**
* Gets the Solr core fileName.
*
* @return The Solr core fileName.
*/
private String getSolrCoreName() {
return this.solrCoreName;
}
/**
*
* Gets the UNC path of the text index.
*
* @return The path.
*/
private String getTextIndexPath() {
return this.textIndexUNCPath;
}
}
/**
* Exception thrown if there is an error executing a search.
*/
static final class MultiCaseSearcherException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs an instance of the exception thrown if there is an error
* executing a search.
*
* @param message The exception message.
*/
private MultiCaseSearcherException(String message) {
super(message);
}
/**
* Constructs an instance of the exception thrown if there is an error
* executing a search.
*
* @param message The exception message.
* @param cause The Throwable that caused the error.
*/
private MultiCaseSearcherException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Tell the MultiCaseSearcher that it's current search can be stopped the
* next time it checks for cancellation.
*/
void stopMultiCaseSearch() {
//This is necessary because if the interrupt occurs during CoreAdminRequest.process,
//CoreAdminRequest.getStatus, or HttpSolrServer.query the interrupt gets ignored
searchStopped = true;
}
/**
* Register an object with the MultiCaseSearcher eventBus so that it's
* subscribe methods can receive results.
*
* @param object the object to register with the eventBus
*/
void registerWithEventBus(Object object) {
eventBus.register(object);
}
/**
* Unregister an object with the MultiCaseSearcher eventBus so that it's
* subscribe methods no longer receive results.
*
* @param object the object to unregister with the eventBus
*/
void unregisterWithEventBus(Object object) {
eventBus.unregister(object);
}
}

View File

@ -0,0 +1,137 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import javax.annotation.concurrent.Immutable;
/**
* A keyword search hit from a multi-case keyword search.
*/
@Immutable
final class SearchHit {
private final String caseDisplayName;
private final String caseDirectoryPath;
private final String dataSourceName;
private final SourceType sourceType;
private final String sourceName;
private final String sourcePath;
/**
* Constructs a keyword search hit from a multi-case search.
*
* @param caseDisplayName The display name of the case where the hit
* occurred.
* @param caseDirectoryPath The path of the directory of the case where the
* hit occurred.
* @param dataSourceName The name of the data source within the case
* where the hit occurred.
* @param sourceType The type of the source content object.
* @param sourceName The name of the source, e.g., a file name, an
* artifact type name, or a report module name.
* @param sourcePath The path of the source content, or the path of
* the parent source content object for an artifact
* source.
*/
SearchHit(String caseDisplayName, String caseDirectoryPath, String dataSourceName, SourceType sourceType, String sourceName, String sourcePath) {
this.caseDisplayName = caseDisplayName;
this.caseDirectoryPath = caseDirectoryPath;
this.dataSourceName = dataSourceName;
this.sourceType = sourceType;
this.sourceName = sourceName;
this.sourcePath = sourcePath;
}
/**
* Gets the display name of the case where the hit occurred.
*
* @return The case display name.
*/
String getCaseDisplayName() {
return this.caseDisplayName;
}
/**
* Gets the path of the directory of the case where the hit occurred.
*
* @return The case directory path.
*/
String getCaseDirectoryPath() {
return this.caseDirectoryPath;
}
/**
* Gets the name of the data source within the case where the hit occurred.
*
* @return
*/
String getDataSourceName() {
return this.dataSourceName;
}
/**
* Gets the type of the source content object.
*
* @return The source type.
*/
SourceType getSourceType() {
return this.sourceType;
}
/**
* Gets the name of the source, e.g., a file name, an artifact type name, or
* a report module name.
*
* @return The source name.
*/
String getSourceName() {
return this.sourceName;
}
/**
* Gets the path of the source content, or the path of the parent source
* content object for an artifact source.
*
* @return The source object path.
*/
String getSourcePath() {
return this.sourcePath;
}
/**
* An enumeration of the source types for keyword search hits.
*/
enum SourceType {
FILE("File"),
LOCAL_FILE("Local File"),
ARTIFACT("Artifact"),
REPORT("Report");
private final String displayName;
private SourceType(String displayName) {
this.displayName = displayName;
}
String getDisplayName() {
return this.displayName;
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.keywordsearch.Server;
/**
* A keyword search query.
*/
@Immutable
final class SearchQuery {
private static final String SEARCH_TERM_CHARS_TO_ESCAPE = "/+-&|!(){}[]^\"~*?:\\";
private static final String SOLR_DOC_CONTENT_STR_FIELD = Server.Schema.CONTENT_STR.toString(); //NON-NLS
private final String searchTerm;
/**
* Constructs a multicase keyword search query.
*
* @param queryType The query type.
* @param searchTerm The search term for the query.
*/
SearchQuery(QueryType queryType, String searchTerm) {
switch (queryType) {
case EXACT_MATCH:
this.searchTerm = prepareExactMatchSearchTerm(searchTerm);
break;
case SUBSTRING:
this.searchTerm = prepareSubstringSearchTerm(searchTerm);
break;
case REGEX:
this.searchTerm = prepareRegexSearchTerm(searchTerm);
break;
default:
this.searchTerm = searchTerm;
break;
}
}
/**
* Gets the search term.
*
* @return The query.
*/
String getSearchTerm() {
return searchTerm;
}
/**
* Escapes and quotes a given search term as required for an exact match
* search query.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for an exact match query.
*/
private static String prepareExactMatchSearchTerm(String searchTerm) {
String escapedSearchTerm = escapeSearchTerm(searchTerm);
if (!searchTerm.startsWith("\"")) {
escapedSearchTerm = "\"" + escapedSearchTerm;
}
if (!searchTerm.endsWith("\"")) {
escapedSearchTerm += "\"";
}
return escapedSearchTerm;
}
/**
* Adds delimiters and possibly wildcards to a given search terms as
* required for a regular expression search query.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for a regex query.
*/
private static String prepareRegexSearchTerm(String searchTerm) {
/*
* Add slash delimiters and, if necessary, wildcards (.*) at the
* beginning and end of the search term. The wildcards are added because
* Lucerne automatically adds a '^' prefix and '$' suffix to the search
* terms for regex searches. Without the '.*' wildcards, the search term
* will have to match the entire content_str field, which is not
* generally the intent of the user.
*/
String regexSearchTerm = SOLR_DOC_CONTENT_STR_FIELD
+ ":/"
+ (searchTerm.startsWith(".*") ? "" : ".*")
+ searchTerm.toLowerCase()
+ (searchTerm.endsWith(".*") ? "" : ".*")
+ "/";
return regexSearchTerm;
}
/**
* Escapes and adds delimiters and wpossibly wildcards to a given search
* term as required for a substring search.
*
* @param searchTerm A "raw" input search term.
*
* @return A search term suitable for a substring query.
*/
private static String prepareSubstringSearchTerm(String searchTerm) {
String escapedSearchTerm = escapeSearchTerm(searchTerm);
return prepareRegexSearchTerm(escapedSearchTerm);
}
/**
* Escapes a search term as required for a Lucene query.
*
* @param searchTerm A "raw" input search term.
*
* @return An escaped version of the "raw" input search term.
*/
public static String escapeSearchTerm(String searchTerm) {
String rawSearchTerm = searchTerm.trim();
if (0 == rawSearchTerm.length()) {
return rawSearchTerm;
}
StringBuilder escapedSearchTerm = new StringBuilder(rawSearchTerm.length());
for (int i = 0; i < rawSearchTerm.length(); ++i) {
final char nextChar = rawSearchTerm.charAt(i);
if (SEARCH_TERM_CHARS_TO_ESCAPE.contains(Character.toString(nextChar))) {
escapedSearchTerm.append("\\");
}
escapedSearchTerm.append(nextChar);
}
return escapedSearchTerm.toString();
}
/**
* An enumeration of the supported query types for keywod searches.
*/
enum QueryType {
EXACT_MATCH,
SUBSTRING,
REGEX;
}
}

View File

@ -0,0 +1,63 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
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;
/**
* Customizer for SelectMultiUserCasesPanel. Displays the 'Create date' and
* 'Directory' columns
*/
public class SelectMultiUserCaseDialogCustomizer implements MultiUserCaseBrowserCustomizer {
@Override
public List<Column> getColumns() {
List<Column> properties = new ArrayList<>();
properties.add(Column.CREATE_DATE);
properties.add(Column.DIRECTORY);
return properties;
}
@Override
public List<SortColumn> getSortColumns() {
List<SortColumn> sortColumns = new ArrayList<>();
sortColumns.add(new SortColumn(Column.CREATE_DATE, false, 1));
return sortColumns;
}
@Override
public boolean allowMultiSelect() {
return true;
}
@Override
public List<Action> getActions(CaseNodeData nodeData) {
return new ArrayList<>();
}
@Override
public Action getPreferredAction(CaseNodeData nodeData) {
return null;
}
}

View File

@ -0,0 +1,90 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import java.awt.Dialog;
import java.beans.PropertyVetoException;
import org.openide.nodes.Node;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.keywordsearch.multicase.MultiCaseKeywordSearchPanel.ChangeListener;
/**
* Dialog that will display the SelectMultiUserCasesPanel
*/
class SelectMultiUserCasesDialog extends javax.swing.JDialog {
private static final long serialVersionUID = 1L;
private static SelectMultiUserCasesDialog instance;
private static SelectMultiUserCasesPanel multiUserCasesPanel;
/**
* Gets the singleton JDialog that allows a user to open a multi-user case.
*
* @return The singleton JDialog instance.
*/
public synchronized static SelectMultiUserCasesDialog getInstance() {
if (instance == null) {
instance = new SelectMultiUserCasesDialog();
instance.init();
}
return instance;
}
/**
* Listen for new case selections from the user.
*
* @param l Listener on new case selection events
*/
void subscribeToNewCaseSelections(ChangeListener l) {
multiUserCasesPanel.subscribeToNewCaseSelections(l);
}
/**
* Set the node selections for the window
*
* @param selections Nodes to be automatically selected in the explorer view
*/
void setNodeSelections(Node[] selections) {
try {
multiUserCasesPanel.setSelections(selections);
} catch (PropertyVetoException ex) {
//Do-nothing
}
}
/**
* Constructs a singleton JDialog that allows a user to open a multi-user
* case.
*/
private SelectMultiUserCasesDialog() {
super(WindowManager.getDefault().getMainWindow(), "Select Multi-User Cases", Dialog.ModalityType.APPLICATION_MODAL);
}
/**
* Registers a keyboard action to hide the dialog when the escape key is
* pressed and adds a OpenMultiUserCasePanel child component.
*/
private void init() {
multiUserCasesPanel = new SelectMultiUserCasesPanel(this);
add(multiUserCasesPanel);
pack();
setResizable(false);
multiUserCasesPanel.refreshDisplay();
}
}

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="multiUserCaseScrollPane" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="selectAllButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deselectAllButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" pref="365" max="-2" attributes="0"/>
<EmptySpace pref="286" max="32767" attributes="0"/>
<Component id="refreshButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="confirmSelections" min="-2" pref="63" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cancelButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="multiUserCaseScrollPane" min="-2" pref="486" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="selectAllButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="deselectAllButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="confirmSelections" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cancelButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="refreshButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="15" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="multiUserCaseScrollPane">
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JButton" name="selectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="SelectMultiUserCasesPanel.selectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectAllButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="deselectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/multicase/Bundle.properties" key="SelectMultiUserCasesPanel.deselectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deselectAllButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jLabel1">
</Component>
<Component class="javax.swing.JButton" name="confirmSelections">
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="confirmSelectionsActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="refreshButton">
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="refreshButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,228 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.keywordsearch.multicase;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JDialog;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCasesBrowserPanel;
import org.sleuthkit.autopsy.keywordsearch.multicase.MultiCaseKeywordSearchPanel.ChangeListener;
/**
* Panel for multi-user case selection
*/
class SelectMultiUserCasesPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final JDialog parentDialog;
private final MultiUserCasesBrowserPanel caseBrowserPanel;
private final List<ChangeListener> listeners;
/**
* Constructs a JPanel that allows a user to open a multi-user case.
*
* @param parentDialog The parent dialog of the panel, may be null. If
* provided, the dialog is hidden when this poanel's
* cancel button is pressed.
*/
SelectMultiUserCasesPanel(JDialog parentDialog) {
initComponents();
this.parentDialog = parentDialog;
initComponents(); // Machine generated code
caseBrowserPanel = new MultiUserCasesBrowserPanel(new ExplorerManager(), new SelectMultiUserCaseDialogCustomizer());
multiUserCaseScrollPane.add(caseBrowserPanel);
multiUserCaseScrollPane.setViewportView(caseBrowserPanel);
listeners = new ArrayList<>();
}
/**
* Refreshes the child component that displays the multi-user cases known to
* the coordination service..
*/
void refreshDisplay() {
caseBrowserPanel.displayCases();
}
/**
* Subscribes to the selections when the user presses the OK button.
*
* @param listener
*/
void subscribeToNewCaseSelections(ChangeListener listener) {
listeners.add(listener);
}
/**
* Sets the selections in the panel
*
* @param selections
*
* @throws PropertyVetoException
*/
void setSelections(Node[] selections) throws PropertyVetoException {
caseBrowserPanel.getExplorerManager().setSelectedNodes(selections);
caseBrowserPanel.requestFocus();
}
/**
* 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
multiUserCaseScrollPane = new javax.swing.JScrollPane();
selectAllButton = new javax.swing.JButton();
deselectAllButton = new javax.swing.JButton();
jLabel1 = new javax.swing.JLabel();
confirmSelections = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
refreshButton = new javax.swing.JButton();
org.openide.awt.Mnemonics.setLocalizedText(selectAllButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.selectAllButton.text")); // NOI18N
selectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectAllButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.deselectAllButton.text")); // NOI18N
deselectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deselectAllButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.jLabel1.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(confirmSelections, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.confirmSelections.text")); // NOI18N
confirmSelections.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
confirmSelectionsActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.cancelButton.text")); // NOI18N
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(SelectMultiUserCasesPanel.class, "SelectMultiUserCasesPanel.refreshButton.text")); // NOI18N
refreshButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
refreshButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(multiUserCaseScrollPane)
.addGroup(layout.createSequentialGroup()
.addComponent(selectAllButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deselectAllButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 365, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 286, Short.MAX_VALUE)
.addComponent(refreshButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(confirmSelections, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cancelButton)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(multiUserCaseScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(selectAllButton)
.addComponent(deselectAllButton)
.addComponent(confirmSelections)
.addComponent(cancelButton)
.addComponent(refreshButton))
.addContainerGap(15, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed
try {
caseBrowserPanel.getExplorerManager().setSelectedNodes(caseBrowserPanel.getExplorerManager().getRootContext().getChildren().getNodes());
} catch (PropertyVetoException ex) {
//Ignore
}
}//GEN-LAST:event_selectAllButtonActionPerformed
private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed
try {
caseBrowserPanel.getExplorerManager().setSelectedNodes(new Node[0]);
} catch (PropertyVetoException ex) {
//Ignore
}
}//GEN-LAST:event_deselectAllButtonActionPerformed
private void confirmSelectionsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_confirmSelectionsActionPerformed
//Pull out the CaseNodeData objects from the selections
Node[] selections = caseBrowserPanel.getExplorerManager().getSelectedNodes();
List<CaseNodeData> caseNodeData = Stream.of(selections)
.map(n -> n.getLookup().lookup(CaseNodeData.class))
.collect(Collectors.toList());
listeners.forEach((l) -> {
l.nodeSelectionChanged(selections, caseNodeData);
});
parentDialog.setVisible(false);
}//GEN-LAST:event_confirmSelectionsActionPerformed
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
parentDialog.setVisible(false);
}//GEN-LAST:event_cancelButtonActionPerformed
private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
caseBrowserPanel.displayCases();
}//GEN-LAST:event_refreshButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton cancelButton;
private javax.swing.JButton confirmSelections;
private javax.swing.JButton deselectAllButton;
private javax.swing.JLabel jLabel1;
private javax.swing.JScrollPane multiUserCaseScrollPane;
private javax.swing.JButton refreshButton;
private javax.swing.JButton selectAllButton;
// End of variables declaration//GEN-END:variables
}

View File

@ -27,9 +27,14 @@
;Get the list of names of algorithms
Global $algorithms[3] ;increase size of array when adding new algorithms
$algorithms[0] = "Single Data Source"
$algorithms[1] = "Folder of Logical Files"
$algorithms[2] = "One Data Source Per Folder"
$algorithms[0] = "Single data source"
$algorithms[1] = "Folder of logical files"
$algorithms[2] = "One data source per folder"
Global $algorithmDescriptions[3] ;increase size of array when adding new algorithms
$algorithmDescriptions[0] = "Create a single auto ingest manifest file for a single disk image or VM file."
$algorithmDescriptions[1] = "Create a single auto ingest manifest file for a single folder of logical files."
$algorithmDescriptions[2] = "Create a manifest file for the first supported image of each subfolder of a case folder. If no supported images exist in the folder a manifest will be generated for the folders contents as a logical file set. Supported disk image or VM files: .e01, .l01, .001, .ad1"
; $algorithms[2] = "All Files In One Folder"
Global $progressArea = Null
@ -42,6 +47,11 @@ Func GetAlgorithmNames()
Return $algorithms
EndFunc
;Return the description for the specified algorithm index
Func GetAlgorithmDescription($index)
Return $algorithmDescriptions[$index]
EndFunc
;Return the name of the first algorithm as a default algorithm
Func GetDefaultAlgorithmName()
Return $algorithms[0]

View File

@ -30,9 +30,9 @@ Opt("GUIOnEventMode", 1) ; Change to OnEvent mode
;Draw GUI and declare variables
;
;==============================================
local $windowHeight = 500
local $windowWidth = 400
local $windowTitle = "Autopsy AutoIngest Manifest File Generator"
local $windowHeight = 560
local $windowWidth = 460
local $windowTitle = "Autopsy Auto Ingest Manifest File Generator"
Global $hMainGUI = GUICreate($windowTitle, $windowWidth, $windowHeight) ;To make GUI resize add following args -1, -1, $WS_OVERLAPPEDWINDOW)
;GUICtrlSetResizing ($hMainGUI, $GUI_DOCKBORDERS)
GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEButton")
@ -48,16 +48,17 @@ local $progressAreaInset = 8
local $distanceFromTop = $topMargin
local $distanceFromLeft = $leftMargin
Global $defaultDirectory = @MyDocumentsDir & "\"
local $labelWidth = 58
local $fieldWidth = 235
local $buttonWidth = 60
local $labelWidth = 63
local $fieldWidth = 255
local $buttonWidth = 95
local $fieldHeight = 20
local $descriptionHeight = 50
local $progressAreaWidth = $windowWidth - 2*($progressAreaInset+$leftMargin)
local $gapBetweenWidth = 10
local $gapBetweenHeight = 10
;Draw the GUI Code
GUICtrlCreateLabel("Algorithm", $distanceFromLeft, $distanceFromTop+$labelOffset)
GUICtrlCreateLabel("Input", $distanceFromLeft, $distanceFromTop+$labelOffset)
$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth
Global $algorithmComboBox = GUICtrlCreateCombo(GetDefaultAlgorithmName(), $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight, $CBS_DROPDOWNLIST)
@ -67,12 +68,19 @@ for $algorithmName IN $allAlgorithmNames
; Add additional items to the combobox.
GUICtrlSetData($algorithmComboBox, $algorithmName)
Next
$distanceFromLeft = $leftMargin
$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight
Global $rootFolderLabel = GUICtrlCreateLabel("Root Folder", $distanceFromLeft, $distanceFromTop+$labelOffset)
GUICtrlCreateLabel("Description", $distanceFromLeft, $distanceFromTop+$labelOffset)
$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth
;calculate height of progress area to use remaining space minus space for exit button
Global $descriptionArea = GUICtrlCreateEdit("", $distanceFromLeft, $distanceFromTop, $fieldWidth, $descriptionHeight, BitOr($ES_READONLY,$WS_VSCROLL, $ES_MULTILINE))
$distanceFromLeft = $leftMargin
$distanceFromTop = $distanceFromTop + $descriptionHeight + $gapBetweenHeight
Global $caseDirectoryLabel = GUICtrlCreateLabel("Case Directory", $distanceFromLeft, $distanceFromTop+$labelOffset)
$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth
Global $rootFolderField = GUICtrlCreateInput("", $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight)
$distanceFromLeft = $distanceFromLeft +$fieldWidth+$gapBetweenWidth
@ -83,13 +91,14 @@ $distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight
Global $caseNameLabel = GUICtrlCreateLabel("Case Name", $distanceFromLeft, $distanceFromTop+$labelOffset)
$distanceFromLeft = $distanceFromLeft+$labelWidth+$gapBetweenWidth
Global $caseNameField = GUICtrlCreateInput("", $distanceFromLeft, $distanceFromTop, $fieldWidth, $fieldHeight)
$distanceFromLeft = $leftMargin
$distanceFromLeft = $distanceFromLeft +$fieldWidth+$gapBetweenWidth
$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight
$distanceFromTop = $distanceFromTop + $gapBetweenHeight ;add an extra gap before run button
Global $runButton = GUICtrlCreateButton("Run", $distanceFromLeft, $distanceFromTop+$buttonOffset, $buttonWidth)
GUICtrlSetOnEvent($runButton, "AlgorithmRunAction")
$distanceFromTop = $distanceFromTop + $gapBetweenHeight ;add an extra gap before Generate Manifest button
Global $generateManifestButton = GUICtrlCreateButton("Generate Manifest", $distanceFromLeft, $distanceFromTop+$buttonOffset, $buttonWidth)
GUICtrlSetOnEvent($generateManifestButton, "AlgorithmGenerateManifestAction")
$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight
$distanceFromLeft = $leftMargin
$distanceFromTop = $distanceFromTop + $fieldHeight + $gapBetweenHeight ;add extra gap before progress area
local $ProgressLabel = GUICtrlCreateLabel("Progress", $distanceFromLeft, $distanceFromTop+$labelOffset)
@ -170,46 +179,50 @@ Func Redraw()
;Move controls based on what is hidden or shown using ControlGetPos() and GUICtrlSetPos()
If $selectedAlgName == $allAlgorithmNames[2] Then ;"One Data Source Per Folder"
ChangeToDefaultGUI()
GUICtrlSetData($descriptionArea, GetAlgorithmDescription(2))
ElseIf $selectedAlgName == $allAlgorithmNames[0] Then ;"Single Data Source"
ChangeToSingleDataSourceGUI()
GUICtrlSetData($descriptionArea, GetAlgorithmDescription(0))
ElseIf $selectedAlgName == $allAlgorithmNames[1] Then ;"Folder of Logical Files"
ChangeToFolderOfLogicalFilesGUI()
GUICtrlSetData($descriptionArea, GetAlgorithmDescription(1))
EndIf
EndFunc ;==>AlgorithmComboBox
;Change the controls displayed in the GUI to the ones needed for the Single Data Source algorithm
Func ChangeToSingleDataSourceGUI()
ClearFields()
GUICtrlSetData($rootFolderLabel, "Data Source")
GUICtrlSetData($caseDirectoryLabel, "Data Source")
GUICtrlSetState($caseNameField, $GUI_SHOW)
GUICtrlSetState($caseNameLabel, $GUI_SHOW)
GUICtrlSetOnEvent($browseButton, "BrowseForDataSourceFile")
GUICtrlSetState($runButton, $GUI_DISABLE)
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
EndFunc
;Change the controls displayed in the GUI to the ones needed for the Folder of Logical Files algorithm
Func ChangeToFolderOfLogicalFilesGUI()
ClearFields()
GUICtrlSetData($rootFolderLabel, "Data Source")
GUICtrlSetData($rootFolderLabel, "Data Source")
GUICtrlSetData($caseDirectoryLabel, "Data Source")
GUICtrlSetData($caseDirectoryLabel, "Data Source")
GUICtrlSetState($caseNameField, $GUI_SHOW)
GUICtrlSetState($caseNameLabel, $GUI_SHOW)
GUICtrlSetOnEvent($browseButton, "Browse")
GUICtrlSetState($runButton, $GUI_DISABLE)
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
EndFunc
;Change the controls displayed in the GUI to the ones needed for One
;Change the controls displayed in the GUI to the ones needed for One Data Source Per Folder
Func ChangeToDefaultGUI()
ClearFields()
GUICtrlSetData($rootFolderLabel, "Root Folder")
GUICtrlSetData($caseDirectoryLabel, "Case Directory")
GUICtrlSetState($rootFolderField, $GUI_SHOW)
GUICtrlSetState($rootFolderLabel, $GUI_SHOW)
GUICtrlSetState($caseDirectoryLabel, $GUI_SHOW)
GUICtrlSetState($caseNameField, $GUI_HIDE)
GUICtrlSetState($caseNameLabel, $GUI_HIDE)
GUICtrlSetOnEvent($browseButton, "Browse")
;rename to RootDirectory to root directory
;hide case name field
GUICtrlSetState($runButton, $GUI_DISABLE)
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
EndFunc
;ensure that all fields for the selected algorithm are valid
@ -231,18 +244,18 @@ EndFunc
;ensure that the settings for the default algorithm are valid before enabling it
Func ValidateDefaultFields($rootFolderPath)
if ($rootFolderPath <> "" And FileExists($rootFolderPath)) Then
GUICtrlSetState($runButton, $GUI_ENABLE)
GUICtrlSetState($generateManifestButton, $GUI_ENABLE)
Else
GUICtrlSetState($runButton, $GUI_DISABLE)
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
EndIf
EndFunc
;ensure that the settings for the Single Data Source and Folder of Logical Files algorithms are valid
Func ValidateSingleDataSourceFields($dataSourcePath, $caseName)
if ($dataSourcePath <> "" And FileExists($dataSourcePath) And $caseName <> "") Then
GUICtrlSetState($runButton, $GUI_ENABLE)
GUICtrlSetState($generateManifestButton, $GUI_ENABLE)
Else
GUICtrlSetState($runButton, $GUI_DISABLE)
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
EndIf
EndFunc
@ -264,6 +277,12 @@ Func Browse()
$defaultDirectory = $caseDrive & $caseDir
GUICtrlSetData($rootFolderField, $selectedDirectory)
EndIf
If GUICtrlRead($algorithmComboBox) == $allAlgorithmNames[2] Then ;"One Data Source Per Folder"
If ($selectedDirectory == $defaultDirectory) Then ;Don't allow root drives as selected directory for this algorithm
MsgBox(0, "Invalid Case Directory", "The directory is used to determine the case name and can not be the root directory of a disk.")
GUICtrlSetData($rootFolderField, "")
EndIf
EndIf
GUICtrlSetState($caseNameField, $GUI_FOCUS)
GUICtrlSetState($browseButton, $GUI_ENABLE)
EndFunc ;==>BrowseButton
@ -284,13 +303,13 @@ Func BrowseForDataSourceFile()
GUICtrlSetState($browseButton, $GUI_ENABLE)
EndFunc
;Perform the action associated with the run button which should be defined in ManifestGenerationAlgorithms.au3
Func AlgorithmRunAction()
; Note: At this point @GUI_CtrlId would equal $runButton
GUICtrlSetState($runButton, $GUI_DISABLE)
;Perform the action associated with the generate manifest button which should be defined in ManifestGenerationAlgorithms.au3
Func AlgorithmGenerateManifestAction()
; Note: At this point @GUI_CtrlId would equal $generateManifestButton
GUICtrlSetState($generateManifestButton, $GUI_DISABLE)
RunAlgorithm(GUICtrlRead($algorithmComboBox), GetSettings(), $progressField)
GUICtrlSetState($runButton, $GUI_ENABLE)
EndFunc ;==>RunButton
GUICtrlSetState($generateManifestButton, $GUI_ENABLE)
EndFunc ;==>GenerateManifestButton
;Get an array of settings as they are set on this panel
Func GetSettings()
@ -306,11 +325,8 @@ Func CLOSEButton()
; @GUI_WinHandle will be either $hMainGUI or $hDummyGUI
GUICtrlSetState($exitButton, $GUI_DISABLE)
If @GUI_WinHandle = $hMainGUI Then
Local $msgBoxAnswer = MsgBox(1, "Close Tool Confirmation", "Press OK to confirm closing the tool")
if $msgBoxAnswer == 1 Then
WritePropertiesFile()
Exit
EndIf
WritePropertiesFile()
Exit
EndIf
GUICtrlSetState($exitButton, $GUI_ENABLE)
EndFunc ;==>CLOSEButton

Binary file not shown.

View File

@ -2,6 +2,10 @@ cannotBuildXmlParser=Unable to build XML parser:
cannotLoadSEUQA=Unable to load Search Engine URL Query Analyzer settings file, SEUQAMappings.xml:
cannotParseXml=Unable to parse XML file:
ChromeCacheExtractor.moduleName=ChromeCacheExtractor
ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}
DataSourceUsage_AndroidMedia=Android Media Card
DataSourceUsage_FlashDrive=Flash Drive
# {0} - OS name
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
DataSourceUsageAnalyzer.parentModuleName=Recent Activity
Extract.indexError.message=Failed to index artifact for keyword search.
@ -14,11 +18,10 @@ ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer
ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file
ExtractIE.getBookmark.errMsg.errGettingBookmarks=Error getting Internet Explorer Bookmarks.
ExtractIE.getBookmark.errMsg.errPostingBookmarks=Error posting Internet Explorer Bookmark artifacts.
ExtractIE.getCookie.errMsg.errPostinCookiess=Error posting Internet Explorer Cookie artifacts.
ExtractIE.getCookie.errMsg.errPostingCookies=Error posting Internet Explorer Cookie artifacts.
ExtractIE.getCookie.errMsg.errPostingCookiess=Error posting Internet Explorer Cookie artifacts.
ExtractIE.getHistory.errMsg.errPostingHistory=Error posting Internet Explorer History artifacts.
ExtractIE.getHistory.errMsg.errPostinHistory=Error posting Internet Explorer History artifacts.
ExtractIE.parentModuleName.noSpace=RecentActivity
#{0} - the module name
Extractor.errPostingArtifacts=Error posting {0} artifacts to the blackboard.
ExtractOs.androidOs.label=Android
ExtractOs.androidVolume.label=OS Drive (Android)
@ -138,6 +141,7 @@ Progress_Message_Analyze_Registry=Analyzing Registry Files
Progress_Message_Analyze_Usage=Data Sources Usage Analysis
Progress_Message_Chrome_AutoFill=Chrome Auto Fill
Progress_Message_Chrome_Bookmarks=Chrome Bookmarks
Progress_Message_Chrome_Cache=Chrome Cache
Progress_Message_Chrome_Cookies=Chrome Cookies
Progress_Message_Chrome_Downloads=Chrome Downloads
Progress_Message_Chrome_FormHistory=Chrome Form History
@ -181,13 +185,13 @@ RAImageIngestModule.process.ingestMsg.results={0} - Browser Results
RAImageIngestModule.complete.errMsg.failed={0} failed to complete - see log for details <br>
RAImageIngestModule.getName=Recent Activity
RAImageIngestModule.getDesc=Extracts recent user activity, such as Web browsing, recently used documents and installed programs.
RecentDocumentsByLnk.getRecDoc.errMsg.errCreatingArtifact={0}: Error creating Recent Document artifact.
RecentDocumentsByLnk.getRecDoc.errMsg.errGetLnkFiles={0}: Error getting lnk Files.
RecentDocumentsByLnk.getRecDoc.errParsingFile={0}: Error parsing Recent File {1}
RecentDocumentsByLnk.parentModuleName.noSpace=RecentActivity
RecentDocumentsByLnk.parentModuleName=Recent Activity
RegRipperFullNotFound=Full version RegRipper executable not found.
RegRipperNotFound=Autopsy RegRipper executable not found.
# {0} - file name
SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}.
SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine
SearchEngineURLQueryAnalyzer.engineName.none=NONE

View File

@ -92,6 +92,7 @@ class Chrome extends Extract {
"Progress_Message_Chrome_FormHistory=Chrome Form History",
"Progress_Message_Chrome_AutoFill=Chrome Auto Fill",
"Progress_Message_Chrome_Logins=Chrome Logins",
"Progress_Message_Chrome_Cache=Chrome Cache",
})
@ -125,7 +126,8 @@ class Chrome extends Extract {
progressBar.progress(Bundle.Progress_Message_Chrome_Downloads());
this.getDownload();
ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context);
progressBar.progress(Bundle.Progress_Message_Chrome_Cache());
ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar);
chromeCacheExtractor.getCaches();
}

View File

@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
import org.sleuthkit.autopsy.ingest.IngestServices;
@ -92,12 +93,17 @@ final class ChromeCacheExtractor {
private final Content dataSource;
private final IngestJobContext context;
private final DataSourceIngestModuleProgress progressBar;
private final IngestServices services = IngestServices.getInstance();
private Case currentCase;
private FileManager fileManager;
// A file table to cache copies of index and data_n files.
private final Map<String, CacheFileCopy> filesTable = new HashMap<>();
// A file table to cache the f_* files.
private final Map<String, AbstractFile> externalFilesTable = new HashMap<>();
/**
* Encapsulates abstract file for a cache file as well as a temp file copy
* that can be accessed as a random access file.
@ -126,12 +132,14 @@ final class ChromeCacheExtractor {
}
@NbBundle.Messages({
"ChromeCacheExtractor.moduleName=ChromeCacheExtractor"
"ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
"ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}"
})
ChromeCacheExtractor(Content dataSource, IngestJobContext context ) {
ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar ) {
moduleName = Bundle.ChromeCacheExtractor_moduleName();
this.dataSource = dataSource;
this.context = context;
this.progressBar = progressBar;
}
@ -170,6 +178,7 @@ final class ChromeCacheExtractor {
void subInit(String cachePath) throws IngestModuleException {
filesTable.clear();
externalFilesTable.clear();
String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
File outDir = new File(cacheAbsOutputFolderName);
@ -283,6 +292,9 @@ final class ChromeCacheExtractor {
return;
}
}
// find all f_* files in a single query.
findExternalFiles(cachePath);
} catch (TskCoreException | IngestModuleException ex) {
String msg = "Failed to find cache files in path " + cachePath; //NON-NLS
@ -305,8 +317,10 @@ final class ChromeCacheExtractor {
// Process each address in the table
for (int i = 0; i < indexHdr.getTableLen(); i++) {
CacheAddress addr = new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cachePath);
if (addr.isInitialized()) {
progressBar.progress( NbBundle.getMessage(this.getClass(),
"ChromeCacheExtractor.progressMsg",
moduleName, i, indexHdr.getTableLen(), cachePath) );
try {
List<DerivedFile> addedFiles = this.getCacheEntry(addr, sourceArtifacts, webCacheArtifacts);
derivedFiles.addAll(addedFiles);
@ -411,13 +425,10 @@ final class ChromeCacheExtractor {
moduleName,
dataFile.get().getUniquePath()));
long pathID = Util.findID(dataSource, dataFile.get().getUniquePath());
if (pathID != -1) {
webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
moduleName, pathID));
}
webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
moduleName, dataFile.get().getId()));
webCacheArtifacts.add(webCacheArtifact);
webCacheArtifacts.add(webCacheArtifact);
}
if (isBrotliCompressed) {
@ -458,12 +469,10 @@ final class ChromeCacheExtractor {
webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
moduleName,
derivedFile.getUniquePath()));
long pathID = Util.findID(dataSource, derivedFile.getUniquePath());
if (pathID != -1) {
webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
moduleName, pathID));
}
webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
moduleName, derivedFile.getId()));
webCacheArtifacts.add(webCacheArtifact);
}
@ -484,13 +493,37 @@ final class ChromeCacheExtractor {
}
/**
* Finds abstract file for cache file with a specified name
* Finds all the f_* files in the specified path, and fills them in the
* effFilesTable, so that subsequent searches are fast.
*
* @param cachePath path under which to look for.
*
* @throws TskCoreException
*/
private void findExternalFiles(String cachePath) throws TskCoreException {
List<AbstractFile> effFiles = fileManager.findFiles(dataSource, "f_%", cachePath); //NON-NLS
for (AbstractFile abstractFile : effFiles ) {
this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
}
}
/**
* Finds abstract file for cache file with a specified name.
* First checks in the file tables.
*
* @param cacheFileName
* @return Opt
* @return Optional abstract file
* @throws TskCoreException
*/
Optional<AbstractFile> findCacheFile(String cacheFileName, String cachePath) throws TskCoreException {
String fileTableKey = cachePath + cacheFileName;
if (cacheFileName.startsWith("f_") && externalFilesTable.containsKey(fileTableKey)) {
return Optional.of(externalFilesTable.get(fileTableKey));
}
if (filesTable.containsKey(fileTableKey)) {
return Optional.of(filesTable.get(fileTableKey).getAbstractFile());
}
List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cachePath); //NON-NLS
if (!cacheFiles.isEmpty()) {
@ -913,9 +946,11 @@ final class ChromeCacheExtractor {
return;
}
cacheFileCopy = getCacheFileCopy(address.getFilename(), address.getCachePath()).get();
// Don't extract data from external files.
if (!address.isInExternalFile() ) {
cacheFileCopy = getCacheFileCopy(address.getFilename(), address.getCachePath()).get();
this.data = new byte [length];
ByteBuffer buf = cacheFileCopy.getByteBuffer();
int dataOffset = DATAFILE_HDR_SIZE + address.getStartBlock() * address.getBlockSize();
@ -951,8 +986,8 @@ final class ChromeCacheExtractor {
i++;
}
// hhtp headers are terminated by 0x00 0x00
if (data[i+1] == 0) {
// http headers are terminated by 0x00 0x00
if (i == data.length || data[i+1] == 0) {
done = true;
}
@ -964,10 +999,11 @@ final class ChromeCacheExtractor {
httpResponse = headerLine;
} else {
int nPos = headerLine.indexOf(':');
String key = headerLine.substring(0, nPos);
String val= headerLine.substring(nPos+1);
httpHeaders.put(key.toLowerCase(), val);
if (nPos > 0 ) {
String key = headerLine.substring(0, nPos);
String val= headerLine.substring(nPos+1);
httpHeaders.put(key.toLowerCase(), val);
}
}
i++;

View File

@ -32,7 +32,10 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Analyzes data sources using heuristics to determine which types of operating
@ -43,6 +46,14 @@ import org.sleuthkit.datamodel.TskCoreException;
class DataSourceUsageAnalyzer extends Extract {
private static final Logger logger = Logger.getLogger(DataSourceUsageAnalyzer.class.getName());
private static final int FAT_EXFAT_FLAGS = TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_FAT16.getValue() |
TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_FAT32.getValue() |
TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_EXFAT.getValue();
private static final long HUNDRED_GB = 100*1024*1024*1024l;
private static final String ANDROID_MEDIACARD_ROOT_FILENAMES[] = // files expected in root folder of an Android media card
{".android_secure", "android", "audio",
"photos", "dcim", "music", "pictures", "videos"}; //NON-NLS
private Content dataSource;
@Messages({
@ -62,13 +73,18 @@ class DataSourceUsageAnalyzer extends Extract {
}
private void createDataSourceUsageArtifacts() throws TskCoreException {
createOSInfoDataSourceUsageArtifacts();
createAndroidMediaCardArtifacts();
}
/**
* Create TSK_DATA_SOURCE_USAGE artifacts based on OS_INFO artifacts
* existing as well as other criteria such as specific paths existing.
*
* @throws TskCoreException
*/
private void createDataSourceUsageArtifacts() throws TskCoreException {
private void createOSInfoDataSourceUsageArtifacts() throws TskCoreException {
boolean windowsOsDetected = false;
List<BlackboardArtifact> osInfoArtifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO);
for (BlackboardArtifact osInfoArt : osInfoArtifacts) {
@ -143,4 +159,54 @@ class DataSourceUsageAnalyzer extends Extract {
}
}
}
/**
* Checks to see if the data source might be an Android media card or a Flash drive.
* If so, creates TSK_DATA_SOURCE_USAGE artifact.
*
* @return true if any specified files exist false if none exist
*
* @throws TskCoreException
*/
@Messages({
"DataSourceUsage_AndroidMedia=Android Media Card",
"DataSourceUsage_FlashDrive=Flash Drive"
})
private void createAndroidMediaCardArtifacts() throws TskCoreException {
if (dataSource instanceof Image) {
Image image = (Image) dataSource;
try {
if (image.getSize() > HUNDRED_GB) {
return;
}
List<FileSystem> fileSystems = image.getFileSystems();
if (fileSystems.isEmpty() || fileSystems.size() > 1) {
return;
}
FileSystem fileSystem = fileSystems.get(0);
if ( fileSystem == null || (fileSystem.getFsType().getValue() & FAT_EXFAT_FLAGS) == 0) {
return ;
}
FileManager fileManager = currentCase.getServices().getFileManager();
for (String fileName : ANDROID_MEDIACARD_ROOT_FILENAMES ) {
for (AbstractFile file : fileManager.findFiles(dataSource, fileName, "/")) { // NON-NLS
if (file.getParentPath().equals("/") && file.getName().equalsIgnoreCase(fileName)) { // NON-NLS
createDataSourceUsageArtifact(Bundle.DataSourceUsage_AndroidMedia());
return;
}
}
}
// If none of the Android paths is found but it meets other criteria, it might be just a flash drive
createDataSourceUsageArtifact(Bundle.DataSourceUsage_FlashDrive());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while checking image: {0} for Andriod media card", image.getName() + ex.getMessage()); //NON-NLS
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More