mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 4590-DataSourceProfiler
This commit is contained in:
commit
eb146f9198
@ -65,8 +65,8 @@
|
||||
<property environment="env"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/dist/sleuthkit-postgresql-${TSK_VERSION}.jar"
|
||||
tofile="${ext.dir}/sleuthkit-postgresql-${TSK_VERSION}.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.8.11.jar"
|
||||
tofile="${ext.dir}/sqlite-jdbc-3.8.11.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.25.2.jar"
|
||||
tofile="${ext.dir}/sqlite-jdbc-3.25.2.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/postgresql-9.4.1211.jre7.jar"
|
||||
tofile="${ext.dir}/postgresql-9.4.1211.jre7.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/mchange-commons-java-0.2.9.jar"
|
||||
|
@ -432,8 +432,8 @@
|
||||
<binary-origin>release/modules/ext/tika-parsers-1.17.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.8.11.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.8.11.jar</binary-origin>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.25.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.25.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/json-simple-1.1.1.jar</runtime-relative-path>
|
||||
|
@ -67,6 +67,7 @@ import org.sleuthkit.autopsy.appservices.AutopsyService;
|
||||
import org.sleuthkit.autopsy.appservices.AutopsyService.CaseContext;
|
||||
import static org.sleuthkit.autopsy.casemodule.Bundle.*;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
|
||||
import org.sleuthkit.autopsy.casemodule.datasourceSummary.DataSourceSummaryAction;
|
||||
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
@ -1165,6 +1166,7 @@ public class Case {
|
||||
CallableSystemAction.get(AddImageAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
|
||||
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
|
||||
@ -1218,6 +1220,7 @@ public class Case {
|
||||
CallableSystemAction.get(AddImageAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
|
||||
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
|
||||
|
@ -55,7 +55,7 @@ final class CasePropertiesAction extends CallableSystemAction {
|
||||
casePropertiesDialog = new JDialog(mainWindow, title, true);
|
||||
CaseInformationPanel caseInformationPanel = new CaseInformationPanel();
|
||||
caseInformationPanel.addCloseButtonAction((ActionEvent e) -> {
|
||||
casePropertiesDialog.setVisible(false);
|
||||
casePropertiesDialog.dispose();
|
||||
});
|
||||
casePropertiesDialog.add(caseInformationPanel);
|
||||
casePropertiesDialog.setResizable(true);
|
||||
|
@ -61,7 +61,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
|
||||
private void customizeComponents() {
|
||||
refresh();
|
||||
this.ingestJobTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
|
||||
this.ingestModuleTableModel = new IngestModuleTableModel(this.ingestJobs.get(ingestJobTable.getSelectedRow()));
|
||||
IngestJobInfo currJob = (ingestJobTable.getSelectedRow() < 0 ? null : this.ingestJobs.get(ingestJobTable.getSelectedRow()));
|
||||
this.ingestModuleTableModel = new IngestModuleTableModel(currJob);
|
||||
this.ingestModuleTable.setModel(this.ingestModuleTableModel);
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
DataSourceSummaryPanel.fileCountsLabel.text=Files (based on mimetype)
|
||||
DataSourceSummaryPanel.opperatingSystemLabel.text=OS:
|
||||
DataSourceSummaryPanel.ingestJobsLabel.text=Ingest Jobs
|
||||
DataSourceSummaryPanel.closeButton.text=Close
|
||||
DataSourceSummaryPanel.gotoDataSourceButton.text=Goto Data Source
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.casemodule.datasourceSummary;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.EnumSet;
|
||||
import javax.swing.Action;
|
||||
import org.openide.awt.ActionID;
|
||||
import org.openide.awt.ActionRegistration;
|
||||
import org.openide.util.HelpCtx;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.util.actions.CallableSystemAction;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
|
||||
@ActionID(category = "Case", id = "org.sleuthkit.autopsy.casemodule.datasourceSummary.DataSourceSummaryAction")
|
||||
@ActionRegistration(displayName = "#CTL_DataSourceSummaryAction", lazy = false)
|
||||
@Messages({"CTL_DataSourceSummaryAction=Data Source Summary"})
|
||||
/**
|
||||
* DataSourceSummaryAction action for the Case menu to activate a ViewSummaryInformationAction selecting the first data source.
|
||||
*/
|
||||
public class DataSourceSummaryAction extends CallableSystemAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create a datasource summary action which will be disabled when no case is
|
||||
* open.
|
||||
*/
|
||||
DataSourceSummaryAction() {
|
||||
putValue(Action.NAME, Bundle.CTL_DataSourceSummaryAction());
|
||||
this.setEnabled(false);
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> {
|
||||
setEnabled(null != evt.getNewValue());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performAction() {
|
||||
//perform the action of a ViewSummaryInformationAction with a ActionEvent which will not be used
|
||||
new ViewSummaryInformationAction(null).actionPerformed(new ActionEvent(Boolean.TRUE, 0, ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return Bundle.CTL_DataSourceSummaryAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpCtx getHelpCtx() {
|
||||
return HelpCtx.DEFAULT_HELP;
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.6" 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">
|
||||
<Component id="jSeparator1" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="dataSourcesScrollPane" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<Component id="gotoDataSourceButton" linkSize="1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="closeButton" linkSize="1" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="fileCountsLabel" max="32767" attributes="0"/>
|
||||
<Component id="fileCountsScrollPane" alignment="1" min="-2" pref="140" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="ingestJobsLabel" max="32767" attributes="0"/>
|
||||
<Component id="ingestJobsScrollPane" pref="474" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="opperatingSystemLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="opperatingSystemValueLabel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</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" pref="8" max="-2" attributes="0"/>
|
||||
<Component id="dataSourcesScrollPane" pref="120" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jSeparator1" min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="fileCountsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="ingestJobsLabel" alignment="3" min="-2" pref="14" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="fileCountsScrollPane" min="-2" pref="107" max="-2" attributes="0"/>
|
||||
<Component id="ingestJobsScrollPane" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="opperatingSystemLabel" alignment="1" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="opperatingSystemValueLabel" min="-2" pref="14" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="closeButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="gotoDataSourceButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="dataSourcesScrollPane">
|
||||
<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.JTable" name="dataSourcesTable">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="dataSourceTableModel" type="code"/>
|
||||
</Property>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JScrollPane" name="ingestJobsScrollPane">
|
||||
<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.JTable" name="ingestJobsTable">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="ingestJobTableModel" type="code"/>
|
||||
</Property>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JScrollPane" name="fileCountsScrollPane">
|
||||
<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.JTable" name="fileCountsTable">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="filesTableModel" type="code"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="opperatingSystemLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryPanel.opperatingSystemLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="opperatingSystemValueLabel">
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="fileCountsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryPanel.fileCountsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="ingestJobsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryPanel.ingestJobsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="closeButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="gotoDataSourceButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryPanel.gotoDataSourceButton.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="gotoDataSourceButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -0,0 +1,698 @@
|
||||
/*
|
||||
* 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.casemodule.datasourceSummary;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.datamodel.utils.FileTypeUtils;
|
||||
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.IngestJobInfo;
|
||||
import org.sleuthkit.datamodel.OSInfo;
|
||||
import org.sleuthkit.datamodel.OSUtility;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
final class DataSourceSummaryPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final List<IngestJobInfo> allIngestJobs = new ArrayList<>();
|
||||
private List<IngestJobInfo> ingestJobs = new ArrayList<>();
|
||||
private DataSourceTableModel dataSourceTableModel = new DataSourceTableModel();
|
||||
private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel();
|
||||
private FilesTableModel filesTableModel = new FilesTableModel(null);
|
||||
private final List<DataSource> dataSources = new ArrayList<>();
|
||||
private final DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
||||
private static final Logger logger = Logger.getLogger(DataSourceSummaryPanel.class.getName());
|
||||
private List<OSInfo> osInfoList;
|
||||
|
||||
/**
|
||||
* Creates new form DataSourceSummaryPanel for displaying a summary of the
|
||||
* data sources for the fcurrent case and the contents found for each
|
||||
* datasource.
|
||||
*/
|
||||
@Messages({"DataSourceSummaryPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
|
||||
"DataSourceSummaryPanel.getDataSources.error.title=Load Failure"})
|
||||
DataSourceSummaryPanel() {
|
||||
initComponents();
|
||||
ingestJobsTable.getTableHeader().setReorderingAllowed(false);
|
||||
fileCountsTable.getTableHeader().setReorderingAllowed(false);
|
||||
dataSourcesTable.getTableHeader().setReorderingAllowed(false);
|
||||
try {
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
allIngestJobs.addAll(skCase.getIngestJobs());
|
||||
dataSources.addAll(skCase.getDataSources());
|
||||
osInfoList = OSUtility.getOSInfo(skCase);
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
|
||||
JOptionPane.showMessageDialog(this, Bundle.DataSourceSummaryPanel_getDataSources_error_text(), Bundle.DataSourceSummaryPanel_getDataSources_error_title(), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
dataSourcesTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
DataSource selectedDataSource = (dataSourcesTable.getSelectedRow() < 0 ? null : dataSources.get(dataSourcesTable.getSelectedRow()));
|
||||
gotoDataSourceButton.setEnabled(selectedDataSource != null);
|
||||
updateIngestJobs(selectedDataSource);
|
||||
filesTableModel = new FilesTableModel(selectedDataSource);
|
||||
fileCountsTable.setModel(filesTableModel);
|
||||
opperatingSystemValueLabel.setText(getOSName(selectedDataSource));
|
||||
this.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the operating system if it is available. Otherwise get
|
||||
* and empty string.
|
||||
*
|
||||
* @param selectedDataSource the datasource to get the OS information for
|
||||
*
|
||||
* @return the name of the opperating system on the specified datasource,
|
||||
* empty string if no opperating system info found
|
||||
*/
|
||||
private String getOSName(DataSource selectedDataSource) {
|
||||
String osName = "";
|
||||
if (selectedDataSource != null) {
|
||||
for (OSInfo osInfo : osInfoList) {
|
||||
try {
|
||||
//assumes only one Opperating System per datasource
|
||||
//get the datasource id from the OSInfo's first artifact if it has artifacts
|
||||
if (!osInfo.getArtifacts().isEmpty() && osInfo.getArtifacts().get(0).getDataSource().getId() == selectedDataSource.getId()) {
|
||||
osName = osInfo.getOSName();
|
||||
//if this OSInfo object has a name use it otherwise keep checking OSInfo objects
|
||||
if (!osName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ignored) {
|
||||
//unable to get datasource for the OSInfo Object
|
||||
//continue checking for OSInfo objects to try and get get the desired information
|
||||
}
|
||||
}
|
||||
}
|
||||
return osName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ingestJobs list with the ingest jobs for the
|
||||
* selectedDataSource
|
||||
*
|
||||
* @param selectedDataSource the datasource to find the ingest jobs for
|
||||
*/
|
||||
@Messages({"DataSourceSummaryPanel.loadIngestJob.error.text=Failed to load ingest jobs.",
|
||||
"DataSourceSummaryPanel.loadIngestJob.error.title=Load Failure"})
|
||||
private void updateIngestJobs(DataSource selectedDataSource) {
|
||||
ingestJobs.clear();
|
||||
if (selectedDataSource != null) {
|
||||
for (IngestJobInfo ingestJob : allIngestJobs) {
|
||||
if (ingestJob.getObjectId() == selectedDataSource.getId()) {
|
||||
ingestJobs.add(ingestJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
ingestJobTableModel = new IngestJobTableModel();
|
||||
ingestJobsTable.setModel(ingestJobTableModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jSeparator1 = new javax.swing.JSeparator();
|
||||
dataSourcesScrollPane = new javax.swing.JScrollPane();
|
||||
dataSourcesTable = new javax.swing.JTable();
|
||||
ingestJobsScrollPane = new javax.swing.JScrollPane();
|
||||
ingestJobsTable = new javax.swing.JTable();
|
||||
fileCountsScrollPane = new javax.swing.JScrollPane();
|
||||
fileCountsTable = new javax.swing.JTable();
|
||||
opperatingSystemLabel = new javax.swing.JLabel();
|
||||
opperatingSystemValueLabel = new javax.swing.JLabel();
|
||||
fileCountsLabel = new javax.swing.JLabel();
|
||||
ingestJobsLabel = new javax.swing.JLabel();
|
||||
closeButton = new javax.swing.JButton();
|
||||
gotoDataSourceButton = new javax.swing.JButton();
|
||||
|
||||
dataSourcesTable.setModel(dataSourceTableModel);
|
||||
dataSourcesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
dataSourcesScrollPane.setViewportView(dataSourcesTable);
|
||||
|
||||
ingestJobsTable.setModel(ingestJobTableModel);
|
||||
ingestJobsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
ingestJobsScrollPane.setViewportView(ingestJobsTable);
|
||||
|
||||
fileCountsTable.setModel(filesTableModel);
|
||||
fileCountsScrollPane.setViewportView(fileCountsTable);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(opperatingSystemLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.opperatingSystemLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(fileCountsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.fileCountsLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(ingestJobsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.ingestJobsLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.closeButton.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(gotoDataSourceButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryPanel.class, "DataSourceSummaryPanel.gotoDataSourceButton.text")); // NOI18N
|
||||
gotoDataSourceButton.setEnabled(false);
|
||||
gotoDataSourceButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
gotoDataSourceButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(dataSourcesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addGap(0, 0, Short.MAX_VALUE)
|
||||
.addComponent(gotoDataSourceButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(closeButton))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(fileCountsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(fileCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(ingestJobsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(ingestJobsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 474, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(opperatingSystemLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(opperatingSystemValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {closeButton, gotoDataSourceButton});
|
||||
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(8, 8, 8)
|
||||
.addComponent(dataSourcesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 120, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 5, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(fileCountsLabel)
|
||||
.addComponent(ingestJobsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(fileCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(ingestJobsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(opperatingSystemLabel, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(opperatingSystemValueLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(closeButton)
|
||||
.addComponent(gotoDataSourceButton))
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
/**
|
||||
* Adds an action listener to the Close button of the panel.
|
||||
*
|
||||
* @param action
|
||||
*/
|
||||
void addCloseButtonAction(ActionListener action) {
|
||||
this.closeButton.addActionListener(action);
|
||||
//the gotoDataSourceButton should also close the dialog
|
||||
this.gotoDataSourceButton.addActionListener(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the data source with the specicied data source id. If no data
|
||||
* source matches the dataSourceID it will select the first datasource.
|
||||
*
|
||||
* @param dataSourceID the ID of the datasource to select, null will cause
|
||||
* the first datasource to be selected
|
||||
*/
|
||||
void selectDataSource(Long dataSourceID) {
|
||||
if (dataSourceID != null) {
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
if (dataSources.get(i).getId() == dataSourceID) {
|
||||
dataSourcesTable.setRowSelectionInterval(i, i);
|
||||
//scroll down from top of table to where selected datasource is
|
||||
dataSourcesTable.scrollRectToVisible(new Rectangle(dataSourcesTable.getCellRect(i, 0, true)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if there are data sources in the list and none were found that matched the specied dataSourceID select the first one
|
||||
if (!dataSources.isEmpty()) {
|
||||
dataSourcesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performed when the Goto Data Source button is clicked, will cause the
|
||||
* window to be closed and the data source which was selected to be
|
||||
* navigated to in the tree.
|
||||
*/
|
||||
private void gotoDataSourceButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoDataSourceButtonActionPerformed
|
||||
//the dialog will be closed due to the action listener added in addCloseButtonAction
|
||||
DataSource selectedDataSource = (dataSourcesTable.getSelectedRow() < 0 ? null : dataSources.get(dataSourcesTable.getSelectedRow()));
|
||||
if (selectedDataSource != null) {
|
||||
new ViewContextAction("", selectedDataSource).actionPerformed(evt);
|
||||
}
|
||||
}//GEN-LAST:event_gotoDataSourceButtonActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton closeButton;
|
||||
private javax.swing.JScrollPane dataSourcesScrollPane;
|
||||
private javax.swing.JTable dataSourcesTable;
|
||||
private javax.swing.JLabel fileCountsLabel;
|
||||
private javax.swing.JScrollPane fileCountsScrollPane;
|
||||
private javax.swing.JTable fileCountsTable;
|
||||
private javax.swing.JButton gotoDataSourceButton;
|
||||
private javax.swing.JLabel ingestJobsLabel;
|
||||
private javax.swing.JScrollPane ingestJobsScrollPane;
|
||||
private javax.swing.JTable ingestJobsTable;
|
||||
private javax.swing.JSeparator jSeparator1;
|
||||
private javax.swing.JLabel opperatingSystemLabel;
|
||||
private javax.swing.JLabel opperatingSystemValueLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
/**
|
||||
* Table model for the Data source table, to display all data sources for
|
||||
* the current case.
|
||||
*/
|
||||
@Messages({"DataSourceSummaryPanel.DataSourceTableModel.dataSourceName.header=Data Source Name",
|
||||
"DataSourceSummaryPanel.DataSourceTableModel.type.header=Type",
|
||||
"DataSourceSummaryPanel.DataSourceTableModel.files.header=Files",
|
||||
"DataSourceSummaryPanel.DataSourceTableModel.results.header=Results",
|
||||
"DataSourceSummaryPanel.DataSourceTableModel.tags.header=Tags"})
|
||||
private class DataSourceTableModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<String> columnHeaders = new ArrayList<>();
|
||||
private final Map<Long, Long> fileCountsMap;
|
||||
private final Map<Long, Long> artifactCountsMap;
|
||||
private final Map<Long, Long> tagCountsMap;
|
||||
|
||||
/**
|
||||
* Create a new DataSourceTableModel for the current case.
|
||||
*/
|
||||
DataSourceTableModel() {
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_dataSourceName_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_type_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_files_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_results_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_DataSourceTableModel_tags_header());
|
||||
fileCountsMap = getCountsOfFiles();
|
||||
artifactCountsMap = getCountsOfArtifacts();
|
||||
tagCountsMap = getCountsOfTags();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return dataSources.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return columnHeaders.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
DataSource currentDataSource = dataSources.get(rowIndex);
|
||||
Long count;
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return currentDataSource.getName();
|
||||
case 1:
|
||||
return "";
|
||||
case 2:
|
||||
//display 0 if no count is found
|
||||
count = fileCountsMap.get(currentDataSource.getId());
|
||||
return count == null ? 0 : count;
|
||||
case 3:
|
||||
//display 0 if no count is found
|
||||
count = artifactCountsMap.get(currentDataSource.getId());
|
||||
return count == null ? 0 : count;
|
||||
case 4:
|
||||
//display 0 if no count is found
|
||||
count = tagCountsMap.get(currentDataSource.getId());
|
||||
return count == null ? 0 : count;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map containing the number of files in each data source in the
|
||||
* current case.
|
||||
*
|
||||
* @return Collection which maps datasource id to a count for the number
|
||||
* of files in the datasource, will only contain entries for
|
||||
* datasources which have at least 1 file
|
||||
*/
|
||||
private Map<Long, Long> getCountsOfFiles() {
|
||||
try {
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
DataSourceCountsCallback callback = new DataSourceCountsCallback();
|
||||
final String countFilesQuery = "data_source_obj_id, COUNT(*) AS count"
|
||||
+ " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
|
||||
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
|
||||
+ " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
|
||||
skCase.getCaseDbAccessManager().select(countFilesQuery, callback);
|
||||
return callback.getMapOfCounts();
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get counts of files for all datasources, providing empty results", ex);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map containing the number of artifacts in each data source in
|
||||
* the current case.
|
||||
*
|
||||
* @return Collection which maps datasource id to a count for the number
|
||||
* of artifacts in the datasource, will only contain entries for
|
||||
* datasources which have at least 1 artifact
|
||||
*/
|
||||
private Map<Long, Long> getCountsOfArtifacts() {
|
||||
try {
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
DataSourceCountsCallback callback = new DataSourceCountsCallback();
|
||||
final String countArtifactsQuery = "data_source_obj_id, COUNT(*) AS count"
|
||||
+ " FROM blackboard_artifacts WHERE review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID()
|
||||
+ " GROUP BY data_source_obj_id"; //NON-NLS
|
||||
skCase.getCaseDbAccessManager().select(countArtifactsQuery, callback);
|
||||
return callback.getMapOfCounts();
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get counts of artifacts for all datasources, providing empty results", ex);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map containing the number of tags which have been applied in
|
||||
* each data source in the current case. Not necessarily the same as the
|
||||
* number of items tagged, as an item can have any number of tags.
|
||||
*
|
||||
* @return Collection which maps datasource id to a count for the number
|
||||
* of tags which have been applied in the datasource, will only
|
||||
* contain entries for datasources which have at least 1 item
|
||||
* tagged.
|
||||
*/
|
||||
private Map<Long, Long> getCountsOfTags() {
|
||||
try {
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
DataSourceCountsCallback fileCountcallback = new DataSourceCountsCallback();
|
||||
final String countFileTagsQuery = "data_source_obj_id, COUNT(*) AS count"
|
||||
+ " FROM content_tags as content_tags, tsk_files as tsk_files"
|
||||
+ " WHERE content_tags.obj_id = tsk_files.obj_id"
|
||||
+ " GROUP BY data_source_obj_id"; //NON-NLS
|
||||
skCase.getCaseDbAccessManager().select(countFileTagsQuery, fileCountcallback);
|
||||
Map<Long, Long> tagCountMap = new HashMap<>(fileCountcallback.getMapOfCounts());
|
||||
DataSourceCountsCallback artifactCountcallback = new DataSourceCountsCallback();
|
||||
final String countArtifactTagsQuery = "data_source_obj_id, COUNT(*) AS count"
|
||||
+ " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts"
|
||||
+ " WHERE artifact_tags.artifact_id = arts.artifact_id"
|
||||
+ " GROUP BY data_source_obj_id"; //NON-NLS
|
||||
skCase.getCaseDbAccessManager().select(countArtifactTagsQuery, artifactCountcallback);
|
||||
//combine the results from the count artifact tags query into the copy of the mapped results from the count file tags query
|
||||
artifactCountcallback.getMapOfCounts().forEach((key, value) -> tagCountMap.merge(key, value, (value1, value2) -> value1 + value2));
|
||||
return tagCountMap;
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get counts of tags for all datasources, providing empty results", ex);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
return columnHeaders.get(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of Data Source ID to counts of items found for a query
|
||||
* which selects data_source_obj_id and count(*) with a group by
|
||||
* data_source_obj_id clause.
|
||||
*/
|
||||
private class DataSourceCountsCallback implements CaseDbAccessQueryCallback {
|
||||
|
||||
Map<Long, Long> dataSourceObjIdCounts = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
try {
|
||||
while (rs.next()) {
|
||||
try {
|
||||
dataSourceObjIdCounts.put(rs.getLong("data_source_obj_id"), rs.getLong("count"));
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get data_source_obj_id or count from result set", ex);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get next result for counts by datasource", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the processed results
|
||||
*
|
||||
* @return Collection which maps datasource id to a count for the
|
||||
* number of items found with that datasource id, only
|
||||
* contains entries for datasources with at least 1 item
|
||||
* found.
|
||||
*/
|
||||
Map<Long, Long> getMapOfCounts() {
|
||||
return Collections.unmodifiableMap(dataSourceObjIdCounts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Table model for the Ingest Job table to display ingest jobs for the
|
||||
* selected datasource.
|
||||
*/
|
||||
@Messages({"DataSourceSummaryPanel.IngestJobTableModel.StartTime.header=Start Time",
|
||||
"DataSourceSummaryPanel.IngestJobTableModel.EndTime.header=End Time",
|
||||
"DataSourceSummaryPanel.IngestJobTableModel.IngestStatus.header=Ingest Status"})
|
||||
private class IngestJobTableModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<String> columnHeaders = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new IngestJobTableModel
|
||||
*/
|
||||
IngestJobTableModel() {
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_StartTime_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_EndTime_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_IngestJobTableModel_IngestStatus_header());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return ingestJobs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return columnHeaders.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
IngestJobInfo currIngestJob = ingestJobs.get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return datetimeFormat.format(currIngestJob.getStartDateTime());
|
||||
case 1:
|
||||
Date endDate = currIngestJob.getEndDateTime();
|
||||
if (endDate.getTime() == 0) {
|
||||
return "N/A";
|
||||
}
|
||||
return datetimeFormat.format(currIngestJob.getEndDateTime());
|
||||
case 2:
|
||||
return currIngestJob.getStatus().getDisplayName();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
return columnHeaders.get(column);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Table model for the files table model to display counts of specific file
|
||||
* types found in the currently selected data source.
|
||||
*/
|
||||
@Messages({"DataSourceSummaryPanel.FilesTableModel.type.header=File Type",
|
||||
"DataSourceSummaryPanel.FilesTableModel.count.header=Count"})
|
||||
private class FilesTableModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final DataSource currentDataSource;
|
||||
private final List<String> columnHeaders = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a FilesTableModel for the speicified datasource.
|
||||
*
|
||||
* @param selectedDataSource the datasource which this filesTablemodel
|
||||
* will represent
|
||||
*/
|
||||
FilesTableModel(DataSource selectedDataSource) {
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_FilesTableModel_type_header());
|
||||
columnHeaders.add(Bundle.DataSourceSummaryPanel_FilesTableModel_count_header());
|
||||
currentDataSource = selectedDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
//should be kept equal to the number of types we are displaying in the tables
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return columnHeaders.size();
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"DataSourceSummaryPanel.FilesTableModel.images.row=Images",
|
||||
"DataSourceSummaryPanel.FilesTableModel.videos.row=Videos",
|
||||
"DataSourceSummaryPanel.FilesTableModel.audio.row=Audio",
|
||||
"DataSourceSummaryPanel.FilesTableModel.documents.row=Documents",
|
||||
"DataSourceSummaryPanel.FilesTableModel.executables.row=Executables"
|
||||
})
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (columnIndex == 0) {
|
||||
switch (rowIndex) {
|
||||
case 0:
|
||||
return Bundle.DataSourceSummaryPanel_FilesTableModel_images_row();
|
||||
case 1:
|
||||
return Bundle.DataSourceSummaryPanel_FilesTableModel_videos_row();
|
||||
case 2:
|
||||
return Bundle.DataSourceSummaryPanel_FilesTableModel_audio_row();
|
||||
case 3:
|
||||
return Bundle.DataSourceSummaryPanel_FilesTableModel_documents_row();
|
||||
case 4:
|
||||
return Bundle.DataSourceSummaryPanel_FilesTableModel_executables_row();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (columnIndex == 1) {
|
||||
switch (rowIndex) {
|
||||
case 0:
|
||||
return getCountOfFiles(FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes());
|
||||
case 1:
|
||||
return getCountOfFiles(FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes());
|
||||
case 2:
|
||||
return getCountOfFiles(FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes());
|
||||
case 3:
|
||||
return getCountOfFiles(FileTypeUtils.FileTypeCategory.DOCUMENTS.getMediaTypes());
|
||||
case 4:
|
||||
return getCountOfFiles(FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of files in the case database for the current data
|
||||
* source which have the specified mimetypes.
|
||||
*
|
||||
* @param setOfMimeTypes the set of mime types which we are finding the
|
||||
* number of occurences of
|
||||
*
|
||||
* @return a Long value which represents the number of occurrences of
|
||||
* the specified mime types in the current case for the
|
||||
* specified data source, null if no count was retrieved
|
||||
*/
|
||||
private Long getCountOfFiles(Set<String> setOfMimeTypes) {
|
||||
if (currentDataSource != null) {
|
||||
try {
|
||||
String inClause = String.join("', '", setOfMimeTypes);
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
return skCase.countFilesWhere("data_source_obj_id=" + currentDataSource.getId()
|
||||
+ " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
|
||||
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
|
||||
+ " AND mime_type IN ('" + inClause + "')"
|
||||
+ " AND name<>''");
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get count of files for specified mime types", ex);
|
||||
//unable to get count of files for the specified mimetypes cell will be displayed as empty
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
return columnHeaders.get(column);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.casemodule.datasourceSummary;
|
||||
|
||||
import java.awt.Frame;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.WindowManager;
|
||||
|
||||
/**
|
||||
* ViewSummaryInformationAction action for opening a Data Source Summary Panel
|
||||
* with the specified data source selected if it is present.
|
||||
*/
|
||||
public final class ViewSummaryInformationAction extends AbstractAction {
|
||||
|
||||
private static JDialog dataSourceSummaryDialog;
|
||||
private static Long selectDataSource;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create a ViewSummaryInformationAction for the selected datasource.
|
||||
*
|
||||
* @param selectedDataSource - the data source which is currently selected
|
||||
* and will be selected initially when the
|
||||
* DataSourceSummaryPanel opens.
|
||||
*/
|
||||
@Messages({"ViewSummaryInformationAction.name.text=View Summary Information"})
|
||||
public ViewSummaryInformationAction(Long selectedDataSource) {
|
||||
super(Bundle.ViewSummaryInformationAction_name_text());
|
||||
selectDataSource = selectedDataSource;
|
||||
}
|
||||
|
||||
@Messages({"ViewSummaryInformationAction.window.title=Data Source Summary"})
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
String title = Bundle.ViewSummaryInformationAction_window_title();
|
||||
Frame mainWindow = WindowManager.getDefault().getMainWindow();
|
||||
dataSourceSummaryDialog = new JDialog(mainWindow, title, true);
|
||||
DataSourceSummaryPanel dataSourceSummaryPanel = new DataSourceSummaryPanel();
|
||||
//allow the buttons in DataSourceSummaryPanel to close this dialog
|
||||
dataSourceSummaryPanel.addCloseButtonAction((ActionEvent event) -> {
|
||||
dataSourceSummaryDialog.dispose();
|
||||
});
|
||||
//select the specifed data source
|
||||
dataSourceSummaryPanel.selectDataSource(selectDataSource);
|
||||
dataSourceSummaryDialog.add(dataSourceSummaryPanel);
|
||||
dataSourceSummaryDialog.setResizable(true);
|
||||
dataSourceSummaryDialog.pack();
|
||||
dataSourceSummaryDialog.setLocationRelativeTo(mainWindow);
|
||||
dataSourceSummaryDialog.setVisible(true);
|
||||
dataSourceSummaryDialog.toFront();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.core;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import org.sleuthkit.autopsy.coreutils.TextConverter;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo;
|
||||
@ -38,7 +39,6 @@ import org.sleuthkit.datamodel.TskData.DbType;
|
||||
*/
|
||||
public final class UserPreferences {
|
||||
|
||||
private static final boolean IS_WINDOWS_OS = PlatformUtil.isWindowsOS();
|
||||
private static final Preferences preferences = NbPreferences.forModule(UserPreferences.class);
|
||||
public static final String KEEP_PREFERRED_VIEWER = "KeepPreferredViewer"; // NON-NLS
|
||||
public static final String HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE = "HideKnownFilesInDataSourcesTree"; //NON-NLS
|
||||
@ -75,6 +75,7 @@ public final class UserPreferences {
|
||||
public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags";
|
||||
public static final String HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES = "HideCentralRepoCommentsAndOccurrences";
|
||||
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
|
||||
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
|
||||
|
||||
// Prevent instantiation.
|
||||
private UserPreferences() {
|
||||
@ -471,4 +472,23 @@ public final class UserPreferences {
|
||||
public static void setLogFileCount(int count) {
|
||||
preferences.putInt(MAX_NUM_OF_LOG_FILE, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HdX path.
|
||||
*
|
||||
* @param executablePath User-inputted path to HxD executable
|
||||
*/
|
||||
public static void setExternalHexEditorPath(String executablePath) {
|
||||
preferences.put(EXTERNAL_HEX_EDITOR_PATH, executablePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the HdXEditor path set by the User. If not found, the default
|
||||
* will be the default install location of HxD.
|
||||
*
|
||||
* @return Path to HdX
|
||||
*/
|
||||
public static String getExternalHexEditorPath() {
|
||||
return preferences.get(EXTERNAL_HEX_EDITOR_PATH, Paths.get("C:", "Program Files", "HxD", "HxD.exe").toString());
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
<attr name="noIconInMenu" boolvalue="false"/>
|
||||
</file>
|
||||
<file name="org-sleuthkit-autopsy-casemodule-CasePropertiesAction.instance"/>
|
||||
<file name="org-sleuthkit-autopsy-casemodule-datasourceSummary-DataSourceSummaryAction.instance"/>
|
||||
<file name="org-sleuthkit-autopsy-casemodule-CaseDeleteAction.instance"/>
|
||||
<file name="org-sleuthkit-autopsy-casemodule-CaseSaveAction.instance">
|
||||
<attr name="delegate" newvalue="org.sleuthkit.autopsy.casemodule.CaseSaveAction"/>
|
||||
@ -182,6 +183,10 @@
|
||||
<attr name="originalFile" stringvalue="Actions/Case/org-sleuthkit-autopsy-casemodule-CasePropertiesAction.instance"/>
|
||||
<attr name="position" intvalue="301"/>
|
||||
</file>
|
||||
<file name="org-sleuthkit-autopsy-casemodule-datasourceSummary-DataSourceSummaryAction.shadow">
|
||||
<attr name="originalFile" stringvalue="Actions/Case/org-sleuthkit-autopsy-casemodule-datasourceSummary-DataSourceSummaryAction.instance"/>
|
||||
<attr name="position" intvalue="302"/>
|
||||
</file>
|
||||
<file name="org-netbeans-modules-print-action-PageSetupAction.shadow_hidden"/>
|
||||
<file name="org-netbeans-modules-print-action-PrintAction.shadow_hidden"/>
|
||||
<file name="org-openide-actions-SaveAction.shadow_hidden"/>
|
||||
|
@ -193,3 +193,34 @@ ViewPreferencesPanel.keepCurrentViewerRadioButton.text=Stay on the same file vie
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.toolTipText=For example, change from Hex to Media when a JPEG is selected.
|
||||
ViewPreferencesPanel.useBestViewerRadioButton.text=Change to the most specific file viewer
|
||||
ViewPreferencesPanel.fileNameTranslationColumnCheckbox.text=Add column in result viewer for file name translation
|
||||
DataContentViewerHex.launchHxDButton.text=Launch in HxD
|
||||
ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text=Browse
|
||||
ExternalViewerGlobalSettingsPanel.jLabel2.text=Set launchable content viewer extensions
|
||||
ExternalViewerGlobalSettingsPanel.jLabel1.text=HxD Editor Path:
|
||||
ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
|
||||
ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
|
||||
ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Set aplication viewer to use for files with specific mime types/extensions:
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1=Application
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0=Mime type/Extension
|
||||
AddExternalViewerRulePanel.nameLabel.text=MIME type or extension
|
||||
AddExternalViewerRulePanel.browseButton.text=Browse
|
||||
AddExternalViewerRulePanel.exePathTextField.text=
|
||||
AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension
|
||||
AddExternalViewerRulePanel.extRadioButton.text=Extension
|
||||
AddExternalViewerRulePanel.mimeRadioButton.text=MIME type
|
||||
AddExternalViewerRulePanel.nameTextField.text=
|
||||
ExternalViewerGlobalSettingsPanel.jButton2.text_1=jButton2
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton1.text_1=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton.text_1=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text_1=Browse
|
||||
ExternalViewerGlobalSettingsPanel.jLabel2.text_1=Set launchable content viewer extensions
|
||||
ExternalViewerGlobalSettingsPanel.jLabel1.text_1=HxD Editor Path:
|
||||
ExternalViewerGlobalSettingsPanel.HxDPath.text=C:/Program Files/HxD/HxD.exe
|
||||
ExternalViewerGlobalSettingsPanel.editRuleButton.text_1=Edit Rule
|
||||
ExternalViewerGlobalSettingsPanel.deleteRuleButton.text_1=Delete Rule
|
||||
ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text_1=Set aplication viewer to use for files with specific mime types/extensions:
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title1_1=Application
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extension
|
||||
|
@ -45,7 +45,7 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane2" pref="100" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane2" pref="827" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane3" alignment="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -54,7 +54,7 @@
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jScrollPane2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane3" pref="27" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane3" pref="239" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -120,7 +120,9 @@
|
||||
<Component id="goToOffsetLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="goToOffsetTextField" min="-2" pref="79" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="32" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="launchHxDButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="146" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -140,7 +142,10 @@
|
||||
<Component id="goToPageLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="goToPageTextField" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="goToOffsetLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="goToOffsetTextField" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
|
||||
<Component id="goToOffsetTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="launchHxDButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
@ -289,6 +294,19 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="goToOffsetTextFieldActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="launchHxDButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerHex.launchHxDButton.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="launchHxDButtonActionPerformed"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_AddingCodePost" type="java.lang.String" value="launchHxDButton.setEnabled(PlatformUtil.isWindowsOS());"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -21,7 +21,11 @@ package org.sleuthkit.autopsy.corecomponents;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -31,7 +35,13 @@ import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Utilities;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||
import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.DataConversion;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
@ -105,6 +115,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
goToPageLabel = new javax.swing.JLabel();
|
||||
goToOffsetLabel = new javax.swing.JLabel();
|
||||
goToOffsetTextField = new javax.swing.JTextField();
|
||||
launchHxDButton = new javax.swing.JButton();
|
||||
|
||||
copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.copyMenuItem.text")); // NOI18N
|
||||
rightClickMenu.add(copyMenuItem);
|
||||
@ -187,6 +198,13 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
}
|
||||
});
|
||||
|
||||
launchHxDButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.launchHxDButton.text")); // NOI18N
|
||||
launchHxDButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
launchHxDButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout hexViewerPanelLayout = new javax.swing.GroupLayout(hexViewerPanel);
|
||||
hexViewerPanel.setLayout(hexViewerPanelLayout);
|
||||
hexViewerPanelLayout.setHorizontalGroup(
|
||||
@ -214,7 +232,9 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
.addComponent(goToOffsetLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(32, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(launchHxDButton)
|
||||
.addContainerGap(146, Short.MAX_VALUE))
|
||||
);
|
||||
hexViewerPanelLayout.setVerticalGroup(
|
||||
hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -231,17 +251,21 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
.addComponent(goToPageLabel)
|
||||
.addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(goToOffsetLabel)
|
||||
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(launchHxDButton)))
|
||||
.addGap(0, 0, 0))
|
||||
);
|
||||
|
||||
launchHxDButton.setEnabled(PlatformUtil.isWindowsOS());
|
||||
|
||||
jScrollPane2.setViewportView(hexViewerPanel);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 827, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
@ -249,7 +273,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 239, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@ -334,6 +358,37 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
}
|
||||
}//GEN-LAST:event_goToOffsetTextFieldActionPerformed
|
||||
|
||||
@NbBundle.Messages({"DataContentViewerHex.launchError=Unable to launch HxD Editor. "
|
||||
+ "Please set-up the HdX install location in Tools -> Options -> External Viewer"})
|
||||
private void launchHxDButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_launchHxDButtonActionPerformed
|
||||
try {
|
||||
File HdXExecutable = new File(UserPreferences.getExternalHexEditorPath());
|
||||
if(!HdXExecutable.exists() || !HdXExecutable.canExecute()) {
|
||||
JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
|
||||
return;
|
||||
}
|
||||
|
||||
String tempDirectory = Case.getCurrentCaseThrows().getTempDirectory();
|
||||
File dataSourceInTempDirectory = Paths.get(tempDirectory,
|
||||
FileUtil.escapeFileName(dataSource.getId() + dataSource.getName())).toFile();
|
||||
ContentUtils.writeToFile(dataSource, dataSourceInTempDirectory);
|
||||
|
||||
try {
|
||||
ProcessBuilder launchHdXExecutable = new ProcessBuilder();
|
||||
launchHdXExecutable.command(String.format("\"%s\" \"%s\"",
|
||||
HdXExecutable.getAbsolutePath(),
|
||||
dataSourceInTempDirectory.getAbsolutePath()));
|
||||
launchHdXExecutable.start();
|
||||
} catch (IOException ex) {
|
||||
JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
|
||||
dataSourceInTempDirectory.delete();
|
||||
}
|
||||
} catch (NoCurrentCaseException | IOException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to copy file into temp directory", ex);
|
||||
JOptionPane.showMessageDialog(null, DataContentViewerHex_launchError());
|
||||
}
|
||||
}//GEN-LAST:event_launchHxDButtonActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JMenuItem copyMenuItem;
|
||||
private javax.swing.JLabel currentPageLabel;
|
||||
@ -344,6 +399,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
private javax.swing.JPanel hexViewerPanel;
|
||||
private javax.swing.JScrollPane jScrollPane2;
|
||||
private javax.swing.JScrollPane jScrollPane3;
|
||||
private javax.swing.JButton launchHxDButton;
|
||||
private javax.swing.JButton nextPageButton;
|
||||
private javax.swing.JLabel ofLabel;
|
||||
private javax.swing.JTextArea outputTextArea;
|
||||
@ -504,6 +560,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
goToPageLabel.setVisible(isVisible);
|
||||
goToOffsetTextField.setVisible(isVisible);
|
||||
goToOffsetLabel.setVisible(isVisible);
|
||||
launchHxDButton.setVisible(isVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -528,17 +585,4 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
public Component getComponent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the right click menu only if evt is the correct mouse event
|
||||
*/
|
||||
private void maybeShowPopup(java.awt.event.MouseEvent evt) {
|
||||
if (evt.isPopupTrigger()) {
|
||||
rightClickMenu.setLocation(evt.getLocationOnScreen());
|
||||
rightClickMenu.setVisible(true);
|
||||
copyMenuItem.setEnabled(outputTextArea.getSelectedText() != null);
|
||||
} else {
|
||||
rightClickMenu.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.datasourceSummary.ViewSummaryInformationAction;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
|
||||
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
|
||||
@ -108,6 +109,7 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
|
||||
actionsList.add(new FileSearchAction(
|
||||
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
|
||||
actionsList.add(new ViewSummaryInformationAction(content.getId()));
|
||||
actionsList.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
|
||||
actionsList.add(new NewWindowViewAction(
|
||||
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
|
||||
|
@ -23,6 +23,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.swing.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.datasourceSummary.ViewSummaryInformationAction;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
|
||||
@ -63,6 +64,7 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNode<Spec
|
||||
actions.add(null); // creates a menu separator
|
||||
actions.add(new FileSearchAction(Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
|
||||
if (content.isDataSource()) {
|
||||
actions.add(new ViewSummaryInformationAction(content.getId()));
|
||||
actions.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
|
||||
} else {
|
||||
actions.add(new RunIngestModulesAction(content));
|
||||
|
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import javax.imageio.ImageIO;
|
||||
import static org.apache.commons.collections4.ListUtils.removeAll;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with file/mime-types
|
||||
*/
|
||||
public final class FileTypeUtils {
|
||||
|
||||
private static final ImmutableSet<String> IMAGE_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.addAll(removeAll(asList(ImageIO.getReaderMIMETypes()),
|
||||
asList("application/octet-stream"))) //this claims to be supported, but is not really an image.
|
||||
.add("image/bmp", //NON-NLS
|
||||
"image/gif", //NON-NLS
|
||||
"image/jpeg", //NON-NLS
|
||||
"image/png", //NON-NLS
|
||||
"image/tiff", //NON-NLS
|
||||
"image/vnd.adobe.photoshop", //NON-NLS
|
||||
"image/x-raw-nikon", //NON-NLS
|
||||
"image/x-ms-bmp", //NON-NLS
|
||||
"image/x-icon", //NON-NLS
|
||||
"image/webp", //NON-NLS
|
||||
"image/vnd.microsoft.icon", //NON-NLS
|
||||
"image/x-rgb", //NON-NLS
|
||||
"image/x-ms-bmp", //NON-NLS
|
||||
"image/x-xbitmap", //NON-NLS
|
||||
"image/x-portable-graymap", //NON-NLS
|
||||
"image/x-portable-bitmap" //NON-NLS
|
||||
).build();
|
||||
private static final ImmutableSet<String> AUDIO_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.add("audio/midi", //NON-NLS
|
||||
"audio/mpeg", //NON-NLS
|
||||
"audio/webm", //NON-NLS
|
||||
"audio/ogg", //NON-NLS
|
||||
"audio/wav", //NON-NLS
|
||||
"audio/vnd.wave", //NON-NLS
|
||||
"audio/x-ms-wma"//NON-NLS
|
||||
).build();
|
||||
private static final ImmutableSet<String> VIDEO_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.add("video/webm", //NON-NLS
|
||||
"video/3gpp", //NON-NLS
|
||||
"video/3gpp2", //NON-NLS
|
||||
"video/ogg", //NON-NLS
|
||||
"video/mpeg", //NON-NLS
|
||||
"video/mp4", //NON-NLS
|
||||
"video/quicktime", //NON-NLS
|
||||
"video/x-msvideo", //NON-NLS
|
||||
"video/x-flv", //NON-NLS
|
||||
"video/x-m4v", //NON-NLS
|
||||
"video/x-ms-wmv"//NON-NLS
|
||||
).build();
|
||||
private static final ImmutableSet<String> DOCUMENT_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.add("text/plain", //NON-NLS
|
||||
"text/css", //NON-NLS
|
||||
"text/html", //NON-NLS
|
||||
"text/csv", //NON-NLS
|
||||
"text/xml", //NON-NLS
|
||||
"text/x-log", //NON-NLS
|
||||
"application/rtf", //NON-NLS
|
||||
"application/pdf", //NON-NLS
|
||||
"application/json", //NON-NLS
|
||||
"application/javascript", //NON-NLS
|
||||
"application/xml", //NON-NLS
|
||||
"application/x-msoffice", //NON-NLS
|
||||
"application/x-ooxml", //NON-NLS
|
||||
"application/msword", //NON-NLS
|
||||
"application/msword2", //NON-NLS
|
||||
"application/vnd.wordperfect", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
|
||||
"application/vnd.ms-powerpoint", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
|
||||
"application/vnd.ms-excel", //NON-NLS
|
||||
"application/vnd.ms-excel.sheet.4", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
|
||||
"application/vnd.oasis.opendocument.presentation", //NON-NLS
|
||||
"application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
|
||||
"application/vnd.oasis.opendocument.text" //NON-NLS
|
||||
).build();
|
||||
private static final ImmutableSet<String> EXECUTABLE_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.add("application/x-bat",//NON-NLS
|
||||
"application/x-dosexec",//NON-NLS
|
||||
"application/vnd.microsoft.portable-executable",//NON-NLS
|
||||
"application/x-msdownload",//NON-NLS
|
||||
"application/exe",//NON-NLS
|
||||
"application/x-exe",//NON-NLS
|
||||
"application/dos-exe",//NON-NLS
|
||||
"vms/exe",//NON-NLS
|
||||
"application/x-winexe",//NON-NLS
|
||||
"application/msdos-windows",//NON-NLS
|
||||
"application/x-msdos-program"//NON-NLS
|
||||
).build();
|
||||
private static final ImmutableSet<String> MULTI_MEDIA_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.addAll(IMAGE_MIME_TYPES)
|
||||
.addAll(AUDIO_MIME_TYPES)
|
||||
.addAll(VIDEO_MIME_TYPES)
|
||||
.build();
|
||||
private static final ImmutableSet<String> VISUAL_MEDIA_MIME_TYPES
|
||||
= new ImmutableSet.Builder<String>()
|
||||
.addAll(IMAGE_MIME_TYPES)
|
||||
.addAll(VIDEO_MIME_TYPES)
|
||||
.add("application/vnd.ms-asf", //NON-NLS
|
||||
"application/vnd.rn-realmedia", //NON-NLS
|
||||
"application/x-shockwave-flash" //NON-NLS
|
||||
).build();
|
||||
|
||||
private FileTypeUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of categories/groups of file types.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"FileTypeCategory.Audio.displayName=Audio",
|
||||
"FileTypeCategory.Video.displayName=Video",
|
||||
"FileTypeCategory.Image.displayName=Image",
|
||||
"FileTypeCategory.Media.displayName=Media",
|
||||
"FileTypeCategory.Visual.displayName=Visual",
|
||||
"FileTypeCategory.Documents.displayName=Documents",
|
||||
"FileTypeCategory.Executables.displayName=Executables"})
|
||||
public enum FileTypeCategory {
|
||||
|
||||
IMAGE(Bundle.FileTypeCategory_Image_displayName(),
|
||||
IMAGE_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
VIDEO(Bundle.FileTypeCategory_Video_displayName(),
|
||||
VIDEO_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
AUDIO(Bundle.FileTypeCategory_Audio_displayName(),
|
||||
AUDIO_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
/**
|
||||
* Images, Videos, flash Animations, etc
|
||||
*/
|
||||
VISUAL(Bundle.FileTypeCategory_Media_displayName(),
|
||||
VISUAL_MEDIA_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
/**
|
||||
* VISUAL plus AUDIO.
|
||||
*/
|
||||
MEDIA(Bundle.FileTypeCategory_Media_displayName(),
|
||||
MULTI_MEDIA_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
EXECUTABLE(Bundle.FileTypeCategory_Executables_displayName(),
|
||||
EXECUTABLE_MIME_TYPES,
|
||||
Collections.emptyList()),
|
||||
/**
|
||||
* (Plain) Text and "Office" documents.
|
||||
*/
|
||||
DOCUMENTS(Bundle.FileTypeCategory_Documents_displayName(),
|
||||
DOCUMENT_MIME_TYPES,
|
||||
Collections.emptyList());
|
||||
|
||||
private final String displayName;
|
||||
private final ImmutableSet<String> mediaTypes;
|
||||
private final ImmutableSet<String> extensions;
|
||||
|
||||
private FileTypeCategory(String displayName, Collection<String> mediaTypes, Collection<String> extensions) {
|
||||
this.displayName = displayName;
|
||||
this.mediaTypes = ImmutableSet.copyOf(mediaTypes);
|
||||
this.extensions = ImmutableSet.copyOf(extensions);
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getMediaTypes() {
|
||||
return mediaTypes;
|
||||
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getExtensions() {
|
||||
throw new UnsupportedOperationException("This method is not implemented yet."); //just to be explicit.
|
||||
}
|
||||
}
|
||||
}
|
@ -90,12 +90,6 @@ ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space\: {0}
|
||||
ExtractAction.done.notifyMsg.extractErr=Error extracting files\: {0}
|
||||
OptionsCategory_Name_ExternalViewer=External Viewer
|
||||
OptionsCategory_Keywords_ExternalViewer=ExternalViewer
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
|
||||
ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
|
||||
ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Add your custom rules for external viewers
|
||||
ExternalViewerGlobalSettingsPanel.ruleListLabel.text=MIME type and extensions
|
||||
ExternalViewerGlobalSettingsPanel.exePathLabel.text=Program associated with this MIME type or extension
|
||||
ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text=Program associated with this MIME type
|
||||
ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text=Program associated with this extension
|
||||
ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text=No MIME type or extension selected
|
||||
@ -108,7 +102,6 @@ ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.message=The extension i
|
||||
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.title=Invalid extension
|
||||
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.message=The path to the program executable is invalid
|
||||
ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.title=Invalid Path
|
||||
ExternalViewerGlobalSettingsPanel.exePathNameLabel.text=No MIME type or extension currently selected
|
||||
ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message=A rule already exists with this MIME type or extension. Please edit that one.
|
||||
ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title=Rule not added
|
||||
AddExternalViewerRulePanel.mimeRadioButton.text=MIME type
|
||||
@ -124,3 +117,16 @@ GroupDataSourcesDialog.yesButton.text=Yes
|
||||
GroupDataSourcesDialog.noButton.text=No
|
||||
GroupDataSourcesDialog.title=Group by Data Source?
|
||||
DirectoryTreeTopComponent.openViewPreferencesButton.text=
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title3=Title 4
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title2=Title 3
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title3_1=Title 4
|
||||
ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title2_1=Title 3
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton1.text=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
|
||||
ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
|
||||
ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Add your custom rules for external viewers:
|
||||
ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
|
||||
ExternalViewerGlobalSettingsPanel.jButton2.text=jButton2
|
||||
ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text=Browse
|
||||
ExternalViewerGlobalSettingsPanel.HxDLabel.text=HxD Editor Path:
|
||||
ExternalViewerGlobalSettingsPanel.ContentViewerExtensionLabel.text=Add content viewer extensions:
|
||||
|
@ -1,6 +1,31 @@
|
||||
<?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.JButton" name="newRuleButton1">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/images/add16.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.newRuleButton1.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="[111, 25]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[111, 25]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="jButton2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.jButton2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</NonVisualComponents>
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[701, 453]"/>
|
||||
@ -21,12 +46,12 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="jPanel1" alignment="0" pref="1191" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
@ -41,135 +66,15 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="externalViewerTitleLabel" pref="681" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</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="externalViewerTitleLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="428" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="32" max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="externalViewerTitleLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JSplitPane" name="jSplitPane1">
|
||||
<Properties>
|
||||
<Property name="dividerSize" type="int" value="1"/>
|
||||
</Properties>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="exePanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[311, 224]"/>
|
||||
</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>
|
||||
<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="exePathLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="exePathNameLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jPanel2" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="exePathLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="exePathNameLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="355" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jPanel3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="exePathLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.exePathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="exePathNameLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.exePathNameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="rulesPanel">
|
||||
<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>
|
||||
<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="ruleListLabel" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
<Component id="newRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<Component id="editRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<Component id="deleteRuleButton" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane2" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="547" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -177,13 +82,51 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="ruleListLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jPanel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jPanel3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="83" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="jPanel2">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane2" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane4" min="-2" pref="634" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="externalViewerTitleLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="newRuleButton" min="-2" pref="97" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="editRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deleteRuleButton" min="-2" pref="109" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="externalViewerTitleLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane4" min="-2" pref="219" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="newRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="editRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="newRuleButton" alignment="3" max="32767" attributes="0"/>
|
||||
<Component id="editRuleButton" alignment="3" max="32767" attributes="0"/>
|
||||
<Component id="deleteRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
@ -192,13 +135,6 @@
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="ruleListLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.ruleListLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="newRuleButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
@ -218,6 +154,75 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newRuleButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane4">
|
||||
<Properties>
|
||||
<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>
|
||||
<Color PropertyName="color" blue="cc" green="cc" red="cc" type="rgb"/>
|
||||
</LineBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="ExternalViewerRulesTable">
|
||||
<Properties>
|
||||
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
|
||||
<TableColumnModel selectionModel="0">
|
||||
<Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true">
|
||||
<Title editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Title>
|
||||
<Editor/>
|
||||
<Renderer/>
|
||||
</Column>
|
||||
<Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true">
|
||||
<Title editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Title>
|
||||
<Editor/>
|
||||
<Renderer/>
|
||||
</Column>
|
||||
</TableColumnModel>
|
||||
</Property>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new javax.swing.JTable()"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="externalViewerTitleLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="deleteRuleButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/images/delete16.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.deleteRuleButton.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="[111, 25]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[111, 25]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteRuleButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="editRuleButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
@ -237,37 +242,73 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="editRuleButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="deleteRuleButton">
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="jPanel3">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="ContentViewerExtensionLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
|
||||
<Component id="HxDLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="HxDPath" pref="441" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="browseHxDDirectory" min="-2" pref="82" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="ContentViewerExtensionLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="HxDPath" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="HxDLabel" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
|
||||
<Component id="browseHxDDirectory" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextField" name="HxDPath">
|
||||
<Properties>
|
||||
<Property name="editable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="HxDLabel">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/images/delete16.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.deleteRuleButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.HxDLabel.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="[111, 25]"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="ContentViewerExtensionLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.ContentViewerExtensionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[111, 25]"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="browseHxDDirectory">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.browseHxDDirectory.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="deleteRuleButtonActionPerformed"/>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="browseHxDDirectoryActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane2">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JList" name="rulesList">
|
||||
<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="<ExternalViewerRule>"/>
|
||||
<AuxValue name="JavaCodeGenerator_AddingCodePost" type="java.lang.String" value="browseHxDDirectory.setEnabled(PlatformUtil.isWindowsOS());"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
@ -275,10 +316,4 @@
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -18,16 +18,21 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.directorytree;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import org.netbeans.spi.options.OptionsPanelController;
|
||||
import org.openide.util.NbBundle;
|
||||
import java.util.List;
|
||||
import javax.swing.JFileChooser;
|
||||
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.netbeans.spi.options.OptionsPanelController;
|
||||
import org.sleuthkit.autopsy.casemodule.GeneralFilter;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
|
||||
/**
|
||||
* An options panel for the user to create, edit, and delete associations for
|
||||
@ -37,39 +42,67 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel implements OptionsPanel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ExternalViewerGlobalSettingsPanel.class.getName());
|
||||
private DefaultListModel<ExternalViewerRule> rulesListModel;
|
||||
private java.util.List<ExternalViewerRule> rules;
|
||||
private ExternalViewerGlobalSettingsTableModel tableModel;
|
||||
|
||||
public ExternalViewerGlobalSettingsPanel() {
|
||||
this(new ExternalViewerGlobalSettingsTableModel(new String[] {
|
||||
"Mime type/Extension", "Application"}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new form ExternalViewerGlobalSettingsPanel
|
||||
*/
|
||||
public ExternalViewerGlobalSettingsPanel() {
|
||||
public ExternalViewerGlobalSettingsPanel(ExternalViewerGlobalSettingsTableModel tableModel) {
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
this.tableModel = tableModel;
|
||||
customizeComponents(tableModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes field variables. Adds a listener to the list of rules.
|
||||
*/
|
||||
private void customizeComponents() {
|
||||
rulesListModel = new DefaultListModel<>();
|
||||
rules = new ArrayList<>();
|
||||
rulesList.setModel(rulesListModel);
|
||||
rulesList.addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting() == false) {
|
||||
if (rulesList.getSelectedIndex() == -1) {
|
||||
clearExePath();
|
||||
} else {
|
||||
populateExePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
private void customizeComponents(ExternalViewerGlobalSettingsTableModel tableModel) {
|
||||
ExternalViewerRulesTable.setModel(tableModel);
|
||||
ExternalViewerRulesTable.setAutoCreateRowSorter(true);
|
||||
ExternalViewerRulesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
ListSelectionModel selectionModel = ExternalViewerRulesTable.getSelectionModel();
|
||||
|
||||
selectionModel.addListSelectionListener((ListSelectionEvent selection) -> {
|
||||
enableButtons();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate the delete rule button click action.
|
||||
*
|
||||
* @param selectedIndex Index to delete in JTable
|
||||
*/
|
||||
public void deleteRuleButtonClick(int selectedIndex) {
|
||||
setSelectionInterval(selectedIndex, selectedIndex);
|
||||
deleteRuleButton.getListeners(ActionListener.class)[0].actionPerformed(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate selecting an entry in the JTable.
|
||||
*
|
||||
* In single selection mode, only the endIndex is used.
|
||||
*
|
||||
* @param rowIndex Index to select
|
||||
*/
|
||||
public void setSelectionInterval(int startingIndex, int endIndex) {
|
||||
ExternalViewerRulesTable.getSelectionModel().setSelectionInterval(startingIndex, endIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the index is selected.
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public boolean isSelected(int index) {
|
||||
return ExternalViewerRulesTable.getSelectionModel().isSelectedIndex(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
@ -79,60 +112,33 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
newRuleButton1 = new javax.swing.JButton();
|
||||
jButton2 = new javax.swing.JButton();
|
||||
jPanel1 = new javax.swing.JPanel();
|
||||
externalViewerTitleLabel = new javax.swing.JLabel();
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
jSplitPane1 = new javax.swing.JSplitPane();
|
||||
exePanel = new javax.swing.JPanel();
|
||||
exePathLabel = new javax.swing.JLabel();
|
||||
exePathNameLabel = new javax.swing.JLabel();
|
||||
rulesPanel = new javax.swing.JPanel();
|
||||
ruleListLabel = new javax.swing.JLabel();
|
||||
jPanel2 = new javax.swing.JPanel();
|
||||
newRuleButton = new javax.swing.JButton();
|
||||
editRuleButton = new javax.swing.JButton();
|
||||
jScrollPane4 = new javax.swing.JScrollPane();
|
||||
ExternalViewerRulesTable = new javax.swing.JTable();
|
||||
externalViewerTitleLabel = new javax.swing.JLabel();
|
||||
deleteRuleButton = new javax.swing.JButton();
|
||||
jScrollPane2 = new javax.swing.JScrollPane();
|
||||
rulesList = new javax.swing.JList<>();
|
||||
editRuleButton = new javax.swing.JButton();
|
||||
jPanel3 = new javax.swing.JPanel();
|
||||
HxDPath = new javax.swing.JTextField();
|
||||
HxDLabel = new javax.swing.JLabel();
|
||||
ContentViewerExtensionLabel = new javax.swing.JLabel();
|
||||
browseHxDDirectory = new javax.swing.JButton();
|
||||
|
||||
newRuleButton1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(newRuleButton1, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton1.text")); // NOI18N
|
||||
newRuleButton1.setMaximumSize(new java.awt.Dimension(111, 25));
|
||||
newRuleButton1.setMinimumSize(new java.awt.Dimension(111, 25));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.jButton2.text")); // NOI18N
|
||||
|
||||
setPreferredSize(new java.awt.Dimension(701, 453));
|
||||
|
||||
jPanel1.setPreferredSize(new java.awt.Dimension(701, 453));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N
|
||||
|
||||
jSplitPane1.setDividerSize(1);
|
||||
|
||||
exePanel.setPreferredSize(new java.awt.Dimension(311, 224));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(exePathLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(exePathNameLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathNameLabel.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout exePanelLayout = new javax.swing.GroupLayout(exePanel);
|
||||
exePanel.setLayout(exePanelLayout);
|
||||
exePanelLayout.setHorizontalGroup(
|
||||
exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(exePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(exePathLabel)
|
||||
.addComponent(exePathNameLabel))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
exePanelLayout.setVerticalGroup(
|
||||
exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(exePanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(exePathLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(exePathNameLabel)
|
||||
.addContainerGap(355, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
jSplitPane1.setRightComponent(exePanel);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(ruleListLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ruleListLabel.text")); // NOI18N
|
||||
|
||||
newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton.text")); // NOI18N
|
||||
newRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
|
||||
@ -143,6 +149,26 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
|
||||
}
|
||||
});
|
||||
|
||||
jScrollPane4.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(204, 204, 204)));
|
||||
|
||||
jScrollPane4.setViewportView(ExternalViewerRulesTable);
|
||||
if (ExternalViewerRulesTable.getColumnModel().getColumnCount() > 0) {
|
||||
ExternalViewerRulesTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title0")); // NOI18N
|
||||
ExternalViewerRulesTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ExternalViewerRulesTable.columnModel.title1")); // NOI18N
|
||||
}
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N
|
||||
|
||||
deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N
|
||||
deleteRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
|
||||
deleteRuleButton.setMinimumSize(new java.awt.Dimension(111, 25));
|
||||
deleteRuleButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
deleteRuleButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.editRuleButton.text")); // NOI18N
|
||||
editRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
|
||||
@ -153,91 +179,112 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
|
||||
}
|
||||
});
|
||||
|
||||
deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N
|
||||
deleteRuleButton.setMaximumSize(new java.awt.Dimension(111, 25));
|
||||
deleteRuleButton.setMinimumSize(new java.awt.Dimension(111, 25));
|
||||
deleteRuleButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
deleteRuleButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jScrollPane2.setViewportView(rulesList);
|
||||
|
||||
javax.swing.GroupLayout rulesPanelLayout = new javax.swing.GroupLayout(rulesPanel);
|
||||
rulesPanel.setLayout(rulesPanelLayout);
|
||||
rulesPanelLayout.setHorizontalGroup(
|
||||
rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(rulesPanelLayout.createSequentialGroup()
|
||||
javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
|
||||
jPanel2.setLayout(jPanel2Layout);
|
||||
jPanel2Layout.setHorizontalGroup(
|
||||
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel2Layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(ruleListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, rulesPanelLayout.createSequentialGroup()
|
||||
.addGap(6, 6, 6)
|
||||
.addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(16, 16, 16)
|
||||
.addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(16, 16, 16)
|
||||
.addComponent(deleteRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane2))
|
||||
.addContainerGap())
|
||||
);
|
||||
rulesPanelLayout.setVerticalGroup(
|
||||
rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(rulesPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(ruleListLabel)
|
||||
.addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 634, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(jPanel2Layout.createSequentialGroup()
|
||||
.addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(externalViewerTitleLabel)
|
||||
.addGroup(jPanel2Layout.createSequentialGroup()
|
||||
.addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, 97, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane2)
|
||||
.addGap(12, 12, 12)
|
||||
.addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(newRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(editRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, 109, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
);
|
||||
jPanel2Layout.setVerticalGroup(
|
||||
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel2Layout.createSequentialGroup()
|
||||
.addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(12, 12, 12)
|
||||
.addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 219, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(newRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(editRuleButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(deleteRuleButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
jSplitPane1.setLeftComponent(rulesPanel);
|
||||
HxDPath.setEditable(false);
|
||||
|
||||
jScrollPane1.setViewportView(jSplitPane1);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(HxDLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.HxDLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(ContentViewerExtensionLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ContentViewerExtensionLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(browseHxDDirectory, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.browseHxDDirectory.text")); // NOI18N
|
||||
browseHxDDirectory.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
browseHxDDirectoryActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
|
||||
jPanel3.setLayout(jPanel3Layout);
|
||||
jPanel3Layout.setHorizontalGroup(
|
||||
jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel3Layout.createSequentialGroup()
|
||||
.addComponent(ContentViewerExtensionLabel)
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(jPanel3Layout.createSequentialGroup()
|
||||
.addGap(21, 21, 21)
|
||||
.addComponent(HxDLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(HxDPath, javax.swing.GroupLayout.DEFAULT_SIZE, 441, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(browseHxDDirectory, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
);
|
||||
jPanel3Layout.setVerticalGroup(
|
||||
jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel3Layout.createSequentialGroup()
|
||||
.addComponent(ContentViewerExtensionLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(HxDPath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(HxDLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(browseHxDDirectory))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
browseHxDDirectory.setEnabled(PlatformUtil.isWindowsOS());
|
||||
|
||||
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
|
||||
jPanel1.setLayout(jPanel1Layout);
|
||||
jPanel1Layout.setHorizontalGroup(
|
||||
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 681, Short.MAX_VALUE)
|
||||
.addContainerGap())
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jScrollPane1)
|
||||
.addContainerGap()))
|
||||
.addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addGap(547, 547, 547))
|
||||
);
|
||||
jPanel1Layout.setVerticalGroup(
|
||||
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(externalViewerTitleLabel)
|
||||
.addContainerGap(428, Short.MAX_VALUE))
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGap(32, 32, 32)
|
||||
.addComponent(jScrollPane1)
|
||||
.addContainerGap()))
|
||||
.addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(83, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 1191, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@ -247,16 +294,13 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
|
||||
if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
|
||||
ExternalViewerRule newRule = dialog.getRule();
|
||||
// Only allow one association for each MIME type or extension.
|
||||
if (rules.contains(newRule)) {
|
||||
if (tableModel.containsRule(newRule)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
|
||||
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
} else {
|
||||
rules.add(newRule);
|
||||
updateRulesListModel();
|
||||
int index = rules.indexOf(newRule);
|
||||
rulesList.setSelectedIndex(index);
|
||||
tableModel.addRule(newRule);
|
||||
enableButtons();
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
@ -264,113 +308,107 @@ final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel impleme
|
||||
}//GEN-LAST:event_newRuleButtonActionPerformed
|
||||
|
||||
private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed
|
||||
int selected = rulesList.getSelectedIndex();
|
||||
AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog(rulesListModel.get(rulesList.getSelectedIndex()));
|
||||
int selectedIndex = ExternalViewerRulesTable.convertRowIndexToModel(ExternalViewerRulesTable.getSelectedRow());
|
||||
ExternalViewerRule selectedRule = tableModel.getRuleAt(selectedIndex);
|
||||
AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog(selectedRule);
|
||||
AddExternalViewerRuleDialog.BUTTON_PRESSED result = dialog.getResult();
|
||||
if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
|
||||
rules.remove(selected);
|
||||
ExternalViewerRule newRule = dialog.getRule();
|
||||
// Only allow one association for each MIME type or extension.
|
||||
if (rules.contains(newRule)) {
|
||||
if (tableModel.containsRule(newRule)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
|
||||
NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
} else {
|
||||
rules.add(selected, dialog.getRule());
|
||||
updateRulesListModel();
|
||||
tableModel.setRule(selectedIndex, dialog.getRule());
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}
|
||||
rulesList.setSelectedIndex(selected);
|
||||
enableButtons();
|
||||
}//GEN-LAST:event_editRuleButtonActionPerformed
|
||||
|
||||
private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed
|
||||
ExternalViewerRule rule = rulesList.getSelectedValue();
|
||||
rules.remove(rule);
|
||||
updateRulesListModel();
|
||||
tableModel.removeRule(ExternalViewerRulesTable.convertRowIndexToModel(ExternalViewerRulesTable.getSelectedRow()));
|
||||
ExternalViewerRulesTable.getSelectionModel().clearSelection();
|
||||
enableButtons();
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}//GEN-LAST:event_deleteRuleButtonActionPerformed
|
||||
|
||||
private void browseHxDDirectoryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseHxDDirectoryActionPerformed
|
||||
JFileChooser fileWindow = new JFileChooser();
|
||||
fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
GeneralFilter exeFilter = new GeneralFilter(GeneralFilter.EXECUTABLE_EXTS, GeneralFilter.EXECUTABLE_DESC);
|
||||
File HxDPathFile = new File(HxDPath.getText());
|
||||
if(HxDPathFile.exists() && HxDPathFile.canExecute()) {
|
||||
fileWindow.setCurrentDirectory(new File(HxDPath.getText()));
|
||||
}
|
||||
fileWindow.setDragEnabled(false);
|
||||
fileWindow.setFileFilter(exeFilter);
|
||||
fileWindow.setMultiSelectionEnabled(false);
|
||||
int returnVal = fileWindow.showSaveDialog(this);
|
||||
if(returnVal == JFileChooser.APPROVE_OPTION) {
|
||||
File HxDExecutable = fileWindow.getSelectedFile();
|
||||
HxDPath.setForeground(Color.BLACK);
|
||||
HxDPath.setText(HxDExecutable.getAbsolutePath());
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_browseHxDDirectoryActionPerformed
|
||||
|
||||
@Override
|
||||
public void store() {
|
||||
//Dump rules from table model into a list to be stored by the rules manager.
|
||||
List<ExternalViewerRule> rules = new ArrayList<>();
|
||||
for(int i = 0; i < tableModel.getRowCount(); i++) {
|
||||
rules.add(tableModel.getRuleAt(i));
|
||||
}
|
||||
ExternalViewerRulesManager.getInstance().setUserRules(rules);
|
||||
UserPreferences.setExternalHexEditorPath(HxDPath.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
rules = ExternalViewerRulesManager.getInstance().getUserRules();
|
||||
updateRulesListModel();
|
||||
List<ExternalViewerRule> rules = ExternalViewerRulesManager.getInstance().getUserRules();
|
||||
for(ExternalViewerRule rule : rules) {
|
||||
if(!tableModel.containsRule(rule)) {
|
||||
tableModel.addRule(rule);
|
||||
}
|
||||
}
|
||||
String editorPath = UserPreferences.getExternalHexEditorPath();
|
||||
File HdXExecutable = new File(editorPath);
|
||||
if(HdXExecutable.exists() || HdXExecutable.canExecute()) {
|
||||
HxDPath.setText(editorPath);
|
||||
} else {
|
||||
HxDPath.setForeground(Color.RED);
|
||||
HxDPath.setText(editorPath);
|
||||
}
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable edit and delete buttons if there is a rule selected.
|
||||
*/
|
||||
private void enableButtons() {
|
||||
boolean ruleIsSelected = rulesList.getSelectedIndex() != -1;
|
||||
boolean enableButtons() {
|
||||
boolean ruleIsSelected = ExternalViewerRulesTable.getSelectedRow() >= 0;
|
||||
editRuleButton.setEnabled(ruleIsSelected);
|
||||
deleteRuleButton.setEnabled(ruleIsSelected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list model for the rules list component, sorted by the MIME
|
||||
* type or extension alphabetically.
|
||||
*/
|
||||
private void updateRulesListModel() {
|
||||
rulesListModel.clear();
|
||||
Collections.sort(rules);
|
||||
for (ExternalViewerRule rule : rules) {
|
||||
rulesListModel.addElement(rule);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the .exe file path label if a rule is selected.
|
||||
*/
|
||||
private void populateExePath() {
|
||||
ExternalViewerRule rule = rulesList.getSelectedValue();
|
||||
if (rule != null) {
|
||||
if (rule.getRuleType() == ExternalViewerRule.RuleType.MIME) {
|
||||
exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
|
||||
"ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text"));
|
||||
} else {
|
||||
exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
|
||||
"ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text"));
|
||||
}
|
||||
exePathNameLabel.setText(rule.getExePath());
|
||||
}
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the .exe file path label.
|
||||
*/
|
||||
private void clearExePath() {
|
||||
rulesList.clearSelection();
|
||||
exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
|
||||
"ExternalViewerGlobalSettingsPanel.exePathLabel.text"));
|
||||
exePathNameLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
|
||||
"ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text"));
|
||||
enableButtons();
|
||||
return ruleIsSelected;
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel ContentViewerExtensionLabel;
|
||||
private javax.swing.JTable ExternalViewerRulesTable;
|
||||
private javax.swing.JLabel HxDLabel;
|
||||
private javax.swing.JTextField HxDPath;
|
||||
private javax.swing.JButton browseHxDDirectory;
|
||||
private javax.swing.JButton deleteRuleButton;
|
||||
private javax.swing.JButton editRuleButton;
|
||||
private javax.swing.JPanel exePanel;
|
||||
private javax.swing.JLabel exePathLabel;
|
||||
private javax.swing.JLabel exePathNameLabel;
|
||||
private javax.swing.JLabel externalViewerTitleLabel;
|
||||
private javax.swing.JButton jButton2;
|
||||
private javax.swing.JPanel jPanel1;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JScrollPane jScrollPane2;
|
||||
private javax.swing.JSplitPane jSplitPane1;
|
||||
private javax.swing.JPanel jPanel2;
|
||||
private javax.swing.JPanel jPanel3;
|
||||
private javax.swing.JScrollPane jScrollPane4;
|
||||
private javax.swing.JButton newRuleButton;
|
||||
private javax.swing.JLabel ruleListLabel;
|
||||
private javax.swing.JList<ExternalViewerRule> rulesList;
|
||||
private javax.swing.JPanel rulesPanel;
|
||||
private javax.swing.JScrollPane rulesScrollPane;
|
||||
private javax.swing.JButton newRuleButton1;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.directorytree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
/**
|
||||
* Model that stores the External Viewer rules for Mime types and extensions.
|
||||
* Located at Tools -> Options -> External Viewer
|
||||
*/
|
||||
class ExternalViewerGlobalSettingsTableModel extends AbstractTableModel {
|
||||
|
||||
private final List<ExternalViewerRule> rules;
|
||||
private final String[] columnNames;
|
||||
|
||||
public ExternalViewerGlobalSettingsTableModel(String... columnNames) {
|
||||
this.columnNames = Arrays.copyOf(columnNames, columnNames.length);
|
||||
this.rules = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new external viewer rule.
|
||||
*
|
||||
* @param rule User-inputted rule
|
||||
*/
|
||||
public void addRule(ExternalViewerRule rule) {
|
||||
rules.add(rule);
|
||||
fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rules stored in this model.
|
||||
*
|
||||
* @return Integer denoting row count in table model
|
||||
*/
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return rules.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column name at the given index.
|
||||
*
|
||||
* @param columnIndex
|
||||
*
|
||||
* @return Column name
|
||||
*/
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
return columnNames[columnIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves column class type. As for now, this is only type string.
|
||||
*
|
||||
* @param columnIndex
|
||||
*
|
||||
* @return String.class
|
||||
*/
|
||||
@Override
|
||||
public Class<String> getColumnClass(int columnIndex) {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of columns in this table model.
|
||||
*
|
||||
* @return Integer denoting column count
|
||||
*/
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return columnNames.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves value at a given row and column in the table.
|
||||
*
|
||||
* @param rowIndex Desired row index
|
||||
* @param columnIndex Desired column index
|
||||
*
|
||||
* @return A generic pointer to the underlying data.
|
||||
*/
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (columnIndex == 0) {
|
||||
return rules.get(rowIndex).getName();
|
||||
} else {
|
||||
return rules.get(rowIndex).getExePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entire rule instance from a given index.
|
||||
*
|
||||
* @param rowIndex Desired row
|
||||
*
|
||||
* @return User-inputted rule at the desired rowIndex
|
||||
*/
|
||||
public ExternalViewerRule getRuleAt(int rowIndex) {
|
||||
return rules.get(rowIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an existing rule in the table.
|
||||
*
|
||||
* @param rowIndex Desired row index
|
||||
* @param rule New rule to overwrite the old.
|
||||
*/
|
||||
public void setRule(int rowIndex, ExternalViewerRule rule) {
|
||||
rules.set(rowIndex, rule);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the rule from the table model.
|
||||
*
|
||||
* @param rowIndex Desired row index to delete
|
||||
*/
|
||||
public void removeRule(int rowIndex) {
|
||||
rules.remove(rowIndex);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* This table model is not editable.
|
||||
*
|
||||
* @param rowIndex
|
||||
* @param colIndex
|
||||
*
|
||||
* @return False
|
||||
*/
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int colIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests containment of a given rule in the table model.
|
||||
*
|
||||
* @param rule Rule in question
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean containsRule(ExternalViewerRule rule) {
|
||||
return rules.contains(rule);
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -300,6 +301,8 @@ public class DataSourceIntegrityIngestModule implements DataSourceIngestModule {
|
||||
BlackboardArtifact verificationFailedArtifact = Case.getCurrentCase().getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED, img.getId());
|
||||
verificationFailedArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
|
||||
DataSourceIntegrityModuleFactory.getModuleName(), artifactComment));
|
||||
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(DataSourceIntegrityModuleFactory.getModuleName(),
|
||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_VERIFICATION_FAILED));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error creating verification failed artifact", ex);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -258,7 +258,7 @@ class SevenZipExtractor {
|
||||
org.sleuthkit.datamodel.Blackboard tskBlackboard = tskCase.getBlackboard();
|
||||
// Create artifact if it doesn't already exist.
|
||||
if (!tskBlackboard.artifactExists(archiveFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, attributes)) {
|
||||
BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
artifact.addAttributes(attributes);
|
||||
|
||||
try {
|
||||
|
@ -90,51 +90,51 @@ public class EmbeddedFileTest extends NbTestCase {
|
||||
CaseUtils.closeCurrentCase();
|
||||
}
|
||||
|
||||
// public void testEncryptionAndZipBomb() {
|
||||
// try {
|
||||
// List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'");
|
||||
// final String zipBombSetName = "Possible Zip Bomb";
|
||||
// final String protectedName1 = "password_protected.zip";
|
||||
// final String protectedName2 = "level1_protected.zip";
|
||||
// final String protectedName3 = "42.zip";
|
||||
// final String depthZipBomb = "DepthTriggerZipBomb.zip";
|
||||
// final String ratioZipBomb = "RatioTriggerZipBomb.zip";
|
||||
// int zipBombs = 0;
|
||||
// assertEquals("The number of files in the test image has changed", 2221, results.size());
|
||||
// int passwdProtectedZips = 0;
|
||||
// for (AbstractFile file : results) {
|
||||
// //.zip file has artifact TSK_ENCRYPTION_DETECTED
|
||||
// if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)) {
|
||||
// ArrayList<BlackboardArtifact> artifacts = file.getAllArtifacts();
|
||||
// assertEquals("Password protected zip file " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
|
||||
// for (BlackboardArtifact artifact : artifacts) {
|
||||
// assertEquals("Artifact for password protected zip file " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID());
|
||||
// passwdProtectedZips++;
|
||||
// }
|
||||
// } else if (file.getName().equalsIgnoreCase(depthZipBomb) || file.getName().equalsIgnoreCase(ratioZipBomb)) {
|
||||
// ArrayList<BlackboardArtifact> artifacts = file.getAllArtifacts();
|
||||
// assertEquals("Zip bomb " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
|
||||
// for (BlackboardArtifact artifact : artifacts) {
|
||||
// assertEquals("Artifact for Zip bomb " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID());
|
||||
// BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
// assertNotNull("No attribute found for artifact on zip bomb " + file.getName(), attribute);
|
||||
// assertEquals("Interesting artifact on file, " + file.getName() + ", does not reflect it being a zip bomb", zipBombSetName, attribute.getDisplayString());
|
||||
// zipBombs++;
|
||||
// }
|
||||
// } else {//No other files have artifact defined
|
||||
// assertEquals("Unexpected file, " + file.getName() + ", has artifacts", 0, file.getAllArtifacts().size());
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// //Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing.
|
||||
// assertEquals("Unexpected number of artifacts reflecting password protected zip files found", 3, passwdProtectedZips);
|
||||
// //Make sure 2 zip bomb files have been tested: DepthTriggerZipBomb.zip and RatioTriggerZipBomb.zip.
|
||||
// assertEquals("Unexpected number of artifacts reflecting zip bombs found", 2, zipBombs);
|
||||
// } catch (TskCoreException ex) {
|
||||
// Exceptions.printStackTrace(ex);
|
||||
// Assert.fail(ex.getMessage());
|
||||
// }
|
||||
// }
|
||||
public void testEncryptionAndZipBomb() {
|
||||
try {
|
||||
List<AbstractFile> results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'");
|
||||
final String zipBombSetName = "Possible Zip Bomb";
|
||||
final String protectedName1 = "password_protected.zip";
|
||||
final String protectedName2 = "level1_protected.zip";
|
||||
final String protectedName3 = "42.zip";
|
||||
final String depthZipBomb = "DepthTriggerZipBomb.zip";
|
||||
final String ratioZipBomb = "RatioTriggerZipBomb.zip";
|
||||
int zipBombs = 0;
|
||||
assertEquals("The number of files in the test image has changed", 2221, results.size());
|
||||
int passwdProtectedZips = 0;
|
||||
for (AbstractFile file : results) {
|
||||
//.zip file has artifact TSK_ENCRYPTION_DETECTED
|
||||
if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)) {
|
||||
ArrayList<BlackboardArtifact> artifacts = file.getAllArtifacts();
|
||||
assertEquals("Password protected zip file " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
assertEquals("Artifact for password protected zip file " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID());
|
||||
passwdProtectedZips++;
|
||||
}
|
||||
} else if (file.getName().equalsIgnoreCase(depthZipBomb) || file.getName().equalsIgnoreCase(ratioZipBomb)) {
|
||||
ArrayList<BlackboardArtifact> artifacts = file.getAllArtifacts();
|
||||
assertEquals("Zip bomb " + file.getName() + " has incorrect number of artifacts", 1, artifacts.size());
|
||||
for (BlackboardArtifact artifact : artifacts) {
|
||||
assertEquals("Artifact for Zip bomb " + file.getName() + " has incorrect type ID", artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID());
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
assertNotNull("No attribute found for artifact on zip bomb " + file.getName(), attribute);
|
||||
assertEquals("Interesting artifact on file, " + file.getName() + ", does not reflect it being a zip bomb", zipBombSetName, attribute.getDisplayString());
|
||||
zipBombs++;
|
||||
}
|
||||
} else {//No other files have artifact defined
|
||||
assertEquals("Unexpected file, " + file.getName() + ", has artifacts", 0, file.getAllArtifacts().size());
|
||||
}
|
||||
|
||||
}
|
||||
//Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing.
|
||||
assertEquals("Unexpected number of artifacts reflecting password protected zip files found", 3, passwdProtectedZips);
|
||||
//Make sure 2 zip bomb files have been tested: DepthTriggerZipBomb.zip and RatioTriggerZipBomb.zip.
|
||||
assertEquals("Unexpected number of artifacts reflecting zip bombs found", 2, zipBombs);
|
||||
} catch (TskCoreException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
Assert.fail(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testBigFolder() {
|
||||
final int numOfFilesToTest = 1000;
|
||||
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.directorytree;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Test;
|
||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerRule.RuleType;
|
||||
|
||||
/**
|
||||
* 72% code coverage of ExternalViewerGlobalSettingsPanel
|
||||
*/
|
||||
public class ExternalViewerGlobalSettingsPanelTest {
|
||||
|
||||
static final String[] testColumnNames = {"A", "B"};
|
||||
|
||||
/**
|
||||
* Default constructor for JUnit
|
||||
*/
|
||||
public ExternalViewerGlobalSettingsPanelTest(){
|
||||
//Codacy complains if there is no comment here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnableButtons() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
ExternalViewerGlobalSettingsPanel panel = new ExternalViewerGlobalSettingsPanel(testModel);
|
||||
Assert.assertFalse(panel.enableButtons());
|
||||
|
||||
testModel.addRule(new ExternalViewerRule("image/png", "fake.exe", RuleType.MIME));
|
||||
|
||||
Assert.assertFalse(panel.enableButtons());
|
||||
panel.setSelectionInterval(0, 0);
|
||||
Assert.assertTrue(panel.enableButtons());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableButtons() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
ExternalViewerGlobalSettingsPanel panel = new ExternalViewerGlobalSettingsPanel(testModel);
|
||||
|
||||
testModel.addRule(new ExternalViewerRule("image/png", "fake.exe", RuleType.MIME));
|
||||
Assert.assertFalse(panel.enableButtons());
|
||||
panel.setSelectionInterval(0, 0);
|
||||
Assert.assertTrue(panel.enableButtons());
|
||||
|
||||
testModel.removeRule(0);
|
||||
Assert.assertFalse(panel.enableButtons());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRuleButtonClick() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
|
||||
Assert.assertFalse(testPanel.enableButtons());
|
||||
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
testPanel.setSelectionInterval(0, 0);
|
||||
Assert.assertTrue(testPanel.enableButtons());
|
||||
Assert.assertEquals(1, testModel.getRowCount());
|
||||
|
||||
testPanel.deleteRuleButtonClick(0);
|
||||
|
||||
Assert.assertFalse(testPanel.enableButtons());
|
||||
Assert.assertEquals(0, testModel.getRowCount());
|
||||
}
|
||||
|
||||
@Test(expected = IndexOutOfBoundsException.class)
|
||||
public void testDeleteButtonClickFail() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
|
||||
|
||||
testPanel.deleteRuleButtonClick(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleSelection() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
ExternalViewerGlobalSettingsPanel testPanel = new ExternalViewerGlobalSettingsPanel(testModel);
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
testModel.addRule(new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT));
|
||||
|
||||
testPanel.setSelectionInterval(0, 2);
|
||||
|
||||
Assert.assertFalse(testPanel.isSelected(0));
|
||||
Assert.assertFalse(testPanel.isSelected(1));
|
||||
Assert.assertTrue(testPanel.isSelected(2));
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 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.directorytree;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerRule.RuleType;
|
||||
|
||||
/**
|
||||
* 100% code coverage of ExternalViewerGlobalSettingsTableModel
|
||||
*/
|
||||
public class ExternalViewerGlobalSettingsTableModelTest {
|
||||
|
||||
static final String[] testColumnNames = {"A", "B"};
|
||||
private ExternalViewerRule pngMime;
|
||||
private ExternalViewerRule txtExt;
|
||||
private ExternalViewerRule wavExt;
|
||||
|
||||
/**
|
||||
* Initialize JUnit test
|
||||
*/
|
||||
public ExternalViewerGlobalSettingsTableModelTest() {
|
||||
//Empty constructor
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
pngMime = new ExternalViewerRule("image/png", "test.exe", RuleType.MIME);
|
||||
txtExt = new ExternalViewerRule(".txt", "notepad.exe", RuleType.EXT);
|
||||
wavExt = new ExternalViewerRule(".wav", "video.exe", RuleType.EXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of addRule method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testAddRule() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
|
||||
Assert.assertEquals(1, testModel.getRowCount());
|
||||
|
||||
ExternalViewerRule rule = testModel.getRuleAt(0);
|
||||
Assert.assertEquals("image/png", rule.getName());
|
||||
Assert.assertEquals("test.exe", rule.getExePath());
|
||||
Assert.assertEquals(RuleType.MIME, rule.getRuleType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getRowCount method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRowCount() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
Assert.assertEquals(0, testModel.getRowCount());
|
||||
|
||||
testModel.addRule(pngMime);
|
||||
testModel.addRule(txtExt);
|
||||
testModel.addRule(wavExt);
|
||||
|
||||
Assert.assertEquals(3, testModel.getRowCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getColumnName method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetColumnName() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
Assert.assertEquals("A", testModel.getColumnName(0));
|
||||
Assert.assertEquals("B", testModel.getColumnName(1));
|
||||
}
|
||||
|
||||
@Test(expected = ArrayIndexOutOfBoundsException.class)
|
||||
public void testColumnNameOutOfBounds() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.getColumnName(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getColumnClass method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetColumnClass() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
Assert.assertEquals(String.class, testModel.getColumnClass(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getColumnCount method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetColumnCount() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
Assert.assertEquals(2, testModel.getColumnCount());
|
||||
ExternalViewerGlobalSettingsTableModel testModelTwo = new ExternalViewerGlobalSettingsTableModel(new String[] {"A", "B", "C", "D", "E"});
|
||||
Assert.assertEquals(5, testModelTwo.getColumnCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getValueAt method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetValueAt() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
testModel.addRule(txtExt);
|
||||
testModel.addRule(wavExt);
|
||||
|
||||
Assert.assertEquals(".txt", testModel.getValueAt(1,0));
|
||||
Assert.assertEquals("notepad.exe", testModel.getValueAt(1,1));
|
||||
Assert.assertEquals("image/png", testModel.getValueAt(0,0));
|
||||
Assert.assertEquals("test.exe", testModel.getValueAt(0,1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of getRuleAt method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testGetRuleAt() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
testModel.addRule(txtExt);
|
||||
testModel.addRule(wavExt);
|
||||
|
||||
ExternalViewerRule rule = testModel.getRuleAt(1);
|
||||
Assert.assertEquals(".txt", rule.getName());
|
||||
Assert.assertEquals("notepad.exe", rule.getExePath());
|
||||
Assert.assertEquals(RuleType.EXT, rule.getRuleType());
|
||||
|
||||
ExternalViewerRule ruleTwo = testModel.getRuleAt(0);
|
||||
Assert.assertEquals("image/png", ruleTwo.getName());
|
||||
Assert.assertEquals("test.exe", ruleTwo.getExePath());
|
||||
Assert.assertEquals(RuleType.MIME, ruleTwo.getRuleType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of setRule method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testSetRule() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
testModel.addRule(txtExt);
|
||||
testModel.addRule(wavExt);
|
||||
|
||||
testModel.setRule(0, txtExt);
|
||||
ExternalViewerRule rule = testModel.getRuleAt(1);
|
||||
Assert.assertEquals(".txt", rule.getName());
|
||||
Assert.assertEquals("notepad.exe", rule.getExePath());
|
||||
Assert.assertEquals(RuleType.EXT, rule.getRuleType());
|
||||
|
||||
testModel.setRule(2, pngMime);
|
||||
ExternalViewerRule ruleTwo = testModel.getRuleAt(2);
|
||||
Assert.assertEquals("image/png", ruleTwo.getName());
|
||||
Assert.assertEquals("test.exe", ruleTwo.getExePath());
|
||||
Assert.assertEquals(RuleType.MIME, ruleTwo.getRuleType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of removeRule method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveRule() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
Assert.assertEquals(1, testModel.getRowCount());
|
||||
|
||||
testModel.removeRule(0);
|
||||
Assert.assertEquals(0, testModel.getRowCount());
|
||||
Assert.assertFalse(testModel.containsRule(pngMime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of isCellEditable method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testIsCellEditable() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
Assert.assertFalse(testModel.isCellEditable(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of containsRule method, of class ExternalViewerGlobalSettingsTableModel.
|
||||
*/
|
||||
@Test
|
||||
public void testContainsRule() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
Assert.assertTrue(testModel.containsRule(pngMime));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotContains() {
|
||||
ExternalViewerGlobalSettingsTableModel testModel = new ExternalViewerGlobalSettingsTableModel(testColumnNames);
|
||||
testModel.addRule(pngMime);
|
||||
Assert.assertFalse(testModel.containsRule(new ExternalViewerRule("not", "a rule", RuleType.EXT)));
|
||||
Assert.assertFalse(testModel.containsRule(null));
|
||||
}
|
||||
}
|
@ -15,11 +15,4 @@
|
||||
out-of-date/unneeded stuff in the installer-->
|
||||
<delete dir="${ext.dir}"/>
|
||||
</target>
|
||||
|
||||
<target name="get-thirdparty-jars" description="get third-party jar dependencies">
|
||||
<mkdir dir="${ext.dir}"/>
|
||||
<copy file="${thirdparty.dir}/sqlite-jdbc/sqlite-jdbc-3.7.8-SNAPSHOT.jar" todir="${ext.dir}" />
|
||||
</target>
|
||||
|
||||
<target name="init" depends="get-thirdparty-jars,harness.init" />
|
||||
</project>
|
||||
|
@ -142,8 +142,8 @@
|
||||
</module-dependencies>
|
||||
<public-packages/>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.7.8-SNAPSHOT.jar</binary-origin>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.25.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.25.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
</data>
|
||||
</configuration>
|
||||
|
@ -206,8 +206,8 @@
|
||||
<binary-origin>release/modules/ext/quartz-2.2.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.19.3.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.19.3.jar</binary-origin>
|
||||
<runtime-relative-path>ext/sqlite-jdbc-3.25.2.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.25.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/guava-17.0.jar</runtime-relative-path>
|
||||
|
@ -4,9 +4,7 @@
|
||||
Overview
|
||||
-----
|
||||
|
||||
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/4.1/"> developer's guide</a> will help you develop your own Autopsy modules.
|
||||
|
||||
Autopsy 4 (and 3) are a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
|
||||
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/"> developer's guide</a> will help you develop your own Autopsy modules.
|
||||
|
||||
Help Topics
|
||||
-------
|
||||
|
@ -1,28 +1,36 @@
|
||||
/*! \page quick_start_guide Quick Start Guide
|
||||
|
||||
\section s1 Adding a Data Source (image, local disk, logical files)
|
||||
\section s1 Cases and Data Sources
|
||||
|
||||
Data sources are added to a <strong>case</strong>. A case can have a single data source or it can have multiple data sources. Currently, a single report is generated for an entire case, so if you need to report on individual data sources, then you should use one data source per case. If there are many drives/phones/other data sources for one investigation, then your case should have multiple data sources.
|
||||
Autopsy organizes data by <strong>case</strong>. Each case can have one or more <strong>data sources</strong>, which can be a disk image, a set of logical files, a USB-connected device, etc.
|
||||
|
||||
\subsection s2 Creating a Case
|
||||
Cases can either be single-user or multi-user. Multi-user cases allow several examiners to review the data at the same time and collaborate, but require some additional open source servers to be configured.
|
||||
|
||||
When you have several data sources and are deciding about creating creating a case, consider:
|
||||
- You can have only one case open at a time
|
||||
- Reports are generated at a case-level
|
||||
- The application can slow down when there are many large data sources in the same case
|
||||
|
||||
\subsection s1a Creating a Case
|
||||
To create a case, use either the "Create New Case" option on the Welcome screen or from the "Case" menu. This will start the <strong>New Case Wizard</strong>. You will need to supply it with the name of the case and a directory to store the case results into. You can optionally provide case numbers and reviewer names.
|
||||
|
||||
\subsection s3 Adding a Data Source
|
||||
\subsection s1b Adding a Data Source
|
||||
The next step is to add an input data source to the case. The <strong>Add Data Source Wizard</strong> will start automatically after the case is created or you can manually start it from the "Case" menu or toolbar. You will need to choose the type of input data source to add (image, local disk, or logical files and folders). Next, supply it with the location of the source to add.
|
||||
|
||||
|
||||
- For a disk image, browse to the first file in the set (Autopsy will find the rest of the files). Autopsy currently supports E01 and raw (dd) files.
|
||||
- For local disk, select one of the detected disks. Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). However, the individual file content (not meta-data) does get updated with the changes made to the disk. Note, you may need run Autopsy as an Administrator to detect all disks.
|
||||
- For local disk, select one of the detected disks. Autopsy will add the current view of the disk to the case (i.e. snapshot of the meta-data). However, the individual file content (not meta-data) does get updated with the changes made to the disk. You can optionally create a copy of all data read from the local disk to a VHD file, which can be useful for triage situations. Note, you may need run Autopsy as an Administrator to detect all disks.
|
||||
- For logical files (a single file or folder of files), use the "Add" button to add one or more files or folders on your system to the case. Folders will be recursively added to the case.
|
||||
|
||||
There are a couple of options in the wizard that will allow you to make the ingest process faster. These typically deal with deleted files. It will take longer if unallocated space is analyzed and the entire drive is searched for deleted files. In some scenarios, these recovery steps must be performed and in other scenarios these steps are not needed and instead fast results on the allocated files are needed. Use these options to control how long the analysis will take.
|
||||
|
||||
Autopsy will start to analyze these data sources and add them to the case and the internal database. While it is doing that, it will prompt you to configure the Ingest Modules.
|
||||
After supplying the needed data, Autopsy will quickly review the data sources and add minimal metadata to the case databases so that it can schedule the files for analysis. While it is doing that, it will prompt you to configure the Ingest Modules.
|
||||
|
||||
|
||||
\subsection s4 Ingest Modules
|
||||
\subsection s1c Ingest Modules
|
||||
|
||||
You will next be prompted to configure the Ingest Modules. Ingest modules will run in the background and perform specific tasks. The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. Ingest modules can be developed by third-parties. The standard ingest modules included with Autopsy are:
|
||||
Ingest modules are responsible for analyzing the data source contents and will run in the background. The Ingest Modules analyze files in a prioritized order so that files in a user's directory are analyzed before files in other folders. Ingest modules can be developed by third-parties.
|
||||
|
||||
The standard ingest modules included with Autopsy are:
|
||||
|
||||
- <strong>\subpage recent_activity_page</strong> extracts user activity as saved by web browsers and the OS. Also runs Regripper on the registry hive.
|
||||
- <strong>\subpage hash_db_page</strong> uses hash sets to ignore known files from the NIST NSRL and flag known bad files. Use the "Advanced" button to add and configure the hash sets to use during this process. You will get updates on known bad file hits as the ingest occurs. You can later add hash sets via the Tools -> Options menu in the main UI. You can download an index of the NIST NSRL from http://sourceforge.net/projects/autopsy/files/NSRL/
|
||||
@ -36,7 +44,7 @@ You will next be prompted to configure the Ingest Modules. Ingest modules will r
|
||||
- <strong>\subpage android_analyzer_page</strong> allows you to parse common items from Android devices. Places artifacts into the BlackBoard.
|
||||
- <strong>\subpage interesting_files_identifier_page</strong> searches for files and directories based on user-specified rules in Tools, Options, Interesting Files. It works as a "File Alerting Module". It generates messages in the inbox when specified files are found.
|
||||
- <strong>\subpage photorec_carver_page</strong> carves files from unallocated space and sends them through the file processing chain.
|
||||
- <strong>\subpage cr_ingest_module</strong> adds file hashes and other extracted properties to a central repository.
|
||||
- <strong>\subpage cr_ingest_module</strong> adds file hashes and other extracted properties to a central repository for future correlation and to flag previously notable files.
|
||||
- <strong>\subpage encryption_page</strong> looks for encrypted files.
|
||||
- <strong>\subpage vm_extractor_page</strong> extracts data from virtual machine files
|
||||
|
||||
@ -44,7 +52,9 @@ When you select a module, you will have the option to change its settings. For
|
||||
|
||||
While ingest modules are running in the background, you will see a progress bar in the lower right. You can use the GUI to review incoming results and perform other tasks while ingesting at the same time.
|
||||
|
||||
\section s1a Analysis Basics
|
||||
\section s2 Analysis Basics
|
||||
|
||||
After the ingest modules start to analyze the data source, you'll see the main analysis interface. You can choose to search for specific items, browse to specific folders, or review ingest module results.
|
||||
|
||||
\image html screenshot.PNG
|
||||
|
||||
@ -53,7 +63,7 @@ You will start all of your analysis techniques from the tree on the left.
|
||||
- The Data Sources root node shows all data in the case.
|
||||
- The individual image nodes show the file system structure of the disk images or local disks in the case.
|
||||
- The LogicalFileSet nodes show the logical files in the case.
|
||||
- The Views node shows the same data from a file type or timeline perspective.
|
||||
- The Views node shows the same data from a different perspective, such as organized by file type.
|
||||
- The Results node shows the output from the ingest modules.
|
||||
|
||||
When you select a node from the tree on the left, a list of files will be shown in the upper right. You can use the Thumbnail view in the upper right to view the pictures. When you select a file from the upper right, its contents will be shown in the lower right. You can use the tabs in the lower right to view the text of the file, an image, or the hex data.
|
||||
@ -66,23 +76,23 @@ The tree on the left as well as the table on the right have a \ref ui_quick_sear
|
||||
|
||||
You can tag (bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report.
|
||||
|
||||
\subsection s2a Ingest Inbox
|
||||
|
||||
As you are going through the results in the tree, the ingest modules are running in the background.
|
||||
The results are shown in the tree as soon as the ingest modules find them and report them.
|
||||
\section s3 Other Analysis Interfaces
|
||||
|
||||
The Ingest Inbox receives messages from the ingest modules as they find results.
|
||||
You can open the inbox to see what has been recently found.
|
||||
It keeps track of what messages you have read.
|
||||
In addition to the 3-panel UI with the tree on the left, there are other interfaces that are more specialized.
|
||||
|
||||
The intended use of this inbox is that you can focus on some data for a while and then check back on the inbox at a time that is convenient for them.
|
||||
You can then see what else was found while you were focused on the previous task.
|
||||
You may learn that a known bad file was found or that a file was found with a relevant keyword and then decide to focus on that for a while.
|
||||
\subsection s3a Timeline
|
||||
|
||||
When you select a message, you can then jump to the Results tree where more details can be found or jump to the file's location in the filesystem.
|
||||
The timeline feature can be opened from the "Tools" menu or the toolbar. This will show you file system and other events organized by time using various display techniques. See the \subpage timeline_page section for more details.
|
||||
|
||||
\subsection s2b Timeline
|
||||
There is a basic timeline view that you can access via the "Tools", "Make Timeline" feature. This will take a few minutes to create the timeline for analysis. Its features are still in development.
|
||||
|
||||
\subsection s3b Image Gallery
|
||||
|
||||
The Image Gallery focuses on showing the pictures and videos from the data source organized by folder. It will show you files as soon as they have been hashed and EXIF data extracted. You can open it from the "Tools" menu. See the \subpage image_gallery_page section for more details.
|
||||
|
||||
\subsection s3c Communications
|
||||
|
||||
The Communications interface focuses on showing which accounts were communicated with the most and what messages were sent. It allows you to focus on certain relationships or communications within a certain date rage. You can open it from the "Tools" menu. See the \subpage communications_page section for more details.
|
||||
|
||||
|
||||
\section s5 Example Use Cases
|
||||
@ -110,16 +120,14 @@ If you want to see all images and video on the disk image, then go to the "
|
||||
Select either "Images" or "Videos".
|
||||
You can use the thumbnail option in the upper right to view thumbnails of all images.
|
||||
|
||||
<strong>Note</strong>: We are working on making this more efficient when there are lots of images. We are also working on the feature to display video thumbnails.
|
||||
|
||||
You can select an image or video from the upper right and view the video or image in the lower right. Video will be played with sound.
|
||||
|
||||
\section s6 Reporting
|
||||
|
||||
A final report can be generated that will include all analysis results.
|
||||
Use the "Generate Report" button to create this.
|
||||
It will create an HTML or XLS report in the Reports folder of the case folder.
|
||||
If you forgot the location of your case folder, you can determine it using the "Case Properties" option in the "Case" menu.
|
||||
There is also an option to export report files to a separate folder outside of the case folder.
|
||||
A final report can be generated that will include all analysis results using the "Generate Report" toolbar button. Reports can be generated in HTML, XLS, KML, and other formats.
|
||||
|
||||
You can later find your generated reports by going to the tree and opening the Reports node at the bottom.
|
||||
|
||||
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user