mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
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:
commit
886356dcd5
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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>
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -168,7 +168,7 @@ public class CorrelationDataSource implements Serializable {
|
||||
*
|
||||
* @return the ID or -1 if unknown
|
||||
*/
|
||||
int getID() {
|
||||
public int getID() {
|
||||
return dataSourceID;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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, "{key}")"/>
|
||||
</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">
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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<>();
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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}...
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/report/images/image.png
Executable file
BIN
Core/src/org/sleuthkit/autopsy/report/images/image.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 516 B |
@ -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
|
||||
}
|
||||
}
|
||||
}
|
0
Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css → Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css
Normal file → Executable file
0
Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css → Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css
Normal file → Executable 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>
|
@ -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">
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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/>
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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>
|
@ -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
|
||||
}
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
137
KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/SearchHit.java
Executable file
137
KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/multicase/SearchHit.java
Executable 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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>
|
@ -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
|
||||
}
|
@ -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]
|
||||
|
@ -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.
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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++;
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user